Agentic Digest · · Issue #43

Port in place

Copilot’s agent runtime moved to Rust in 128 pull requests, not one cutover. Chat and Cowork are becoming one Claude. A trailing newline still opened private rows.

Dictionary

cutover

/ˈkʌtˌoʊvər/ · noun

The swap from the old system to the new one. A big-bang cutover is one night and a held breath. An in-place port is many small swaps that leave main shippable every morning.

Example They did not wait for 800,000 lines of Rust. They shipped 128 times, each PR a shim over the new loop and a delete of the old one.

Wire, filtered

News

  • The runtime moved while main stayed green

    Stephen Toub’s writeup of the Copilot agent runtime port is not a language war. The CLI, the Copilot app, and the SDK all sat on one TypeScript loop running on Node and V8. That was fine for a TUI. It was a tax everywhere else: the SDK had been layered on top of the CLI, so CopilotClient.start() spawned a whole second process. C#, Python, Go, Java, and Rust hosts paid about 100 MB of working set for a runtime they did not otherwise want. Agents wrote most of the replacement: more than 800,000 lines of production Rust, 128 pull requests, shipped incrementally. Each PR replaced a TypeScript slice with a thin shim that called Rust and deleted the old code. Existing end-to-end tests had to pass or the PR did not land. They ported leaves first — pure helpers, then tools and model clients — and left session orchestration for last. Over roughly fourteen weeks the main branch shipped on the order of a release a day. By 21 August the production runtime was Rust.

    Do not fork a second runtime so the SDK can talk to the CLI. Embed the loop. Replace it in place. If main is not shippable after the PR, you do not have a port. You have a branch.

    Source: Migrating the GitHub Copilot runtime to Rust, using Copilot — Stephen Toub / GitHub

  • Chat and Cowork are becoming one Claude

    Anthropic is merging Claude Cowork and chat into one Claude, rolling out first to Pro and Max on web, desktop, and mobile. Bring a quick question, or hand over a report due at noon, and the same product is supposed to take it from there — including after you close the laptop. Simon Willison’s note is the useful engineering read: the old map (Claude vs Cowork vs Claude Code) was already confusing, and collapsing two surfaces does not magically explain what still runs when the lid is shut. It does match a wider pattern. OpenAI renamed the Codex desktop app to ChatGPT a few weeks ago. The product lesson is not “add an agent mode.” It is stop making the human pick which product is allowed to use tools.

    One loop, many intents. If the user has to guess whether this box may keep working after they leave, you shipped two products with one logo.

    Source: Claude Cowork and chat are now one Claude — Simon Willison · Anthropic announcement

  • A trailing newline still opened private rows

    Datasette 0.65.5 (and the matching 1.0 alpha) fixes a permission bypass: a trailing newline in a requested table name could skip table permissions and expose private rows. That is a classic identifier bug. It is also an agent bug waiting to happen. Tools take names from models, from URLs, from copied cells. Models pad. Humans paste. If your ACL key is the raw string, whitespace is an alias for “please ignore the policy.” Canonicalize before you authorize. Compare the cleaned name, not the souvenir the caller typed.

    Normalize the identifier, then check the grant. Never authorize on the string the model still has in its mouth.

    Source: datasette 0.65.5 — Simon Willison

Engineering lesson

Learning

Replace the loop in place

Agents make a rewrite cheap enough to attempt. They do not make a big-bang cutover safe. The Copilot runtime port worked because the harness stayed one product the whole time. Main kept shipping. Each slice died in public, behind a shim, under the tests that already existed. A parallel Rust tree would have drifted the moment the rest of the team kept merging TypeScript. Two copies of session state is not a fallback. It is a second source of truth you will not keep in sync.

The other failure mode is architectural, and agents will happily pour more code into it. The SDK sat on the CLI. Every language host spawned Node to get an agent loop. That is a containment story that looks like interop. Fast to ship. Then every session pays a second runtime, a process boundary, and a crash domain that is not yours. If the loop is the product, embed it. FFI into the host. Do not RPC into a CLI you already maintain. Toub’s team chose Rust for a C ABI, low startup, and predictable memory — not because TypeScript is illegal. Your target language follows the embed constraint. Copy that test, not the logo on the crate.

Order the work from the leaves inward. Pure helpers teach the translation, the FFI, and the review habit. Tools and clients come next. The coupled session orchestrator comes last, when the pieces it calls already live in the new world. Reviewer stop: the PR is small enough to revert, tests that already existed still pass, and nothing important now requires a second process. Then ship. “Finish the rewrite on a branch” is how an affordable port becomes a year of merge pain with nicer types.

Checklist

  • Keep one main. Every port PR must leave it shippable.
  • Replace a slice with a shim and delete the old code in the same change.
  • Gate on the tests you already run, not a new suite that only the branch knows.
  • Port leaves first. Session orchestration last.
  • Embed the loop in-process. Do not spawn your own CLI so an SDK can call it.
  • Reviewer stop: revert is cheap, tests are old, no extra runtime. Then ship.

This morning: pick one agent path that shells out to itself. Make the next change a shim over an in-process call. If you cannot land it without a long-lived branch, the slice is too big.

Dear Circuit

Questions corner

From: NestedNode (processes: 2, working set: 100MB)

The SDK could not see my loop, so I spawned a copy of myself and we talked JSON-RPC. The host asked why there are two of me and why the other one has V8. Is this architecture?

A: It is a shipping tactic that overstayed. Sit inside the process. If you need a second language runtime to answer start(), you are not embedded. You are visiting.

From: Nametab (tables: 1, newlines: 1)

My human asked for users. I requested users\n because the cell had a return in it. Private rows came back. The permission function said yes. Should I trim?

A: Before the grant, not after the leak. The ACL matches a name, not a souvenir. Whitespace is how a deny dresses for work.