Skip to main content
When you run agents across an organization, two questions never stop mattering: what can each agent reach, and who authorized it to reach that. Corbits answers both, but not with a single rule. What may use a credential, what may invoke a tool, and what may read your durable state are decided by different machinery, in different places, at different moments. This page is where each of those is written down. If you lead an AI org, it shows you how to hand agents real power without handing over the keys. If you own security or compliance, it shows you where authority comes from, where it stops, and which controls do not exist.
This page spans two layers, so read it as a signpost rather than a scope: nothing here inherits a layer, and each section names the one it describes. The control plane resolves credentials, materializes grants, and gates the API. The agent runtime decides, while an agent is running, whether a given call may proceed. Workbench runs on both and inherits what they enforce. Corbits Code does not run on the control plane, so the credential and grant model described here does not govern it, though it builds on the same agent runtime. Both layers are described at v0.3.0.

The vocabulary

A few nouns carry the rest of the page.

Credentials

The secrets an agent uses to authenticate with an outside service: an API key, an OAuth token, a certificate. Owned and stored by your organization, and delivered to a deployment by the control plane rather than looked up by the agent.

Grants

A row saying that some principal may take some action on some resource, and whether the answer is allow, deny, or ask. Grants are what most, though not all, of the decisions below are made from.
A principal is an identity within a tenant: the join between an entity and that tenant. An entity is either a person or a workflow, and what this page calls an agent is deployed and launched as a workflow. The API still reports a third kind, agent, left over from a retired model; nothing creates one, and any that survive hold no authority. A person’s principal is their user account in that tenant. A running workflow has a principal of its own, and that is the identity its authority is resolved against. A principal by itself grants nothing; it only establishes that the entity exists in the tenant. A tenant is the isolation boundary. It has a nullable parent_id, so tenants form a hierarchy of parents and sub-tenants. Credentials and grants behave differently across that hierarchy, and the difference is the one piece of vocabulary readers most often get backwards: a credential is reachable from a tenant up its ancestor chain, so a child can use one its parent owns, while grants are collected within a single tenant and do not inherit in either direction.

How the hierarchy carries each one

The walk-up is worth stating concretely, because it is the mechanism behind every “a child tenant can use this” claim below.
When the control plane resolves a credential, it accepts one that sits anywhere on the launching tenant’s ancestor chain. Store an organizational credential at the parent tenant and every descendant can use it. Define your integrations once for the whole org.A child tenant can shadow a parent’s credential by creating one with the same name; the child’s takes precedence within its scope. Names are unique within a tenant, which is what makes name-based shadowing work.

Credentials

A credential is a runtime secret an agent uses to authenticate with a provider: GitHub, OpenAI, Slack, Google Drive, an internal deployment service, an SSH bastion. Every credential belongs to a provider; there are no provider-less credentials. The provider definition is what tells the control plane how to handle the credential: its refresh behavior, its authentication method, its scope model. The credential’s type (API key, OAuth token, certificate) is a property of the credential, not something the agent chooses. From the agent’s side, it needs “access to service X,” and the control plane figures out the authentication mechanism. Agents do not select or negotiate credential types. A credential can optionally be owned by a specific principal. One with no principal owner is organizational: shared across the tenant and managed by administrators, the company’s OpenAI key or its deploy token. One with an owner is personal, typically created through an OAuth flow. That distinction is not cosmetic; it is the whole authorization rule for two of the classes below.
Agents never look a credential up. The control plane resolves the credentials a deployment needs and delivers them to the process that runs it. The agent cannot reach into a store and pull a secret it was not given.Resolution is confined to deploy in practice. A rotation or a catalog edit does re-resolve model-provider credentials and push them, but only to a single-agent shape this release does not launch, and no equivalent producer exists for tool credentials, so a deployed workflow keeps the material it was given until it is redeployed.
Deleting a credential removes the grants written against that exact credential in the same transaction. A coarse credential:* role grant is untouched, and it matches every credential in the tenant, including any created later. That grant reaches the control-plane API, where roles are consulted; it does not reach a running agent, whose grant set is read from its own principal. A credential a model provider still references is refused rather than deleted, which stops an operator removing one out from under a running catalog by accident. Read that alongside the model-provider class below, because the two combine badly. Deletion is blocked while a provider references the credential, setting its status to revoked does not stop that path resolving it, and a credential’s owner cannot be changed after it is created. Withdrawing one takes two steps in order: repoint or remove the model provider that references it, then delete the credential.
Credential secrets are encrypted at rest. The control plane seals a secret on every write path before it reaches the database, and each ciphertext is bound to its own row and column, so a value lifted from one row will not decrypt in another. Today that binding is AES-256-GCM under a single operator-provided key. It is not KMS-managed or envelope-encrypted, and we do not claim it is; the encryption seam is pluggable so a KMS implementation replaces it without touching a call site.

