feat(token-tracking): introduce token note taxonomy (measured/userbased/workplan/heuristic)

Tier 1 (exact counts) now defaults to note="measured" instead of null,
signalling the counts were read from the Claude Code status bar.
Callers can pass note="userbased" when a human provided the numbers.

  measured  — agent read exact counts from the Claude Code status bar
  userbased — counts provided by a human
  workplan  — prorated from workplan total across task count
  heuristic — server fallback, 1000/500, no agent input

Added token_note field to TaskUpdate schema and exposed note param on
update_task_status and record_interactive_task MCP tools.
TOOLS.md documents the full taxonomy. 185 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 18:47:40 +02:00
parent 8c87a9a799
commit af3fdfde80
5 changed files with 59 additions and 11 deletions

View File

@@ -75,7 +75,7 @@ async def update_task(
raise HTTPException(status_code=404, detail="Task not found")
# Separate token fields from task fields
token_field_names = {"tokens_in", "tokens_out", "workplan_tokens_in", "workplan_tokens_out", "model", "agent", "session_id"}
token_field_names = {"tokens_in", "tokens_out", "workplan_tokens_in", "workplan_tokens_out", "token_note", "model", "agent", "session_id"}
update_data = body.model_dump(exclude_unset=True)
token_data = {k: update_data.pop(k) for k in list(update_data.keys()) if k in token_field_names}
@@ -87,8 +87,10 @@ async def update_task(
# Token event — three-tier logic, only when marking done
if update_data.get("status") == "done":
if "tokens_in" in token_data and "tokens_out" in token_data:
# Tier 1: exact counts provided
tin, tout, tnote = token_data["tokens_in"], token_data["tokens_out"], None
# Tier 1: exact counts — default note "measured"; caller may override with token_note
tin = token_data["tokens_in"]
tout = token_data["tokens_out"]
tnote = token_data.get("token_note") or "measured"
elif "workplan_tokens_in" in token_data and "workplan_tokens_out" in token_data:
# Tier 2: prorate workplan total across task count
count_result = await session.execute(

View File

@@ -39,13 +39,15 @@ class TaskUpdate(BaseModel):
intervention_note: str | None = None
parent_task_id: uuid.UUID | None = None
# Token passthrough — three tiers (highest precision wins):
# 1. tokens_in + tokens_out → exact counts (best practice)
# 1. tokens_in + tokens_out → exact counts; note defaults to "measured"
# 2. workplan_tokens_in + workplan_tokens_out → prorated across task count (note="workplan")
# 3. neither provided, status=done → heuristic 1000/500 (note="heuristic")
# token_note overrides the auto-assigned note for Tier 1 only (e.g. "userbased")
tokens_in: int | None = None
tokens_out: int | None = None
workplan_tokens_in: int | None = None
workplan_tokens_out: int | None = None
token_note: str | None = None
model: str | None = None
agent: str | None = None
session_id: str | None = None