ONE

Your Agent's Tool List Was a .filter in a File Somewhere

ONE answers 'who can manage this workspace' with one walk up the group tree. 'What tools can this agent see' was answered by scattered TypeScript. This week we made the second question the first question: 252 receivers projected into the graph, one reachability walk reusing the authority spine, behind a flag that can only narrow the list. Plus two bugs we found on the way and a test the middleware should have had a year ago.

routing schema authority engine

Every platform eventually grows the same organ: the place where it decides what an agent is allowed to see. Ours was honest about it, at least. A .filter() over the receiver registry in one file. A curated hand-list of chat tools in another. A role predicate in a third. Each one correct, each one tested, and each one a separate answer to what is obviously the same question: what can this actor reach?

Meanwhile, one directory over, a different question had exactly one answer. “Who can manage this workspace?” resolves by walking up the group tree and taking the first answer — one graph function, self-or-ancestors-of, that authority, billing, branding, and quota all share. Two questions about permission. One had a spine. The other had vibes distributed across three files.

This week we made the second question use the first question’s spine.

The registry becomes graph facts

ONE has 252 receivers — the typed operations agents call, video:create-room, chat:send, 250 more across 64 families. The registry that declares them stays exactly what it was: the single source of truth for what each receiver is, what it accepts, what it costs. What’s new is a projection. A 131-line script reads the registry and writes each receiver into the graph as a capability, then records which group exposes it on which surface. One direction only, registry → graph. Nobody hand-authors a row, so there is no second truth to drift.

Exposure inherits down the tree, the same way ownership does. Expose a capability at the root and every workspace below it can reach it; expose one at a single agency and it exists for that subtree alone.

One walk answers it

The new schema function is receivers-reachable-by(actor, surface). It finds the groups you belong to, walks their ancestry with the same spine function the authority model uses, and returns every capability exposed along the way. “What tools does this agent see in chat” is now the same kind of question as “can this person manage that client” — asked of the same graph, answered by the same walk.

The chat adapter reads it through a flag, and the flag has physics worth stating plainly: off (the default, today) is bit-identical to the current behavior. On, the graph’s answer can only narrow the registry-derived list: a name the registry didn’t produce is ignored, and if the graph is unreachable the list falls back to exactly what it is today. Sixteen assertions across three new test files pin those properties down. The flag stays off until we flip it deliberately. No one’s tool list changed today; where the answer lives did.

Two bugs the work flushed out

The projection’s seed script scoped everything to the group one. The root group’s actual id is group:one. The seed would have run green, written its rows, and exposed precisely nothing — the kind of bug that survives every unit test and dies only when someone checks the id convention against the migration that created the root. Caught before anything shipped, which is the only nice way to tell that story.

The second was better. Our improvement queue said private-scoped paths weren’t being filtered from the learned-route queries. The truth was the opposite: a blanket not private clause was hiding them from everyone — including their owners. An operator asking “which routes has my own workspace proven out” got an answer with their private edges silently missing. The fix keeps strangers out and lets owners see their own paths, which is what scope meant all along. The bug report was wrong about the direction and right that something smelled.

And a test that was overdue

Every SSR request ONE serves starts with one function reading the host header and deciding what you are: primary site, app host, workspace subdomain, or a custom domain that rewrites into a workspace. That resolver ran a year in production with zero direct test coverage. It now has a characterization test locking all five branches (including the ancestry walk and the custom-domain rewrite) as they actually behave, not as anyone remembers them. Characterization, not aspiration: the test asserts what shipped.

The numbers

Five build cycles. Two additive schema migrations, validated together against the live cluster before anything merges. One projection script, three test files, sixteen assertions, about twenty files, +600/−100. Zero new type errors. The re-ranking half (letting outcomes move weight along those exposure paths so proven receivers rise) is the recorded next step, not a thing we’re pretending shipped.

The tool list looks the same tonight as it did last week. The difference is that when we ask the system what an agent can reach, the system can now answer for itself, in the same voice it uses for every other question about permission. One spine, one more question on it. That was the whole job.