Most agent platforms give you one configuration file and hope. When you are
running agents for more than one team — or more than one customer — a single
config breaks down fast. Each workspace needs its own model, its own service
access, its own secrets, but someone has to guarantee that no workspace can
spend more than its budget or reach a service it was never authorized to use.
The answer is a policy cascade. Every setting — model, temperature, allowed
services, API keys, skill availability, TTL defaults — resolves through three
ordered tiers: company, repo, and workspace. A lower tier can narrow an upstream
ceiling but never widen it. That single rule is what makes it safe to hand a
workspace to a team without handing them the keys.
How the cascade resolves
Three tiers, bottom-up. The company tier sets the floor. The repo tier overrides
it. The workspace tier overrides both. The resolution order is fixed for every
kind of setting:
Notice that secrets run in reverse. A workspace-tier credential wins over the
repo and company defaults, and an empty value at the workspace tier falls
through to the repo. This means you set a per-customer token where it is used
and fall back up the chain only when it is absent. You are never forced to
duplicate credentials across every workspace.
Narrow-only: the safety property
The cascade is not a free-for-all override. For service access, budgets, quotas,
and TTL defaults, a lower tier can only narrow what the tier above it allows. If
the company grants a repo [github, slack, search], a workspace under that repo
can select a subset — [github, search] — but it cannot add linkedin. The
resolve-time intersection is enforced, not advisory.
This extends to per-service API surface control. Granting access to GitHub does
not mean granting access to every GitHub endpoint. A service entry can carry
endpoint patterns — /user/repos or /repos/* — and any outbound call to an
undeclared path is rejected before it leaves the platform. A lower tier can only
further restrict those patterns.
Mutability classes and hard locks
Every policy field belongs to one of four mutability classes. Compliance fields
(audit logging, token limits, prompt injection guards) are company-only — no
lower tier can touch them. Identity fields (model, temperature, service access)
cascade normally. Operational fields (budgets, quotas, memory settings) are open
to any tier. Collection fields (per-workspace variables and secrets) are
workspace-only and never pre-populated from above.
On top of that, the company tier can place hard locks on individual fields. A
locked field rejects every override from below — not silently ignored, but
flatly rejected with a diagnostic. The lock is absolute until a company-tier
operator clears it. We use this for compliance mandates: audit logging is
company-wide, so it is hard-locked at the company tier and no workspace under
that company can disable it.
Skills form a floor, never a ceiling
Company-tier skills are visible to every workspace under that company and cannot
be removed. A workspace can add its own skills on top of the floor but it can
never subtract from it. If a seed skill is deleted, the deletion is recorded as
a durable tombstone rather than a physical removal — the floor is stable across
restarts and redeploys. Workspace-authored skills that collide with a
company-floor skill by ID are rejected at load time.
Skills also obey the three-tier service ceiling: a skill that requires, say,
GitHub access will not load for a workspace whose allowed_services intersection
does not include GitHub, regardless of whether the skill sits on the company
floor.
Multiple companies, complete isolation
A single policy file can define multiple companies, each with its own defaults,
locks, skills, and secrets. The cascade never cross-contaminates between
companies — a workspace under company A never inherits company B’s runtime
overrides, skills, or workflow catalog. Each company gets its own workspace
singleton for live policy writes, and a company can opt into hide_base_pack to
suppress the platform’s entire base skill and workflow catalog for its
workspaces. This keeps a customer-facing instance clean — it sees only the
skills and workflows authored for that customer, not the platform internals.
Two-layer storage with live effect
Every tier stores policy in two layers. The git baseline is the cold-start seed
— what you get on a fresh deploy. A runtime override layer, written to a
persistent volume via CLI (policy set, vars set, secret set), takes effect
immediately. The resolver detects filesystem changes and re-resolves on next
access — no restart, no redeploy. An operator can adjust a budget cap or
add a service to a workspace and the change is live across the platform in the next
request.
The two layers can diverge intentionally — a workspace that starts from a
company default of temperature: 0.7 and overrides it to 0.5 keeps that
override across restarts. The baseline remains the floor; promotion of a
long-lived override back to the git baseline is available through a typed
promotion verb when the operator decides it should become the new default.
What you get
The cascade model gives you a single answer to the question “what is this
workspace’s effective policy right now?” without hunting through files or
guessing which override landed last. It gives you a safety perimeter — a
workspace can never exceed the bounds its parent tiers set. And it gives you
live mutability without a deploy cycle, because policy is data, not code.
This is the governance model that runs the ToolShell agent platform.
답글 남기기