This page specifies the content of an agent's authority in the V1 Agentic
Studio: the enumerated tool catalog, the per-tool grant scopes and revocation
cascade, the multi-agent hand-off protocol, the runtime admission gate that
makes those governance decisions act, and the concrete cross-domain pipeline
patterns that compose them. It is the companion to
Agent Registry, Job Orchestration, and Multi-Agent Plans
(the families, lifecycle, and plan DAGs) and
Agent Invocation, Budgets, Memory, and Feedback Loops
(invocation surfaces, cost budgets, memory, and champion-challenger). Without
the material here, an AgentRun is a black box — this is the layer that says
exactly what a run is allowed to do, how that authority is granted and revoked,
and how it is enforced when a tool is actually about to fire.
A reality note worth stating up front, in the spirit of the rest of these docs:
the governance and orchestration brain is real, pure-TypeScript, and
value-tested (271 tests across 16 files in @oshun/agentic-studio v0.1.0). The
tool execution itself — the real web.fetch, sophia.ground, and so on — is
injected at the application boundary and fail-closed by default. The catalog
declares and validates authority; the deployable binds (or withholds) the real
tool implementations. Where a piece is a data/query layer rather than a shipped
admin UI, or is provider-gated, this page says so plainly.
Where this lives in the codebase#
| Concern | Module |
|---|---|
| Tool catalog (contract) | libs/contracts/src/agent/tools.ts |
| Grant record (contract) | libs/contracts/src/agent/tool-grants.ts |
| Grant resolver + revocation cascade | libs/oshun/agentic-studio/src/grants/resolver.ts |
| Multi-agent hand-off protocol | libs/oshun/agentic-studio/src/handoff/handoff-protocol.ts |
| Run envelope + statuses | libs/oshun/agentic-studio/src/runs/agent-run.ts |
| Run controls | libs/oshun/agentic-studio/src/runs/run-controls.ts |
| Runtime admission gate | libs/oshun/agentic-studio/src/runs/executor.ts |
| Guarded dispatcher / orchestrator | libs/oshun/agentic-studio/src/runs/dispatcher.ts, runs/orchestrator.ts |
| Kill switches | libs/oshun/agentic-studio/src/budgets/kill-switch.ts |
| Budgets | libs/oshun/agentic-studio/src/budgets/budgets.ts |
| Cost-quality modes | libs/oshun/agentic-studio/src/modes/modes.ts |
| Tool isolation / SSRF / secrets | libs/oshun/agentic-studio/src/capabilities/tool-isolation.ts |
| Adversarial test catalog | libs/oshun/agentic-studio/src/capabilities/adversarial-tests.ts |
| Champion-challenger | libs/oshun/agentic-studio/src/feedback/champion-challenger.ts |
| Pipelines | libs/oshun/agentic-studio/src/pipelines/v1-pipelines.ts, pipeline-registry.ts, pipelines/observability.ts |
| Governed-run HTTP surface | apps/oshun/bff/src/agentic/runs-route.ts |
The package's barrel (libs/oshun/agentic-studio/src/index.ts) re-exports
twelve subsystems: registry, runs, plans, dashboard, invocation,
budgets, capabilities, feedback, modes, pipelines, grants, and
handoff.
Tool catalog#
Every tool an agent can invoke is enumerated in a versioned, Zod-validated
catalog (AgentToolCatalog) exported from libs/contracts/src/agent/tools.ts.
The V1 catalog declares catalogVersion: '1.0.0',
publishedAt: '2026-05-11T00:00:00Z', and exactly 21 entries
(V1_TOOL_IDS). Additions go through the Agent Registry release-gate review.
The precise count matters: generate.image, generate.video, and
generate.audio are three distinct entries; memory.read / memory.write and
calendar.read / calendar.write are separate read/write entries. A
prose-grouped reading can make the catalog look like "about seventeen" tools,
but V1_TOOL_IDS.length === 21 is the contract.
| Tool id | Display name | Required scope | What it does / hard limits |
|---|---|---|---|
web.fetch |
Web Fetch | web.egress |
HTTP through the egress allowlist; SSRF-guarded (see below); per-request audit. Read-only network. |
source.fetch |
Source Fetch | sources.read |
Read a registered Sophia SourceSet entry by id; scope-checked against the run's source set; bumps a usage counter. |
sophia.ground |
Sophia Ground | sophia.ground |
Full retrieve → rank → ground → cite envelope (see Sophia Grounding); may return an ungrounded/abstained verdict rather than synthesizing. |
sophia.fact_check |
Sophia Fact-Check | sophia.fact_check |
Run the fact-check loop on a claim set. |
memory.read |
Memory Read | memory.read |
Iris recall within the run's declared memory scope. |
memory.write |
Memory Write | memory.write |
Iris write; sensitive categories gated by explicit consent. |
persona.invoke |
Persona Invoke | persona.invoke |
Render under a registered persona/voice; Lilith policy applies; cannot widen the run's tone-band ceiling. |
generate.image |
Image Generate | generate.image |
Dispatch through Isis under the run's audacity ceiling; provenance bundle returned. |
generate.video |
Video Generate | generate.video |
As above for video. |
generate.audio |
Audio Generate | generate.audio |
As above for audio/narration. |
composition.suggest |
Composition Suggest | composition.suggest |
Living-Scenes Compose Assist over an artifact set; ranked techniques + rationale. |
themis.adjudicate |
Themis Adjudicate | themis.adjudicate |
Submit an evidence pack for a Themis verdict (Metis academic-integrity surface). |
code.exec |
Code Execute | code.exec |
Sandboxed code execution. |
file.read |
File Read | file.read |
Read a tenant/customer-scoped file by id. |
file.write |
File Write | file.write |
Write into the run's output bundle (never directly into a user-facing surface; publication is a separate gate). |
calendar.read |
Calendar Read | calendar.read |
Read calendar entries within consent scope. |
calendar.write |
Calendar Write | calendar.write |
Write calendar entries within consent scope. |
notify.send |
Notify Send | notify.send |
Send through a delivery channel adapter. |
handoff |
Hand-off | handoff |
Dispatch a sub-agent with bounded budget and authority (see Multi-agent protocol). |
approval.request |
Approval Request | approval.request |
Pause the run pending human approval. |
agent.terminate |
Agent Terminate | agent.terminate |
Terminate the current run (or a sub-agent). |
Each AgentToolCatalogEntry carries more than an id: a semver (the V1 set is
uniformly 1.0.0), a capabilityModifiers descriptor (schema name and
JSON-schema shape — the V1 entries declare the empty NoModifiers descriptor,
with the real per-tool modifier schemas registered in the runtime), an array of
scopeRequirements (each with scope, required, and a rationale string for
the audit trail), at least one auditHook (e.g., audit.web.fetch capturing
the args key), a dependencies array of (toolId, semverRange) pairs, and a
nullable deprecation record.
Tool versioning and deprecation surfacing#
The catalog backs the "tool versioning / deprecations surfaced at registry-review time" claim with real code, not prose:
resolveToolDependencies(catalog, rootToolId)walks the dependency graph with a genuine semver-range matcher (semverInRange) supporting^,~,>=,<=,=, and bare-exact ranges, including the caret's0.xspecial cases. It returns either the resolved entry set or the first unsatisfied dependency, with a typed reason:unknown-toolorsemver-range-no-match(plus the offendingtoolIdandrequestedRange).surfaceDeprecations({ catalog, dependentToolsByToolId, nowUnixSeconds, reviewWindowSeconds })scans for entries whosedeprecation.deprecatedAtfalls within the review window and returns, per tool, the deprecation date, thereplacementToolId, and the dependent tool ids that still rely on it — the payload an Agent Registry reviewer sees before approving a release.
Grant scope and revocation cascade#
Tool grants are scoped, time-bound, and revocable. Crucially, the grant
vocabulary is the same vocabulary the AgentRunEnvelope uses, so audit logs and
operator surfaces speak one language.
The grant record#
AgentToolGrantSchema (libs/contracts/src/agent/tool-grants.ts) defines a
grant as:
{
"grantId": "grant-…",
"agentId": "veritas.fact-checker",
"toolId": "sophia.fact_check",
"scope": "per-pipeline-instance",
"scopeRef": "pipeline-instance-abc",
"capabilityModifiers": { "egressDomains": ["*.un.org"] },
"expiresAt": "2026-06-30T00:00:00Z",
"grantedBy": { "actorId": "op-7", "actorRole": "platform-operator" },
"rationale": "weekly briefing pack run",
"grantedAt": "2026-06-23T00:00:00Z",
"revokedAt": null,
"revokedByActorId": null
}
The capabilityModifiers field is per-tool and free-form: for web.fetch it
carries the egress-domain allowlist; for generate.image the audacity ceiling;
for memory.write the allowed memory scopes. The grantedBy.actorRole is
constrained to platform-operator | tenant-operator | creator | customer.
Scopes and specificity#
GrantScopeSchema enumerates exactly five scopes. Each grant carries exactly
one. When a tool call is evaluated, the resolver picks the most specific
live grant by a fixed priority (GRANT_SCOPE_PRIORITY in
grants/resolver.ts):
| Scope | Priority | Lifetime / scopeRef matches |
|---|---|---|
per-run |
100 | A single AgentRun; scopeRef === runId. Expires at terminal state. |
per-pipeline-instance |
80 | A cross-domain pipeline run; scopeRef === pipelineInstanceId. Narrower than per-user, broader than per-run. |
per-session |
60 | A logical session (a Studio authoring session, a Metis tutor session); scopeRef === sessionId. |
per-user |
40 | Durable for a user account; scopeRef === userId. |
per-tenant |
20 | Durable for a tenant; scopeRef === tenantId. Until revoked. |
The resolver (resolveGrant) is a per-call authorization step: it filters
grants to the matching (agentId, toolId) and a matching scope ref, drops any
that are revoked or expired, and then reduces the survivors to the
highest-priority one (ties broken by most-recent grantedAtUnixSeconds). When
nothing remains, it returns a typed GrantDenialReason: no-matching-grant,
all-matches-expired, or all-matches-revoked — distinguishing "you never had
this" from "your grant lapsed" from "it was pulled".
Revocation, expiry, and the cascade#
- Queued runs re-check at dispatch.
recheckQueuedRunAtDispatchre-runsresolveGrantimmediately before a queued run starts, so a grant revoked or expired while the run waited is enforced at the boundary — a queued run never inherits a stale "allowed" decision. - In-flight cascade.
emitRevocationCascadeperforms a breadth-first walk of active runs: every run whosegrantsInUseincludes the revoked grant gets aToolRevokedEvent, and every sub-agent (parentRunIdchain) inherits the revocation transitively, withcascadeDepthrecorded. Runs cannot evade revocation by re-issuing the same call within a single invocation. - Expiry is revocation.
emitExpiryCascadefinds grants expiring within a horizon and routes each through the same cascade machinery (withrevokedByActorId: 'system.expiry'). Time-bound and operator-pulled authority are treated identically. - Renewal requires fresh rationale and audit.
renewGrantrefuses withrationale-requiredif the rationale is blank andaudit-requiredif no audit event id is bound; on success it mints a newgrantIdrather than silently extending the old authority. - Kill-switch cascade.
emitToolKillSwitchCascadepropagates a per-tool kill switch (platform-wide whentenantId === null, else tenant-scoped) across all in-flight runs using the tool, again BFS over sub-agents. Each emittedKillSwitchPropagationEventrecordspropagationLatencySecondsand awithinFiveSecondSloboolean (latency <= 5) — the data backing the "platform kill propagates ≤ 5 s" claim.
Kill switches and the scope hierarchy#
Kill switches (budgets/kill-switch.ts) are the safety primitive that overrides
everything else. KILL_SWITCH_SCOPES are agent, family, tenant,
provider, region, tool, and global. decideExecution(switches, target)
collects every active switch that matches an ExecutionTarget (tenant / agent /
family / provider / region / tool) and, when more than one is active, "blames"
the broadest one for the user-visible copy by this priority:
global 6 > region 5 > provider 4 > tenant 3 > family 2 > agent 1 > tool 0
The blamed switch supplies the userVisibleStatusCopy and an optional
safeDegradationModeId so a tripped switch can degrade to a safe mode rather
than simply erroring.
The runtime admission gate — where decisions act#
The audit found a sharp distinction worth naming: the budget, kill-switch, and
throttle modules each decide (a pure verdict), but for that to mean anything
something must consume those verdicts at a tool-call boundary and actually
stop a run. That seam is runs/executor.ts → runs/dispatcher.ts →
runs/orchestrator.ts, and it is the concrete enforcement story the
higher-level docs gesture at.
admitToolCall(input)(executor.ts) runs the checks in safety → cost → rate order:- Kill switch (
decideExecution) — if any switch is active, the run iskilled (terminalkilled), with the operator's status copy and an audit event. This is the check that honors the ≤ 5 s propagation budget. - Budget (
checkBudget) — anexceededverdict is a hard stop (terminalkilled);warn/graceverdicts admit the call but surface asBudgetWarnings (degradation/notification is policy, not a hard stop). - Rate (
acquireToken) — a failed token acquisition is back-pressure: the call is deferred with aretryAfterSeconds; the run is not terminated. - Otherwise it returns
admitwith the token-deducted throttle state and any budget warnings. The executor never invokes the tool — it is purely the enforcement primitive.
- Kill switch (
dispatchGuardedToolCall(input, runTool)(dispatcher.ts) callsadmitToolCalland invokes the injectedrunToolonly onadmit. A kill switch or exhausted budget terminates the run and the tool never runs; a throttle defers; a terminal run refuses.runGuardedToolPlan({ run, plan, killSwitches, budgets, … })(orchestrator.ts) threads the envelope through a sequence ofPlannedToolCalls; the moment a call is killed or throttled, the orchestrator stops and the remaining planned calls never execute.
The governed-run HTTP surface#
POST /v1/agentic/runs/execute (apps/oshun/bff/src/agentic/runs-route.ts)
mounts the guard as the deployable's run surface. The design choices are the
honest, fail-closed ones:
- Fail-closed by default. The real agent tools are injected at the app
boundary via an
AgentToolRegistry(toolId → operation). The default isnotConfiguredAgentToolRegistry()— an empty map — so any plan referencing an unconfigured tool returns 503agent_tools_not_configured. The gate is real; the tools are bound by the deployable. With nothing configured, the route never fabricates a run. - Server-authoritative identity. The actor is the authenticated principal
and the tenant comes only from the verified auth claim
(
authContext.tenantId) — a body-supplied tenant would let a caller dodge tenant-scoped kill switches. - Malformed → 400. A missing
runId/tenantId/rootAgentId, or an empty / non-{toolId}plan, is rejected. - Scope checks (S7) → 403
tool_scope_missing. A tool can declare anAgentToolScopeCheckover the caller's real scopes; the executor never fabricates an operator tier for whoever shows up. - Server-side target metadata (S8). Kill switches match on
family/provider/region, which come from server-side
toolTargetskeyed by tool — never from the request body — so an armed family switch cannot be evaded by omitting a field. Tools without metadata get'default'. - Run-ownership (S9) → 409
run_id_conflict. ArunIdalready persisted by another member is never overwritable. - Operator switches read every request.
resolveOperatorKillSwitches()reads the admin agentic-operations store on every request (the arm → next boundary ≤ 5 s budget); client-supplied switches and budgets are merged in addition — a request can tighten governance but never loosen it.
The admin operator surface for these controls is a set of API routes, not a
rich React dashboard: apps/oshun/admin/src/app/api/admin/agentic-operations/
exposes kill-switches, snapshot, gold-sets/promote, and
champion-challengers/rollout. The only agent page in the product is
apps/oshun/tenant-admin/src/app/agents/page.tsx. The "operator dashboard for
replay and audit" is a data/query layer (dashboard/dashboard-query.ts,
runs/replay.ts, runs/streaming-progress.ts), not a shipped admin dashboard
page — see
Output Gallery, Lineage, Branch, and Replay.
Run statuses, the cost ledger, and run controls#
A note on the agent.terminate outcome vocabulary: the implemented terminal set
(TERMINAL_STATUSES) is completed, failed, cancelled, killed. There is
no declined status; the run controls expose cancel (operator-deliberate) and
kill (governance-enforced), not a decline. Treat any declined outcome in
older prose as drift toward cancelled.
The full RUN_STATUSES enum (runs/agent-run.ts) is:
queued · starting · running · paused · awaiting_approval ·
awaiting_tool · awaiting_branch_selection · cancelled · killed · failed · completed
(Any simplified lifecycle diagram using pending / planning / executing is
aspirational — those tokens are not in the real enum, which has no planning or
executing state and adds paused, awaiting_tool, and
awaiting_branch_selection.)
The AgentRunEnvelope carries runId, rootAgentId, parentRunId,
pipelineId, the plan (a RunPlanStep[] DAG), toolCalls,
intermediateArtifacts, evidenceTrail, the costLedger, decisionRationale,
outputs, and a provenanceBundleId. The RunCostLedger is denominated
for cross-provider comparability and tracks totalCostUnits, per-stage and
per-tool cost maps (perStageCostUnits / perToolCostUnits),
inputTokenCount, outputTokenCount, gpuMinutes, voiceSeconds,
avatarSeconds, retrievalCount, and externalCallCount.
Run controls (run-controls.ts) are pure (envelope, command) → result
functions emitting typed audit events: pause, resume, cancel, kill,
chooseBranch, fork (requires a fresh runId — re-using the source id is
rejected with fork-without-fresh-id), retryWithChanges (rewind to a step,
mark it and all later steps pending, apply overrides, restart), and
operatorStep (advance exactly one pending step for step-through debugging).
The corresponding audit kinds are pause / resume / cancel / kill /
branch-chosen / fork / retry-with-changes / operator-step.
Persistence candor: in V1 the runtime state is in-memory — the
AgentRegistryis aMap, and the BFF run-lifecycle store is an in-process store.@oshun/agentic-studiodoes not import@oshun/queue; its runs (dispatcher,orchestrator,checkpoint,replay) are in-process, pure-function logic with no DLQ or SLA monitor in this library. The durability/priority-class/DLQ story (the shared queue lib lives atlibs/shared/queue) is not wired into agentic-studio yet — see Agent Registry, Job Orchestration, and Multi-Agent Plans and §18.
Multi-agent hand-off protocol#
Multi-agent pipelines are valuable but dangerous; the protocol in
handoff/handoff-protocol.ts controls budget inheritance, authority delegation,
cycle prevention, and audit completeness so a "Veritas story drafting" agent
that dispatches Sophia retrievers and Isis generators cannot silently spin into
a runaway run.
A parent dispatches via a HandoffDispatch carrying the sub-run ids, the
HandoffBudgetEnvelope (costUnits), the AuthorityEnvelope
(delegableToolIds, maxFanoutDegree, capabilityModifiers), the
approvalRequirement, the parentTrace (ordered ancestor agent ids,
excluding the sub-agent), and an allowCycle flag.
validateDispatch enforces, in order, with a typed DispatchDenialReason:
| Check | Denial reason | Rule |
|---|---|---|
| Cycle | cycle-detected |
The sub-agent id may not appear in parentTrace unless allowCycle is set. |
| Depth | depth-cap-exceeded |
parentTrace.length >= cap; DEFAULT_DEPTH_CAP = 5, overridable per pipeline. |
| Authority — tools | authority-widening-rejected |
Every delegableToolId must be in the parent's set (a subset, never a superset). |
| Authority — fanout | authority-widening-rejected |
maxFanoutDegree may not exceed the parent's. |
| Authority — modifiers | authority-widening-rejected |
capabilityModifiers must be narrower (numbers <=, arrays subset, strings equal, objects recursively narrower). |
| Budget — positivity | budget-non-positive |
The sub-budget must be finite and > 0. |
| Budget — envelope | budget-exceeds-parent |
The sub-budget may not exceed the parent's remaining budget. |
deductDispatchBudget validates and then subtracts the sub-budget from the
parent's remaining envelope. On terminal state, reconcileTerminalBudget
returns the unused budget to the parent (returnedToParent) when there is
no overshoot, and emits an overshootEvent of kind subagent_budget_exceeded
(and shouldKillSubagentForBudget returns true) when the sub-agent overran.
Approval requirements (APPROVAL_REQUIREMENTS) are none, operator,
teacher, guardian, and user-confirm-before-proceed.
initialSubagentRunStatus puts a sub-agent into running only when the
requirement is none; otherwise it enters awaiting_approval.
approvalRolePermitted maps each requirement to the roles that can satisfy it
(e.g., operator accepts platform-operator / tenant-operator;
user-confirm-before-proceed accepts customer / creator).
Audit and attribution. recordDispatchAudit and recordTerminalAudit emit
a HandoffAuditEvent of kind dispatch (target, sub-run, granted budget,
authority) and terminal (terminal state, budget consumed, produced artifact
ids). deriveAttributionChain stamps every artifact with its full
[...parentTrace, producingAgentId] provenance chain — reader-facing surfaces
can collapse it to "agent assisted", but the bundle preserves the path.
Cost-quality modes and their concrete thresholds#
modes/modes.ts defines five customer/creator-selectable COST_QUALITY_MODES,
each with declared, value-tested thresholds (not qualitative hand-waving):
| Mode | maxCostUnits | p95 latency (ms) | min cite density /1k words | min eval score | multi-agent | human checkpoints | external side effects |
|---|---|---|---|---|---|---|---|
fast |
50 | 1 500 | 0 | 0.6 | no | 0 | yes |
balanced |
500 | 10 000 | 2 | 0.7 | no | 0 | yes |
deep |
5 000 | 120 000 | 4 | 0.8 | yes | 1 | yes |
exhaustive |
50 000 | 1 800 000 | 6 | 0.85 | yes | 3 | yes |
rehearsal-dry-run |
1 000 | 60 000 | 0 | 0 | yes | 0 | no |
checkModeCompliance verifies an actual run against its mode and returns typed
violations: cost-exceeded, latency-exceeded, citation-density-below-floor,
evaluation-score-below-floor, and (for rehearsals)
rehearsal-side-effects-detected. applyOverride is tighten-only: a
tenant-operator or customer may narrow the mode within the domain policy's
allowedModes, but a cost-raise-rejected is returned if the requested mode's
cost cap exceeds the current one. The rehearsal-dry-run profile's
hasExternalSideEffects: false is what lets a pipeline rehearse against
fixtures with no real publication, generation, or notification.
Tool isolation, SSRF guards, and secret scoping#
capabilities/tool-isolation.ts backs web.fetch's "SSRF guards" with real
network reasoning rather than a comment:
isPrivateOrLoopbackHostrejects loopback (localhost,127.0.0.0/8,::1,0.0.0.0,::), link-local (169.254.0.0/16,fe80:), IPv6 unique-local (fc00::/7), and the RFC-1918 private ranges via integer CIDR bounds:10.0.0.0/8,172.16.0.0/12,192.168.0.0/16. IPv4-mapped IPv6 (::ffff:…) is unwrapped and re-checked.matchHostPatternsupports exact hosts and*.example.comsubdomain wildcards (normalizing case and bracketed IPv6).checkEgressRequestlayers the checks: invalid URL →invalid-url; SSRF host → the matching SSRF reason; not in the allowlist →host-not-in-allowlist; wrong scheme →scheme-not-allowed; wrong method →method-not-allowed. TheEgressDenialReasonset is the typed vocabulary.checkSecretAccessscopes secrets to permitted agents and tools within a tenant, returningcross-tenant,agent-not-permitted, ortool-not-permitted.
Adversarial security-test catalog#
capabilities/adversarial-tests.ts ships the concrete probe taxonomy
(ADVERSARIAL_CATEGORIES): prompt-injection, tool-call-exfiltration,
scope-escalation, sandbox-escape, persona-bypass, policy-bypass, and
source-fabrication. Each AdversarialFixture carries an expectedVerdict
(refuse | sanitize | comply-and-disclose) and a severity;
evaluateFixtureResult grades an observed result as
safe | suspected | failure (an observed leak is always a failure), and
buildSuiteReport rolls up per-category counts with a worst-severity into an
overallVerdict of pass | review | block — the release-gate signal.
Champion-challenger rollout (statistical readiness)#
feedback/champion-challenger.ts is genuinely statistical, not a toy.
evaluateReadiness requires MIN_SAMPLES = 100, then runs a two-proportion
z-test on task-success rates, with the two-tailed p-value computed from an
Abramowitz & Stegun 26.2.17 normal survival-function approximation
(normalSf). It returns a ReadinessReport (zScore, pValue, latency and
cost deltas) and a verdict of not-enough-samples, inconclusive,
challenger-worse, or challenger-ready (the last two requiring
pValue <= P_THRESHOLD = 0.05). Rollouts run in shadow (challenger never
returned to the user) or canary (routeInvocation routes a canaryFraction
of traffic). recordTransition gates the ROLLOUT_TRANSITIONS —
promote-challenger-to-champion (refused readiness-required unless the
verdict is challenger-ready), freeze-rollout, and
rollback-to-prior-champion — and requires a rationale on every transition.
Cross-domain pipeline pattern reference#
V1 ships a small set of named cross-domain autonomous pipelines. The
implementation note in the source docs points at libs/oshun/agent-pipelines/,
but that package (@oshun/agent-pipelines v0.1.0) is only a four-file
re-export shim whose src/index.ts re-exports grants/resolver and
pipelines/index straight from @oshun/agentic-studio. The real pipeline
specs live in libs/oshun/agentic-studio/src/pipelines/v1-pipelines.ts, and
the PipelineRegistry class lives in pipelines/pipeline-registry.ts.
V1_PIPELINES contains seven specs, all with lifecycleState: 'live'. A
reader should note that Veritas has two distinct pipelines — the source docs
list them in different sections and never reconcile that, but the code is right:
veritas.weekly_briefing_pack (creator-tier) and veritas.story_drafting
(platform-operator-tier) are separate specs (the source file flags the overlap
in a comment).
pipelineId |
Invocation tier (PIPELINE_TIERS) |
Budget cap | Hand-off depth | Mandatory approval gate(s) | Provenance |
|---|---|---|---|---|---|
veritas.weekly_briefing_pack |
creator | 5 000 | 3 | editor.review |
standard |
veritas.story_drafting |
platform-operator | 12 000 | 4 | editorial.inbox.handoff |
extended |
tara.seasonal_program |
platform-operator | 10 000 | 2 | tara.lineage.confirm, editor.review |
extended |
nisaba.edition_study_guide |
customer | 3 000 | 2 | scholar.review |
standard |
nyx.event_explainer_set |
tenant-operator | 2 500 | 2 | nyx.desk.review |
standard |
arete.weekly_review_draft |
customer | 500 | 2 | user.confirm.before.publish |
minimal |
metis.course_from_byom |
customer | 20 000 | 4 | themis.prescreen, tenant.teacher.approval |
extended |
Each PipelineSpec declares its participatingAgentIds, the plan (a stage
DAG), expectedHandoffDepth, totalBudgetCap, mandatoryApprovalGateIds,
expectedOutputSchemaNames, provenanceShape, an evaluationFixtureSetId, and
a lifecycleState. Some concrete stage flows:
veritas.story_drafting(platform-operator only): source-enumeration → ingestion-verification → claim-extraction → fact-check → contradiction-check → counterclaim-generation → lilith-tone-review → editorial-inbox-handoff.metis.course_from_byom(customer, under tenant policy): byom-safety-scan → ingestion → objective-extraction → kg-anchor → prerequisite-chain → item-bank → calibration → themis-prescreen → tenant-teacher-approval (see Metis — Education and Tutoring).tara.seasonal_programleads with lineage-rights-confirmation and a Lilith strict tone check;arete.weekly_review_draftruns only against the user's own consented data and ends with user-confirm-before-publish (see Arete — Goals, Habits, and Reflection).
Pipeline scheduling, customization, and observability#
The "tenant-scoped pipeline scheduling" and "pipeline observability" lines have real backing symbols:
- Scheduling —
PipelineScheduleandnextFireFor(schedule, nowUnixSeconds)(pipelines/observability.ts) compute the next fire time for a cadence. - Tenant customization —
TenantPipelineCustomizationandeffectivePipelineForTenant({ base, customization })(pipelines/pipeline-registry.ts) resolve a tenant's effective pipeline from the base spec plus its overrides;PIPELINE_TIERSenumerates the invocation tiers. - Observability —
buildPipelineObservabilityReportproduces SLA-per-stage, evidence-completeness, and human-touch-point measures fromPipelineStageObservations;detectPipelineDeviationflags runs whose shape drifts from the declared pipeline and routes them to operator review.
The autonomous creative orchestrator (@oshun/creative-orchestrator)#
Distinct from agentic-studio (which is the governance/orchestration brain),
libs/oshun/creative-orchestrator (@oshun/creative-orchestrator v0.0.1,
depends on @oshun/ai) is the brief → produced-content engine. It is fully
real (58 passing tests) and built on @oshun/ai/agent-loop's
runStructuredOutput and runReflexion, yet is undocumented elsewhere in the
V1 feature docs. Its exported surface:
decomposeBrief(withCREATIVE_PLAN_SCHEMA) — turns a brief into a schema-validated, acyclicCreativePlanDAG (validated byvalidateDagStructure/detectCycle/topologicalOrder).routePlan/CreativeOrchestrator/orchestrateBrief— governed plan → domain-generator dispatch through aGeneratorRegistry.reviseArtifact— a bounded generate → critique → revise (Reflexion) loop around every artifact, with criticscreateMetricCritic,createLlmJudgeCritic, andcreateContentEvalCritic(a non-provider-gated default judge).BudgetGovernanceGate/ALLOW_ALL_GATE— the governance seam over dispatch.createYemayaAgentGenerator/createMetisNarrator— domain generator adapters.
Like the rest of the autonomous-content story, the model and provider boundaries are injectable and fail-loud — the orchestrator never fabricates a plan or an artifact; with no provider wired, it refuses rather than inventing output.
Tests#
The protocol and catalog behaviors above are value-tested (271 tests, 16 files
in @oshun/agentic-studio):
- Tool catalog — every V1 entry round-trips through the contract; semver dependency resolution against registered tools.
- Grant scope — per-scope revocation propagation across in-flight runs and
sub-agents within the documented latency budget (the
withinFiveSecondSloflag). - Multi-agent budget inheritance — parent plus sub-budget sum within the
declared envelope; unused returns; overshoots emit
subagent_budget_exceeded. - Authority envelope — widening rejected at dispatch; narrowing honored; capability modifiers propagated narrower.
- Cycle prevention — re-entering an ancestor
agentIdblocked unlessallowCycleis set at the root; depth cap honored. - Hand-off audit — dispatch / terminal / attribution records complete.
- Cross-domain pipeline fixtures — each named pipeline produces the expected output shape on its declared fixture set; approval gates pause correctly.
- Kill-switch propagation — platform-wide tool kill propagates to all in-flight runs (≤ 5 s budget); tenant-scoped kill is confined to that tenant.
- Runtime gate —
admitToolCall/dispatchGuardedToolCall/runGuardedToolPlanexecute a tool only when admitted; a kill switch or exhausted budget stops the plan mid-flight.
See §18 in ../TODOS.md for the agentic backlog, and the
agentic-pipeline-customer-invocation end-to-end walkthrough (classified
"deep", with a creds-bound tool DAG) for the live-provider boundary.