# Agent Registry, Job Orchestration, and Multi-Agent Plans

The Agentic AI Studio is V1's first-class surface for autonomous content
development, research, tutoring, curation, education, and operations. Agents
here are not hidden orchestration: they are observable, governed, budgeted,
attributable products consumed by customers, creators, operators, and Metis
institutional partners across Oshun Web, Mobile, Studio, Admin, and the Tenant
Console. This page is the foundational third of the Studio — the canonical
**agent registry** (who exists and what authority they carry), the **job
orchestration** layer that executes long-lived agent runs durably, and the
**multi-agent plan / hand-off / approval-gate** machinery that keeps a research
agent dispatching a Sophia retriever and an Isis generator from silently
spinning into a runaway run. Its siblings cover the rest:
[Agent Invocation, Budgets, Memory, and Feedback Loops](./agentic-invocation-budgets-grants.md)
and
[Tool Catalog, Grant Semantics, and Multi-Agent Protocol](./agentic-tool-catalog.md).

## Where this sits, and what is real versus gated

Unusually for V1's substrate libraries, the agentic Studio's data and logic
plane is overwhelmingly **real and value-tested**, not aspirational. The package
`@oshun/agentic-studio` (v0.1.0, `libs/oshun/agentic-studio`) re-exports twelve
implemented subsystems from its `src/index.ts` — `registry`, `runs`, `plans`,
`dashboard`, `invocation`, `budgets`, `capabilities`, `feedback`, `modes`,
`pipelines`, `grants`, and `handoff` — and ships **271 passing tests across 16
test files**. The state machines, the SSRF egress guards, the grant-resolution
specificity ordering, the champion-challenger z-test, and the hand-off
budget/authority/cycle/depth validation are all genuine domain logic. It is
honest, though, to draw the line precisely:

- **Real, in-repo, unit-tested.** Everything described below as a function,
  enum, schema, or validation rule. The registry's integrity checks, the
  `AgentRunEnvelope`, the run-control state transitions, the checkpoint/resume
  fingerprinting, the replay timeline, the streaming-progress visibility tiers,
  the plan DAG validator (cycle detection and topological order), the approval
  gates, the branching selection policies, the re-plan provenance, the operator
  dashboard query/audit/action data plane, and the runtime governance seam
  (`admitToolCall` → `dispatchGuardedToolCall` → `runGuardedToolPlan`).
- **In-memory / in-process, not durable.** The persistence is deliberately a
  data-and-logic layer, not yet wired to durable infrastructure. `AgentRegistry`
  is backed by a `Map<string, AgentRegistryEntry>`; the BFF run-lifecycle store
  (`apps/oshun/bff/src/agentic/run-lifecycle-store.ts`) is an in-process store.
  The Studio does **not** import `@oshun/queue` (the durable-jobs lib lives at
  `libs/shared/queue`), so claims of "durable jobs with priority classes / DLQ /
  SLA monitor" are aspirational _at the Studio layer_. The runs modules
  (`runs/dispatcher.ts`, `runs/orchestrator.ts`, `runs/checkpoint.ts`,
  `runs/replay.ts`) are pure-function logic with no queue binding. Checkpoints
  model durability (a `RunCheckpoint` snapshots envelope state plus an artifact
  manifest with `persistedKey`s reachable from a side object store), but the
  side store itself is a deploy-time integration.
- **Data/query layers, not a rich admin UI.** "Replay and time-travel
  debugging," "streaming progress," and the "operator dashboard" are
  _implemented as query/projection modules_ (`runs/replay.ts`,
  `runs/streaming-progress.ts`, `dashboard/dashboard-query.ts`,
  `dashboard/run-detail-page.ts`). There is no rich admin React dashboard page:
  the admin app exposes only API routes under
  `apps/oshun/admin/src/app/api/admin/agentic-operations/` (`kill-switches`,
  `snapshot`, `gold-sets/promote`, `champion-challengers/rollout`), and the only
  agent _page_ in the codebase is
  `apps/oshun/tenant-admin/src/app/agents/page.tsx`. (Companion-doc note:
  `ARCHITECTURE.md`'s reference to an operator dashboard at
  `apps/oshun/admin/src/.../agents/` does not resolve — no such directory
  exists.)
- **Governance brain real; live tool execution boundary-gated.** Tool grants and
  capability modifiers are declared, validated, and enforced as ceilings, but
  the actual tool _execution_ (the real `web.fetch`, `sophia.ground`, and so on)
  is injected at the app boundary and fail-closed by default. The Studio is the
  enforcement and accounting engine; "autonomous content gets produced
  end-to-end through live providers" is a seam-and-fail-loud story, not a wired
  one. See
  [Tool Catalog, Grant Semantics, and Multi-Agent Protocol](./agentic-tool-catalog.md)
  for the catalog and grant semantics.

This page documents the registry, jobs, and plans honestly against that line.

