Aviary
Concepts

Governance

Four layers stack on every turn — who's calling and what they may do, how many tokens they've spent today, what it cost, and an audit trail of everything that happened.

"Governance" isn't one feature — it's four independent layers the loop applies to every turn. Each has its own SPI, its own configuration point, and its own guide; none of them requires the others.

LayerWhat it doesWhere configuredGuide
Identity + authorizationResolves the actor from the request, no insecure default; gates each tool by role intersection or a delegated abilityrequired actorResolver + per-tool roles/ability on AgentModule.forRootIdentity & Authorization
Daily quotaBlocks a turn before it runs when the actor is over their daily token budgetquota (a QuotaStore) or quotaLimitTokens (built-in LedgerQuotaStore) on AgentModule.forRoot; omit both and quota checking is skippedCost & Governance
CostA usage row per model call, rolled into spend per model/actor/day; a gateway's reported cost wins, else a cache-aware token estimateThe store's AgentGovernanceQueries + a versioned AgentModelPricing tableCost & Governance
ReliabilityEvery run's start/end is durably recorded (success/failure, duration, retries, promptHash), independent of thread historyAutomatic once the bound AgentStore implements the optional recording SPI (both bundled stores do)Cost & Governance
Approvals inboxA cross-thread queue of action tool calls awaiting a HITL decision, decidable from the console without re-deriving thread ownershipAGENT_APPROVAL_PORT — bound automatically by AgentModule/AgentDurableModuleHuman-in-the-loop & Durability
AuditEvery stage of a run announced on Node's diagnostics_channel, plus tracing spans for a per-run waterfallNothing to configure — aviary:agent:* always fires; consumed by Telescope or your own subscriberCost & Governance

Identity + authorization

Two questions gate every tool call: who is calling, and may they call this. The agent never invents an actor — actorResolver is a required field on AgentModule.forRoot, not an optional one with a throwing placeholder. Once an actor is resolved, each tool declares exactly one gate: roles (a tool with none declared reaches the policy as roles: undefined, and DefaultRolesPolicy applies defaultRoles there) or ability (delegated to a nestjs-authz Gate via AgentAuthzModule). Both checks run server-side, before the tool's execute is ever called — a model can ask for anything, it can't talk its way past the gate.

The same "the agent never fabricates a caller" principle carries into the governance endpoints: POST /agent/tool-call/approve · /reject and the /agent/threads/:id family resolve the acting actor and assert they own the target thread/tool-call — someone else's returns 403, a missing one 404.

Quota

A per-actor daily token budget, checked before the model is even called and bumped after every turn. It counts inputTokens + outputTokens — unaffected by the cache-token breakdown that only refines cost. Over budget throws QuotaExceededError and emits aviary:agent:quota.exceeded; GET /agent/quota/today reports the actor's current usage.

Cost

Every model call appends a usage row regardless of outcome. Cost is resolved per row: a gateway's real reported cost (Vercel AI Gateway, OpenRouter) wins when present; otherwise the estimate accounts for prompt caching (cache writes at a premium, cache reads at a discount) against the current pricing row for that model. AgentGovernanceQueries rolls the rows up into spend by model, spend by actor, a daily trend, and recent activity — the same read-model the standalone dashboard and the Telescope "Agent" tab both consume.

Reliability

Cost and quota answer "what did this cost"; reliability answers "did the run actually succeed." Every run's start and end are recorded through an optional AgentStore surface (recordRunStart/recordRunEnd/bumpRunRetries — a store that doesn't implement it degrades to no reliability data, not an error), including the sha256 promptHash of the run's resolved system prompt, so an error-rate shift can be correlated with a prompt change. AgentGovernanceQueries exposes the rollups — aggregate (runMetrics, runsByAgent, runErrors, runTrend) and per-run (recentRuns, and a paginated/filterable runsPage) — consumed by the same two surfaces as cost: the standalone dashboard's Reliability section and Telescope's Agent tab. See Architecture for how a run's outcome gets recorded when its steps are dispatched across the fleet.

Approvals inbox

An action tool's suspend (see The agent loop and Human-in-the-loop & Durability) is normally decided from the thread it happened in. The approvals inbox is the cross-thread view of the same decision: every tool call sitting pending_approval, across every thread and actor, oldest first (AgentGovernanceQueries.pendingApprovals). The optional AGENT_APPROVAL_PORT SPI (AgentApprovalPort) — bound automatically by AgentModule/AgentDurableModule — routes a console-side approve/reject through the exact same decision path a chat approval uses (a durable signal, or inline resolution), without re-deriving thread ownership; the console's own guards are expected to front that authorization instead. Decision carries an optional executedByRef, so the loop can persist who actually decided (decision.executedByRef ?? the run's actor) — the dashboard's approvalActorRef option stamps it from the live request, defaulting to the same ActorResolver chat already uses. No port bound → the API answers 501 and the console renders read-only rather than silently no-op-ing an approval.

Audit

Every stage of a run — start, message, tool call, delegation, retrieval, quota exceeded, failure, finish — is announced on Node's diagnostics_channel under aviary:agent:*. This is unconditional instrumentation, not an opt-in: the library doesn't instrument your code, but it always announces its own. Alongside those point events, every run also emits tracing spans (llm.turn/tool.execution/retrieval/follow-ups, correlated by traceId = runId) so a single turn renders as a waterfall rather than a flat list — see Architecture. Telescope consumes both the channel (for live activity and per-tool stats, toolStats) and the spans (for the waterfall); any other subscriber (your own metrics, an alerting rule) can tap the point events the same way.

Where to go next

On this page