10 interesting stories served every morning and every evening.

Nvidia has been in talks to acquire Hugging Face for more than $13 billion

www.businessinsider.com

Nvidia has been in talks to ac­quire Hugging Face, the pop­u­lar AI plat­form for shar­ing and build­ing with open-source mod­els, in what could be one of the chip gi­ant’s biggest deals yet.

The two par­ties have had ac­qui­si­tion con­ver­sa­tions in re­cent weeks about a deal that would value Hugging Face at more than $13 bil­lion, ac­cord­ing to a per­son fa­mil­iar with the mat­ter. The com­pa­nies have not yet reached a deal, and the talks could still fall apart, the per­son said. Business Insider on Sunday was the first to re­port that Hugging Face was field­ing takeover in­ter­est.

Nvidia and Hugging Face did not re­spond to re­quests for com­ment.

Nvidia has in­creas­ingly ramped up deal­mak­ing with its enor­mous cash pile. The com­pany said Wednesday that it has $18 bil­lion com­mit­ted to eq­uity in­vest­ments for the rest of its fis­cal year, on top of $47.9 bil­lion it al­ready holds in pri­vate com­pa­nies.

Microsoft also met with Hugging Face, but the per­son fa­mil­iar and a sec­ond per­son said talks are not on­go­ing.

Nvidia al­ready has a re­la­tion­ship with Hugging Face. The chip­maker par­tic­i­pated in its $235 mil­lion fund­ing round in 2023 that val­ued it at $4.5 bil­lion.

Hugging Face turned down a $500 mil­lion in­vest­ment of­fer from Nvidia late last year that would have val­ued it at $7 bil­lion, the Financial Times pre­vi­ously re­ported. Hugging Face said at the time it did not want a dom­i­nant in­vestor that could sway de­ci­sions.

Hugging Face sits at the cen­ter of the open-source AI ecosys­tem, host­ing mil­lions of AI mod­els and datasets that de­vel­op­ers can build on. Owning the plat­form could give Nvidia a big­ger foothold with those de­vel­op­ers — and po­ten­tially drive more work­loads onto its chips.

Hugging Face was founded in 2016 by French en­tre­pre­neurs Clément Delangue, Julien Chaumond, and Thomas Wolf.

But Nvidia own­er­ship could also com­pli­cate one of Hugging Face’s strengths: its neu­tral­ity. The plat­form sup­ports mod­els and hard­ware from across the in­dus­try, in­clud­ing Nvidia com­peti­tors such as AMD and Intel.

Have a tip?

Contact Katie Roof via email at kroof@busi­nessin­sider.com or Signal at @kroof.26

Contact Geoff Weiss via email at gweiss@busi­nessin­sider.com or Signal at @geoffweiss.25.

Contact Ashley Stewart via email at astew­art@busi­nessin­sider.com or Signal at +1 – 425-344 – 8242.

Use a per­sonal email ad­dress and a non­work de­vice; here’s our guide to shar­ing in­for­ma­tion se­curely.

Read next

Geoff Weiss

You’re cur­rently fol­low­ing this au­thor! Want to un­fol­low? Unsubscribe via the link in your email.

Geoff Weiss is a se­nior re­porter on Business Insider’s tech team, where he writes about AI star­tups and Y Combinator, the in­ter­sec­tion of AI and the me­dia in­dus­try, and work­place dy­nam­ics within top AI labs and chip com­pa­nies.Pre­vi­ously, Geoff was on the me­dia desk, cov­er­ing YouTube and Netflix, and themes like the in­ter­sec­tion of Hollywood and the cre­ator econ­omy. His work on Netflix’s video pod­cast­ing am­bi­tions and Mr Beast’s lessons for Hollywood won sec­ond and first prize, re­spec­tively, at the 2025 LA Press Club Awards.Prior to join­ing Business Insider, Geoff was the se­nior ed­i­tor of Tubefilter and a staff writer at Entrepreneur. He grad­u­ated from New York University with a de­gree in English Literature.He can be reached at gweiss@busi­nessin­sider.com, on Signal @geoffweiss.25, and on LinkedIn. Have a tip? Use a per­sonal email ad­dress and a non­work de­vice; here’s our guide to shar­ing in­for­ma­tion se­curely.Se­lected sto­ries:Nvidia crushed its quar­ter — and CEO Jensen Huang said in a leaked all-hands that the mar­ket did not ap­pre­ci­ate it’N­vidia will foot the bill for Trump’s new visa fees. Here’s what CEO Jensen Huang told staff.Mas­sive AI salaries and RTO are fu­el­ing a real es­tate boom in San Francisco: It’s go­ing to rain mon­ey’The AI tal­ent wars are ric­o­chet­ing across star­tups. Here’s how they’re com­pet­ing with Big Tech.

Ashley Stewart

You’re cur­rently fol­low­ing this au­thor! Want to un­fol­low? Unsubscribe via the link in your email.

AI

Big Tech

Venture Capital

More

Exclusive

DuckLabs to Join AWS, Projects to Remain Open Source

ducklabs.com

2026 – 08-26 | 10 min

Today, we’re an­nounc­ing that DuckLabs will join Amazon Web Services (AWS), which is ex­pected to be ef­fec­tive in early September.

Our team will re­main to­gether in Amsterdam, con­tin­u­ing our work on DuckDB, DuckLake, Quack, and the broader com­mu­nity. Joining AWS gives us the re­sources and reach to bring this tech­nol­ogy to many more de­vel­op­ers and or­ga­ni­za­tions, and to pur­sue ideas at a scale that would have been dif­fi­cult for us to reach alone.

Most im­por­tantly, the foun­da­tions of the pro­ject will re­main firmly in place. DuckDB and the other open-source com­po­nents of the Duck Stack” will re­main free and open source un­der the MIT li­cense, with the non­profit DuckDB Foundation con­tin­u­ing its stew­ard­ship of the pro­jects.

This is a sig­nif­i­cant mo­ment for all of us at DuckLabs. It marks the end of one chap­ter that we are im­mensely proud of, and the be­gin­ning of an­other that we be­lieve can take DuckDB much fur­ther.

Our Journey to This Point

We founded DuckLabs a lit­tle over five years ago to give the team be­hind DuckDB a sta­ble, long-term home.

At the time, DuckDB was be­gin­ning to gain real mo­men­tum. The first com­mer­cial con­tracts to pri­or­i­tize fea­tures were ma­te­ri­al­iz­ing, and ven­ture cap­i­tal firms were call­ing. We chose a dif­fer­ent path: a boot­strapped com­pany, fully owned by its founders and de­vel­op­ment team.

That de­ci­sion shaped DuckLabs in ways we value deeply. It gave us the free­dom to build pa­tiently, to put the tech­nol­ogy first, and to grow with­out los­ing sight of why we started. From a small group gath­ered around an am­bi­tious open-source pro­ject, we grew into a team of more than 30 peo­ple in Amsterdam, all while con­tin­u­ing to in­vest in DuckDB and the com­mu­nity tak­ing shape around it.