## Agent Registry and Catalog

Every agent that can run is a row in the canonical `AgentRegistry`
(`registry/agent-registry.ts`). The registry is the single source of truth for
an agent's identity and authority: a stable `agentId`, a `semver`, the agent
`family`, versioned `capabilities`, declared `toolGrants` and `dataScopes`, a
`modelBinding`, a `personaBinding`, a `tonePolicyId` / `groundingPolicyId` /
`auditPolicyId`, a `costClass`, a `lifecycleState`, and the `visibility` /
`disclosureRequirements` that drive catalog exposure.

### The `AgentRegistryEntry` schema

| Field                     | Type                                                    | Meaning                                                                                              |
| ------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `agentId`                 | `string`                                                | Stable identifier; duplicates rejected at `register`.                                                |
| `semver`                  | `string`                                                | Agent version; must match the semver regex.                                                          |
| `family`                  | `AgentFamily`                                           | One of the 20 canonical families (below).                                                            |
| `displayName` / `summary` | `string`                                                | Catalog-card copy.                                                                                   |
| `ownerTeamId`             | `string`                                                | Owning team — a dashboard filter dimension.                                                          |
| `tenantScope`             | `'platform' \| { tenantId }`                            | Platform-wide or tenant-bound.                                                                       |
| `capabilities`            | `AgentCapability[]`                                     | Each with its own `semver`, input/output schema names, supported locales/domains.                    |
| `toolGrants`              | `string[]`                                              | Tool ids the agent may call — hard-capped by the family ceiling.                                     |
| `dataScopes`              | `DataScope[]`                                           | From the 8 declared scopes (below).                                                                  |
| `modelBinding`            | `AgentModelBinding`                                     | `providerId`/`modelId`/`semver` + `defaultMaxTokens`/`defaultTemperature` + fallback provider/model. |
| `personaBinding`          | `string`                                                | Lilith persona (see [Lilith Persona Policy](./lilith-persona-policy.md)).                            |
| `costClass`               | `AgentCostClass`                                        | `micro \| small \| medium \| large \| flagship`.                                                     |
| `lifecycleState`          | `AgentLifecycleState`                                   | `draft \| rehearsal \| shadow \| champion \| challenger \| deprecated \| retired`.                   |
| `experimental`            | `boolean`                                               | Gates catalog visibility behind a feature flag.                                                      |
| `visibility`              | `('platform' \| 'tenant' \| 'creator' \| 'customer')[]` | Which roles see the agent.                                                                           |
| `disclosureRequirements`  | `string[]`                                              | Mandatory for a `champion`.                                                                          |

### The 20 canonical agent families

`AGENT_FAMILIES` (`registry/agent-families.ts`) declares exactly twenty
families. Each family carries `AgentFamilyMeta` with a `summary`,
`primaryDomains`, a `defaultGroundingPolicy`
(`strict-cite | cite-where-available | no-citation`), a `defaultPersonaPolicy`
(`lilith.teacher | guide | scribe | host`), and — the load-bearing field — a
**`toolGrantCeiling`** that the registry enforces as a hard ceiling on an
agent's declared `toolGrants`.

| Family                       | Summary                                                  | Tool-grant ceiling                                               |
| ---------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------- |
| `research`                   | Find and triage sources for a topic.                     | `web.fetch`, `source.fetch`, `sophia.ground`, `memory.read`      |
| `drafting`                   | Produce an initial draft against a brief.                | `source.fetch`, `sophia.ground`, `memory.read`, `persona.invoke` |
| `editing`                    | Edit a draft for clarity, tone, structure, and accuracy. | `memory.read`, `sophia.fact_check`, `persona.invoke`             |
| `fact-checking`              | Verify claims against evidence packs.                    | `sophia.fact_check`, `sophia.ground`, `source.fetch`             |
| `citation-verification`      | Re-anchor citations and verify quotes.                   | `source.fetch`, `sophia.ground`                                  |
| `illustration`               | Generate or compose illustrative imagery.                | `generate.image`, `composition.suggest`                          |
| `narration`                  | Render audio narration with a voice profile.             | `generate.audio`, `persona.invoke`                               |
| `translation`                | Translate text/transcripts between locales.              | `memory.read`                                                    |
| `course-generation`          | Generate a full course from a brief/BYOM.                | `source.fetch`, `sophia.ground`, `persona.invoke`, `handoff`     |
| `lesson-scaffolding`         | Per-lesson scaffolds with objectives and checks.         | `source.fetch`, `memory.read`, `persona.invoke`                  |
| `assessment-generation`      | Items with answer keys and rubrics.                      | `source.fetch`, `themis.adjudicate`                              |
| `study-plan-synthesis`       | Personalized study plan from mastery state.              | `memory.read`                                                    |
| `recommendation-explanation` | Human-readable rationales for a recommendation.          | `memory.read`                                                    |
| `moderation-triage`          | Pre-screen content for safety/abuse/policy.              | `themis.adjudicate`                                              |
| `support-triage`             | Route support requests to the right human queue.         | `memory.read`, `handoff`                                         |
| `ritual-scriptwriting`       | Draft ritual scripts with lineage-aware tone.            | `source.fetch`, `persona.invoke`, `approval.request`             |
| `sky-event-briefing`         | Explainer set for an upcoming celestial event.           | `source.fetch`, `sophia.ground`, `generate.image`                |
| `claim-extraction`           | Extract atomic claims from a passage/draft.              | `memory.read`                                                    |
| `source-ingestion`           | Onboard a new source with metadata.                      | `web.fetch`, `source.fetch`, `file.read`                         |
| `kg-promotion`               | Promote concepts into the knowledge graph.               | `memory.read`, `memory.write`, `approval.request`                |

