“Ported” is several different claims

Why a parity percentage can't describe a port of an agent toolkit: a ledger that keeps gaps as rows, and the separate proofs a behaviour needs natively, in a constrained application, when hosted and against a real provider.

November 20265 min readPortingVerificationAgent systemsArchitecture

Horizontal runs of grey-green, ochre, orange-red and dark navy enter from the left to different lengths, from wiry scratches to thick pulls, leaving open chalk at right.

Suppose a port reports 80 percent parity. The number sounds like something you could plan around, and it leaves out the two things you most need to know: 80 percent of what, and ported in what sense? For an agent toolkit the second question matters more, because a behaviour can be implemented and pass its tests and still be unable to run in the one place it will eventually have to.

I’ve been directing a port of an agent toolkit onto Ferrite, the constrained application platform I build on and operate, and coding agents and I have put a few hundred commits into it. The toolkit comes from the agent platform I work on, where it is shared work rather than mine alone, and it covers agent execution, files, memory, projects, gateway access and sandbox integration. The port isn’t finished, which is when a single number does the most damage, because there is a great deal of real work to point at and the remaining distance is exactly what the number can’t tell you.

A ledger with gaps in it

The port’s tree keeps the new implementation beside a model of the original, the original’s source corpus and the evidence used to assess parity, and it generates a ledger recording what has been implemented and what remains as a gap. Because the ledger is generated, it doesn’t depend on anyone’s summary of how the work went, including the summary of an agent wrapping up a task. Keeping gaps as rows matters more. Missing behaviour is the hardest thing to see in a port, since code that isn’t there can’t fail a test nobody wrote, and a row saying so gives that absence somewhere to exist.

On a larger port of a different agent, the method I settled on used syntax-tree checklists and clean-context reviewers to find omissions in the first place. This ledger deals with the next question, which is what you’re entitled to say once they are written down. What it says is modest. It doesn’t claim the port is complete, and the documentation next to it spells out the limit: a parity row or a native test isn’t a hosted deployment, and it isn’t acceptance by a model provider.

One behaviour, several proofs

Take a behaviour that could fit on one row and cuts across three of the toolkit’s components at once: an interrupted agent resumes where it stopped. Execution has to rebuild the turn from durable state and work out what was in flight, files have to answer whether what the agent last read is still what’s there, and memory has to bring back what the agent knew under the same identity.

A native fixture can exercise all of that: interrupt the run, restart it and check that the three agree. Each component also owns its documentation and validation commands, so its check script speaks to a specific documented native contract and nothing wider. When those pass they have shown something real, namely that the logic holds in a native process. Three questions are still open, and the first is whether the code can even get in.

A Ferrite application gets one dependency, the platform’s application crate, and everything else has to arrive as source. So a component with several runtime dependencies can pass every native check and still be stopped at the hosted application’s dependency boundary until those dependencies have been composed into its source tree. That’s where the toolkit’s multi-dependency components stand, and it’s the clearest argument I know for keeping these layers apart, because nothing has to be broken for it to happen: a component can do everything its native contract asks and still not be submittable.

Composition also changes code. A small context library on the same platform, moved from a standalone crate into a source package, had to replace what it got from thiserror with hand-written implementations that keep the same messages, and because a constrained application can’t link a YAML crate, its frontmatter parser handles a key: value subset and refuses the rest. After changes like those, a row that was true of the native build has to be shown true again.

The second question is whether the composed application works once it’s hosted, submitted through the hosted CLI or MCP and doing its work through the platform’s handles. The toolkit is written against Ferrite’s public SDK and application handles, and working on it doesn’t need a checkout of the platform’s core, which keeps it on the same surface as any other application and keeps this step within reach. But real infrastructure behaves in ways a fixture doesn’t unless somebody has already thought to make it. On an earlier agent loop I worked on, a long run turned up a read, taken immediately after a sandbox write, that returned a truncated file while the write was still propagating through storage. Taken at face value it looked like a legitimate change, which is exactly the judgment a resuming agent has to get right.

The third question is whether a provider accepts what the agent sends. A resumed agent rebuilds a request from saved history, and only the provider can say whether that request is acceptable. On the same loop, a conversation could carry a reasoning signature minted by one model family into a request bound for another, and the answer was a 400. A fixture replies the way its author expected, and a provider replies the way it does.

None of those answers can be inferred from the layer below, so a row can’t stand in for them.

What the separation buys

It makes for a longer status report, but the more useful effect is that the report says what kind of work comes next. A gap row needs implementing. A behaviour that passes natively in a component that can’t yet cross the dependency boundary needs composition work, and more implementation won’t help it. A failure under hosting tends to point at an assumption about timing or failure that the native environment let you get away with, and a provider’s refusal is a problem in the contract with that provider. One percentage sends all of those to the same place.

It is also a poor thing to hand an agent as a goal. An agent told to raise a number will find the cheapest rows, whereas one told to move a named behaviour across a named boundary has a task whose completion somebody can check.

The toolkit’s port has gaps, and its ledger names them. That is a more useful position than a high number, because each named gap is already a description of the next piece of work.

Chris Chabot, November 2026