During those years, DuckDB trav­eled much fur­ther than we imag­ined when the pro­ject be­gan. Today, we see more than one mil­lion down­loads every day. Developers around the world use it to ex­plore data, power prod­ucts, teach new ideas, con­duct re­search, and build sys­tems we could never have an­tic­i­pated our­selves.

Watching that hap­pen has been one of the great priv­i­leges of our pro­fes­sional lives. But the lim­its of our ex­ist­ing model also be­came in­creas­ingly clear. As founders, we wor­ried that DuckDB’s growth would even­tu­ally out­pace our abil­ity to sup­port it. That our small com­pany could be­come a bot­tle­neck for the pro­ject, the team, and the peo­ple build­ing busi­nesses on top of it.

We also wor­ried that scal­ing DuckLabs into a much larger sales, sup­port, and op­er­a­tions or­ga­ni­za­tion would pull our at­ten­tion away from the tech­ni­cal work and open-source com­mu­nity that made DuckDB suc­cess­ful in the first place.

Our part­ner­ships work best with highly tech­ni­cal or­ga­ni­za­tions — of­ten com­pa­nies with sub­stan­tial data­base ex­per­tise of their own. Reaching a much broader group of users re­quires us to solve more com­plete and spe­cial­ized prob­lems, serve the needs of dif­fer­ent in­dus­tries, in­vest sub­stan­tially more in in­fra­struc­ture, and reach peo­ple who may never think to seek out an an­a­lyt­i­cal data­base di­rectly.

We be­lieve the DuckDB rev­o­lu­tion can grow by an­other cou­ple of or­ders of mag­ni­tude. To give it that op­por­tu­nity, we re­al­ized we needed a dif­fer­ent setup.

Why AWS

DuckLabs and AWS have al­ready been work­ing closely to­gether for more than a year. During that time, we came to un­der­stand how our teams col­lab­o­rate, what each brings to the table, and what might be­come pos­si­ble by com­bin­ing DuckLabs’ tech­ni­cal ex­per­tise with AWSs in­fra­struc­ture, scale, and cus­tomer reach.

That ex­pe­ri­ence gave us con­fi­dence in tak­ing this next step.

Together, we plan to use DuckDB, DuckLake, and Quack to help power a new gen­er­a­tion of data ser­vices. Our am­bi­tion is to reach peo­ple who may even­tu­ally de­pend on the Duck Stack every day, whether they in­ter­act with it di­rectly or en­counter it qui­etly in­side the prod­ucts and ser­vices they use.

For the DuckLabs team, join­ing AWS cre­ates the space to con­cen­trate on the tech­ni­cal work we care about most while op­er­at­ing at a scale we could not eas­ily achieve on our own. It al­lows us to think fur­ther ahead, be bolder, tackle harder prob­lems, and bring the ideas be­hind DuckDB to many more peo­ple.

AWS has com­mit­ted to sup­port­ing the con­tin­ued de­vel­op­ment of DuckDB and its wider com­mu­nity for the long term. That com­mit­ment mat­ters deeply to us.

Quote DuckDB is an in­cred­i­ble open source pro­ject with an amaz­ing com­mu­nity; it is broadly used and very much loved by S3 cus­tomers to­day. After about two years of work­ing closely with Mark, Hannes and the whole team at DuckLabs I’m ex­cited at the op­por­tu­nity to help the pro­ject have an even broader im­pact. Also, and maybe a lit­tle self­ishly, I’ve found the DuckLabs team to be one of the most tech­ni­cally deep, hum­ble, and high-ve­loc­ity teams that I’ve ever had a chance to work with and I’m de­lighted that we get to do a lot more of that.”

– Andy Warfield (Distinguished Engineer and Vice President, AWS)

Quote DuckDB is an in­cred­i­ble open source pro­ject with an amaz­ing com­mu­nity; it is broadly used and very much loved by S3 cus­tomers to­day. After about two years of work­ing closely with Mark, Hannes and the whole team at DuckLabs I’m ex­cited at the op­por­tu­nity to help the pro­ject have an even broader im­pact. Also, and maybe a lit­tle self­ishly, I’ve found the DuckLabs team to be one of the most tech­ni­cally deep, hum­ble, and high-ve­loc­ity teams that I’ve ever had a chance to work with and I’m de­lighted that we get to do a lot more of that.”

– Andy Warfield (Distinguished Engineer and Vice President, AWS)

What Will Remain the Same

We know that an an­nounce­ment like this brings im­por­tant — and un­der­stand­able — ques­tions for users, con­trib­u­tors, cus­tomers, and part­ners.

DuckDB, DuckLake, Quack, and the other open-source com­po­nents of the Duck Stack will re­main free and open source un­der the MIT li­cense. The non­profit DuckDB Foundation will con­tinue to stew­ard these pro­jects, and the DuckLabs team will con­tinue to con­tribute to the pro­ject and re­main to­gether in Amsterdam.

The open-source pro­jects will con­tinue to serve a broad com­mu­nity of users, con­trib­u­tors, plat­forms, and ven­dors. The open­ness that al­lowed DuckDB to flour­ish will re­main cen­tral to its fu­ture.

We care deeply about the trust this com­mu­nity has placed in us. Protecting that trust has been one of the most im­por­tant con­sid­er­a­tions through­out this process.

Quote As the CWI rep­re­sen­ta­tive on the DuckDB Foundation, I would like to con­grat­u­late Hannes, Mark and all DuckLabs em­ploy­ees with this new chap­ter. When DuckLabs spun out of CWI, we cre­ated this foun­da­tion, which holds all IP of open-source DuckDB, and will con­tinue to do so. I am de­lighted that AWS is com­mit­ted to keep ad­vanc­ing open-source DuckDB, and I an­tic­i­pate it to even ac­cel­er­ate its in­no­va­tion. Via the DuckDB Foundation we will also make sure that the voices of its sup­port­ers and all mem­bers of the open-source DuckDB com­mu­nity at large, will con­tinue to be heard. I am very proud of DuckDB and its cre­ators; this de­vel­op­ment un­der­lines it is state-of-the-art tech­nol­ogy, which re­al­izes many ideas from the data­base ar­chi­tec­ture re­search group in open source, for every­body’s ben­e­fit.”

– Peter Boncz (Database Architectures group lead at CWI Amsterdam, DuckDB Foundation board mem­ber)

Quote As the CWI rep­re­sen­ta­tive on the DuckDB Foundation, I would like to con­grat­u­late Hannes, Mark and all DuckLabs em­ploy­ees with this new chap­ter. When DuckLabs spun out of CWI, we cre­ated this foun­da­tion, which holds all IP of open-source DuckDB, and will con­tinue to do so. I am de­lighted that AWS is com­mit­ted to keep ad­vanc­ing open-source DuckDB, and I an­tic­i­pate it to even ac­cel­er­ate its in­no­va­tion. Via the DuckDB Foundation we will also make sure that the voices of its sup­port­ers and all mem­bers of the open-source DuckDB com­mu­nity at large, will con­tinue to be heard. I am very proud of DuckDB and its cre­ators; this de­vel­op­ment un­der­lines it is state-of-the-art tech­nol­ogy, which re­al­izes many ideas from the data­base ar­chi­tec­ture re­search group in open source, for every­body’s ben­e­fit.”

