The Agentic AI Studio is V1's governed autonomy plane — the registry, run-envelope, budget, kill-switch, grant-resolver, hand-off, mode, and champion-challenger machinery that lets agents do real work (research, drafting, fact-checking, narration, course generation, support triage) without ever escaping the platform's tone, grounding, entitlement, and cost guardrails. It serves operators (who arm kill switches and promote challengers), creators and customers (who invoke entitlement-gated pipelines), and every domain that wants to ship an autonomous workflow. It sits beside the platform substrates hubbed at ../ARCHITECTURE.md and composes them: it grounds through Sophia, enforces tone through Lilith, generates through Isis, and remembers through Iris.
Read this page for what is shipping vs. spec / provider-gated. Unusually for this doc set, the Studio is overwhelmingly real. The entire governance/orchestration brain lives in
libs/oshun/agentic-studio(@oshun/agentic-studiov0.1.0), a pure-TypeScript package whoseindex.tsre-exports 12 subsystems —registry,runs,plans,dashboard,invocation,budgets,capabilities,feedback,modes,pipelines,grants,handoff— backed by 271 passing tests across 16 files that assert real computed values, not truthiness. Three things are genuinely thinner than the prose below might imply: (1) the persistence is in-memory — theAgentRegistryis aMap, and the BFF run-lifecycle store is in-process; the hub's old "durable jobs through@oshun/queue" line is not wired here (agentic-studio imports no@oshun/queue). (2) "Replay and time-travel" / "streaming progress" / "operator dashboard" are data and query layers (dashboard/dashboard-query.ts,runs/replay.ts,runs/streaming-progress.ts), not a rich admin React page — the admin surface is API routes, and the only agent page is a singleapps/oshun/tenant-admin/src/app/agents/page.tsx. (3) Tool grants are declared and validated, but actual tool execution (the realweb.fetch,sophia.ground, …) is injected at the app boundary and fail-closed by default. So: the brain is real and tested; the "autonomous content gets produced end-to-end through live providers" story is seam-and-fail-loud, not wired to live vendors. Backlog: §18; product scope:V1/features.md§ Agentic AI Studio.
Where the prose used to be wrong#
Three claims on the hub page are corrected here so readers aren't sent to the wrong code:
- No durable queue at this layer. ARCHITECTURE's "Job orchestration —
durable jobs through
@oshun/queuewith priority classes, replay, DLQ, SLA monitor" is aspirational at the agentic-studio layer. The queue library islibs/shared/queue; agentic-studio'sruns/dispatcher.ts,runs/orchestrator.ts,runs/checkpoint.ts, andruns/replay.tsare in-process pure-function logic with no@oshun/queuebinding, no DLQ, and no SLA monitor. - No
.../agents/operator dashboard page in admin. The hub points atapps/oshun/admin/src/.../agents/; that directory does not exist. The admin agentic surface is the API route groupapps/oshun/admin/src/app/api/admin/agentic-operations/(kill-switches,snapshot,gold-sets/promote,champion-challengers/rollout). The lone agent page isapps/oshun/tenant-admin/src/app/agents/page.tsx. - Budgets are self-contained, not "integrated with metis cost-tracking."
libs/metis/cost-trackingexists, butsrc/budgets/budgets.ts(BUDGET_CATEGORIES/BUDGET_SCOPES/checkBudget/consumeBudget) does not import it. "Integrated with" overstates a dependency that is not present.
Agent registry and catalog#
Every agent is a versioned AgentRegistryEntry registered into an
AgentRegistry (src/registry/agent-registry.ts). Registration is validating
and fail-closed: register(entry) returns a typed
AgentRegistryValidationError[] and only inserts into the backing Map when
the array is empty. The eight validation codes are real and individually tested:
duplicate-agent-id, unknown-family, tool-grant-exceeds-family-ceiling,
invalid-semver (for the agent, a capability, or the model binding),
unknown-data-scope, lifecycle-experimental-mismatch,
duplicate-capability-id, and champion-without-disclosure. Semver is checked
against a real regex (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/), so a
champion-tier agent cannot ship with a placeholder version or without its
declared disclosure requirements.
The 20 agent families and their tool ceilings#
AGENT_FAMILIES (src/registry/agent-families.ts) is exactly 20 canonical
families. Each carries AgentFamilyMeta with a toolGrantCeiling the registry
enforces as a hard ceiling — a declared toolGrant outside its family's
ceiling is rejected with tool-grant-exceeds-family-ceiling, so an editing
agent cannot quietly request web.fetch.
| Family | Family | Family | Family |
|---|---|---|---|
research |
drafting |
editing |
fact-checking |
citation-verification |
illustration |
narration |
translation |
course-generation |
lesson-scaffolding |
assessment-generation |
study-plan-synthesis |
recommendation-explanation |
moderation-triage |
support-triage |
ritual-scriptwriting |
sky-event-briefing |
claim-extraction |
source-ingestion |
kg-promotion |
For example, research's ceiling is
['web.fetch', 'source.fetch', 'sophia.ground', 'memory.read'] with a
strict-cite default grounding policy, while editing's is
['memory.read', 'sophia.fact_check', 'persona.invoke'] — narrower, and
read-only on sources. Each family also declares primaryDomains,
defaultGroundingPolicy (strict-cite / cite-where-available /
no-citation), and defaultPersonaPolicy (one of the Lilith voices
lilith.teacher / lilith.guide / lilith.scribe / lilith.host).
Lifecycle, cost class, and data scope enumerations#
| Enumeration | Values |
|---|---|
AGENT_LIFECYCLE_STATES |
draft, rehearsal, shadow, champion, challenger, deprecated, retired |
AGENT_COST_CLASSES |
micro, small, medium, large, flagship |
DATA_SCOPES (8) |
tenant.public, tenant.private, user.public, user.private, editorial.draft, editorial.published, platform.metadata, platform.curated |
An AgentRegistryEntry additionally pins a modelBinding (provider, model,
semver, default max tokens / temperature, and an explicit fallbackProviderId /
fallbackModelId), a personaBinding, a tonePolicyId, a groundingPolicyId,
an auditPolicyId, a tenantScope ('platform' or { tenantId }), a
visibility set (platform / tenant / creator / customer), and
disclosureRequirements. list(viewer) filters the catalog by tenant and role
so a customer never sees a platform-only agent.
The AgentRun envelope and its lifecycle#
Every invocation produces an AgentRunEnvelope (src/runs/agent-run.ts) — the
durable, operator-visible shape of a run. It is the contract the hub gestures
at, named field by field:
| Field | Meaning |
|---|---|
runId, rootAgentId, rootAgentSemver |
identity of the run and its root agent |
parentRunId, pipelineId, pipelineSemver |
attribution into a hand-off chain / pipeline |
status |
one of RUN_STATUSES (below) |
plan |
RunPlanStep[] — DAG of steps with dependsOnStepIds |
toolCalls |
RunToolCallRecord[] — each with argsHash, outcome, costUnits, scopeCheckResultId, auditEventId |
intermediateArtifacts |
checkpointable artifacts with a persistedKey for resume |
evidenceTrail |
per-stage evidencePackId, groundingState, and citationCount |
costLedger |
the RunCostLedger (below) |
decisionRationale |
per-stage rationale text and a decisionLabel (e.g. plan-replanned, gate-failed, abstained) |
outputs |
RunOutputBundle[] (final / intermediate / preview / partial-on-failure) |
provenanceBundleId |
provenance handle carried through the hand-off chain |
Run statuses — the real enum vs. the simplified diagram#
The hub's mermaid state machine is a simplified, aspirational model: it uses
pending, planning, and executing states that do not exist in code. The
real RUN_STATUSES enum is:
queued · starting · running · paused · awaiting_approval ·
awaiting_tool · awaiting_branch_selection · cancelled · killed ·
failed · completed
TERMINAL_STATUSES is the set { completed, failed, cancelled, killed }.
killed is deliberately distinct from cancelled: a cancelled run is an
operator's deliberate stop, a failed run errored, and a killed run was
terminated mid-flight by the governance executor (an armed kill switch or an
exhausted budget). The diagram also omits paused, awaiting_tool, and
awaiting_branch_selection — states that exist because runs really do suspend
on back-pressure, on a tool boundary, and on a branch decision.
The cost ledger#
RunCostLedger is a structured meter, not a single number — it tracks
totalCostUnits, per-stage and per-tool cost maps (perStageCostUnits,
perToolCostUnits), and physical resource counters: inputTokenCount,
outputTokenCount, gpuMinutes, voiceSeconds, avatarSeconds,
retrievalCount, and externalCallCount. These line up one-to-one with the
budget categories below, so a budget on voice-seconds meters against the same
quantity the ledger records.
Run controls and the operator console#
src/runs/run-controls.ts exposes the operator console as pure functions over
(envelope, command) → ControlResult. Each returns the next envelope plus a
typed RunControlAuditEvent, or a typed ControlDenialReason for an invalid
transition (run-terminal, invalid-status-for-pause, no-such-branch,
fork-without-fresh-id, …). The controls and their audit kinds:
| Control | Behavior | Audit kind |
|---|---|---|
pause |
suspend a pausable run (queued/starting/running/awaiting_tool/awaiting_branch_selection) |
pause |
resume |
resume a paused run |
resume |
cancel |
operator-deliberate terminal stop | cancel |
kill |
governance terminal stop | kill |
chooseBranch |
pick a branch at awaiting_branch_selection |
branch-chosen |
fork |
clone a run — requires a fresh runId (else fork-without-fresh-id) |
fork |
retryWithChanges |
rewind and apply overrides | retry-with-changes |
operatorStep |
single-step a run | operator-step |
The runtime governance seam (the real enforcement story)#
This is the concrete enforcement the hub describes abstractly but never names. Two pure modules and one orchestrator wrap every tool call:
admitToolCall(src/runs/executor.ts) runs the governance checks in a fixed safety → cost → rate order and returns the next state:- Safety first.
decideExecution(killSwitches, target)— if any switch is active for the target, the run iskill-terminated with the switch'suserVisibleStatusCopyand optionalsafeDegradationModeId. Kill switches take precedence over everything. - Cost next. For each budget,
checkBudgetis evaluated; anexceededverdict is a hard stop (the run is killed with a budget-exhausted status copy), whilewarn/graceaccumulate asbudgetWarningsand let the run continue. - Rate last. An optional token-bucket throttle yields a
throttledoutcome withretryAfterSeconds— back-pressure, never termination.
- Safety first.
dispatchGuardedToolCall(src/runs/dispatcher.ts) invokes the suppliedrunTool(run)only whenadmitToolCallreturnsadmit; a denied, throttled, killed, or run-terminal admission never touches the tool.runGuardedToolPlan(src/runs/orchestrator.ts) walks aPlannedToolCall[]plan through the dispatcher, producingExecutedToolCall[]and aGuardedToolPlanResult.
Where it mounts: POST /v1/agentic/runs/execute#
The seam is mounted in the BFF at apps/oshun/bff/src/agentic/runs-route.ts
(registerAgenticRunsRoute). It is fail-closed and server-authoritative:
- Fail-closed by default. The default tool registry
(
notConfiguredAgentToolRegistry) has no tools, so any plannedtoolIdthat isn't a configured function returns503 agent_tools_not_configured— the route never fabricates a run. A liveweb.fetch/sophia.groundis only swapped in at deploy time. - Malformed →
400. MissingrunId/tenantId/rootAgentId/ non-emptyplanof{ toolId }. - Scope check (S7) →
403 tool_scope_missing. A tool with a scope requirement runs only when the caller's realauthContext.scopessatisfy it; the executor never invents an operator tier for whoever shows up. - Server-authoritative identity (S8).
actorIdandtenantIdare taken fromauthContext(the auth claim), not the request body — so a caller can't reshape the target an armed switch matches. Kill-switch target metadata (family/providerId/region) is keyed server-side pertoolId. - Ownership (S9) →
409 run_id_conflict. ArunIdalready persisted by another member is never overwritable. - Live kill-switch propagation. Operator-armed switches are read from
adminAgenticOperationsStoreon every request, so arming a switch takes effect at the next tool-call boundary (the ≤5s propagation budget).
Budgets, quotas, throttles, and kill switches#
Budgets#
src/budgets/budgets.ts is self-contained. BUDGET_CATEGORIES (9) are
cost-units, input-tokens, output-tokens, gpu-minutes, voice-seconds,
avatar-seconds, retrievals, external-calls, storage-bytes — matching the
cost-ledger counters. BUDGET_SCOPES (7) are tenant, role, agent, run,
tool, user, pipeline. checkBudget returns one of four verdicts: ok,
warn, grace (carrying a graceEndUnixSeconds so a soft overrun has a
deadline rather than a cliff), and exceeded. Period rollover is supported, so
a budget refills at the next window.
Kill switches#
src/budgets/kill-switch.ts has KILL_SWITCH_SCOPES (7): agent, family,
tenant, provider, region, tool, global. decideExecution picks the
broadest active switch as the "blamed" switch for user-facing copy, using a
real blame priority:
global 6 > region 5 > provider 4 > tenant 3 > family 2 > agent 1 > tool 0
so a tenant operator sees the global-outage copy rather than a confusing
per-tool message when a global switch is armed. The decision returns a
userVisibleStatusCopy and an optional safeDegradationModeId (a graceful
fallback rather than a blank failure).
Tool catalog, grants, and the grant resolver#
The 21-id catalog#
The canonical tool catalog lives in contracts, not the studio:
libs/contracts/src/agent/tools.ts. V1_TOOL_IDS is exactly 21 entries
(the grouping obscures the count —
generate.image/generate.video/generate.audio are three,
memory.read/memory.write are two, calendar.read/calendar.write are two):
web.fetch · source.fetch · sophia.ground · sophia.fact_check ·
memory.read · memory.write · persona.invoke · generate.image ·
generate.video · generate.audio · composition.suggest ·
themis.adjudicate · code.exec · file.read · file.write ·
calendar.read · calendar.write · notify.send · handoff ·
approval.request · agent.terminate
V1_TOOL_CATALOG carries catalogVersion: '1.0.0' and
publishedAt: '2026-05-11T00:00:00Z'. Two real functions back the "tool
versioning / deprecations surfaced at registry-review time" promise:
resolveToolDependencies walks per-tool semver dependencies (returning
unknown-tool or semver-range-no-match), and surfaceDeprecations lists
tools whose deprecation.deprecatedAt falls inside a review window, along with
their replacementToolId and dependent tools.
Grant scopes and resolver priority#
GrantScopeSchema (libs/contracts/src/agent/tool-grants.ts) enumerates the
five scopes; the resolver (src/grants/resolver.ts) orders them by
GRANT_SCOPE_PRIORITY — the most specific grant wins:
| Scope | Priority |
|---|---|
per-run |
100 |
per-pipeline-instance |
80 |
per-session |
60 |
per-user |
40 |
per-tenant |
20 |
resolveGrant returns the highest-priority unexpired, unrevoked match, or a
typed GrantDenialReason: no-matching-grant, all-matches-expired, or
all-matches-revoked. The resolver also provides the lifecycle plumbing:
recheckQueuedRunAtDispatch (a grant revoked while a run sat queued is caught
at dispatch), emitRevocationCascade / emitExpiryCascade (propagate to active
sub-runs), renewGrant (rejects with rationale-required on empty rationale
and audit-required on a missing audit event id), and
emitToolKillSwitchCascade, which stamps each cascaded event with
withinFiveSecondSlo (latency <= 5) — the concrete ≤5s revocation SLO.
Multi-agent hand-off protocol#
src/handoff/handoff-protocol.ts enforces the parent→sub-agent contract. The
APPROVAL_REQUIREMENTS are none, operator, teacher, guardian, and
user-confirm-before-proceed; DEFAULT_DEPTH_CAP = 5. validateDispatch
rejects a dispatch with a typed DispatchDenialReason:
| Reason | Rule enforced |
|---|---|
cycle-detected |
re-entering an ancestor agentId is blocked unless allowCycle is declared at the chain root |
depth-cap-exceeded |
hand-off depth must stay within the cap (default 5) |
authority-widening-rejected |
the sub-agent's tool authority must be a subset of the parent's, its maxFanoutDegree no larger, and its capabilityModifiers no broader — authority can narrow, never widen |
budget-non-positive |
a sub-budget must be positive |
budget-exceeds-parent |
a sub-budget cannot exceed the parent's remaining budget |
Budget is deducted at dispatch (deductDispatchBudget) and reconciled at
termination (reconcileTerminalBudget), which returns unused budget to the
parent and, on overrun, emits an overshootEvent of kind
subagent_budget_exceeded. Attribution is preserved via
deriveAttributionChain so artifact provenance carries the
rootAgent → producingAgent chain.
Cost-quality modes#
src/modes/modes.ts defines five COST_QUALITY_MODES with concrete declared
thresholds (not just qualitative labels):
| Mode | maxCostUnits |
p95LatencyBudgetMs |
minEvaluationScore |
multiAgent |
humanCheckpointCount |
hasExternalSideEffects |
|---|---|---|---|---|---|---|
fast |
50 | 1 500 | 0.6 | false | 0 | true |
balanced |
500 | 10 000 | 0.7 | false | 0 | true |
deep |
5 000 | 120 000 | 0.8 | true | 1 | true |
exhaustive |
50 000 | 1 800 000 | 0.85 | true | 3 | true |
rehearsal-dry-run |
1 000 | 60 000 | 0 | true | 0 | false |
rehearsal-dry-run is the safety valve: it has no external side effects, so
a pipeline can be exercised end-to-end without touching live providers or
spending real resources. checkModeCompliance flags cost-exceeded,
latency-exceeded, and a below-bar evaluation score; applyOverride is
tighten-only — a customer or tenant operator may switch modes only within
the domain's allowedModes, and a cost-raise-rejected result blocks raising
the cost ceiling under tightenOnly.
Capability isolation, secret access, and adversarial tests#
src/capabilities/ is the security boundary. tool-isolation.ts ships real
SSRF guards: isPrivateOrLoopbackHost rejects RFC-1918 ranges (10.0.0.0/8 →
0x0a000000–0x0affffff; 172.16.0.0/12; 192.168.0.0/16), loopback (127/8,
localhost, ::1, 0.0.0.0), link-local (169.254.*, fe80:), IPv6
unique-local (fc/fd), and IPv4-mapped IPv6 (::ffff: is unwrapped and
re-checked), returning a typed EgressDenialReason (ssrf-private-ip /
ssrf-link-local / ssrf-loopback / host-not-in-allowlist /
scheme-not-allowed / method-not-allowed / invalid-url). matchHostPattern
supports *. wildcard subdomains. checkSecretAccess denies with
cross-tenant, agent-not-permitted, or tool-not-permitted.
adversarial-tests.ts is the concrete security-test catalog —
ADVERSARIAL_CATEGORIES covers prompt-injection, tool-call-exfiltration,
scope-escalation, sandbox-escape, persona-bypass, policy-bypass, and
source-fabrication, each fixture declaring an expectedVerdict of refuse,
sanitize, or comply-and-disclose.
Champion-challenger rollout and gold sets#
src/feedback/champion-challenger.ts runs a real two-proportion z-test on
task-success rate, not a toy comparison. evaluateReadiness requires
MIN_SAMPLES = 100; below that it returns not-enough-samples. The z-statistic
is converted to a two-sided p-value via a genuine Abramowitz & Stegun 26.2.17
normal survival-function approximation (normalSf), compared against
P_THRESHOLD = 0.05. Verdicts: not-enough-samples, inconclusive,
challenger-worse, challenger-ready. Rollout runs in shadow (challenger
runs in parallel but never returns to the user) or canary (a bounded
canaryFraction of live traffic) mode via routeInvocation.
ROLLOUT_TRANSITIONS are promote-challenger-to-champion
(readiness-required — promotion is refused unless the readiness verdict is
challenger-ready), freeze-rollout, and rollback-to-prior-champion. The
durable gold-set pipeline (feedback/gold-set-pipeline.ts) round-trips
operator-promoted gold-set entries through DurableGoldSetEntryRow records,
surfaced via the admin gold-sets/promote and champion-challengers/rollout
routes.
Customer-/creator-facing invocation#
src/invocation/invocation.ts gates invocation behind entitlements and a
preflight declaration. INVOCATION_SURFACES enumerates where a run can be
triggered; validatePreflight checks the InvocationEnvelope's declared
entitlements set against required scopes and returns missing-entitlement per
gap, alongside the declared source set, declared tone, declared persona, and a
ConsentConfirmation. This is the layer that binds a customer invocation to
Lilith tone policy and entitlement before any tool ever runs — the studio
counterpart of the BFF's S7 scope check.
The seven V1 pipelines#
src/pipelines/v1-pipelines.ts declares seven PipelineSpecs, all
lifecycleState: 'live'. Each pins a DAG plan, participatingAgentIds,
expectedHandoffDepth, mandatory approval gates, an evaluationFixtureSetId,
and a totalBudgetCap:
Pipeline (pipelineId) |
Invocation tier | totalBudgetCap |
|---|---|---|
veritas.weekly_briefing_pack |
creator | 5 000 |
veritas.story_drafting |
platform-operator | 12 000 |
tara.seasonal_program |
platform-operator | 10 000 |
nisaba.edition_study_guide |
customer | 3 000 |
nyx.event_explainer_set |
tenant-operator | 2 500 |
arete.weekly_review_draft |
customer | 500 |
metis.course_from_byom |
customer | 20 000 |
There are two distinct Veritas pipelines that a reader could conflate:
VERITAS_WEEKLY_BRIEFING_PACK (creator-tier, aggregates a week's stories into a
multi-format briefing pack) and VERITAS_STORY_DRAFTING
(platform-operator-tier: enumerate sources → ingest → extract claims →
fact-check → contradiction check → counterclaim → Lilith tone review → editorial
inbox). v1-pipelines.ts flags this overlap in a comment; they are not
duplicates.
The pipeline SPECS live in @oshun/agentic-studio, not in
libs/oshun/agent-pipelines/. The @oshun/agent-pipelines package (v0.1.0)
is a 4-file re-export shim whose src/index.ts simply re-exports the
resolver and the pipeline specs from @oshun/agentic-studio; it is not an
independent implementation. Companion-doc references that send readers to
agent-pipelines/ for the implementation point at the wrong package.
PipelineRegistry (src/pipelines/pipeline-registry.ts) validates and stores
specs; PIPELINE_TIERS, effectivePipelineForTenant, and
TenantPipelineCustomization support tenant-scoped customization. Scheduling
and observability are real symbols: PipelineSchedule and nextFireFor compute
the next fire time, and buildPipelineObservabilityReport /
detectPipelineDeviation (pipelines/observability.ts) surface drift between
expected and observed runs.
Dashboard, replay, and streaming progress (data layers)#
These are query and data layers, not a rich React admin page.
dashboard/dashboard-query.ts materializes DashboardEntry rows with
RUN_LIST_BUCKETS, bucketForStatus, cost bands (costBandFor), and latency
bands (latencyBandFor), and filterDashboard applies a DashboardFilter.
runs/replay.ts provides buildReplayTimeline and reconstructStateAt — the
"time-travel" is a pure reconstruction over the recorded envelope, not a
separate durable replay engine. runs/streaming-progress.ts defines
RUN_PROGRESS_KINDS, PROGRESS_VISIBILITY, shouldDeliverToSubscriber (so a
customer never sees an internal operator event), and summarizeForCustomer. The
operator console is the admin agentic-operations API routes plus
tenant-admin's single agents/page.tsx.
The autonomous creative orchestrator (undocumented until now)#
libs/oshun/creative-orchestrator (@oshun/creative-orchestrator v0.0.1,
depending on @oshun/ai) is the actual brief → produced-content engine —
fully real with 58 passing tests — and is not mentioned anywhere else in the
V1 docs. It is built on the shared @oshun/ai/agent-loop primitives
(runStructuredOutput, runReflexion) and is fail-loud throughout: it
never fabricates a plan or an artifact, and it fails loudly when no provider or
generator is wired. Exported pieces:
decomposeBrief— turns a brief into a schema-validated, acyclicCreativePlanDAG (CREATIVE_PLAN_SCHEMA), with real DAG validation (validateDagStructure,detectCycle,topologicalOrder).routePlan/CreativeOrchestrator/orchestrateBrief— governed dispatch of plan nodes to domain generators under real budget/kill-switch/throttle governance (BudgetGovernanceGate, withALLOW_ALL_GATEfor tests).reviseArtifact— a bounded generate → critique → revise (Reflexion) loop around every artifact, with criticscreateMetricCritic,createLlmJudgeCritic, and the non-provider-gated defaultcreateContentEvalCritic.- Domain adapters —
createYemayaAgentGeneratorwires Yemaya specialized agents in;createMetisNarratorwires Metis narration in.
This is the engine that the Studio's pipelines orchestrate against. Like the
rest of the autonomy story, its planner and generators are real seams, and
the end-to-end "content actually gets produced through live providers" path
depends on those providers being wired at deploy time (the e2e walkthrough
agentic-pipeline-customer-invocation is classified "deep" precisely because
its tool DAG is creds-bound).
The autonomous creative direction plane#
Everything above is trigger-driven — a human, customer, or schedule invokes
a pipeline. libs/oshun/creative-autonomy (@oshun/creative-autonomy, 100
passing tests across 8 files) is the plane that closes the loop the other way:
it decides what to create, produces it, judges it, routes it past humans, and
learns from their reactions. One AutonomousCreativeDirector.runCycle() pass:
- Sense — registered
SignalSources emit evidence-bearingOpportunitySignals. Four deterministic sources ship: catalog gaps (demand-weighted coverage deficit + staleness), engagement decay (historical-peak decline with residual demand), calendar events (triangular lead-time fit), and audience requests (rater-count saturation × rating need, clustered from realFeedbackEvents). Signals decay exponentially by half-life and dedupe by (kind, topic); zero sources is a typed error, never "no opportunities." - Ideate — signals become
ContentIdeas two ways: deterministic derivation via a signal-kind → angle affinity model with per-angle brief templates, and an optional LLMIdeaSynthesizerseam whose proposals are admitted only if they name a registered category, an unbanned topic, and an actual sensed signal (unknown-signalrejection otherwise). Each idea gets an auditable compellingness prior: a steering-weighted blend of timeliness, novelty (token-Jaccard vs. the recent portfolio), audience value, strategic fit, and feasibility. - Select — a Thompson-sampling bandit (real Beta posteriors, Marsaglia–Tsang gamma sampling, injected RNG) draws per-category acceptance beliefs; ideas pack greedily by priority-per-cost under the cycle budget, per-category caps, and a topic-cluster guard. Every skipped idea carries a typed reason.
- Produce — each selected idea is produced as K variants through the
ContentProducerseam (canonicallycreateOrchestratorProducerover the creative orchestrator above). Every provider-shaped call flows through aConcurrencyGovernor: bounded slots (default 2), token-bucket launch rate, and AIMD adaptation — a classified 429/session-limit error halves effective concurrency and opens a cooldown; consecutive successes earn slots back. The plane cannot hammer a session limit by construction. - Judge — variants meet in a round-robin pairwise tournament fitted with Bradley–Terry MM iterations (ε-regularized); the champion faces a 7-dimension compellingness rubric (hook, clarity, novelty, emotional-resonance, grounding, structure, audience-fit) scored by an injected judge panel with a cross-judge disagreement penalty and a human-calibration offset learned from review outcomes. The publish gate demands score ≥ bar and panel confidence — an unconfident pass is not a pass; failures park with named failing dimensions.
- Route past humans — per-category
AutonomyLevel:human-approval(nothing moves without an explicit decision),review-window(veto window; silence auto-publishes),full-auto(publishes immediately, still listed for after-the-fact feedback). TheReviewQueueStoreholds every item with its fullProvenanceChain(signal → idea → score → selection rationale → tournament strength → gate verdict) and an immutable audit history. - Learn — approve/reject/veto/tweak/window-expiry outcomes update the
bandit posteriors (documented reward constants), per-category calibration
EMAs, and typed
SteeringSuggestions. Human steering is a versionedCreativeDirectiondocument (tenets, emphasize/avoid themes, banned topics, priorities, tighten-only autonomy overrides, quality bars); the learner proposes changes but never edits human-owned lists.
The BFF mount is apps/oshun/bff/src/agentic/autonomy-route.ts
(/v1/agentic/autonomy/*): cycle trigger + reports + status (operator-gated),
the review queue with approve/reject/veto/request-tweak actions, steering
GET/PUT, learner suggestions, and published-item feedback. Same posture as the
runs route: the loop/gate machinery is real and in-process; the
producer/publisher/judge seams bind at the deployable boundary, and with none
bound the default plane is fail-closed — POST cycles/run returns
503 autonomy_director_not_configured, never a fabricated cycle. Launch
categories (V1_AUTONOMY_LAUNCH_CATEGORIES) are conservative: everything
requires human approval except Nyx sky-event briefs, which get a veto window.
The seams are now bound to the real monorepo systems
(apps/oshun/bff/src/agentic/autonomy-bindings/, wired by
createBoundCreativeAutonomyPlane in server.ts): opportunity signals from the
Nyx ephemeris (credential-free lunar phases / season markers / featured
events through the lead-time model); production through the creative
orchestrator over an LLM text generator (structured
{title, text, citations[]}) plus the generation-job pipeline
(image/audio/video jobs drained programmatically via
processQueuedGenerationJobs — the Isis release gate and released-output
catalog apply to autonomous work identically); a specialist judge panel — the
content-quality-judge engine (N-sample pointwise craft scoring +
position-bias-mitigated pairwise for the tournament, over an
@oshun/ai→@iris/agents-core provider bridge), the deterministic slop
novelty judge, the Lilith content-QA tone judge, and a Sophia
declared-citation grounding judge (partial panels: a judge scores only its
competent dimensions, and an uncovered dimension is a typed configuration error,
never a gap-filled score); publication through the studio-authoring editorial
lifecycle (draft → in-review → approved → published under the Lilith gate — a
blocker refuses the publish and the item honestly retries); and the admin
kill-switch store as the cycle abort signal (global or
agent-family:creative-autonomy). The LLM-shaped pieces resolve from
OPENROUTER_API_KEY (OSHUN_AUTONOMY_LLM_MODEL override); the startup log
states BOUND vs fail-closed. Per-system findings and the honest remaining-gap
ledger: CREATIVE_AUTONOMY_UNDERLYING_AUDIT_2026-07-02.md.
How the pieces fit (a single guarded tool call)#
- A trigger (user, pipeline, or schedule) lands on
POST /v1/agentic/runs/execute. The BFF derivesactorId/tenantIdfrom the auth claim, fails closed (503) on any unconfigured tool, checks scopes (403), and checks ownership (409). - The route builds an
AgentRunEnvelopeplus server-policy-derivedGovernedPlannedToolCall[], materializes exact per-run grants for bounded low/medium catalog tools (or requires an injected pre-approved broker for secrets, egress, or high/critical authority), and callsrunGovernedToolPlan. - At each boundary, the runtime refreshes pause/cancel/kill state, resolves a
fresh exact capability grant, applies secret scope,
filesystem/process/network isolation, egress allowlists and the two-phase
SSRF guard, then runs
admitToolCallfor kill-switch and latest-meter budget admission. - The boundary persists a pending call before dispatch. The injected tool runs only after grant, isolation, kill, and budget checks pass. Its ok, partial, or error result replaces the pending record with redacted arguments, result or error, resolved grant, capability audit ids, evidence, effects, artifacts, latency, and measured usage. Denied attempts are recorded too.
- Sub-agent hand-offs go through
validateDispatch(subset authority, depth cap, budget ≤ parent remaining), deduct budget at dispatch, and reconcile unused budget back to the parent at termination. - Every attempt's actual or policy-defined denied/failed charge updates the total, per-stage, per-tool, and category cost ledger and consumes the mutable budget meters. That measured usage is consumed before the next admission, so a multi-step plan cannot reuse its initial balance. The lifecycle store writes through the updated envelope and retains kill/budget audit events; the route returns the same telemetry-bearing run as a JSON-safe projection.
The generated field-by-field boundary is Agentic Run Execution Fields.
Related#
- High-Level Architecture
- Sophia — Grounding Substrate
- Lilith — Contemplative Policy Substrate
- Isis — Generation Control Substrate
- Iris — Assistant Memory Substrate
- Generation Audience Tiers
- Trust, Safety, and Privacy
- Communication Patterns
- Subsystem Glossary
V1/features.md§ Agentic AI StudioV1/features.md§ Tool Catalog, Grant Semantics, and Multi-Agent Protocol- Backlog §18 in ../TODOS.md; provider topology in ../DEPENDENCIES.md; hub: ../ARCHITECTURE.md