@@ -285,6 +285,9 @@ type Card struct {
285285 Custom map [string ]any ` yaml:"custom,omitempty" json:"custom,omitempty"`
286286 Autonomous bool ` yaml:"autonomous,omitempty" json:"autonomous"`
287287 UseOpusOrchestrator bool ` yaml:"use_opus_orchestrator,omitempty" json:"use_opus_orchestrator,omitempty"`
288+ ModelOrchestrator string ` yaml:"model_orchestrator,omitempty" json:"model_orchestrator,omitempty"`
289+ ModelCoder string ` yaml:"model_coder,omitempty" json:"model_coder,omitempty"`
290+ ModelReviewer string ` yaml:"model_reviewer,omitempty" json:"model_reviewer,omitempty"`
288291 Vetted bool ` yaml:"vetted,omitempty" json:"vetted"`
289292 FeatureBranch bool ` yaml:"feature_branch,omitempty" json:"feature_branch,omitempty"`
290293 CreatePR bool ` yaml:"create_pr,omitempty" json:"create_pr,omitempty"`
@@ -293,7 +296,9 @@ type Card struct {
293296 PRUrl string ` yaml:"pr_url,omitempty" json:"pr_url,omitempty"`
294297 ReviewAttempts int ` yaml:"review_attempts,omitempty" json:"review_attempts,omitempty"`
295298 RunnerStatus string ` yaml:"runner_status,omitempty" json:"runner_status,omitempty"`
299+ Phase string ` yaml:"phase,omitempty" json:"phase,omitempty"`
296300 TokenUsage *TokenUsage ` yaml:"token_usage,omitempty" json:"token_usage,omitempty"`
301+ UsageBreakdown []UsageBucket ` yaml:"usage_breakdown,omitempty" json:"usage_breakdown,omitempty"`
297302 Created time.Time ` yaml:"created" json:"created"`
298303 Updated time.Time ` yaml:"updated" json:"updated"`
299304 ActivityLog []ActivityEntry ` yaml:"activity_log,omitempty" json:"activity_log,omitempty"`
@@ -328,6 +333,17 @@ type TokenUsage struct {
328333 CacheCreationTokens int64 ` yaml:"cache_creation_tokens,omitempty" json:"cache_creation_tokens,omitempty"`
329334 EstimatedCostUSD float64 ` yaml:"estimated_cost_usd" json:"estimated_cost_usd"`
330335}
336+
337+ type UsageBucket struct {
338+ Agent string ` yaml:"agent" json:"agent"`
339+ Model string ` yaml:"model" json:"model"`
340+ PromptTokens int64 ` yaml:"prompt_tokens" json:"prompt_tokens"`
341+ CompletionTokens int64 ` yaml:"completion_tokens" json:"completion_tokens"`
342+ CacheReadTokens int64 ` yaml:"cache_read_tokens,omitempty" json:"cache_read_tokens,omitempty"`
343+ CacheCreationTokens int64 ` yaml:"cache_creation_tokens,omitempty" json:"cache_creation_tokens,omitempty"`
344+ CostUSD float64 ` yaml:"cost_usd" json:"cost_usd"`
345+ CostSource string ` yaml:"cost_source" json:"cost_source"`
346+ }
331347```
332348
333349` CacheReadTokens ` and ` CacheCreationTokens ` are optional (` omitempty ` ); they are
@@ -351,6 +367,26 @@ and 1-hour cache-write tiers. Claude Code uses the 5-minute tier by default.
351367Agents should pass the ` cache_creation_input_tokens ` field from Claude's
352368stream-json ` usage ` frame directly — no tier distinction is required.
353369
370+ ### Usage breakdown
371+
372+ ` UsageBreakdown ` holds one ` UsageBucket ` per ` (agent, model) ` pair, merging every
373+ ` report_usage ` call for that pair into a single row. It exists to attribute cost
374+ after a card is released (when ` assigned_agent ` is cleared) and across multiple
375+ agents or models on one card. Empty-agent buckets roll up to the dashboard's
376+ ` unassigned ` label.
377+
378+ ` cost_source ` is ` actual ` when the bucket's cost came from the provider (passed
379+ on ` report_usage ` as ` actual_cost_usd ` ) or ` estimated ` when it was priced from
380+ the local rate table. ** Actual is authoritative and is never re-priced** —
381+ ` RecalculateCosts ` re-prices only ` estimated ` buckets from the current rate
382+ table and leaves ` actual ` buckets untouched. A bucket that has ever received an
383+ actual-cost report stays ` actual ` .
384+
385+ The cumulative ` TokenUsage ` (counters and ` estimated_cost_usd ` ) is kept equal to
386+ the bucket sum for breakdown cards: each report increments both the matching
387+ bucket and the cumulative total. Legacy cards reported before this feature have
388+ no buckets; the dashboard falls back to ` assigned_agent ` for their agent rollup.
389+
354390``` go
355391// internal/board/project.go
356392
@@ -406,17 +442,24 @@ generation.
406442** Server-managed fields** (set by service layer, not by clients directly): ` id ` ,
407443` created ` , ` updated ` , ` assigned_agent ` , ` last_heartbeat ` , ` activity_log ` ,
408444` runner_status ` , ` review_attempts ` , ` branch_name ` , ` token_usage ` ,
409- ` dependencies_met ` .
445+ ` usage_breakdown ` , ` dependencies_met ` .
446+
447+ ** Agent-managed field** — ` phase ` : the agent-orchestrator's progress within a run
448+ (` plan ` | ` execute ` | ` review ` | ` integrate ` | ` done ` ), orthogonal to ` state ` .
449+ Enum-validated; the empty string clears it and means "not agent-driven". Settable
450+ via the ` update_card ` MCP tool and REST (PUT/PATCH).
410451
411452** Human-only fields** (may only be set by agents whose ` X-Agent-ID ` starts with
412453` human: ` ): ` vetted ` , ` autonomous ` , ` use_opus_orchestrator ` , ` feature_branch ` ,
413- ` create_pr ` , and ` base_branch ` . POST ` /api/projects/{project}/cards `
454+ ` create_pr ` , the three model pins (` model_orchestrator ` , ` model_coder ` ,
455+ ` model_reviewer ` ), and ` base_branch ` . POST ` /api/projects/{project}/cards `
414456(` createCardRequest ` ) and PUT ` /api/projects/{project}/cards/{id} `
415- (` updateCardRequest ` ) gate the first five fields; ` base_branch ` is ** only
416- exposed via PATCH** (` patchCardRequest ` ) — there is no ` base_branch ` field on
417- the create or full-update request bodies, so the human-only check for it applies
418- only on PATCH. Agents that attempt to set these fields receive 403
419- ` HUMAN_ONLY_FIELD ` . The MCP ` update_card ` tool does not expose them.
457+ (` updateCardRequest ` ) gate the first five fields plus the model pins;
458+ ` base_branch ` is ** only exposed via PATCH** (` patchCardRequest ` ) — there is no
459+ ` base_branch ` field on the create or full-update request bodies, so the
460+ human-only check for it applies only on PATCH. The model pins are gated on
461+ create, full-update, and PATCH. Agents that attempt to set any of these fields
462+ receive 403 ` HUMAN_ONLY_FIELD ` . The MCP ` update_card ` tool does not expose them.
420463
421464## Reserved labels
422465
0 commit comments