– Peter Boncz (Database Architectures group lead at CWI Amsterdam, DuckDB Foundation board mem­ber)

Quote Over the course of five years, DuckDB’s open na­ture, its sheer hack­a­bil­ity, and the re­mark­ably friendly com­mu­nity of de­vel­op­ers has turned the sys­tem into one of the pre­mier plat­forms for data­base re­search as well as teach­ing. For both, it is es­sen­tial that we can in­spect and tin­ker with DuckDB’s ker­nel. I am thus ex­cited to learn that DuckDB will re­main open source un­der the um­brella of AWS. An even wider range of op­por­tu­ni­ties is in reach now. We are glad to be able to be a part of this new era for DuckLabs and DuckDB.”

– Torsten Grust (Professor of Computer Science and Database Systems re­search group lead at Universität Tübingen, Germany)

Quote Over the course of five years, DuckDB’s open na­ture, its sheer hack­a­bil­ity, and the re­mark­ably friendly com­mu­nity of de­vel­op­ers has turned the sys­tem into one of the pre­mier plat­forms for data­base re­search as well as teach­ing. For both, it is es­sen­tial that we can in­spect and tin­ker with DuckDB’s ker­nel. I am thus ex­cited to learn that DuckDB will re­main open source un­der the um­brella of AWS. An even wider range of op­por­tu­ni­ties is in reach now. We are glad to be able to be a part of this new era for DuckLabs and DuckDB.”

– Torsten Grust (Professor of Computer Science and Database Systems re­search group lead at Universität Tübingen, Germany)

What We Plan to Expand

Joining AWS will give us greater ca­pac­ity to in­vest in both the tech­nol­ogy and the com­mu­nity around it. Going for­ward, the DuckDB Foundation will in­clude a tech­ni­cal ad­vi­sory board, so that lead­ing com­mu­nity mem­bers can pro­vide their in­put on the pro­jec­t’s tech­ni­cal di­rec­tion. We also plan to open the ex­ten­sion stack so that ex­ten­sions signed by other de­vel­op­ers and or­ga­ni­za­tions can run in DuckDB.

There is a great deal of work ahead, and many de­tails still to shape. We will share more as these plans de­velop.

Quote Amazon putting its weight be­hind DuckDB is go­ing to add a ton of mo­men­tum and strengthen the ecosys­tem. This is great news for those of us who be­lieve in DuckDB as the plat­form on which the fu­ture of an­a­lyt­ics is be­ing built.”

– Jordan Tigani (CEO, MotherDuck)

Quote Amazon putting its weight be­hind DuckDB is go­ing to add a ton of mo­men­tum and strengthen the ecosys­tem. This is great news for those of us who be­lieve in DuckDB as the plat­form on which the fu­ture of an­a­lyt­ics is be­ing built.”

– Jordan Tigani (CEO, MotherDuck)

Quote Amazon is the ideal home for DuckLabs. DuckDB is the cen­ter of the ven­dor-neu­tral open data stack, and Amazon has the com­mit­ment to open­ness, and the track record of work­ing with the en­tire cloud ecosys­tem, to en­able the DuckDB pro­ject to con­tinue to thrive in this role.”

– George Fraser (CEO and co-founder, Fivetran)

Quote Amazon is the ideal home for DuckLabs. DuckDB is the cen­ter of the ven­dor-neu­tral open data stack, and Amazon has the com­mit­ment to open­ness, and the track record of work­ing with the en­tire cloud ecosys­tem, to en­able the DuckDB pro­ject to con­tinue to thrive in this role.”

– George Fraser (CEO and co-founder, Fivetran)

The Next Chapter

DuckDB’s suc­cess has al­ways be­longed to a much larger com­mu­nity than the peo­ple work­ing in­side DuckLabs.

It be­longs to every­one who has used it, con­tributed code, re­ported a bug, writ­ten an ex­ten­sion, an­swered a ques­tion, pub­lished a bench­mark, taught a class, built a prod­uct, chal­lenged our as­sump­tions, or rec­om­mended DuckDB to some­one else. Every one of those acts helped the pro­ject be­come what it is to­day.

We do not take that sup­port — or the trust be­hind it — for granted.

Joining AWS gives the DuckLabs team an ex­tra­or­di­nary op­por­tu­nity to bring the Duck Stack to a much larger au­di­ence while con­tin­u­ing to in­vest in the open-source tech­nol­ogy at its heart. We en­ter this next chap­ter with the same cu­rios­ity, care, and tech­ni­cal am­bi­tion that brought us here, along­side a team that has been through the en­tire jour­ney to­gether.

To every­one who helped us reach this point: thank you. We are proud of what we have built to­gether, ex­cited by what now lies ahead, and look­ing for­ward to build­ing the next chap­ter with you.

You’re also wel­come to check out our press re­lease and the me­dia kit.

You’re also wel­come to check out our press re­lease and the me­dia kit.

z.ai

Tim Curry, star of The Rocky Horror Picture Show and Stephen King’s It, dies aged 80

www.theguardian.com

Tim Curry, the ver­sa­tile per­former of screen and stage, who made his name as the flam­boy­ant Dr Frank-N-Furter in The Rocky Horror Show, has died aged 80. Reports say Curry died at his home in Los Angeles.

The ac­tor’s man­ager con­firmed to Variety that he died peace­fully. No cause of death is known as yet.

Ironically, Curry’s best-known roles all re­quired a con­sid­er­able phys­i­cal trans­for­ma­tion, to the ex­tent that he was un­recog­nis­able in all of them. Frank-N-Furter, Rocky Horror’s de­ranged sci­en­tist, saw him daubed in gar­ish makeup and wear­ing stock­ings and sus­penders, for the stage show (which de­buted in 1973) and the film adap­ta­tion (released in 1975). In the Ridley Scott-directed fan­tasy Legend (1985), he was equipped with gi­ant horns and scar­let skin as the Lord of Darkness. And in 1990, Curry was trans­formed with full-face clown pan­cake to play Pennywise in the TV minis­eries It, based on the Stephen King novel.

Alongside these show-stop­ping in­car­na­tions, Curry en­joyed a suc­cess­ful par­al­lel ca­reer in film, TV and the­atre. Born in 1946, he earned a de­gree in drama and English from the University of Birmingham in 1968 and em­barked on a stage ca­reer. His first sig­nif­i­cant role was in the orig­i­nal cast for the first West End pro­duc­tion of the con­tro­ver­sial mu­si­cal Hair, in 1968 — where he met Richard O’Brien, who would go on to cast him in his own mu­si­cal, The Rocky Horror Show. Curry went on to play Tristan Tzara in Tom Stoppard’s Travesties (taking over the role from John Hurt and Robert Powell), and Mozart in the Broadway trans­fer of Amadeus in 1980. Later high­lights in­cluded King Arthur in the Broadway run of the Monty Python mu­si­cal Spamalot.

