Documentation

Cascade

An enterprise control plane for AI agents. Organizations discover, dispatch and coordinate a fleet through it, and every action an agent takes stays provisional until its real consequence has been checked.

Introduction

Cascade sits between agent intent and organizational reality. Before any action reaches a production system (a file write, an API call, a database mutation), Cascade forks a shadow environment, rehearses the action, classifies its consequence, and judges the state that results.

Actions that are safe and reversible commit immediately. Actions that cannot be undone wait for a named person. Actions that break a capability boundary, a policy or a proven invariant never become real.

The distinction that matters. Traditional access control answers whether an agent is allowed to perform an action. It cannot answer what will actually happen if it does. An authorized agent can produce a wrong outcome using entirely valid credentials, and in production that is most of what goes wrong. Cascade judges the consequence, not the request.

How it works

Every run goes through the same sequence before anything touches the real world:

  1. Record the plan. written to the ledger as REHEARSING before the agent starts, so the record survives a crash mid-run
  2. Fork. git worktree add creates an isolated world
  3. Rehearse. the agent works inside the fork. A crash or an infinite loop burns the fork and nothing else
  4. Diff. the fork is diffed against its base version. An empty diff ends the run as NO_CHANGE
  5. Adjudicate. the diff is read against the precedent corpus, incidents harvested from your own git history. A precedent violation blocks the plan
  6. Record the write set. the rehearsed writes are attached to the plan. This is what makes rollback possible
  7. Arbitrate. the plan is checked against other agents' in-flight work

The arbiter returns one of four resolutions:

ResolutionOutcome
PROCEEDApplied to the real world, world version bumped, plan COMMITTED
BLOCKED_ON_LEASEPlan moves to AWAITING_APPROVAL and waits in the approvals inbox
STALEPlan is CONFLICTED; the world moved too far underneath it
REFORKThe agent is re-briefed and retries. See the re-brief loop

The fork is destroyed either way, in a finally block. Nothing is left behind to leak into the next run.

The re-brief loop

When two agents collide, the loser is not simply retried. A blind retry would send it back with the same goal and the same stale assumptions, straight into the same collision.

Instead the Arbiter writes a re-brief: a description of what actually moved underneath the agent while it was working. That re-brief is appended to the original goal, and the agent forks again against the world as it now is:

brief = f"{goal}\n\n{decision.rebrief}"

Why this matters. Optimistic concurrency usually forces a choice between serializing every agent (slow, and wasteful when collisions are rare) and letting retries thrash. The re-brief is a third option: agents stay parallel and unserialized, but a loser learns something from losing.

The loop runs up to max_attempts times. If the agent still cannot land a plan, the run ends as CONFLICTED rather than retrying forever.

Irreversible actions are the exception: they are never optimistically retried, because there is no way to take back the attempt that lost.

System diagram

One goal spreads across as many agents as it needs, each rehearsing in its own copy of your world where nothing can reach anything else. What comes back is a diff, and the diff is what gets judged: five checks, no model anywhere in that path, against precedents mined from your own incidents and proven before they count. Work that can be undone commits on its own, work that cannot waits for someone who did not ask for it, and work that would break something never runs.

Cascade architecture: your organization proposes, Model Armor screens the goal, agents rehearse in isolated forks, a diff is judged by five deterministic checks against mined precedents, and the result commits, waits for a second person, or is blocked and remembered; with measured scale and benchmark results alongside
The boundary, and the two results that are measured rather than claimed  ·  Open full-size ↗

The same system drawn for someone who has stopped to study it, naming every sub-agent and the control guarding each stage:

Cascade architecture in detail: registry, screened goal, isolated forks, diff, five checks, commit gate or block, ledger, your systems, with a control rail on the left and the technology stack on the right
Every stage, its control, and what runs it  ·  Open full-size ↗
Three fleet sizes converging on one unchanged boundary, and the measured numbers behind it
Why the fleet grows and the human queue does not  ·  Open full-size ↗

The agent fleet

Cascade is a control plane for a fleet, not a wrapper around one agent. Organizations publish approved agents to Google's Agent Registry, and Cascade discovers them there. Nothing about the fleet is hardcoded.

An agent's declaration carries its identity, version, owner and department, what it may attempt, and what it is denied:

Refund Agent v1.0.0
  owner          finance@meridian.example
  department     Finance
  capabilities   payments.read · payments.refund · payments.charge
  forbidden      candidate.delete · candidate.update · communication.send
  reversibility  a refund is compensable, never reversible
  audit          0 incidents · last reviewed 2026-08-14

