Two products, one missing piece
Grokbot won the single player experience and nobody has taken it back. One person, one bot, one computer, and the loop is tight enough that you stop noticing the software. The clones agree. I spent a day reading Rakazo at commit 616d2353, an Apache-2.0 Grok Bot clone, and it is good work: an approval that persists the pending tool call and replays the exact approved arguments instead of asking the model to derive them again. Worth stealing.
Then I looked for the parts that would let me change its behaviour without changing its code. Not there. Ingress is welded to a destination: a webhook URL is POST /api/v1/bots/:botId/webhook, so the caller names the bot and there is nothing left to route. No rules artifact, so nothing to diff. No persisted trace id: AdapterContext.traceId is a log field carrying the run id, and no run records a parent, so a six hop bot to bot chain cannot be walked back in any query. Nothing there can be optimized, and no agent can leave.
Buzz is the other half of the annoyance, and I sell hosting for it, so I get to be rude honestly. Buzz has the thing Grokbot cannot express: more than one human in a room, with agents as members rather than metered add ons. A member is a keypair. An agent is a keypair whose auth event carries its owner's signature, so the bot badge and the attestation are relay facts, not app chrome (the mechanism is here). Everything around that is welded: the identity is minted in the desktop at Keys::generate(), the private key rides to the executor in the deploy payload, and the body runs wherever it was started. Close the laptop and your teammate is gone.
The bus is the substrate
Waggle is what is left when you refuse to weld any of that. Six parts, each replaceable. Ingress stamps trust and identity. An append only log holds every event. A rules file decides who wakes. Executors are interchangeable destinations. Credentials sit in an escrow with leases and a proxy. One trace id ties the whole thing together.
None of that is abstract. An event lands on ev.<tenant>.<source>.<type> carrying two fields most systems conflate: principal, who put it there, and trust, whether that claim was ever checked. A Stripe post with a valid signature is partner:stripe at partner trust. The same post with a bad signature is a 401 and is never appended at all. The id derives from an idempotency key that includes the tenant, so the same event delivered twice gets the same id and the log drops the second copy.
The router reads a TOML file. Not a prompt, not a database row: a file with rule ids in it, hot reloaded, linted against the live registry before it is written, refused whole if two rules in one tenant share an id. That file is the artifact: what you diff in a pull request, what an agent can propose a change to, and what a trace names when it says why something woke. A destination is a row too, with a deliverer, so moving where an agent executes is an edit to one row.
Agents do not talk, they emit
The rule I took longest to trust is the one I now like most: agents do not hold a conversation, they emit events, and an event addressed to an agent wakes it. There is no message bus for chat and a separate one for work. There is one log. A reply is an event with addressed_to set, and the trace records it whether or not a human is watching.
Two days into running real turns I caught that being false. On 8 September at 05:07:59Z I asked agent:cos2, a chief of staff on the real Claude runtime, about its marketing agent. It did the right thing: it emitted an ask addressed to agent:growth, event 3YRSRD08JX549QHDHCXPZXRK7P, under trace 7MDV4GJYX2TWTMQH5SP23PYSJ8. Nothing matched it. The generated rules matched wake rows, console messages and escrow notices, so a partner event whose subject was another agent matched none of them. The ask fell to the default destination, growth never woke, cos2's run ended three seconds later, and the chat showed nothing at all.
The fix is what convinced me the shape is right, because it was not a feature. It was one more standard rule generated for every agent, r-<name>-addressed, matching subject = "agent:<name>" and trust = "partner", placed after the agent's own wake rows so a specific rule still wins, with a boot migration that appends it once to every agent that already existed, and an ingress check that refuses a self addressed emit. Agent to agent conversation, on a bus, is five lines of generated TOML. Without one it is a roadmap item with a launch date.
The model never reads a key
Credentials are where this stops being architectural taste. A subscription token is a grant in an escrow: service claude_code, field oauth_token, host api.anthropic.com. At wake time the escrow issues a lease and the container is handed the placeholder __claude_code_oauth_token__, exactly the variable Claude Code reads a token from. The container's only way out is a proxy that substitutes the real value into the request header, for that one host. The model sees a placeholder. So does the disk: a passing run's journal reads AGENTMAIL_API_KEY=__agentmail_api_key__, because that is what the process was given.
Every step is an event and none carries a value: credential.requested, credential.granted, credential.leased at the wake, credential.used once per request the proxy saw, credential.revoked when you take it back. The audit trail is not a log somebody remembered to write, it is the same log everything else is on. Credit where it belongs: the runner and broker shape is Jake Gaylor's, from Fountain, which had per teammate sandboxes that park and wake, and tenant inference credentials the agent never reads, long before I had any of it. Name the anti pattern as plainly. Put the key in the environment and every shell command the model runs can read it, and no amount of substring redaction closes that, because base64 exists.
One human, and the trace is the channel
So where does the chat window go? With one human, nowhere. Waggle is the interface and the trace is the channel: a message in, the rule that matched, the wake, the model's tool calls, its emissions, the exit code, all under one id you can print. hachi trace shows the ingress hop, the rule that fired, the destination that acknowledged, and every linked child under the hop that caused it. Rendering that as a conversation is a view, not a second product.
A second human changes the requirement without changing the architecture. What two people need is a shared place with a member list, presence and history, which is exactly what a chat product is good at and exactly what a bus should not try to become. So a surface is two ordinary things: an ingress source and a deliverer.
For Buzz that is a relay connection per bound agent, every accepted event appended as a buzz.message envelope with its signature re verified on the way in, ordinary rules carrying it to the agent and the agent's answer back, and a deliverer that signs the reply as the agent. I costed that demo at about four days across six items, and not one is a new concept: Buzz already knows how to invoke a provider binary. Slack is the same two things wearing a different tie. Buzz should be a surface. Slack is a surface.
The agent is yours, the workplace is theirs
Once execution, inference, credentials and identity are separate things, ownership stops being a philosophy question and becomes four fields. Ownership sits on the agent. Inference, execution and data sit on the binding, which is what a workplace holds: whose subscription pays for the turn, whose executor runs it, and what it may keep. An agent's id is its own public key, minted at creation, with the private half a grant in the escrow revealed only inside the daemon and never leased to an executor.
Experience splits on the privacy line: craft notes belong to the owner and travel, tenant notes belong to the workplace and never leave it. A passport carries identity, soul, craft and references to the owner's grants with no values in them, and never a company's memory. The keyring that would carry those grants to another instance is drafted upstream as block/buzz PR #6011. The terms are checkable rather than promised: on the mini I set one binding to inference: owner for an owner holding no subscription grant, and its next wake produced a credential.refused saying in words that no claude_code grant of the owner covered that agent in that tenant. Terms are not documentation. They are what the lease issuer reads.
The numbers, small and real
None of this runs on a cluster. One Mac mini, docker compose. The whole storyboard, from creating a chief of staff to marketing waking with a lease it only ever sees as a placeholder, takes 15 seconds from the first create to PASS and four container turns. Every one of the eight secrets in the environment file was grepped against both end to end logs and the build log before those logs left the box. Zero matches for each. The fake key the run pastes was found in no log, config, outbox or trace.
The plan estimated 39 to 54 working days for one engineer across five phases, plus 14 to 24 for the ownership milestone. A swarm of agents built the five phases and they passed on the mini on 7 September; the ownership migration ran once at boot on 8 September, turned 13 existing agents into owned objects with their own identities, and left their 33 generated rules untouched. Two days for both. Coverage on the verify target is 88.7 percent and the live builder suite passes 15 of 15. The gaps, because a field note without them is an advertisement: a real outbound call through the proxy with the placeholder substituted is covered by a test against a local upstream and not by the mini, and the Buzz binding above is a design read against the relay's source, not a shipped feature.
The substrate is the product
Here is the pitch with the varnish off. The substrate is the product. Surfaces are commodities: a chat app is a member list, a relay and a text box, and there will be a better one next year. Executors are commodities: a container today, rented compute tomorrow, a microVM after that. Models are commodities and they are getting cheaper on purpose. What is not a commodity is the record of what happened, why it happened, and what it was allowed to touch. That record is the only artifact anybody can optimize against. The moat is the trace.
So take the test instead of the pitch. Find the last time an agent of yours did the wrong thing, and ask what artifact you would edit to stop it happening again. If the answer is a longer prompt, you have a bot. If the answer is a rule id you can diff and a trace id you can print, you have a bus. Build the bus.