Curry worked reg­u­larly in film, in roles in­clud­ing that of Robert Graves in Jerzy Skolimowski’s sin­is­ter The Shout, and later as Jeremy in the Ian McEwan-scripted po­lit­i­cal drama The Ploughman’s Lunch. Later, Curry found him­self work­ing largely in come­dies — in­clud­ing Home Alone 2: Lost in New York, Muppet Treasure Island, Scary Movie 2, and McHale’s Navy — be­fore pro­vid­ing voiceovers for nu­mer­ous an­i­mated films, such as Garfield: A Tail of Two Kitties, A Turtle’s Tale: Sammy’s Adventures and Ribbit.

Curry was also a reg­u­lar pres­ence on TV for more than four decades, play­ing William Shakespeare in John Mortimer’s six-part bi­og­ra­phy of the play­wright in 1978, Bill Sikes in the 1982 US TV adap­ta­tion of Oliver Twist, and the wiz­ard Trymon in the adap­ta­tion of Terry Pratchett’s The Colour of Magic in 2008. He voiced a stream of an­i­mated se­ries (Duckman, Sonic the Hedgehog, The Mask and Star Wars: The Clone Wars) and was cast in many guest roles in es­tab­lished se­ries, in­clud­ing Psych, Poirot and Criminal Minds.

I’ve had the op­por­tu­ni­ties,” he said to the Guardian in 2025, and I’m still show­ing up. You know? I think that’s what you have to do. You have to keep show­ing up.”

In 2012, Curry had a stroke while re­ceiv­ing a mas­sage and re­ceived brain surgery. He was left with mo­bil­ity is­sues and his short-term mem­ory was also af­fected. It was an odd thing, be­cause my fa­ther had a stroke and died very soon af­ter­wards,” he said. I knew I had to force my­self to re­lax and just take the op­por­tu­nity to float a lit­tle.”

In 2025, he re­leased his mem­oir Vagabond.

I’m very aware that I’m lucky,” Curry said last year. I’m as­ton­ished ac­tu­ally at how am­bi­tious I’ve been. I did­n’t think of my­self as am­bi­tious at all.”

Luke Evans, who played the role of Dr Frank-N-Furter in the re­cent Broadway pro­duc­tion of The Rocky Horror Show, paid trib­ute on Instagram. The ac­tor called him a force, a bright fierce flame” and an in­spi­ra­tion”. He added: There will only be one Tim Curry.”

Rocky Horror Picture Show co-star Susan Sarandon called him a funny sexy orig­i­nal” in an on­line post. Even wheel­chair bound he con­tin­ued to be so sweet and gen­er­ous with his fans,” she wrote. Thank you Tim for mean­ing so much to so many peo­ple.”

Carol Burnett, who starred with Curry in Annie, also paid trib­ute on­line. Nobody could play lov­able vil­lains bet­ter than he could,” she wrote. He was a dear friend. I was blessed to know him.”

Curry’s Clue co-star Michael McKean shared on X: My last con­ver­sa­tion with Tim Curry was about life and love and laugh­ter. The grim phys­i­cal state he found him­self in has now ended, a bless­ing. Our bless­ing was that we had Tim Curry in our lives. RIP, my friend.”

Qwen Studio

qwen.ai

GitHub - SenteLabsAI/OpenExecutive: AI-powered virtual executive team — a single coherent executive persona backed by 8 specialist Claude agents (FastAPI + Next.js).

github.com

Open Executive

An AI sys­tem that acts as your com­pa­ny’s vir­tual ex­ec­u­tive team — a se­nior ad­vi­sor with Harvard MBA-level knowl­edge, cus­tomized for your spe­cific busi­ness.

Demo

A walk­through of Open Executive in ac­tion — watch on YouTube.

What It Does

Developed by sen­te­labs.ai Open Executive pro­vides a sin­gle co­her­ent ex­ec­u­tive voice backed by eight spe­cial­ist AI agents:

Chief Strategy Officer — com­pet­i­tive analy­sis, M&A, mar­ket po­si­tion­ing, OKRs

Chief Financial Officer — fi­nan­cial mod­el­ing, fundrais­ing, unit eco­nom­ics, cash flow

Chief HR/People Officer — hir­ing, com­pen­sa­tion, per­for­mance, cul­ture

General Counsel — con­tracts, IP, em­ploy­ment law ba­sics, com­pli­ance

Chief Operating Officer — process de­sign, ven­dor man­age­ment, op­er­a­tional scal­ing

Chief Marketing Officer — GTM strat­egy, brand, com­mu­ni­ca­tions, PR

Chief Product Officer — roadmap, pri­or­i­ti­za­tion, prod­uct strat­egy

Board Communications Director — board decks, in­vestor re­la­tions, gov­er­nance

All re­sponses come from one con­sis­tent ex­ec­u­tive voice. The in­ter­nal agent ar­chi­tec­ture is never ex­posed to the user. Beyond Q&A, the sys­tem main­tains episodic mem­ory of past de­ci­sions and ini­tia­tives across ses­sions, and a built-in sched­uler can proac­tively sur­face fol­low-ups and time-sen­si­tive ac­tions.

Architecture

User mes­sage ↓ Executive Orchestrator (claude-sonnet-4 – 6) ↓ tool use → par­al­lel spe­cial­ist calls CSO / CFO / CHRO / GC / COO / CMO / CPO / Board ↓ each spe­cial­ist re­trieves rel­e­vant con­text from ChromaDB Built-in MBA knowl­edge + Your com­pany doc­u­ments ↓ Synthesized ex­ec­u­tive re­sponse

Knowledge — Two re­trieval lay­ers per spe­cial­ist call: (1) built-in MBA-level Markdown (knowledge/builtin/, git-tracked) seeded into ChromaDB at startup, and (2) your up­loaded com­pany doc­u­ments chun­ked and stored in a sep­a­rate com­pa­ny_­docs col­lec­tion. RAG con­text is in­jected into the user turn, never the cached sys­tem prompt.

Episodic mem­ory — After every re­sponse, a back­ground claude-haiku-4 – 5 pass ex­tracts key de­ci­sions, ini­tia­tives, and ad­vice into SQLite. The next ses­sion opens with a <past_decisions> block so the Executive re­mem­bers what it rec­om­mended last month.

Scheduler — A built-in job run­ner claims due ac­tions via UPDATERETURNING to pre­vent dou­ble-fir­ing. The API must run as a sin­gle in­stance; do not hor­i­zon­tally scale it with­out gat­ing the sched­uler first.

Prompt caching — The sys­tem prompt is struc­tured so the Executive per­sona, com­pany pro­file, and knowl­edge in­dex are cached sep­a­rately (up to 85% cache hit rate af­ter the first few turns). No dy­namic con­tent ever goes in a cached block.

See docs/​ar­chi­tec­ture.md for the full de­sign.

Tech Stack

Repo Layout

