Overview

Autonomous agents fail in production for two reasons. They stop at every consequential action and wait for a human, which stalls the workflow. And when they do act, nothing outside the model's own good behaviour constrains what they touch.

Switchboard separates those two problems and solves them in different layers.

For the first, it borrows out-of-order execution from CPU design. A write is not a call; it is an intent recorded in an append-only ledger, and the run continues on a deterministic simulated result rather than blocking. The human approves a batch later, and at that moment the world is re-read and the intent is revalidated before it is applied — because a ledger is a store buffer, not an undo log.

For the second, it splits a control plane from a data plane, after Software-Defined Networking. Gemini resolves an intent it has not seen before and compiles the solution into a deterministic rule. Matching work afterwards runs on the data plane with no model call at all.

And the boundary between those planes is enforced by the cloud, not by the code. The edge and replay service accounts simply do not hold the Vertex AI role — so "the data plane calls no model" is not a promise we make, it is a permission that does not exist. verify_invariants.py impersonates each service account in turn and records the 403. The guarantee is something you can watch us measure.

The measurable claim: as rules accumulate, the model cost per business action falls toward zero — and the authority a model holds never grows, because authority lives in the capability record and in IAM.

Features and Functionality

  • Speculation past the gate. A write step does not block. The broker captures the world state as a precondition, renders a simulated result from a recipe the control plane wrote, records the intent in the ledger, and the run proceeds. On approval, commit re-reads every precondition source once, classifies what changed, and applies only when the difference is irrelevant or self-induced. A relevant divergence is refused, not forced through — because a ledger has no undo.

  • Control plane / data plane split. An unmatched event escalates to Gemini, which synthesises a handler. The handler is validated against 28 machine-readable violation classes before it can enter the table — including static checks a runtime would only discover later, such as binding a not-yet-applied write's SIM: result into a field that requires a real identifier. It then runs in shadow mode, where every write it produces meets a human, and it earns automation only after a threshold of clean approvals.

  • Multi-step plans over whatever the workspace actually has. A goal in natural language becomes a DAG. The model reads the tenant's capability pool with each capability's argument schema and returns steps, dependencies and bindings between them. Its output is treated as untrusted input: a capability with no registry record is dropped and the refusal is shown, and effect and risk are read from the record rather than from the response. Reads execute live; writes become ledger intents.

  • Any provider, by registration rather than by release. Connect an MCP server and its tools are discovered, minted at the strictest classification, and immediately usable in plans. There is no vendor name anywhere in the source. The proof is mechanical: a capability the registry does not hold cannot be indexed, planned, bound or applied.

  • Least privilege the agent can see. An agent distinguishes what is granted to it from what is available in the workspace pool, and it will propose an ungranted capability rather than pretend it cannot. That is not privilege escalation: a write is still an intent, and the human still grants it on the card.

  • Webhooks that carry data, not just a trigger. An inbound payload becomes an Event, and any step's argument can bind to event.extracted.<field>. A webhook can therefore supply the value a later step needs, not merely start the run. Plans are drafts until published, and a published plan's trigger token is required and compared in constant time.

  • Approval cards generated from the schema. A card's editable fields are derived from the capability's own argument schema. The service and the SPA carry no field names of their own — which is what makes a new vertical a record rather than a release.

  • Proactive multi-channel approval. The card is pushed to every linked Telegram account for the tenant at the moment the ledger records the intent, so the same notification happens whichever surface staged it. A batch can be approved from the phone, and an approval from Telegram is the same ledger transition as one from the web.

  • Cascaded classification. Tier 0 matches structured fields with no model at all. Tier 1 uses EmbeddingGemma (300M, weights baked into the image so a cold start touches no network) for nearest-centroid matching, with a deliberate ambiguous band that escalates instead of guessing — the band exists so the system can say "I am not sure" rather than pick the closest thing.

  • Python sandbox as a workflow step. Code execution is an ordinary read capability, so a plan can transform one step's output before feeding the next. Execution is gated by an AST allow-list rather than by pattern matching.

  • A verifiable audit trail. Every entry is hash-chained and the chain is verifiable over HTTP. Corrections are recorded as corrections, retirement as an event, and the promotion counters are visible next to the threshold they have to clear.

Where these stand, precisely. Speculation past the gate runs on the handler path; the Studio's plan runner pauses at the first write rather than continuing on the simulated result. Two classification tiers are loaded in the deployment — the labelling and screening tier exists in the adapter and is switched off. The sandbox runs real code step by step; the Studio's free-form Code Mode surface still returns simulated results. Handler synthesis, validation and promotion are covered by the local smoke suites, and the cloud escalation hop has not been watched end to end yet.

