Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 28 additions & 27 deletions .squad/templates/orchestration-log.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# Orchestration Log Entry

> One file per agent spawn. Saved to `.squad/orchestration-log/{timestamp}-{agent-name}.md`

---

### {timestamp} — {task summary}

| Field | Value |
|-------|-------|
| **Agent routed** | {Name} ({Role}) |
| **Why chosen** | {Routing rationale — what in the request matched this agent} |
| **Mode** | {`background` / `sync`} |
| **Why this mode** | {Brief reason — e.g., "No hard data dependencies" or "User needs to approve architecture"} |
| **Files authorized to read** | {Exact file paths the agent was told to read} |
| **File(s) agent must produce** | {Exact file paths the agent is expected to create or modify} |
| **Outcome** | {Completed / Rejected by {Reviewer} / Escalated} |

---

## Rules

1. **One file per agent spawn.** Named `{timestamp}-{agent-name}.md`. Timestamps must be filename-safe (replace colons with hyphens, e.g., `2026-02-23T20-16-27Z`).
2. **Log BEFORE spawning.** The entry must exist before the agent runs.
3. **Update outcome AFTER the agent completes.** Fill in the Outcome field.
4. **Never delete or edit past entries.** Append-only.
5. **If a reviewer rejects work,** log the rejection as a new entry with the revision agent.
# Orchestration Log Entry

> One file per agent spawn. Saved to `.squad/orchestration-log/{timestamp}-{agent-name}.md`

---

### {timestamp} — {task summary}

| Field | Value |
|-------|-------|
| **Agent routed** | {Name} ({Role}) |
| **Why chosen** | {Routing rationale — what in the request matched this agent} |
| **Mode** | {`background` / `sync`} |
| **Why this mode** | {Brief reason — e.g., "No hard data dependencies" or "User needs to approve architecture"} |
| **Files authorized to read** | {Exact file paths the agent was told to read} |
| **File(s) agent must produce** | {Exact file paths the agent is expected to create or modify} |
| **Outcome** | {Completed / Rejected by {Reviewer} / Escalated} |
| **Token usage** | {inputTokens} in / {outputTokens} out — ${estimatedCostUsd} |

---

## Rules

1. **One file per agent spawn.** Named `{timestamp}-{agent-name}.md`. Timestamps must be filename-safe (replace colons with hyphens, e.g., `2026-02-23T20-16-27Z`).
2. **Log BEFORE spawning.** The entry must exist before the agent runs.
3. **Update outcome AFTER the agent completes.** Fill in the Outcome field.
4. **Never delete or edit past entries.** Append-only.
5. **If a reviewer rejects work,** log the rejection as a new entry with the revision agent.
89 changes: 89 additions & 0 deletions docs/src/content/docs/features/cost-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Token Usage & Cost Tracking

> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases.

Squad can track token usage and estimated cost for each agent spawn, roll that data up by session, and expose it through orchestration logs, terminal summaries, and telemetry backends.

---

## Overview

- Squad tracks token usage (input/output tokens) and estimated cost per agent spawn
- Usage data is recorded in orchestration logs and available via `squad cost` CLI
- Optional budget limits can be configured per agent or per session

---

## How It Works

- The `CostTracker` class (`packages/squad-sdk/src/runtime/cost-tracker.ts`) accumulates token data
- Each orchestration log entry includes a **Token usage** row
- OTel metrics (`squad.tokens.input`, `squad.tokens.output`, `squad.tokens.cost`) are emitted when telemetry is enabled

The orchestration log template stores usage in a markdown table row like this:

```md
| **Token usage** | 12,450 in / 3,200 out — $0.0234 |
```

---

## Viewing Costs

```bash
squad cost # current session costs
squad cost --all # all historical costs
squad cost --agent fenster # costs for specific agent
```

**Example output:**

```text
=== Squad Cost Summary ===
Total input tokens: 12,450
Total output tokens: 3,200
Estimated cost: $0.0234

--- By Agent ---
fenster: 12,450in / 3,200out ($0.0234) [1 turns, model: claude-sonnet-4.5]

--- By Session ---
session-abc123: 12,450in / 3,200out ($0.0234) [1 turns]
```

---

## Budget Configuration

```typescript
import { defineSquad, defineAgent, defineBudget } from '@bradygaster/squad-sdk';

export default defineSquad({
defaults: {
budget: defineBudget({
perAgentSpawn: 50000,
perSession: 500000,
warnAt: 0.8,
}),
},
agents: [
defineAgent({
name: 'fenster',
role: 'Core Dev',
budget: defineBudget({ perAgentSpawn: 100000 }),
}),
],
});
```

- `perAgentSpawn` limits an individual agent invocation
- `perSession` limits the total budget for the coordinator session
- `warnAt` emits warnings when usage reaches a fraction of the configured limit

---

## OTel Integration

- Token metrics are exported as OpenTelemetry counters when telemetry is enabled
- Compatible with Aspire dashboard, Grafana, and any OTel-compatible backend
- Metrics: `squad.tokens.input`, `squad.tokens.output`, `squad.tokens.cost`
4 changes: 4 additions & 0 deletions packages/squad-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
"types": "./dist/cli/commands/copilot.d.ts",
"import": "./dist/cli/commands/copilot.js"
},
"./commands/cost": {
"types": "./dist/cli/commands/cost.d.ts",
"import": "./dist/cli/commands/cost.js"
},
"./commands/copilot-bridge": {
"types": "./dist/cli/commands/copilot-bridge.d.ts",
"import": "./dist/cli/commands/copilot-bridge.js"
Expand Down
20 changes: 20 additions & 0 deletions packages/squad-cli/src/cli-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import path from 'node:path';
import { fatal, SquadError } from './cli/core/errors.js';
import { BOLD, RESET, DIM, RED, GREEN, YELLOW } from './cli/core/output.js';
import { runInit } from './cli/core/init.js';
import { runCost } from './cli/commands/cost.js';
import { getPackageVersion } from './cli/core/version.js';

// Lazy-load squad-sdk to avoid triggering @github/copilot-sdk import on Node 24+
Expand Down Expand Up @@ -145,6 +146,8 @@ async function main(): Promise<void> {
console.log(` ${BOLD}status${RESET} Show which squad is active and why`);
console.log(` ${BOLD}roles${RESET} List built-in Squad roles`);
console.log(` Usage: roles [--category <name>] [--search <query>]`);
console.log(` ${BOLD}cost${RESET} Report token usage from orchestration logs`);
console.log(` Flags: --all, --agent <name>`);
console.log(` ${BOLD}triage${RESET} Scan for work and categorize issues`);
console.log(` Usage: triage [--interval <minutes>]`);
console.log(` Default: checks every 10 minutes (Ctrl+C to stop)`);
Expand Down Expand Up @@ -416,6 +419,23 @@ async function main(): Promise<void> {
return;
}

if (cmd === 'cost') {
const sdk = await lazySquadSdk();
const localSquad = sdk.resolveSquad(process.cwd());
const globalPath = sdk.resolveGlobalSquadPath();
const globalSquadDir = path.join(globalPath, '.squad');
const teamRoot = localSquad
? path.resolve(localSquad, '..')
: (fs.existsSync(globalSquadDir) ? globalPath : null);

if (!teamRoot) {
fatal('No squad found. Run "squad init" first.');
}

await runCost(args.slice(1), teamRoot);
return;
}

if (cmd === 'build') {
const { runBuild } = await import('./cli/commands/build.js');
const hasCheck = args.includes('--check');
Expand Down
Loading
Loading