The family ceiling is a structural guarantee: an `illustration` agent literally
_cannot_ be registered with a `web.fetch` grant, because that tool is absent
from the family's `toolGrantCeiling` — `validateEntry` emits a
`tool-grant-exceeds-family-ceiling` error and `register` refuses the entry. This
is why the family taxonomy is more than documentation: it is the first authority
boundary an agent passes through.

### The eight data scopes and five cost classes

`DATA_SCOPES` enumerates exactly eight declared scopes, partitioned by owner and
sensitivity: `tenant.public`, `tenant.private`, `user.public`, `user.private`,
`editorial.draft`, `editorial.published`, `platform.metadata`, and
`platform.curated`. `AGENT_COST_CLASSES` are `micro`, `small`, `medium`,
`large`, and `flagship` — the same five-step ladder the dashboard cost bands
mirror.

### Registry integrity checks

`AgentRegistry.register(entry)` returns a
`readonly AgentRegistryValidationError[]` (empty array = accepted). Nothing is
stored unless validation passes. The validation is a real
allowlist-and-consistency pass, not a shape check; it rejects:

- `duplicate-agent-id` — the `agentId` is already registered.
- `unknown-family` — `family` is not in `AGENT_FAMILIES`.
- `tool-grant-exceeds-family-ceiling` — a declared tool is outside the family's
  `toolGrantCeiling` (one error per offending tool).
- `invalid-semver` — the agent, a capability, or the model binding has a version
  that fails `^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$`.
- `unknown-data-scope` — a declared scope is not in `DATA_SCOPES`.
- `lifecycle-experimental-mismatch` — an `experimental` agent claims the
  `champion` lifecycle state (an experiment cannot be the production champion).
- `duplicate-capability-id` — two capabilities share a `capabilityId`.
- `champion-without-disclosure` — a `champion` agent declares no
  `disclosureRequirements` (the public must always be told an agent assisted).

`upsert` runs the same validation but allows replacing an existing entry; only
`register` enforces the duplicate-id rule.

### Catalog visibility — per-tenant, per-role, experimental segregation

`isEntryVisibleTo(entry, viewer)` resolves the browsable catalog for a
`CatalogViewer` (`{ tenantId, role, featureFlags }`), where `role` is one of
`platform-operator | tenant-operator | creator | customer`. The rules layer in
order: a `retired` agent is never visible; a tenant-scoped agent is invisible to
other tenants; an `experimental` agent is hidden unless the viewer holds the
flag `agent.experimental.<agentId>` (platform operators always see experimental
agents); and otherwise visibility is keyed off the entry's `visibility` array
for the viewer's role. This is what realizes the "per-tenant catalog views,
per-role visibility, and feature-flagged experimental agents segregated from the
default catalog" promise. Capability cards, sample runs, evaluation scores, cost
ranges, latency ranges, supported domains/locales, required entitlements, and
reviewer comments are projected over this entry set; the entry itself carries
the locale/domain bindings (per `AgentCapability`) and disclosure requirements
that those cards display.

## Job Orchestration and Long-Running Runs

A run is captured in the durable `AgentRunEnvelope` (`runs/agent-run.ts`) — the
record that makes a run observable, attributable, and resumable rather than a
black box.

### The `AgentRunEnvelope`

The envelope threads the run's whole life: `runId`, `rootAgentId` /
`rootAgentSemver`, `parentRunId` (set on a fork or sub-agent), `pipelineId` /
`pipelineSemver`, `status`, `tenantId`, the `inputs`, the `plan`
(`RunPlanStep[]`), the `toolCalls` (`RunToolCallRecord[]`),
`intermediateArtifacts`, the `evidenceTrail`, the `costLedger`, the
`decisionRationale`, the `outputs`, timestamps, and a `provenanceBundleId`. Each
sub-record is purpose-built:

- **`RunToolCallRecord`** — `callId`, `toolId` and `toolSemver`, an `argsHash`
  (never raw args), start/complete timestamps, an `outcome`
  (`pending | ok | error | denied | cancelled`), `costUnits`, `latencyMs`, a
  `scopeCheckResultId`, an `auditEventId`, and `emittedArtifactIds`. The
  argsHash and result-redaction discipline means transcripts are auditable
  without leaking payloads.