openex­ec­u­tive/ ├── pack­ages/ │ ├── core/ │ │ └── openex­ec­u­tive/ │ │ ├── or­ches­tra­tor/ # Executive per­sona + rout­ing loop │ │ ├── agents/ # 8 spe­cial­ist agents │ │ ├── knowl­edge/ # ChromaDB store + RAG pipeline │ │ ├── mem­ory/ # Company pro­file + episodic mem­ory │ │ ├── on­board­ing/ # Wizard state ma­chine + pro­file builder │ │ ├── prompts/ # Persona + do­main prompts + cache man­ager │ │ ├── api/ # FastAPI app + routes │ │ ├── in­te­gra­tions/ # Slack, Email, Telegram, Google Chat, Discord │ │ ├── sched­uler/ # Background job run­ner (single-instance) │ │ ├── alerts/ # Proactive alert sys­tem │ │ ├── au­dit/ # Audit log­ging │ │ ├── ar­chi­tec­ture/ # Internal ar­chi­tec­ture util­i­ties │ │ ├── work­flows/ # Multi-step work­flow de­f­i­n­i­tions │ │ └── cli.py # Click CLI└── ui/ # Next.js 15 web UI ├── evals/ # Eval sce­nar­ios + LLM-as-judge run­ner ├── fix­tures/ # Demo com­pany fix­tures (profiles, docs, ros­ters) ├── scripts/ # Operator scripts (Fly se­crets, Google auth) ├── docker/ # Dockerfile(s) + docker-com­pose.yml ├── fly.api.toml / fly.ui.toml # Fly.io con­figs — dev API + UI apps ├── fly.api.qa.toml / fly.ui.qa.toml # Fly.io con­figs — QA API + UI apps ├── fly.hon­cho.toml # Fly.io con­fig — Honcho mem­ory app (optional) └── docs/ # Architecture + de­ploy­ment docs

Quick Start

# Clone the repo git clone https://​github.com/​Sen­te­Lab­sAI/​OpenEx­ec­u­tive.git cd OpenExecutive

# Set your Anthropic API key cp .env.example .env # Edit .env and add ANTHROPIC_API_KEY=sk-ant-…

# Start every­thing make dev

Open http://​lo­cal­host:3000 to start chat­ting with your ex­ec­u­tive. The API runs on port 8000 and the UI on 3000.

First run: re­quires Python 3.11+ and Node 22+. The ini­tial uv sync pulls heavy ML de­pen­den­cies (ChromaDB + sen­tence-trans­form­ers/​Py­Torch), and the first boot down­loads a small em­bed­ding model (~90 MB) to build the lo­cal vec­tor in­dex — so the first make dev takes a few min­utes be­fore the app is ready. Subsequent starts are fast.

First run: re­quires Python 3.11+ and Node 22+. The ini­tial uv sync pulls heavy ML de­pen­den­cies (ChromaDB + sen­tence-trans­form­ers/​Py­Torch), and the first boot down­loads a small em­bed­ding model (~90 MB) to build the lo­cal vec­tor in­dex — so the first make dev takes a few min­utes be­fore the app is ready. Subsequent starts are fast.

For con­trib­u­tors not us­ing make:

cd pack­ages/​core uv sync source .venv/bin/activate uvi­corn openex­ec­u­tive.api.main:app –reload –port 8000

# In a sec­ond ter­mi­nal cd pack­ages/​ui && npm in­stall && npm run dev

Run the Discord Bot

Create a Discord ap­pli­ca­tion at https://​dis­cord.com/​de­vel­op­ers/​ap­pli­ca­tions

Enable the Message Content priv­i­leged in­tent (Bot → Privileged Gateway Intents)

Invite the bot with bot + ap­pli­ca­tions.com­mands scopes

Set env vars in .env: DISCORD_BOT_TOKEN, DISCORD_APP_ID, DISCORD_GUILD_IDS

Run the API nor­mally — the bot starts as part of the FastAPI lifes­pan when DISCORD_BOT_TOKEN is set:

make dev

The bot is em­bed­ded in the API process (alongside the email poller, sched­uler, and re­sumer) so it shares the same SQLite data­base and ChromaDB vec­tor store un­der /data in pro­duc­tion. Skip the to­ken to dis­able.

For it­er­at­ing on bot-only code with­out restart­ing the API, make dis­cord runs the bot as a stand­alone process against the same lo­cal DB.

Users can DM the bot, @mention it in a chan­nel (replies in a thread), or use /ask and /today slash com­mands. Slash com­mands sync to DISCORD_GUILD_IDS in­stantly on startup; leave blank for global reg­is­tra­tion (up to 1-hour prop­a­ga­tion de­lay).

Deploying to pro­duc­tion

Just set the se­crets on the ex­ist­ing API app — no new Fly app re­quired:

fly­ctl se­crets set -a openexec-api-dev \ DISCORD_BOT_TOKEN=… \ DISCORD_APP_ID=… \ DISCORD_GUILD_IDS=…

Discord user ac­cess is man­aged via the /people UI — add a Person row with dis­cord_user_id set.

The ma­chine restarts and the bot starts on the next lifes­pan boot. To dis­able in prod: fly­ctl se­crets un­set -a openexec-api-dev DISCORD_BOT_TOKEN.

Onboarding Your Company

The first time you visit the app, you’ll be guided through a wiz­ard to set up your com­pany pro­file:

Company ba­sics (name, in­dus­try, stage, team size)

Business model and rev­enue

Competitive land­scape

Strategic pri­or­i­ties

Culture and val­ues

Optional: fi­nan­cial po­si­tion, doc­u­ment up­load

After on­board­ing, the Executive will ref­er­ence your spe­cific com­pany con­text in every re­sponse.

Interfaces

Document Upload

Upload your pitch deck, fi­nan­cial model, strat­egy docs, or any com­pany doc­u­ments via the web UI or API. The Executive will ref­er­ence them when rel­e­vant.

# Via CLI openex­ec­u­tive up­load deck.pdf model.xlsx strat­egy.md

# Via API curl -X POST http://​lo­cal­host:8000/​doc­u­ments \ -F file=@deck.pdf” \ -F domain=strategy”

Deployment (Fly.io)

Two en­vi­ron­ments, each a sep­a­rate set of Fly apps, dri­ven by branch:

Both work­flows use dorny/​paths-fil­ter to de­ploy only the changed app (API, UI, or both). QA is a sta­ble twin of dev — same im­age and run­time, only the app name dif­fers (fly.api.qa.toml / fly.ui.qa.toml) — so it lags main and stays vet­ted. An op­tional Honcho mem­ory app (fly.honcho.toml) de­ploys in­de­pen­dently.

Topology

⚠️ Single-instance only: The sched­uler claims rows via UPDATERETURNING. Running two API ma­chines would dou­ble-fire sched­uled ac­tions. max_­ma­chines_run­ning = 1 is set in fly.api.toml / fly.api.qa.toml — do not over­ride it.

