feat(FN-1266): add Budget Governance documentation to AGENTS.md
This commit is contained in:
124
AGENTS.md
124
AGENTS.md
@@ -711,6 +711,125 @@ Values are stored in `agent.runtimeConfig.budgetConfig` and persisted via `PATCH
|
|||||||
- `HeartbeatMonitor.getAgentHeartbeatConfig(agentId)` — Returns the resolved config for an agent
|
- `HeartbeatMonitor.getAgentHeartbeatConfig(agentId)` — Returns the resolved config for an agent
|
||||||
- `AgentStore.getCachedAgent(agentId)` — Synchronous agent read for hot paths
|
- `AgentStore.getCachedAgent(agentId)` — Synchronous agent read for hot paths
|
||||||
|
|
||||||
|
## Budget Governance
|
||||||
|
|
||||||
|
Per-agent token budget tracking, threshold warnings, and enforcement by the engine's heartbeat execution path.
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Budget governance allows teams to set token consumption limits on agents to control costs and prevent runaway AI spending. The system tracks cumulative token usage (input + output), warns when approaching limits, and can automatically pause agents when budgets are exhausted.
|
||||||
|
|
||||||
|
### AgentBudgetConfig
|
||||||
|
|
||||||
|
Configuration for an agent's token budget. Stored in `agent.runtimeConfig.budgetConfig`.
|
||||||
|
|
||||||
|
| Field | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `tokenBudget` | `number` | — | Total token cap (input + output). When undefined, no budget limit is enforced. |
|
||||||
|
| `usageThreshold` | `number` | `0.8` | Warning threshold as a fraction (0–1). Triggers `isOverThreshold` when `usagePercent >= thresholdPercent * 100`. |
|
||||||
|
| `budgetPeriod` | `string` | `"lifetime"` | Budget accumulation period: `"daily"`, `"weekly"`, `"monthly"`, or `"lifetime"`. |
|
||||||
|
| `resetDay` | `number` | — | Day for period reset. For weekly: 0=Sunday to 6=Saturday. For monthly: 1–31. Optional. |
|
||||||
|
|
||||||
|
### AgentBudgetStatus
|
||||||
|
|
||||||
|
Computed budget status for an agent at a point in time.
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `agentId` | `string` | The agent this status belongs to |
|
||||||
|
| `currentUsage` | `number` | Total tokens consumed (input + output) |
|
||||||
|
| `budgetLimit` | `number \| null` | Token cap from config, or null when no budget is configured |
|
||||||
|
| `usagePercent` | `number \| null` | Usage as a percentage of budget (0–100), or null when no budget |
|
||||||
|
| `thresholdPercent` | `number \| null` | The configured threshold percentage, or null when no budget |
|
||||||
|
| `isOverBudget` | `boolean` | Whether `currentUsage >= budgetLimit` |
|
||||||
|
| `isOverThreshold` | `boolean` | Whether `usagePercent >= thresholdPercent * 100` |
|
||||||
|
| `lastResetAt` | `string \| null` | ISO-8601 timestamp of the last budget reset, or null |
|
||||||
|
| `nextResetAt` | `string \| null` | ISO-8601 timestamp of the next scheduled reset, or null for lifetime/no budget |
|
||||||
|
|
||||||
|
### Engine Behavior
|
||||||
|
|
||||||
|
The heartbeat system enforces budgets at multiple points:
|
||||||
|
|
||||||
|
**HeartbeatMonitor.executeHeartbeat()**
|
||||||
|
- Checks agent budget status before creating any agent session
|
||||||
|
- If `isOverBudget: true` → skips heartbeat, returns `{ reason: "budget_exhausted" }`
|
||||||
|
- If `isOverThreshold: true` and source is `"timer"` → skips heartbeat, returns `{ reason: "budget_threshold" }`
|
||||||
|
- Assignment and on-demand triggers bypass the threshold gate (still respect budget exhaustion)
|
||||||
|
- After `completeRun()`: updates agent token counters and transitions agent state on exhaustion
|
||||||
|
|
||||||
|
**HeartbeatTriggerScheduler.onTimerTick()**
|
||||||
|
- Before triggering timer heartbeat, checks budget status
|
||||||
|
- Timer ticks are skipped when `isOverThreshold: true` or `isOverBudget: true`
|
||||||
|
- Assignment and on-demand triggers still fire even above threshold (only timer triggers are gated)
|
||||||
|
- Budget checks run before the `maxConcurrentRuns` check
|
||||||
|
|
||||||
|
**WakeContext.budgetStatus**
|
||||||
|
- Heartbeat execution propagates `budgetStatus` in the run's `contextSnapshot`
|
||||||
|
- Enables downstream systems to access current budget state
|
||||||
|
|
||||||
|
### AgentStore API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get computed budget status for an agent
|
||||||
|
getBudgetStatus(agentId: string): Promise<AgentBudgetStatus>
|
||||||
|
// Throws if agent not found
|
||||||
|
|
||||||
|
// Reset token counters and update budgetResetAt timestamp
|
||||||
|
resetBudgetUsage(agentId: string): Promise<void>
|
||||||
|
// Sets totalInputTokens = 0, totalOutputTokens = 0, runtimeConfig.budgetResetAt = now()
|
||||||
|
// Throws if agent not found
|
||||||
|
// Emits "agent:updated" event after reset
|
||||||
|
```
|
||||||
|
|
||||||
|
### Budget Reset
|
||||||
|
|
||||||
|
Calling `resetBudgetUsage()`:
|
||||||
|
1. Zeros `totalInputTokens` and `totalOutputTokens` on the agent
|
||||||
|
2. Sets `runtimeConfig.budgetResetAt` to the current ISO-8601 timestamp
|
||||||
|
3. Writes the updated agent to storage
|
||||||
|
4. Emits `agent:updated` event
|
||||||
|
|
||||||
|
For period-based budgets, `nextResetAt` is computed as follows:
|
||||||
|
- **daily**: Next midnight UTC
|
||||||
|
- **weekly**: Next occurrence of `resetDay` (0=Sunday) at midnight UTC
|
||||||
|
- **monthly**: Next occurrence of `resetDay` in the month at midnight UTC
|
||||||
|
- **lifetime**: `null` (no automatic reset)
|
||||||
|
|
||||||
|
After a reset, `isOverBudget` and `isOverThreshold` become `false`, allowing heartbeats to resume.
|
||||||
|
|
||||||
|
### Dashboard Configuration
|
||||||
|
|
||||||
|
The agent detail ConfigTab includes a "Budget Settings" section where users can configure:
|
||||||
|
|
||||||
|
- **Token Budget** — Total token cap (input + output). Leave empty for no limit.
|
||||||
|
- **Usage Threshold (%)** — Warning threshold percentage. Triggers warning when usage reaches this level.
|
||||||
|
- **Budget Period** — How often the budget counter resets: lifetime, daily, weekly, or monthly.
|
||||||
|
- **Reset Day** — Day for reset (weekly: 0=Sunday to 6=Saturday, monthly: 1-31). Optional.
|
||||||
|
|
||||||
|
Values are stored in `agent.runtimeConfig.budgetConfig` and persisted via `PATCH /api/agents/:id`.
|
||||||
|
|
||||||
|
The ConfigTab also displays:
|
||||||
|
- Current usage as a progress bar (green → yellow → red based on threshold)
|
||||||
|
- Budget status indicators (under budget, over threshold, over budget)
|
||||||
|
- Last reset timestamp and next scheduled reset
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `GET` | `/api/agents/:id/budget` | Get budget status for an agent |
|
||||||
|
| `POST` | `/api/agents/:id/budget/reset` | Reset budget usage counters |
|
||||||
|
|
||||||
|
**GET /api/agents/:id/budget**
|
||||||
|
- Response 200: `AgentBudgetStatus` — Current budget computation
|
||||||
|
- Response 404: `{ error: "Agent not found" }`
|
||||||
|
|
||||||
|
**POST /api/agents/:id/budget/reset**
|
||||||
|
- Response 200: `{ success: true }`
|
||||||
|
- Response 404: `{ error: "Agent not found" }`
|
||||||
|
|
||||||
|
Note: Budget configuration is stored via the existing `PATCH /api/agents/:id` endpoint with `runtimeConfig.budgetConfig` in the request body.
|
||||||
|
|
||||||
## Heartbeat Trigger Scheduling
|
## Heartbeat Trigger Scheduling
|
||||||
|
|
||||||
The `HeartbeatTriggerScheduler` class (exported from `@fusion/engine`) manages three trigger mechanisms that wake agents via heartbeat runs:
|
The `HeartbeatTriggerScheduler` class (exported from `@fusion/engine`) manages three trigger mechanisms that wake agents via heartbeat runs:
|
||||||
@@ -742,8 +861,9 @@ interface WakeContext {
|
|||||||
2. Timer triggers: Agents with `heartbeatIntervalMs` configured get periodic `setInterval`-based wakeups
|
2. Timer triggers: Agents with `heartbeatIntervalMs` configured get periodic `setInterval`-based wakeups
|
||||||
3. Assignment triggers: The scheduler subscribes to `agent:assigned` events from `AgentStore`
|
3. Assignment triggers: The scheduler subscribes to `agent:assigned` events from `AgentStore`
|
||||||
4. On-demand triggers: The `POST /api/agents/:id/runs` route creates runs with wake context
|
4. On-demand triggers: The `POST /api/agents/:id/runs` route creates runs with wake context
|
||||||
5. All triggers respect `maxConcurrentRuns` — skipped if the agent already has an active run
|
5. All triggers respect budget gating — timer triggers are skipped when `isOverThreshold` or `isOverBudget`; assignment and on-demand triggers bypass the threshold gate but still block when `isOverBudget`
|
||||||
6. On runtime stop, all timers are cleared and event listeners are removed
|
6. All triggers respect `maxConcurrentRuns` — skipped if the agent already has an active run (budget checks run before this check)
|
||||||
|
7. On runtime stop, all timers are cleared and event listeners are removed
|
||||||
|
|
||||||
### AgentStore Events
|
### AgentStore Events
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user