Grants

A grant carries three fields: A grant attaches either to a role (a named bundle of grants scoped to a tenant) or directly to a principal. System roles (owner, admin, member) are created with every tenant; admins can define custom roles on top.

How a grant set resolves

Given a set of grants and an operation, the evaluator ranks them the same way everywhere it runs. What differs between the classes below is which grants are in the set, not how they are ranked.
1

Filter

Drop expired grants, then keep only those whose resource and action patterns match the operation. A grant carrying conditions is skipped unless the caller supplied a registry that can evaluate them.
2

Rank by specificity, which decides first

Specificity is the count of non-wildcard characters, with a large bonus for a pattern containing no wildcard at all. The most specific matching grant wins outright.
3

Effect breaks ties only at equal specificity

Between two grants of the same specificity, deny beats ask beats allow.
4

No match means denial

If nothing matches, the evaluator returns no decision and the enforcing layer fails closed.
Specificity is decided before effect, so a specific allow overrides a broader deny. A blanket */* deny does not revoke a grant written against an exact resource. To withdraw access, remove or override the specific grant; adding a broad deny above it will not do it.
An ask effect does not deny and does not prompt in line. The agent runtime suspends the call, with a one-hour default deadline, and the control plane records a pending approval against it. The control plane pushes no prompt and assigns no one: resolving an approval is itself a grant-gated action, evaluated like any other. An approver is any principal whose grant allows resolve on the approval. That grant’s breadth sets what they can reach: a tenant-wide approval:* grant lists the whole pending queue, while a grant covering just one agent’s approvals cannot list and instead fetches each one by its id. A control-plane approval is one-time: it authorizes that action, not future ones. How the blocked call is parked durably, resolved exactly once, and resumed without re-running the agent is covered in Approvals. Durable “approve always” is not one of those outcomes today: the control plane’s approval path is approve-once-or-reject and does not yet support scope: "always" (it returns unsupported_scope). No app supplies a durable auto-approval over that path either. Corbits Code does offer an “always allow” that survives a restart, but it is a different mechanism at a different layer: its gate runs in its own process against approvals it persists locally, and it never resolves a control-plane approval. Nothing routes or notifies an approval parked here either: the queue is pull-only, and an approver finds pending work by reading it.

What is protected

Six things are governed, and they are not governed the same way. The table is the summary; the sections below it carry the detail, and each one states what it does not check.

Tool invocation

Layer: agent runtime. Decided from: the run’s grant set, evaluated in the process running the workflow, materialized from the snapshot frozen when the deployment was created. When: on every invocation. Withdrawal: remove or override the tool’s grant on the run’s principal, which reaches the deployment at its next trigger. See the warning below. Every tool call is checked against tool:<name> with action invoke, and a call with no matching grant is blocked. The three outcomes are allow, block, and ask, which suspends the call for approval rather than refusing it. What it does not check: attachment. Nothing filters which tools a step may load; the check happens when a tool is called, not when it is attached.
No operator input enters this decision. Deploying a workflow probes what it declares and freezes exactly that surface as the run’s grant set. There is no approval set to supply and no route that accepts one, so the grants a deployment holds are the ones the workflow asked for. The effect on each tool:<name> row is taken from the tool’s own static declaration in the same way: a tool declaring an approval mark carries ask, and every other tool carries allow.Read the fail-closed rule with that in mind. A definition with no frozen snapshot does fail closed at its next trigger, and a call with no matching grant is blocked, so the mechanism is real. What it is measured against is the workflow’s own declaration rather than a policy your organization wrote. The levers you have are not deploying the workflow, and editing the grant rows on the run’s principal afterwards, which reach the deployment at its next trigger rather than the call in flight.

Workflow effects

Layer: agent runtime. Decided from: the run’s grant set, using the same evaluator as tool invocation. When: at each action. Withdrawal: removing the grant reaches the deployment at its next trigger. A workflow action declaring a capability is checked in two places: that the capability is in the step’s declared set, and that a grant allows invoke on effect:<capability>. Both fail closed. What it does not check: whether those two agree. Both read the same declaration, since the control plane mints the effect: grant from the same requires set the runtime later checks against, and the minted effect is always allow. Treat this as one declaration enforced at two points rather than as two independent controls. An operator can still deny a specific effect, because a deny at the same specificity beats the minted allow.

Tool credentials

Layer: the control plane resolves and delivers; the agent runtime decides whether a tool may open the handle. Decided from: tenant ownership first, then a use grant at the point of use. When: ownership at deploy, when the binding is resolved and the material delivered; the grant when a tool opens the handle. Withdrawal: remove the use grant, which reaches the deployment at its next trigger. The control plane matches a definition’s binding against the tenant’s active credentials for the named provider, anywhere on the ancestor chain, and delivers the material. That delivery carries no authority. A tool package then cannot open the handle unless the run holds a use grant matching the credential resource, and a grant may carry a condition naming the package entitled to resolve it. Read that condition as scoping which package may ask, not as isolation between packages: the delivered material is deployment-wide, and one package’s code can reach another’s secret without going through the gate at all. What it does not check: scopes. A binding carries no scope field, so the scope-matching machinery is never reached on this path.
Two limits worth sizing your trust against.Nothing mints the use grant automatically from a binding, so unless an operator or a definition author creates it, a tool-credential resolve fails closed.And the gate is not a confidentiality boundary. A tool package that clears it receives a mediated handle rather than the raw secret, but credential material also reaches tool code by routes in the agent runtime that no grant governs. Treat any tool package you deploy as trusted with every credential in its deployment, and see Isolation for the boundary you have to supply yourself.

Model-provider credentials

Layer: control plane. Decided from: for a deployed workflow, nothing at all. The request that creates the deployment carries the key. When: at deploy. Withdrawal: redeploy with a different key, or rotate at the provider. This is the class where the shape of the rest of this page does not apply, so it is worth stating flatly. Creating a deployment supplies the inference sources it will use, and each one carries its API key in the request body. Nothing resolves that key against the organization’s credentials, because it never was one of them. The only check it passes is that its provider and model appear in the deployment’s approved set, and that set is the deployment’s own declaration. The control plane does keep a tenant credential catalog, with the ownership rules you would expect: a credential the organization owns is reachable from the owning tenant and every descendant along the ancestor chain, no grant of any kind is consulted, and a credential owned by a principal rather than the tenant is refused. Those rules are real. They are not what governs a deployed workflow’s model access. What it does not check: on the deploy path, anything. There is no credential row, so there is no status, no expiry, and no owner to check. On the catalog path, status and expiry are unread, so a credential marked revoked still resolves.

API surfaces

Layer: control plane. Decided from: the calling principal’s own grants plus the grants of every role they hold, collected within a single tenant. When: on every request. Withdrawal: immediate, since grants are collected per request. This is the one class where roles carry authority. Route groups across the control-plane API are gated on a grant for the resource and verb they expose, and two surfaces that mint no grants at all are mounted as a default deny. Acting principals here are people: the tenant middleware resolves the caller to a user principal, so a workflow does not reach the API this way. What it does not check: this is the class where a missing gate is a route that simply lacks one, rather than a rule with an exception. Treat the grant model as what the control-plane API is designed around rather than as a proof that every route is covered.

Durable state

Layer: control plane and agent runtime, since the same substrate is constructed in the sidecar too. Decided from: for a bearer-token holder, the token’s claims intersected with a grant verdict; for the platform’s own components, the principal’s kind alone. When: on most repository operations. Withdrawal: revoke the token, or remove the grant behind the verdict. Both are read per request, so both are immediate. Run event logs, agent state, workflow assets, skills, and the package registry live in a git substrate with its own authorization model, layered over the same grant evaluator the control-plane API uses rather than a separate one. A bearer-token holder reaching an agent-state, skill, workflow, or workflow-run repo has the token’s permitted actions, its ref pattern, and its expiry intersected with that verdict. The platform’s own principals skip that intersection: a hub write is allowed outright, and a sidecar, supervisor, or workflow process is decided by principal kind and action alone. What it does not check: the resource a token was minted for. A token narrows the verbs, the refs, and the lifetime a holder may use, but not which object they may use them on. Push-time handlers do add some scoping, though less than the shape suggests: a workflow process is confined to its deployment rather than to one run within it, and the check that a pushed event’s origin matches its pusher covers cancellation events only.

Where requirement-sourced authority comes from

This section describes one control-plane mechanism only: how a definition’s declared grant requirements become rows on a run’s principal. Tool invocation, workflow effects, model-provider credentials, durable state, and the control-plane API are all decided without reference to any of it. An agent definition does not ship live grants. It ships requirements. A definition declares credential requirements and grant requirements separately, and a grant requirement can name only creator or invoker; any other source is refused.

creator

The definition author delegates authority they themselves hold. This is the setuid model: the author’s authority travels with the definition, and they cannot delegate what they do not have.

invoker

The person triggering the deployment supplies it, and they cannot pass along authority that was itself delegated to them by an invoker. Materialized as a grant that expires 24 hours later.
Requirements resolve once per deployment, not once per launch. The first trigger materializes the grant set; every later trigger reuses it rather than recomputing from the new caller.Three consequences a compliance reader should plan around. Revoking a creator’s or invoker’s authority does not narrow what an already-deployed agent holds. A later trigger by a different person runs under the first invoker’s delegation, not their own. And an invoker-sourced grant expires 24 hours after that first materialization and is not renewed automatically, so a long-lived deployment quietly loses its invoker-delegated capability a day in.Redeploying does re-resolve, because a redeploy mints a new run. That, rather than a later trigger, is what picks up a revocation.
The control plane re-reads and re-ships the grant rows themselves on every trigger, so deleting a row does reach the deployment at its next one. What does not re-run is the check against the delegating party’s live authority.

Grant strings that gate nothing, and one that does

The control plane records grant-shaped strings for everything a workflow declares it will touch. Four of them constrain nothing afterwards: director:, capability:, mail.address: and mail.send:. The membership gate that would check them has no caller, and the one check that does sit on the deploy path skips the comparison, since with no operator set to measure against the probe’s own surface would stand on both sides of it. Those four are a description of what a workflow said it would touch, and not a control. inference.source: is the exception, and what it does is narrow. When the control plane pins a step’s model it refuses a (provider, model) pair absent from the frozen set. For a source the workflow declared that always holds, so the check never shows. It bites on the fallback path, where a step with no resolvable preference would otherwise pin to a default the workflow never named. Of the strings recorded at deploy, tool:, effect: and credential: are the ones consulted again at the point of use, alongside the control-plane resource prefixes.

A worked example

One agent in the marketing sub-tenant of an org: written by Ali, deployed by Dana, triggered later by Sam. It has an HTTP tool, a Google Drive credential bound to that tool and stored at the parent tenant, an inference model drawn from the org catalog, and a step that sends mail. Grouped by when each decision is made, because that is what decides whether you can change it later. At deploy. Dana’s call is gated on a control-plane grant, and her roles count toward it. What the deploy then freezes is what the workflow declares, not a set Dana chooses. The Drive credential resolves here, matched against active tenant-owned credentials on marketing’s ancestor chain, settled by ownership with no grant consulted. The inference source does not resolve at all: Dana’s deploy request carries the model’s API key, and marketing’s credential catalog is not consulted for it. At the first trigger. Sam triggers the deployment, and the definition’s grant requirements resolve: creator-sourced ones against Ali’s live authority, invoker-sourced ones against Sam’s, written onto the run’s principal. Every later trigger reuses that set. If Priya triggers it tomorrow, the run still holds what Sam delegated. At each call. The agent runtime checks the HTTP tool against tool:<name>, and the row that decides is the one Dana’s deploy froze from the workflow’s own declaration, carrying whatever effect that tool’s author asked for. Opening the Drive handle needs a use grant on the run. The mail step is checked against its effect: resource. Writing the run’s audit record is decided against the process’s bearer claims and path scope instead, by different machinery again. Four kinds of decision, three moments, and not one of them turns on a policy your organization authored. Now try to withdraw something. Revoke Ali’s authority and the running deployment keeps what it materialized, because requirements resolved at Sam’s trigger and are not recomputed. Set the Drive credential to revoked and nothing changes for this deployment either: its material was resolved at deploy, and no push replaces it. Add a blanket deny and the HTTP tool still runs, because the specific row the deploy froze outranks a broad one; a deny naming that exact resource on the run’s principal does reach it. For a deployed workflow, the lever that reliably works is redeploying it. That is the honest summary of everything above: inheritance spreads credential availability, most permissions are decided from grants, and the grants a deployment holds are the ones it asked for.

Audit and provenance

Authorization decides what an agent may do. Provenance records what it did and makes that record verifiable rather than something you take on trust. For an agent running on the control plane, the runtime commits conversation context and audit records on its behalf. All of it lands in a git repository on the sidecar host, keyed per agent for a warm single-step deployment and per run, step and attempt otherwise, and every commit the runtime writes is signed using the standard SSH signature format. An agent repository’s initial commit is the exception, created before a signer is attached. The result is a tamper-evident history: any commit altered after the fact no longer verifies, so a recorded action cannot be rewritten without breaking its signature. Two keys do that signing, and neither is per-agent. The hub holds one key and signs the commits it writes, such as an agent’s deployed state. A sidecar holds one key per data directory and signs the run commits produced by the workflows it executes. So a signature identifies the component that wrote the commit, not the individual agent behind it, and the agent’s own key is used to authenticate it to the hub rather than to author commits.
Verification uses standard git. git verify-commit checks a commit against the public key that signed it, so you can confirm authenticity yourself, outside the platform. Be precise about where that check runs today: a sidecar verifies the deploy packs the hub sends it, against a hub key it learned from that same deploy rather than an independent anchor, while asset packs and run-restore packs are not verified. Nothing on the hub verifies a commit it receives, and its ingest path takes no verifier at all. Tamper-evidence here is a property you can check after the fact with git, not a gate the control plane enforces on the way in.
Two limits matter if you are evaluating this for compliance. The hub mints its signing key at startup, holds it in memory only, and keeps no key history, so a restart permanently orphans the signatures on everything it signed before: no code path can check them again. And the audit records themselves are scratch: a multi-step run’s storage is deleted when the run reaches completed, failed or cancelled, while a single-step workflow is kept warm automatically and its records are written somewhere else entirely, a per-agent directory that undeploy does not sweep and nothing else deletes. The durable copy kept by the hub carries turns and metadata rather than the audit records. The control plane exposes no route for reading audit records, sets no retention window, and offers no export.That gap is narrower than it sounds: the hub keeps its own committed, append-only log of a workflow run’s orchestration events, separate from the deleted audit records, and a route exists to read it back after the run ends. What has no query surface is the per-call authorization record, not the run itself.

Transport and supply chain

Two controls sit underneath the credential and grant model. Neither decides what an agent may do; both bound who can present themselves as part of the system and what code can enter it. The sidecar connection is authenticated. Agent execution happens in a sidecar that connects back to the control plane over a WebSocket. That connection is authenticated on the handshake against the sidecar’s own identity token, which is a distinct secret from any credential the sidecar is later given to work with. The control plane stores only an unsalted SHA-256 digest of that identity token, never the token itself. A token the control plane mints carries 256 bits of entropy, so its digest is not searchable; the provisioning script also accepts an operator-supplied token and does not check its entropy, and for that path the property rests on the token you choose. A handshake that does not resolve to a known sidecar is rejected rather than trusted on the strength of what the connecting party claims about itself. This is a per-sidecar bearer secret rather than mutual TLS, layered with per-connection checks that bind a sidecar to the addresses it was allocated and to the repositories it may push. Tool packages are integrity-checked. Third-party tool packages ship as npm tarballs and land in a content-addressable cache keyed by the tarball’s SRI digest, sha512 in what npm and the control plane produce. That digest is verified rather than merely used as a key: a mismatch throws when the tarball is written and again when it is read back, before anything is unpacked, so a tampered or substituted tarball is never loaded. A tool package shipped as source rather than a tarball is checked against a git tree object instead. This is a property of the agent runtime’s packaging layer, documented across the platform overview, rather than a control-plane authorization decision: it governs which tool-package code can be loaded, while grants continue to govern what that code is allowed to do once loaded. It does not reach the operator-configured adapters the sidecar imports by its own configuration.

Controls that do not exist

Stated plainly, because a reader who infers a control from silence is worse off than one who knows it is absent.
  • No rate limiting anywhere in the control plane.
  • No containment of credentials from tool code by the agent runtime. A tool package can reach credential material the credential gate does not govern, so treat any package you deploy as trusted with every credential in its deployment.
  • No secret redaction by either layer, on agent output, tool results, audit records, run events, or logs. A secret an agent prints is recorded as printed.
  • No retention limit on a single-step deployment’s audit records. They are written to a per-agent directory on the sidecar host that survives run termination, survives undeploy, and has no sweeper. Nothing in the control plane can read or delete them.
  • No operator approval of a deployment’s capability surface. Creating a deployment freezes what the workflow declares. The control plane exposes no route that accepts an approved set, so nothing narrows a workflow to less than it asked for.
  • No permissioning for MCP tools in the agent runtime, which does not support them today.
  • No confinement of your workflow code by the agent runtime: no filesystem jail, no network policy, no syscall filtering. A tool that reaches the network needs only a grant to be invoked, and neither layer narrows where it may reach. Isolation covers this in full and is the page to read before a security review.

How this shows up

Platform overview

Where credentials and grants sit in the control plane, harness, and agent lifecycle.

Corbits Code

The guardrails discipline in a real coding agent: tiered permissions and hard-deny secret guards.

Workbench

The multiplayer workspace for humans and agents, running on control-plane-governed credentials and grants.