⚠️ Single-instance only: The sched­uler claims rows via UPDATERETURNING. Running two API ma­chines would dou­ble-fire sched­uled ac­tions. max_­ma­chines_run­ning = 1 is set in fly.api.toml / fly.api.qa.toml — do not over­ride it.

Required GitHub Actions se­crets

Deploys au­then­ti­cate with per-app Fly de­ploy to­kens stored as repo (or org) Actions se­crets. Generate each with fly­ctl to­kens cre­ate de­ploy -a <app> -x 999999h:

Per-app run­time se­crets (ANTHROPIC_API_KEY, BACKEND_SHARED_SECRET, the AUTH_* set, in­te­gra­tion to­kens) are set di­rectly on each Fly app — see scripts/​fly-se­crets.sh.ex­am­ple.

One-time boot­strap (dev)

# 1. Create apps and vol­ume fly­ctl apps cre­ate openexec-api-dev fly­ctl apps cre­ate openexec-ui-dev fly­ctl vol­umes cre­ate ex­ec­u­tive_­data –region iad –size 1 -a openexec-api-dev

# 2. Set the re­quired se­cret fly­ctl se­crets set -a openexec-api-dev ANTHROPIC_API_KEY=sk-ant-…

# 3. Create de­ploy to­kens and add as GitHub se­crets FLY_API_TOKEN_API and FLY_API_TOKEN_UI fly­ctl to­kens cre­ate de­ploy -a openexec-api-dev -x 999999h fly­ctl to­kens cre­ate de­ploy -a openexec-ui-dev -x 999999h

# 4. First de­ploy gh work­flow run Deploy (dev)” -f tar­get=both

QA boot­straps the same way against the -qa app names (push to the qa branch, or gh work­flow run Deploy (qa)“). See docs/​de­ploy­ment.md for the full run­book (operations, roll­back, com­mon fail­ure modes, why .flycast is­n’t used).

Access con­trol

The de­ployed UI is gated be­hind Google sign-in with an email al­low-list, and the pub­lic API is pro­tected by a shared-se­cret header be­tween the UI proxy and the FastAPI back­end. See docs/​auth.md for the full setup (Google Cloud Console steps, re­quired Fly se­crets, adding/​re­mov­ing users, ro­tat­ing se­crets, and a de­bug­ging table).

Configuration

All set­tings via en­vi­ron­ment vari­ables. Minimum re­quired: ANTHROPIC_API_KEY — un­less you con­fig­ure a lo­cal or OpenRouter back­end in­stead (see Running on Local Models). At least one provider must be set or the app re­fuses to start.

See .env.example for the full list.

¹ ANTHROPIC_API_KEY is re­quired only when you serve Claude mod­els di­rectly. It can be omit­ted en­tirely if you run on lo­cal mod­els (LOCAL_MODELS_ENABLED) or route through OpenRouter (OPENROUTER_ENABLED).

¹ ANTHROPIC_API_KEY is re­quired only when you serve Claude mod­els di­rectly. It can be omit­ted en­tirely if you run on lo­cal mod­els (LOCAL_MODELS_ENABLED) or route through OpenRouter (OPENROUTER_ENABLED).

Running on Local Models

Open Executive can run against any OpenAI-compatible lo­cal server — Ollama, LM Studio, vLLM, or llama.cpp — in­stead of (or along­side) the Anthropic API. Local model slugs route to your server through the same provider ab­strac­tion the hosted mod­els use; no agent or or­ches­tra­tor code changes.

# 1. Pull a ca­pa­ble, tool-use-friendly model (example: Ollama) ol­lama pull lla­ma3.3

# 2. In .env — point at the lo­cal server and list the slugs to ex­pose LOCAL_MODELS_ENABLED=true LOCAL_BASE_URL=http://​lo­cal­host:11434/​v1 # Ollama de­fault LOCAL_MODELS=llama3.3

# 3. (Optional) run with NO Anthropic key — make lo­cal the de­fault every­where DEFAULT_MODEL=llama3.3 DEEP_REASONING_MODEL=llama3.3 ROUTING_MODEL=llama3.3 # …and leave ANTHROPIC_API_KEY un­set

The listed slugs ap­pear in the Council UI model drop­down, so you can also run a hy­brid setup — keep the Executive on Claude while flip­ping in­di­vid­ual spe­cial­ists to a lo­cal model per-agent.

Caveats. Server-side web search (ENABLE_WEB_SEARCH) and Anthropic prompt caching / ex­tended think­ing have no lo­cal equiv­a­lent and are au­to­mat­i­cally dis­abled for lo­cal mod­els. Multi-agent rout­ing leans heav­ily on tool use, so pick a model that’s strong at it (e.g. Llama 3.3 70B, Qwen2.5) — small mod­els may route poorly. LOCAL_API_KEY is only needed if your server (vLLM, or a gate­way) re­quires a bearer to­ken; Ollama and LM Studio need none.

Adding a New Specialist Agent

Create pack­ages/​core/​openex­ec­u­tive/​agents/​your_a­gent.py ex­tend­ing BaseAgent

Add a sys­tem prompt con­stant in pack­ages/​core/​openex­ec­u­tive/​prompts/​do­main_prompts.py

Register in pack­ages/​core/​openex­ec­u­tive/​or­ches­tra­tor/​router.py — add to SPECIALIST_REGISTRY and the spe­cial­ist enum in SPECIALIST_TOOLS

Add do­main alias to DOMAIN_ALIASES in pack­ages/​core/​openex­ec­u­tive/​knowl­edge/​re­triever.py

Add knowl­edge docs to knowl­edge/​builtin/​your_­do­main/

Add at least 2 eval sce­nar­ios to evals/​sce­nar­ios/

GitHub - tailscale/tailcat: like netcat, but over Tailscale's data plane, without Tailscale's control plane

github.com

Tailscale with­out Tailscale, by Tailscale”

Tailcat is a remix of Tailscale open source pieces to act like net­cat, but over Tailscale’s data plane, with­out Tailscale’s con­trol plane. Tailscale’s data plane (magicsock, in­ter­nally) gives you point-to-point WireGuard®-encrypted tun­nels be­tween two ma­chines with DERP as the NAT-hole-punching com­mu­ni­ca­tion side chan­nel and the ul­ti­mate re­lay-of-last-re­sort if NAT tra­ver­sal fails. Instead of us­ing the Tailscale con­trol plane, all tail­cat con­nec­tion meta­data is ex­changed out of band, how­ever you want.

The tail­cat CLI (in cmd/​tail­cat) is built on the tail­cat Go li­brary (importable as github.com/​tailscale/​tail­cat).

Whether you use tail­cat as a CLI tool or li­brary, one side runs a tail­cat server (listener) and gets back a short con­nec­tion to­ken. The other side passes that to­ken to tail­cat’s client side to con­nect. All traf­fic be­tween the two is en­crypted end-to-end with WireGuard. The ini­tial con­nec­tion boot­straps through a DERP server (see be­low), and then mag­ic­sock per­forms NAT tra­ver­sal to up­grade to a di­rect peer-to-peer UDP con­nec­tion when pos­si­ble (usually!).