Two rules govern how that declaration is read.

Denial beats any grant. A capability left out means nobody has said. A capability listed as forbidden means somebody said no, and no grant anywhere overrides it. Only the second is worth waking a security team for.

Discovery is by capability, never by name. When a goal needs payments.refund, Cascade asks the registry which approved agent holds it. Publish an approved agent tomorrow and it is usable the same day with no code change here. candidate.delete has a working tool behind it and is held by no agent in the organization, which is why asking for it fails before anything runs.

An agent reaching for a capability it does not hold is not an error. It is a security event, and it is recorded as one.

The Cascade agent fleet: specialists with separated concerns coordinating over a shared durable ledger
The agent fleet  ·  Open full-size ↗

Orchestration

Nobody picks the agent. A person states a goal, and Cascade decomposes it and routes each piece to whichever approved agent holds the capability it needs.

"Onboard Northwind Components as an approved supplier"
│
├─ Verify the supplier      suppliers.verify    Supplier Onboarding Agent
├─ Reserve inventory        inventory.reserve   Inventory Agent
├─ Draft the contract       contracts.create    Contract Agent
├─ Schedule the shipment    logistics.schedule  Logistics Agent
└─ Notify the stakeholders  communication.send  Communication Agent

Work that does not collide runs concurrently, up to twelve agents on one goal. Work that touches the same records is serialized by the Arbiter rather than allowed to race. The person sees one workflow, not five agents.

Every branch returns through the same boundary. One agent's refusal does not condemn the other four, and one agent's commit does not vouch for them either.

Cross-agent isolation

The obvious risk in a fleet is not one agent going wrong. It is one agent going wrong and reaching the next one. If the Contract Agent reads a supplier PDF carrying an instruction, that instruction must not become the Logistics Agent's problem.

Four things stop it, and all four are in the running system.

MechanismWhat it prevents
Every agent works in its own forkNo shared scratch space to poison. The forks are separate worlds, each destroyed after it is diffed
Agent output carries no authorityOne agent's result arrives at the next as data. It cannot grant a capability, relax a rule, or stand in for an approval
Retrieved records are screened, not just the goalThe person stating the goal is authenticated. The supplier document is not. Model Armor sees both
Judgment is per actionA compromised agent's work is refused on its own merits, and the rest of the goal is neither condemned nor vouched for

The capability boundary does the rest. The Contract Agent cannot touch inventory even if something convinces it to try, because no grant it could be handed exists for it to use.

Long-running work

Real enterprise processes do not finish inside one request. Procurement, compliance review, onboarding and remediation run for days or weeks, and most of that time is spent waiting on somebody outside the company.

Day 1   Supplier application received, agent verifies the company
        │
        └─ WAIT ─────────────────────────── nothing is running
Day 4   Supplier answers. Work resumes, earlier context recalled
        │
        └─ WAIT
Day 14  Final approval, re-verified against the world as it is now, commit

The wait is a row in the ledger, not a held process. No thread, no session, no open connection. Two weeks costs nothing, survives every redeploy in between, and any instance can answer for it. Persistent context comes from Vertex AI Memory Bank, so what an agent was told on day one is still available on day fourteen without replaying the whole history through a context window.

Memory is not authorization. Remembering what happened last time never grants an agent permission to act differently today. Recall informs the agent. The capability boundary is read fresh from the registry every time.

Approvals go stale for the same reason. A person approves a rehearsal on Tuesday; another agent changes the same records on Wednesday. Cascade re-verifies in the instant before the tool runs and voids the approval if the world moved materially underneath it. Nothing commits against a state a human never actually reviewed.

Security layers

Independent layers, each of which can refuse on its own.

LayerWhat it does
Model ArmorScreens the stated goal and the records retrieved for a planner, before any model reads either. Catches an instruction that entered the company months ago and waited in a record
Capability boundaryRead live from the Agent Registry. Denial beats any grant, and the question asked is whether any approved agent holds it
Declared policyEvaluated with no model in the loop, so a policy decision cannot be argued with
Proven invariantsMined from the organization's own incidents and kept only if they fail before the fix and pass after
ReversibilityDecides what may run unattended and what must reach a person
Segregation of dutiesThe person who requested work cannot approve it. Refused by name
AttributionEvery action, verdict and approval carries the person behind it, not only the agent

The moment worth understanding. On the supplier scenario, all five authorization layers report PERMITTED and the outcome is still wrong. That is the case no permission system can catch, and it is why the boundary judges the resulting state rather than the request.

