Repository navigation
docs: auto-update documentation (2026-10-26) #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -95,6 +95,7 @@ output_file: job-2024-01-19-abc123.jsonl | |||||
| | `prompt` | `string?` | The prompt given to the agent | | ||||||
| | `summary` | `string?` | Brief summary of job results (extracted from the final assistant message) | | ||||||
| | `output_file` | `string?` | Filename of the JSONL output log | | ||||||
| | `usage` | `object?` | Token usage and cost data (see [Usage and Cost Tracking](#usage-and-cost-tracking)) | | ||||||
|
|
||||||
| ### Immutable Fields | ||||||
|
|
||||||
|
|
@@ -375,6 +376,79 @@ The cache needs no explicit invalidation — it revalidates against the filesyst | |||||
|
|
||||||
| This mirrors the incremental `AttributionIndexBuilder` used by session discovery, with one difference: the attribution index is TTL-based, while the job index is validated against file mtime and size. | ||||||
|
|
||||||
| ## Usage and Cost Tracking | ||||||
|
|
||||||
| Every completed job now persists detailed token usage data in the `usage` field of its metadata file. This field captures per-model token counts, turn count, and the SDK's authoritative cost estimate, providing complete visibility into resource consumption for every agent run. | ||||||
|
|
||||||
| ### What's Tracked | ||||||
|
|
||||||
| The `usage` field contains: | ||||||
|
|
||||||
| | Field | Type | Description | | ||||||
| |-------|------|-------------| | ||||||
| | `per_model` | `Record<string, ModelTokenUsage>` | Token counts broken down by model (e.g., `claude-opus-4-8`, `claude-haiku-4`) | | ||||||
| | `num_turns` | `number?` | Total agentic turns reported by the runtime | | ||||||
| | `total_cost_usd` | `number?` | SDK-authoritative run cost in USD (absent for CLI/Max-plan runs) | | ||||||
|
|
||||||
| Each `ModelTokenUsage` entry contains four token classes: | ||||||
|
|
||||||
| | Token Class | Description | | ||||||
| |-------------|-------------| | ||||||
| | `input_tokens` | Fresh (non-cached) input tokens | | ||||||
| | `output_tokens` | Generated output tokens | | ||||||
| | `cache_creation_input_tokens` | Tokens written to the prompt cache | | ||||||
| | `cache_read_input_tokens` | Tokens read from the prompt cache | | ||||||
|
|
||||||
| ### Why Per-Model? | ||||||
|
|
||||||
| A single job can use multiple models — for example, an Opus agent delegating to Haiku sub-agents via the Task tool. The `per_model` breakdown ensures you can track which model consumed which tokens, enabling accurate cost attribution even when runs span multiple models. | ||||||
|
|
||||||
| ### Example Usage Data | ||||||
|
|
||||||
| ```yaml | ||||||
| usage: | ||||||
| per_model: | ||||||
| claude-opus-4-8: | ||||||
| input_tokens: 12543 | ||||||
| output_tokens: 3421 | ||||||
| cache_creation_input_tokens: 45023 | ||||||
| cache_read_input_tokens: 38201 | ||||||
| claude-haiku-4: | ||||||
| input_tokens: 2341 | ||||||
| output_tokens: 892 | ||||||
| cache_creation_input_tokens: 0 | ||||||
| cache_read_input_tokens: 0 | ||||||
| num_turns: 14 | ||||||
| total_cost_usd: 0.234 | ||||||
| ``` | ||||||
|
|
||||||
| ### Accessing Usage Data | ||||||
|
|
||||||
| The usage field is available on every `JobMetadata` object returned by: | ||||||
|
|
||||||
| - `FleetManager.getJob(jobId)` | ||||||
| - `FleetManager.getJobs(filters)` | ||||||
| - `JobManager.getJob(jobsDir, jobId)` | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Document the
📝 Suggested fix
Committable suggestion
|
||||||
| - `JobManager.listJobs(jobsDir, filters)` | ||||||
|
|
||||||
| ```typescript | ||||||
| const job = await manager.getJob('job-2024-01-19-abc123'); | ||||||
| if (job.usage) { | ||||||
| console.log(`Total turns: ${job.usage.num_turns}`); | ||||||
| console.log(`Total cost: $${job.usage.total_cost_usd}`); | ||||||
|
|
||||||
| for (const [model, tokens] of Object.entries(job.usage.per_model)) { | ||||||
| console.log(`${model}: ${tokens.input_tokens} input, ${tokens.output_tokens} output`); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Backward Compatibility | ||||||
|
|
||||||
| The `usage` field is **backward-compatible** — job records written before this field existed will have `usage: null` or `undefined`. Always check for the field's presence before accessing its contents. | ||||||
|
|
||||||
| herdctl deliberately **does not persist a $/token price table** — prices drift over time, and Max/CLI plan users have no real per-token spend. The consuming application should apply its own pricing logic if needed. The `total_cost_usd` field is the SDK's own reported figure, not a herdctl-computed price. | ||||||
|
|
||||||
| ## Job Events | ||||||
|
|
||||||
| The FleetManager emits strongly-typed events throughout a job's lifecycle. These events drive the web dashboard, CLI output, and any programmatic subscribers. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ Every schedule needs a trigger to determine when it runs. The trigger type you c | |
| | **Cron** | Precise timing (daily reports, scheduled maintenance) | Available | | ||
| | **Webhook** | Event-driven automation (deployments, external events) | Future | | ||
| | **Chat** | Interactive responses (support, commands) | Available | | ||
| | **Spawned** | Sub-agent jobs created via the Task tool | System-generated | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Remove the automatic No non-test code sets the trigger type to Either revise the Spawned row and the new section in
|
||
|
|
||
| ## Interval Triggers | ||
|
|
||
|
|
@@ -456,6 +457,31 @@ Users can ask the bot questions like "What's the current price?" between schedul | |
| - Background automation (use cron or interval) | ||
| - Event-driven workflows (use webhook when available) | ||
|
|
||
| ## Spawned Trigger Type | ||
|
|
||
| The `spawned` trigger type is a system-generated marker for jobs created by sub-agents via the Task tool. You cannot create a schedule with `type: spawned` — this trigger type exists only as job metadata to record provenance. | ||
|
|
||
| When an agent uses the Task tool to spawn a sub-agent, herdctl creates a job with `trigger_type: spawned`. This allows you to: | ||
|
|
||
| - **Filter jobs** by spawn origin in job listings | ||
| - **Track delegation** patterns — which agents spawn sub-agents and how often | ||
| - **Distinguish** between user-initiated and agent-initiated work | ||
|
|
||
| The `spawned` type is an enum value in the job metadata schema, ensuring type safety and enabling downstream consumers (like dashboards or analytics tools) to distinguish spawned jobs from scheduled, manual, or chat-triggered runs. | ||
|
|
||
| ### Example Job Metadata | ||
|
|
||
| ```yaml | ||
| id: job-2024-01-19-xyz789 | ||
| agent: my-subagent | ||
| trigger_type: spawned # Set by the Task tool | ||
| status: completed | ||
| started_at: "2024-01-19T10:30:00Z" | ||
| finished_at: "2024-01-19T10:32:15Z" | ||
| ``` | ||
|
|
||
| This trigger type is purely informational — it has no scheduling logic of its own. | ||
|
|
||
| ## Choosing the Right Trigger | ||
|
|
||
| ### Decision Guide | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Describe usage as conditional, not universal.
Line 381 claims that every completed job persists usage.
job-executor.tsaddsusageonly whenrunUsageexists, andextractRunUsagereturnsundefinedwhen no terminal result or usable accounting exists. A completed job can therefore omitusage. Qualify this sentence and the later availability wording to include runs without recorded usage.