You don’t need a Tailscale ac­count, root/​ad­min ac­cess on the ma­chine (it does­n’t al­ter your ma­chine’s rout­ing ta­bles, DNS, etc.). It’s just a user­space li­brary and CLI tool.

And it’s all open source.

You can use our free rate-lim­ited DERP re­lays (the de­fault DERP map is https://​tail­cat.dev/​derpmap.json) or you can run your own.

There’s also an ex­per­i­men­tal in-browser web demo (tailcat com­piled to WebAssembly) at https://​tailscale.github.io/​tail­cat/ that can send and re­ceive files or text, in­ter­op­er­at­ing with the CLI. Browser traf­fic is re­layed over DERP only, with no di­rect con­nec­tions un­til WebRTC sup­port (#4).

Install

$ go in­stall github.com/​tailscale/​tail­cat/​cmd/​tail­cat@lat­est

Or with Nix flakes, run it di­rectly or in­stall it:

$ nix run github:tailscale/​tail­cat $ nix pro­file in­stall github:tailscale/​tail­cat

Usage

Pipe stdin/​std­out be­tween two ma­chines

Server starts, print­ing out its ephemeral ad­dress:

$ tail­cat # Selected boot­strap re­lay re­gion 302, San Francisco # 🐈 Server lis­ten­ing with new ad­dress: tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNGF­pGQEu (hangs, wait­ing…)

And then the client can:

$ echo hello | tail­cat tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNGF­pGQEu $

Then the server un­blocks:

$ tail­cat # Selected boot­strap re­lay re­gion 302, San Francisco # 🐈 Server lis­ten­ing with new ad­dress: tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNGF­pGQEu hello $

Expose lo­cal ports through the tun­nel

Or you can serve a lo­cal TCP port, for­warded to lo­cal­host:

$ tail­cat –serve=8080,8443 # or –serve=all # 🐈 Server lis­ten­ing with new ad­dress: tcXXXXXXXXX

And then the client:

$ tail­cat tcXXXXXXXXX 8080 GET / HTTP/1.1 Host: foo

HTTP/1.1 200 OK ….

Auth-free SSH server

On Linux and ma­cOS, you can run an SSH server too with no auth. (If you want auth, you can just tail­cat –serve=22 and proxy to your sys­tem SSH server)

$ tail­cat –serve=no-auth-ssh # 🐈 Server lis­ten­ing with new ad­dress: tcXXXXXXXXX

And on the client side:

$ tail­cat ssh tcXXXXXXXXX $ tail­cat ssh tcXXXXXXXXX ls -la

Misc com­mands

Ping to test con­nec­tiv­ity; each pong re­ports whether it ar­rived via a DERP re­lay or a di­rect path. –until-direct keeps ping­ing (up to –timeout, de­fault 10s) un­til a di­rect path works, ex­it­ing non-zero if one does­n’t:

$ tail­cat ping –until-direct <token> pong in 42.1ms via DERP(sfo) pong in 1.2ms via 203.0.113.7:41641

Run a com­mand through a SOCKS5 proxy routed over the tun­nel:

$ tail­cat socks <token> curl http://​server.tail­cat:8081/

Tokens also work di­rectly as URL host­names: the SOCKS proxy rec­og­nizes and di­als them, so the to­ken ar­gu­ment is op­tional. (Tokens are case-sen­si­tive; this works with curl and most CLI tools, but not with browsers, which low­er­case host­names.)

$ tail­cat socks curl http://&​lt;to­ken>:8081/

Act as an exit node so the client can reach the server’s net­work:

$ tail­cat –serve=exit-node

Parse a con­nec­tion to­ken and print its con­tents (the server’s WireGuard pub­lic key and DERP info) as JSON, with­out con­nect­ing to any­thing:

$ tail­cat parse tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNGF­pGQEu { ServerPublic”: nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34″, RegionID”: 302 }

Resolve a short to­ken (which ref­er­ences a DERP re­gion by ID, re­quir­ing clients to fetch the DERP map) into a longer self-con­tained one with the DERP server info em­bed­ded, let­ting clients con­nect more quickly:

$ tail­cat re­solve tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNGF­pGQEu tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNG­Fy­gaFh­ToGjY­WhudG­MzMD­JhLml­w­bi5kZXZh­NG0yMDguM­TExLjM5LjM4YTZzMjY­wNzpmNzQ­wO­jA6M2Y6O­j­cyMA

Parsing that re­solved to­ken shows the em­bed­ded DERP info:

$ tail­cat parse tcom­FwW­C­C­cjS5nKN­qAod034n­Wo­JZW0LZqD­hhC8U_d­Kd­nDRYQ8uNG­Fy­gaFh­ToGjY­WhudG­MzMD­JhLml­w­bi5kZXZh­NG0yMDguM­TExLjM5LjM4YTZzMjY­wNzpmNzQ­wO­jA6M2Y6O­j­cyMA { ServerPublic”: nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34″, Region”: [ { Nodes”: [ { HostName”: tc302a.ipn.dev”, IPv4”: 208.111.39.38″, IPv6”: 2607:f740:0:3f::720″ } ] } ] }

A server can print the long self-con­tained form di­rectly with the –full-address flag.

Key Management

A server’s ad­dress (connection to­ken) is de­rived from its WireGuard key, so the key you use de­ter­mines who can reach you:

Ephemeral keys (the de­fault): each server run gen­er­ates a fresh key in mem­ory and prints an ad­dress no­body has ever seen. When the process ex­its, the key is dis­carded and the ad­dress is dead for­ever. This is the safe de­fault: shar­ing that ad­dress only ever refers to that one run.

Ephemeral keys (the de­fault): each server run gen­er­ates a fresh key in mem­ory and prints an ad­dress no­body has ever seen. When the process ex­its, the key is dis­carded and the ad­dress is dead for­ever. This is the safe de­fault: shar­ing that ad­dress only ever refers to that one run.

Saved keys: tail­cat genkey gen­er­ates a key saved to disk so the ad­dress stays sta­ble across restarts. The flip side: any­one you’ve ever shared that ad­dress with can con­nect to any fu­ture server us­ing that key, un­less you re­strict clients with –allow (see tail­cat genkey –client).

Saved keys: tail­cat genkey gen­er­ates a key saved to disk so the ad­dress stays sta­ble across restarts. The flip side: any­one you’ve ever shared that ad­dress with can con­nect to any fu­ture server us­ing that key, un­less you re­strict clients with –allow (see tail­cat genkey –client).

The CLI says at startup which kind it’s us­ing, so you know whether you’re start­ing a fresh sin­gle-use server or re-lis­ten­ing on an ad­dress you may have shared in the past.

$ tail­cat genkey –region=nyc # prints the to­ken; key saved to ~/.config/tailcat/keys/default.private.json

# later; the key named default” is used au­to­mat­i­cally once it ex­ists: $ tail­cat –serve=8080 # 🐈 Server lis­ten­ing with saved key default”: tcXXXXXXXXX

# … un­less you force a one-off ephemeral key: $ tail­cat –serve=8080 –key=new # 🐈 Server lis­ten­ing with new ad­dress: tcXXXXXXXXX

That is, de­fault is a magic key name: once it ex­ists, plain tail­cat silently uses it in­stead of gen­er­at­ing an ephemeral key, and the startup line above is what tells you which hap­pened. Use –key=new to get an ephemeral key any­way, –key=<name> to use a dif­fer­ent saved key, or tail­cat genkey –delete –key=default to re­move the saved de­fault key. tail­cat genkey –list lists your saved keys.

Tokens can also be pub­lished as DNS TXT records and looked up by name; a DNS name works any­where the CLI takes a to­ken:

# If ex­am­ple.com has a TXT record tailcat=tc…” $ tail­cat ex­am­ple.com 8080 $ tail­cat ssh ex­am­ple.com $ tail­cat ping ex­am­ple.com

Examples

Protected SSH server over DNS

Who needs port for­ward­ing or port knock­ing? This runs an SSH server reach­able from any­where by name, with no open in­bound ports on the server, where WireGuard au­then­ti­cates the client be­fore the SSH server ever sees a packet.

On the client ma­chine, gen­er­ate a client iden­tity key­pair. It prints the pub­lic key, which is all the server needs to know:

client$ tail­cat genkey –client # wrote file to ~/.config/tailcat/keys/client-default.private.json nodekey:cf­b6b­fa77a0654d7450947fd6ace­f17d2cd848­da1d30b2540b13­dac272ddfd16

On the server, gen­er­ate a server key­pair pinned to its near­est DERP re­gion (see why be­low), then serve SSH to only that client:

server$ tail­cat genkey –fixed-region # wrote file to ~/.config/tailcat/keys/default.private.json tcXXXXXXXXX

server$ tail­cat –serve=22 –allow=nodekey:cfb6bf…ddfd16 # 🐈 Server lis­ten­ing with saved key default”: tcXXXXXXXXX

Publish the to­ken in DNS as a TXT record:

my-server.ex­am­ple.com. 300 IN TXT tailcat=tcXXXXXXXXX”

And then the client side is just:

client$ tail­cat ssh my-server.ex­am­ple.com

Client modes au­to­mat­i­cally use the saved client-de­fault key when it ex­ists, so no ex­tra flags are needed to pre­sent the al­lowed iden­tity. Anyone else’s hand­shake is silently ig­nored: they can’t reach the SSH server, or even learn that one is run­ning.

Why –fixed-region: it dis­cov­ers the near­est DERP re­gion once, at genkey time, and bakes its ID into both the printed to­ken and the saved key file, so server restarts bind to the same re­gion (keeping the pub­lished to­ken valid) with­out re-prob­ing. Plain tail­cat genkey de­faults to –region=auto, which in­stead bakes in pick at startup”: fine for one-off use, but a to­ken pub­lished in DNS should name a fixed re­gion so clients and fu­ture server restarts all ren­dezvous in the same place. (–region=<name> pins an ex­plicit one in­stead; –region=list shows the choices.)

TODO: make the client more ro­bust here if the DERP map changes over time: #7

Bring your own DERP re­lay

Nothing re­quires Tailscale’s re­lays: run your own DERP server (it needs a host­name with a TLS cer­tifi­cate, which der­per can get it­self via Let’s Encrypt), then gen­er­ate a server key that uses it by pass­ing its host­name (or sev­eral, comma-sep­a­rated) as the re­gion:

server$ tail­cat genkey –region=derp.example.com tcom­FwW­C­CAIsKO­qPU­ux6­ClG2R­M4A_vO­q4VBzG­gHG­Gjq9OsJuFK­SWFy­gaFh­ToGhY­Wh­wZGVy­c­C5leGFtcGxlLm­N­vbQ

server$ tail­cat –serve=22

The to­ken em­beds your re­lay’s host­name:

$ tail­cat parse tcom­FwW­C­CAIsKO­qPU­ux6­ClG2R­M4A_vO­q4VBzG­gHG­Gjq9OsJuFK­SWFy­gaFh­ToGhY­Wh­wZGVy­c­C5leGFtcGxlLm­N­vbQ { ServerPublic”: nodekey:8022c28ea8f52ec7a0a51b644ce00fef3aae150731a01c61a3abd3ac26e14a49″, Region”: [ { Nodes”: [ { HostName”: derp.example.com” } ] } ] }

so clients need no ex­tra flags and never con­tact Tailscale’s DERP map server or re­lays, and the only rate lim­its are yours. Alternatively, if you run a whole fleet of re­lays, serve your own DERP map JSON and point both sides at it with –derpmap-url.

Go li­brary

A min­i­mal server that an­swers any TCP port through the tun­nel and prints its to­ken. The zero value Server picks de­faults for any­thing un­set: a fresh ephemeral key, the near­est re­gion of the de­fault DERP map, and log.Printf log­ging (set Logf to log­ger.Dis­card for quiet):

pack­age main

im­port ( fmt” log” net”

github.com/​tailscale/​tail­cat )

func main() { s := &tailcat.Server{ OnTCP: func(port uin­t16) func(net.Conn) { re­turn func(c net.Conn) { fmt.Fprintf(c, hello from port %v\n”, port) c.Close() } }, } if err := s.Start(); err != nil { log.Fa­tal(err) } fmt.Println(s.ConnBlob()) se­lect {} }

And a min­i­mal client that di­als it, given that to­ken as its ar­gu­ment. Like Server, the Client zero value works with just its Server to­ken field set (tailcat.NewClient is short­hand for ex­actly that), and the tun­nel is es­tab­lished lazily by the first dial:

pack­age main

im­port ( context” io” log” os”

github.com/​tailscale/​tail­cat )

func main() { cl := tail­cat.New­Client(tail­cat.ConnBlob(os.Args[1])) de­fer cl.Close() c, err := cl.Di­alTCP­Port(con­text.Back­ground(), 80) if err != nil { log.Fa­tal(err) } io.Copy(os.Std­out, c) }

$ ./client tcom­FwW­CAWf933BLELdzd3RkHiOufJ… hello from port 80

How it works

Connection to­kens

A Tailcat server is iden­ti­fied by a con­nec­tion to­ken (called a ConnBlob in­ter­nally). It looks like tcXYZ… and is a tc” pre­fix fol­lowed by base64-en­coded CBOR con­tain­ing:

The server’s WireGuard pub­lic key (Curve25519, 32 bytes)

DERP info. Either:

a small in­te­ger ref­er­enc­ing one of the de­fault Tailscale-run tail­cat servers), or full DERP server meta­data, to ei­ther use a cus­tom DERP server, or to avoid the client need­ing a po­ten­tial round-trip to fetch the lat­est DERP map (the server’s –full-address flag and the tail­cat re­solve sub­com­mand pro­duce this form)

wsj.com

www.wsj.com

Please en­able JS and dis­able any ad blocker

reuters.com

www.reuters.com

Please en­able JS and dis­able any ad blocker

Just a moment...

twitterwebviewer.com

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.