What the architecture is built for next

Naming these because you will find them in the repository, and because the shape of what is already there is the interesting part.

  • Self-healing workflows. When a capability's schema changes under a rule, the failure should escalate to the control plane, the rule should be repaired, and it should re-enter as version + 1 in shadow mode with provenance pointing at what it replaced — so a repaired rule earns automation again rather than resuming silently. Almost all of it is in place: transient-versus-permanent error classification, a bounded repair budget that escalates to a human when exhausted, SynthPort.replan declared and implemented, the topic provisioned, the versioning fields in the contract. Missing are the trigger and the control /replan endpoint. The replan topic is sitting there with no subscriber.

And the claim this unlocks is not "zero maintenance cost" — a repair costs a model call, and the repaired version needs approvals until it re-promotes. What changes is who maintains it: an engineer editing a workflow becomes a human approving a proposed repair. That is a smaller promise and a much better one.

  • Time-based triggers. Natural language already sets a schedule and the schedule is stored; no scheduler fires it in this deployment. Webhook triggering is live.

  • The cloud escalation hop. The Pub/Sub adapter was written during this build — before it, the escalation and commit topics were publishing into an in-memory dictionary. The commit half is wired and verified. The escalation half has not been watched against a real push envelope yet, and a push delivers an envelope rather than the published payload, which is exactly the defect we found and fixed on the commit side.

Technologies Used

  • AI: Google Vertex AI — Gemini 3.5 Flash for planning, dialogue and handler synthesis (measured: available in the global location only, which is recorded as evidence in the repository). EmbeddingGemma-300M in-process for tier-1 classification.
  • Backend: Python, FastAPI. Five layers with enforced direction — contracts ← ports ← kernel ← adapters ← services — checked by import-linter on every gate run, plus a script that keeps the agent framework out of the data plane. The kernel imports no cloud SDK, no HTTP client and no adapter.
  • Infrastructure: Cloud Run (six services, one per trust boundary, each with its own service account), Firestore (ledger, registry, state), Pub/Sub (the three inter-service boundaries), Secret Manager, Firebase Hosting and Auth. All of it declared in Terraform.
  • Frontend: React SPA with a typed contract generated from the Python models, and a Telegram bot.
  • Standards: Model Context Protocol for external capabilities.

Other Data Sources Used

  • MCP servers registered by the tenant: the tools are discovered, classified at the strictest setting, and enter the capability pool as private records visible only to that tenant. A shared capability shares its definition, never its account — credentials resolve per tenant at call time, inside the single service that holds them.
  • Inbound webhook payloads normalised into the canonical event shape, where a handler binding can read any field.

Findings and Key Learnings

  1. Human approval is not the enemy of flow; blocking is. Waiting at every consequential step makes an agent unusable, but removing the human makes it unsafe. Turning the write into a recorded intent separates the two: the run finishes, the human decides in a batch, and the decision is revalidated against a world that may have moved. The queue depth also becomes a control signal — an additive-increase/multiplicative-decrease window throttles speculation to the rate a human is actually approving.

  2. A solved problem should stop costing money. A model is only needed for what it has not seen. Once its solution is a validated rule, the same work runs with no model call. The corollary is what makes it safe: the rule enters in shadow mode, so the first several runs still meet a human, and automation is earned rather than assumed.

  3. The ledger is why a small model can be trusted. A 300M classifier can be wrong. It cannot be dangerous, because every write meets the same wall regardless of which tier produced it. Isolating the side effect is what buys the speed.

  4. Authority must live in data, not in a declaration. An MCP server can claim its tool is read-only, a model can assert a step is harmless, and a handler could declare its own effect. None of those are trusted: effect and risk come from the capability record, a discovered tool is born at the strictest classification, and an unvetted source can never be promoted whatever class it is assigned. The generalisation held under pressure — when a model began choosing the capabilities, the same rule made its output safe to accept.

  5. A silent fallback is worse than an outage. The most expensive defects in this build were all the same shape: a dead path that produced plausible output. A missing dependency was swallowed and the reply degraded to fixed phrasing that read like a model answer. A queue published into an in-memory dictionary while the topics sat there looking wired. A hand-built ledger entry with a fabricated precondition, so approving a card silently applied nothing. Each was found by making the failure loud, not by reading the code. Fail-closed is not enough on its own — it has to be fail-loud.

Built With

Share this project:

Updates

Submission history