- **`RunEvidenceTrailEntry`** — per stage, the `evidencePackId`, a
  `groundingState`
  (`grounded | partial | ungrounded | abstained | retracted-source`), and a
  `citationCount`. This is how a run proves it stayed grounded (see
  [Sophia Grounding](./sophia-grounding.md)).
- **`RunDecisionRationale`** — per stage, a free-text rationale plus a
  `decisionLabel`
  (`plan-step-completed | plan-branch-chosen | plan-replanned | gate-passed | gate-failed | tool-selected | tool-skipped | abstained`)
  and an optional `evidencePackId`. An agent's _why_ is first-class, not
  reconstructed.
- **`RunPlanStep`** — `stepId` / `stageId`, the bound `agentId` and
  `agentSemver`, a `status`
  (`pending | running | completed | failed | skipped`), timestamps, and
  `dependsOnStepIds`.

### The run cost ledger

`RunCostLedger` is a multi-dimensional accounting structure, not a single cost
number. It tracks `totalCostUnits` plus two `ReadonlyMap`s — `perStageCostUnits`
and `perToolCostUnits` — and the raw resource dimensions: `inputTokenCount`,
`outputTokenCount`, `gpuMinutes`, `voiceSeconds`, `avatarSeconds`,
`retrievalCount`, and `externalCallCount`. `recordToolCallInLedger` folds a tool
call into the ledger, attributing its cost to both the stage and the tool and
incrementing `externalCallCount`. Cost is denominated in **cost-units** (a
token-cost-equivalent normalized across providers) so cross-provider hand-offs
and budgets are comparable — the same unit the budget and hand-off envelopes
speak. (For the budget enforcement that consumes this ledger, see
[Agent Invocation, Budgets, Memory, and Feedback Loops](./agentic-invocation-budgets-grants.md).)

### The real run-status enum

`RUN_STATUSES` is the implemented state vocabulary, and it is worth naming
exactly, because it is richer than the simplified mermaid sketch in the
companion architecture doc (which uses `pending`/`planning`/`executing` — states
that do **not** exist in code):

```
queued · starting · running · paused · awaiting_approval ·
awaiting_tool · awaiting_branch_selection · cancelled · killed ·
failed · completed
```

The `TERMINAL_STATUSES` set is `{ completed, failed, cancelled, killed }`.
`killed` is intentionally distinct from `cancelled`: a `cancelled` run is a
deliberate operator stop, a `failed` run hit an error, and a `killed` run was
terminated mid-flight by the governance executor (an active kill switch or an
exhausted budget). `isTerminal(status)` is the single guard every control
function consults before mutating a run.

### Long-running execution — checkpoint, resume, partial-output preservation

For pipelines that run for hours or days (multi-source course generation, weekly
Veritas briefing batches, multi-locale dubbing of a Tara series),
`runs/checkpoint.ts` provides the durability primitives:

- `takeCheckpoint` snapshots the envelope into a `RunCheckpoint`: a
  `checkpointId`, the `checkpointedAfterStepId` (the last _completed_ step), an
  `envelopeSnapshotFingerprint`, the `artifactManifest` (the intermediate
  artifacts, each with a `persistedKey` reachable from a side object store), and
  a `resumeBudgetUnits`.
- `resumeFromCheckpoint` returns the envelope with completed steps
  **preserved**, every non-completed step reset to `pending`, and status set to
  `queued`. It refuses with `unknown-checkpoint` if the checkpoint's `runId`
  does not match, or `fingerprint-mismatch` if the persisted envelope has
  diverged from the snapshot. The fingerprint (`fingerprintEnvelope`) is
  computed over the deterministic fields — sorted completed-step ids, artifact
  ids, output ids, and total cost — so resume cannot silently re-attach to a
  mutated run.
- `preservePartialOutputs` captures whatever artifacts a `failed`, `cancelled`,
  or `killed` run produced into a `partial-on-failure` output bundle
  (`<runId>.partial`), so a long run that dies near the end does not lose its
  work.

(Durability here is modeled in pure functions; the side object store and durable
queue are deploy-time integrations, not bound inside the Studio library.)

### Streaming progress visibility

`runs/streaming-progress.ts` is the append-only progress stream. Events
(`RunProgressEvent`) carry a monotonic `sequence`, a tenant scope, a
`RunProgressKind` (the 15 kinds include `plan-published`,
`plan-step-started/completed`, `tool-call-started/completed/denied`,
`artifact-produced`, `evidence-pack-bound`, `cost-update`,
`gate-paused/released`, `output-bundle-ready`, `replan-issued`, and `error`),
and — crucially — a `ProgressVisibility` tier:

| Visibility tier          | Delivered to                     |
| ------------------------ | -------------------------------- |
| `operator-only`          | platform + tenant operators only |
| `creator-and-operator`   | operators and the creator        |
| `all-including-customer` | everyone, including the customer |

`shouldDeliverToSubscriber` enforces both the tenant boundary (only a platform
operator crosses tenants) and the visibility tier, so internal tool-call
transcripts never leak to a customer subscriber. `summarizeForCustomer` derives
the plain narrative a customer-facing run card shows — "outline drafted, sources
gathered, claims verified, illustrations generated, narration rendered, review
pending, publish scheduled" — by filtering to `all-including-customer` events
and ordering by sequence. `appendProgressEvent` is the log helper that fails
closed on `non-monotonic-sequence`, `tenant-mismatch`, or `run-mismatch`.

### Run controls — pause, resume, branch, fork, retry, operator-step

`runs/run-controls.ts` implements the operator controls as pure
`(envelope, command) → ControlResult` functions. A success returns the next
envelope plus a `RunControlAuditEvent`; an invalid transition returns a typed
`ControlDenialReason`. Every control emits an audit event of a matching `kind`:

| Control            | Audit kind           | Behavior / guard                                                                                                                                                                            |
| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pause`            | `pause`              | Only from a pausable status (`queued`, `starting`, `running`, `awaiting_tool`, `awaiting_branch_selection`); refuses if terminal.                                                           |
| `resume`           | `resume`             | Only from `paused`; → `running`.                                                                                                                                                            |
| `cancel`           | `cancel`             | Deliberate operator stop → terminal `cancelled`; refuses if already terminal.                                                                                                               |
| `kill`             | `kill`               | Enforced termination → terminal `killed`; the governance executor's stop, distinct from `cancel`.                                                                                           |
| `chooseBranch`     | `branch-chosen`      | Only from `awaiting_branch_selection`; the chosen `stepId` must exist in the plan.                                                                                                          |
| `fork`             | `fork`               | Clones the envelope under a **fresh** `newRunId` (re-using the source id is rejected with `fork-without-fresh-id`); completed steps preserved, status reset to `queued`, `parentRunId` set. |
| `retryWithChanges` | `retry-with-changes` | Rewinds to `rewindToStepId`, marks it and all later steps `pending`, applies caller `stepOverrides`, restarts at `queued`.                                                                  |
| `operatorStep`     | `operator-step`      | Only when `paused`; advances exactly one `pending` step to `running` for manual step-through debugging.                                                                                     |

### Replay and time-travel debugging

`runs/replay.ts` builds an investigation view over a saved run.
`buildReplayTimeline` merges plan steps, tool calls, intermediate artifacts, and
progress events into one chronologically sorted `ReplayTimeline`.
`reconstructStateAt(atUnixSeconds)` is genuine time-travel: it reconstructs the
envelope as of a cutoff instant. Completed steps with `completedAt ≤ t` are kept
as-is, steps merely started by `t` are shown `running`, and later steps are
reset to `pending`. Tool calls, artifacts, and outputs are filtered to those at
or before the cutoff, and the status is taken from the latest applicable
progress event. This is the "full intermediate state and tool-call transcripts"
the source promises — and it is, honestly, a query layer, not an interactive UI.

## Multi-Agent Plans, Hand-Offs, and Approval Gates

A run's behavior is governed by a declared plan, which is independent of the
scheduled `RunPlanStep[]` execution.

### The plan DAG

`plans/plan-dag.ts` models a plan as a `PlanDag` of `PlanStage`s. Each stage
declares its `agentId` and `agentSemver`, `inputSchemaName` /
`outputSchemaName`, an optional `evidenceRequirementId`, an optional
`mandatoryCheckpointId`, `qualityGateIds`, `dependsOnStageIds`, and a
`branchingKind` (`sequential | parallel-fanout | parallel-merge`). This is the
visible "research → claim-extraction → fact-check → drafting → editing →
illustration → narration → safety-review → publish-gate" DAG the source
describes. `validatePlanDag` is a real graph validator that emits
`duplicate-stage-id`, `unknown-dependency`, `unknown-quality-gate`, and
`cycle-detected` (the cycle detector is an iterative white/gray/black DFS that
reconstructs and returns the offending cycle path). `topologicalOrder` returns a
Kahn's-algorithm ordering where dependencies always precede dependents — the
scheduler's execution order.

### Per-stage hand-off contracts

`plans/handoff-contracts.ts` binds `(stageA.outputSchema → stageB.inputSchema)`
plus the evidence and checkpoint guarantees that must hold at the boundary. A
`HandoffContract` declares `fromOutputSchemaName` / `toInputSchemaName`, an
optional `requiredEvidenceRequirementId`, a `requiresCheckpoint` flag, and
`qualityGateIds`. `evaluateHandoff` returns `{ ok: true }` only when _all_ hold;
otherwise it returns a structured list of every failure — `schema-mismatch`
(with expected/actual), `missing-evidence-pack`, `missing-checkpoint`, and one
`unsatisfied-gate` per gate. A failed hand-off forces the plan to re-plan or
pause; it never silently proceeds.

### Operator-defined approval gates

`plans/approval-gates.ts` implements the four gate kinds:
`human-required-before-stage` (pause before a named stage), `before-publish`
(pause before any publishable output bundle),
`expert-required-on-claim-density-above-threshold` (pause when the produced
claim density crosses `claimDensityThresholdPer1000Words`), and `tenant-policy`
(a tenant predicate gate). `gatesApplicableNow` decides which gates apply at the
current execution moment from a `GateEvaluationContext`. `approveGate` /
`rejectGate` enforce that the approver's role is in `requiredApproverRoles`
(else `role-not-permitted`), the gate is still `pending` (else
`already-resolved`), and the rationale is non-empty (else `empty-rationale`).
`overrideGate` is the elevated-authority escape hatch: it bypasses the
required-approver check but records `overridden: true` and demands a non-empty
rationale — the override is captured honestly and **does not pretend the
required approver actually approved**.

### Branching plans for parallel exploration

`plans/branching.ts` realizes "three illustration drafts, two outline
structures, multiple translation candidates." A `BranchingPlanSegment` fans out
at a stage into N `PlanBranch`es that all execute, then merges with a
`BranchSelectionPolicy`: `explicit-selection` (operator/customer picks the
winner), `score-merge` (branches scored by a declared `scoringRubricId`, highest
wins), or `merge-all` (all outputs merged via a `mergeStrategyId`).
`selectBranchOutcome` validates the choice — rejecting `unknown-branch`,
`explicit-choice-missing`, `score-missing`, or `missing-branch-outcome` — and
returns a typed `BranchSelection`.

### Re-planning with audited provenance

`plans/replan.ts` makes mid-run re-planning first-class and audited.
`REPLAN_TRIGGERS` enumerates ten triggers: `gate-failed`, `gate-rejected`,
`new-evidence`, `retracted-source`, `policy-change`, `budget-exceeded`,
`unsupported-claim-detected`, `contradiction-detected`, `tool-revoked`, and
`operator-directed`. `issueReplan` builds a `ReplanEvent` binding the prior plan
id, the new plan id, the trigger, a typed `ReplanEvidence` discriminated union
(e.g., `retracted-source` carries `sourceIds`, `policy-change` carries
`policyId`/`policyVersion`, `tool-revoked` carries the `toolId`), the rationale,
and the actor. It refuses with `unknown-trigger`, `plan-unchanged` (the new plan
id equals the prior), or `evidence-mismatch` (the evidence kind contradicts the
declared trigger). So a re-plan can never lose _why_ it happened.

## The Runtime Governance Seam

Plans, budgets, and kill switches _decide_ (pure verdicts), but a decision is
inert until something acts on it. The Studio's enforcement seam — the concrete
machinery the source gestures at abstractly — is three layers in `runs/`:

1. **`admitToolCall`** (`runs/executor.ts`) is the admission gate evaluated at
   **every tool-call boundary**, running governance checks in **safety → cost →
   rate** order. An active kill switch (`decideExecution`) terminates the run
   (`killed`, terminal) with the operator's user-visible status copy and a
   `safeDegradationModeId`. An exhausted budget (`checkBudget` → `exceeded`)
   likewise terminates; `warn`/`grace` verdicts admit the call but surface as
   `BudgetWarning`s. A failed token acquire (`acquireToken`) is back-pressure:
   the call is `throttled` with a `retryAfterSeconds` and the run is **not**
   terminated. Otherwise the call is admitted.
2. **`dispatchGuardedToolCall`** (`runs/dispatcher.ts`) wraps the injected tool
   function so that `runTool` executes **iff** the call is admitted — a kill
   switch or exhausted budget genuinely prevents the tool from running (and ends
   the run), a throttle defers it, and a terminal run refuses it.
3. **`runGuardedToolPlan`** (`runs/orchestrator.ts`) threads the envelope
   through a sequence of `PlannedToolCall`s; the moment a call is killed or
   throttled, it **stops** — the remaining planned calls never execute. This is
   what makes "budgets honored, kill switches enforced" hold across a whole run,
   not just one call.

### Where the seam is mounted — the BFF run surface

The guard had no runtime caller until it was mounted at
`POST /v1/agentic/runs/execute` (`apps/oshun/bff/src/agentic/runs-route.ts`).
The route is the concrete enforcement story, and it is fail-closed by
construction:

- **`503 agent_tools_not_configured`** — every planned `toolId` must be a
  registered tool; with no tools configured (`notConfiguredAgentToolRegistry`
  returns `{}`), every plan is refused. The route never fabricates a run.
- **`400 invalid_request`** — missing `runId` / `tenantId` / `rootAgentId`, or
  an empty/malformed plan.
- **`403 tool_scope_missing`** — a tool with a `toolScopeChecks` requirement
  runs only for callers whose **real** `authContext.scopes` satisfy it; the
  executor never invents an operator tier for whoever shows up.
- **`409 run_id_conflict`** — a `runId` already persisted by another member is
  never overwritable; re-execution would replace their envelope and approval
  history.
- **Server-authoritative identity** — the `actorId` is the authenticated
  principal and the `tenantId` comes **only** from the verified claim
  (`authContext.tenantId`), never the body, so a body tenant cannot dodge or
  hijack a tenant-scoped kill switch.
- **Server-side kill-switch targets** — each tool's `family` / `providerId` /
  `region` come from server-side `toolTargets` metadata, never the request body
  (a missing field defaults to `'default'`), so an armed family switch is not
  evadable by omitting a field.
- **Every-request kill-switch resolution** — operator-armed switches are read
  from `adminAgenticOperationsStore` on every request and mapped via
  `toExecutionKillSwitch`, honoring the kill switch's ≤ 5 s "arm → next
  tool-call boundary" propagation budget. Client-supplied switches and budgets
  are merged **in addition** — a request can tighten governance but never loosen
  it.

On a fully executed, unterminated plan, the route finalizes the envelope as
`completed` and persists it via `agentRunLifecycleStore.recordExecutedRun`
rather than discarding it after replying, so the operator dashboard sees real
runs.

## Operator Job Dashboard, Replay, and Audit

The operator surface is a **data and query plane** (`dashboard/`), consumed by
the admin agentic-operations API routes — not a bespoke React dashboard.

### Cross-tenant filtered run listing

`dashboard/dashboard-query.ts` buckets runs into `active`, `queued`, `paused`,
`blocked`, and `recently-completed` (`bucketForStatus` maps statuses:
`awaiting_approval`/`awaiting_branch_selection` → `blocked`). A
`DashboardFilter` filters by `bucket`, `agentFamily`, `ownerTeamId`, `tenantId`,
`costBand` (`low < 100 ≤ medium < 1000 ≤ high < 10000 ≤ flagship`, via
`costBandFor`), `latencyBand` (`fast < 1000ms ≤ normal < 10000ms ≤ slow`, via
`latencyBandFor`), `severity`, `approvalState`, and a time window — plus a
`recentlyCompletedWindowSeconds` cutoff. `materializeDashboardEntry` projects an
`AgentRunEnvelope` into a `DashboardEntry` with its bucket, cost, latency, and
approval flags.

### Per-run page data

`dashboard/run-detail-page.ts`'s `buildRunDetailPage` materializes the per-run
view: `dagNodes` (each stage with its status, inbound stages, per-stage cost
from the ledger, and its gate states), a `toolCallTranscript` with **redacted**
args and result previews (via injected `redactArgs` / `redactResult` — raw
payloads never appear), the evidence trail, sampled outputs (capped by
`sampleOutputLimit`), intermediate artifacts, `evaluationScores`, an
`escalationHistory`, bound `reviewPackageIds`, and the `replanEvents`.

### Operator actions and immutable audit

`dashboard/operator-actions.ts` authorizes the bulk and per-run actions —
`pause`, `kill`, `escalate`, `override-gate`, `mark-reviewed`, `defer`,
`reassign` — against an `ACTION_AUTHORIZATION` map (`override-gate` and
`reassign` are `platform-operator`-only; the rest allow tenant operators too).
`authorizeOperatorAction` refuses `no-targets`, `role-not-permitted` (creators
and customers are blocked outright), and `rationale-required`, and emits an
`OperatorActionAudit`. `dashboard/audit-events.ts` is the immutable per-run
audit log: `AUDIT_EVENT_KINDS` covers run lifecycle, plan steps, tool calls,
gate decisions, re-plans, budget-exceeded, tool-revoked, and output emission.
`appendAuditEvent` enforces monotonic per-run sequence and tenant/run scope, and
`searchAudit` powers cross-run search (a tenant operator is scoped to their own
tenant). `SavedInvestigation` persists a saved query, and `exportRunBundle`
produces a redaction-policy-stamped, sequence-sorted `ExportableRunBundle` for
postmortems and run-replay hand-off between operators.

## Cross-Domain Autonomous Pipelines

The Studio ships seven named V1 pipelines as `PipelineSpec`s in
`pipelines/v1-pipelines.ts`, all with `lifecycleState: 'live'`. Each declares
its participating agents, expected hand-off depth, a total budget cap, mandatory
approval gates, expected outputs, a provenance shape, and an evaluation fixture
set:

| Pipeline id                    | Invocation tier     | Budget cap |
| ------------------------------ | ------------------- | ---------- |
| `veritas.weekly_briefing_pack` | `creator`           | 5000       |
| `veritas.story_drafting`       | `platform-operator` | 12000      |
| `tara.seasonal_program`        | `platform-operator` | 10000      |
| `nisaba.edition_study_guide`   | `customer`          | 3000       |
| `nyx.event_explainer_set`      | `tenant-operator`   | 2500       |
| `arete.weekly_review_draft`    | `customer`          | 500        |
| `metis.course_from_byom`       | `customer`          | 20000      |

> **Two distinct Veritas pipelines.** There are genuinely two — and the
> `v1-pipelines.ts` source flags the overlap itself.
> `VERITAS_WEEKLY_BRIEFING_PACK` is a creator-tier pipeline that aggregates the
> week's stories into a multi-format briefing pack; `VERITAS_STORY_DRAFTING` is
> a separate platform-operator-only pipeline that drafts a single candidate
> story (enumerate sources → ingestion verification → claim extraction →
> fact-check → contradiction check → counterclaim generation → Lilith tone
> review → editorial inbox). They are not the same recipe and should not be
> conflated.

`PIPELINE_TIERS`, `effectivePipelineForTenant`, and
`TenantPipelineCustomization` (in `pipeline-registry.ts`) implement per-tenant
customization, and `pipelines/observability.ts` provides the scheduling and SLA
layer: `nextFireFor` / `PipelineSchedule` compute the next run for tenant-scoped
publication cadences, `buildPipelineObservabilityReport` reports SLA / evidence
completeness / human-touch points per stage, and `detectPipelineDeviation`
routes deviations from the declared pipeline shape to operator review.

### Implementation location — `@oshun/agentic-studio`, not `@oshun/agent-pipelines`

The real pipeline specs live in `libs/oshun/agentic-studio/src/pipelines/`
(`v1-pipelines.ts` and `pipeline-registry.ts`). The package
`@oshun/agent-pipelines` (v0.1.0) is **only a four-file re-export shim**: its
`src/index.ts` re-exports `./grants/resolver` and `./pipelines/index`, both of
which simply `export { ... } from '@oshun/agentic-studio'`. It is a
compatibility surface, not an independent implementation — readers looking for
the pipeline logic should open `@oshun/agentic-studio`. (Companion-doc note: the
features-hub's reference to pipelines living "in `libs/oshun/agent-pipelines/`"
points at the shim, not the implementation.)

## The Brief-to-Content Engine: `@oshun/creative-orchestrator`

Adjacent to the Studio — and worth naming because it is real (58 passing tests)
yet undocumented in the broader feature set — is `@oshun/creative-orchestrator`
(v0.0.1, `libs/oshun/creative-orchestrator`, depends on `@oshun/ai`). Where the
Studio governs and accounts for runs, this is the actual brief →
produced-content engine. It exports `decomposeBrief` (a brief → schema-validated
`CreativePlan` DAG via `CREATIVE_PLAN_SCHEMA`), `routePlan` /
`CreativeOrchestrator` / `orchestrateBrief` (governed dispatch over the plan),
`reviseArtifact` (a Reflexion critique→revise loop), the critics
`createMetricCritic` / `createLlmJudgeCritic` / `createContentEvalCritic`,
domain generators `createYemayaAgentGenerator` / `createMetisNarrator`, the
governance gates `BudgetGovernanceGate` / `ALLOW_ALL_GATE`, and DAG utilities
`validateDagStructure` / `detectCycle` / `topologicalOrder`. It builds on
`@oshun/ai/agent-loop`'s `runStructuredOutput` / `runReflexion`. As with the
rest of the autonomous-content story, the structured-LLM and domain-generation
calls are injectable seams that are fail-loud when no provider is wired — the
orchestration loop is real; live end-to-end generation is provider-gated.

## Related

- [Tool Catalog, Grant Semantics, and Multi-Agent Protocol](./agentic-tool-catalog.md)
  — the enumerated 21-tool catalog, grant scope/revocation cascade, and the
  hand-off protocol's budget/authority/cycle/depth rules.
- [Agent Invocation, Budgets, Memory, and Feedback Loops](./agentic-invocation-budgets-grants.md)
  — customer/creator invocation, the budget and kill-switch enforcement, modes,
  capability audits, and champion-challenger rollout.
- [Sophia Grounding](./sophia-grounding.md) — the grounding pipeline behind
  `sophia.ground` / `sophia.fact_check` and the evidence trail.
- [Isis Generation Control](./isis-generation-control.md) — the fail-closed
  dispatch pattern that the agentic runtime seam mirrors.
- [Lilith Persona Policy](./lilith-persona-policy.md) — the persona/tone
  bindings the registry declares.
- [Admin Products — Web and Mobile](./admin-products.md) and
  [Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md) — where the
  operator and tenant agentic surfaces are exposed.
- [Architecture, Platform Foundations, and Security](./platform-foundations-and-security.md)
  — the durable-queue and infrastructure layers the Studio's in-memory runtime
  would bind to.
- Back to [V1 Features hub](../features.md).