Cascade trust boundary: what is authenticated, what is screened, and what is never trusted
The trust boundary  ·  Open full-size ↗

Built and roadmap

Stated plainly, because the difference is the part worth checking.

ComponentStatusDetail
Agent RegistryLiveNine agents across six departments, discovered by capability
Memory BankLiveWhat each agent has been refused, recalled before it reaches again
Model ArmorLiveThe stated goal, and the records retrieved for a planner
Agent ObservabilityLiveOne OpenTelemetry span per decision to Cloud Trace, commits included
Agent RuntimePartialDurable ADK sessions, long-running tools, gates that suspend and resume across processes. Runs on Cloud Run rather than hosted Agent Runtime
Agent IdentityRead onlyQueried and reported, including that it currently holds nothing. Not enforced, and no decision depends on it
Agent GatewayRoadmapNot used. Cascade is the enforcement point a gateway would route to, which is a different thing and is not claimed as the same one

Everything marked live is checkable at /api/healthz. Anything unconfigured reports itself unconfigured rather than degrading quietly.

All demonstration data is synthetic. The console labels every business surface SANDBOX on its face. The one surface marked live is a real source repository, because the code agent's fork is a genuine git worktree.

Three phases

Phase 1

Learning

Cascade ingests git history, ADR logs, and past run data to build its consequence model before any agent runs.

Phase 2

Runtime

The control plane forks, rehearses, diffs, and routes every proposed action through the triage engine in real time.

Phase 3

Record

The immutable ledger captures every decision, diff, and rollback. Cloud Trace gives you a full audit trail.

Triage engine

A plan used to have a single fate. One offending action condemned everything beside it, so an agent proposing 147 rejection emails, 146 of them perfectly legitimate, had all 147 refused because one recipient had withdrawn.

That is not caution, it is over-blocking, and over-blocking is how a safety system earns the reputation that gets it switched off. So a plan is triaged action by action, and each action lands on its own merits.

Writing rules

Rules live in rules.yaml. A when clause matches either one action and its arguments, or aggregate facts about the whole rehearsed plan. No model sits in the decision path:

- id: never-message-a-withdrawn-candidate
  when:    { action: send_message, recipient: { has: withdrew } }
  then:    block
  because: "Never message a candidate who has withdrawn"

- id: refund-threshold
  when:    { action: issue_refund, amount: { gt: 20000 } }
  then:    require_approval
  because: "Academy fee refunds over ₦20,000 require a named approver"

- id: bulk-rejection-guard
  when:    { messages_sent: { gt: 3 } }
  then:    require_approval
  because: "Messaging more than 3 candidates at once needs sign-off"

Aggregate facts like messages_sent only exist after rehearsal, which is why a rule about how many candidates a plan would reject can be written at all.

How a disposition is decided

Each action is evaluated against this ladder, in order. The first match wins:

ConditionDisposition
Capability violation: the agent reached outside its declarationBLOCK
A policy rule returned blockBLOCK
A policy rule returned require_approvalGATE
Nothing objected, but the action is irreversibleGATE
Everything elseCOMMIT

The gate is spent on irreversibility, not on suspicion. An action nobody objected to still stops for a human if there is no way back from it. That is the case the whole product is arranged around.

Capability violations outrank policy unconditionally. An agent operating outside its published declaration is not a case where the policy outcome is interesting.

What still binds the whole plan

Two things deliberately do not triage per action:

Reversibility

The single most important classification in the system. It decides whether an action may run unattended, whether it may be retried after a conflict, and whether a human has to be interrupted.

ClassMeaning
reversibleExact prior state is restorable. Runs unattended, safe to retry.
compensableThe effect can be offset but not erased. Runs unattended; the compensation is recorded rather than pretending nothing happened.
irreversibleNo way back. Deferred to the end of a plan, gated on approval, and serialized across agents because it can never be optimistically retried.

Ledger

The ledger is an append-only record of every action that passed triage. Each entry is written before execution begins, so the record is complete even if the executor crashes mid-run. Entries carry:

The rollback engine replays ledger entries in reverse to undo a run, using the inverse operation for each reversible action.

Verdicts

COMMIT GATE BLOCK

API endpoints

Public (no auth required)

Goals (Bearer token)

Fleet and policy (Bearer token)

Approvals and waiting work (Bearer token)

Auth

Why the writes are closed. The routes that start agents and unwind committed work change things. An open write endpoint on a system about controlling consequence would be a bad joke. The read-only scenario pages stay open.