Skip to content

docs: add a decision record for the toolCalls vs skillCalls index contract #97

Description

@lis186

摘要(繁中)

toolCalls{toolName: count})是 dashboard 多處共用的契約(tc['Skill'] 偵測、tool chips、tool-utilization)。PR #94 早期版本把 Skill:<name> 展開塞進 toolCalls,污染了這個契約並造成 dashboard 連鎖問題;最終改為新增獨立的 skillCalls index 欄位,讓 toolCalls 維持乾淨、dashboard 零改動,per-skill 粒度只給 ccxray usage 用。這個「為何分兩個欄位」的決策目前只散落在 commit message 裡。建議寫一份輕量決策記錄(ADR),避免未來有人「為了少一個欄位」又把兩者合併、重蹈覆轍。此 issue 不在 PR #94 處理,優先度低。

Background

toolCalls is a shared contract consumed by the dashboard (public/miller-columns.js tc['Skill'] detection, entry-rendering.js tool chips, tool-utilization counting). An early PR #94 revision expanded Skill/Workflow tool calls to Skill:<name> keys inside extractToolCalls, which polluted that contract and broke dashboard consumers. The fix introduced a separate skillCalls index field, leaving toolCalls untouched.

This decision is currently only recorded in a commit message. An ADR would prevent a future "let's merge these two fields" regression.

Proposal

Add a lightweight decision record (e.g. docs/decisions/0001-toolcalls-vs-skillcalls.md) capturing:

  • Context: toolCalls is a dashboard contract; per-skill granularity is only needed by ccxray usage, which reads the summarized index (not raw messages).
  • Decision: keep toolCalls a plain {toolName: count} map; add a separate skillCalls {skillName: count} index field populated by extractSkillCalls (Anthropic only; Workflow excluded — it has no skill input).
  • Consequences: dashboard needs zero changes; old entries without skillCalls degrade to (pre-tracking); a small denormalization (toolCalls.Skill count == sum of skillCalls) is accepted as inherent to an index.
  • Rejected alternative: expanding Skill:<name> into toolCalls and teaching every consumer to collapse it (fragile; spreads contract knowledge).

Data flow

              messages[].content[]  (tool_use blocks)
                        │
        ┌───────────────┴────────────────┐
        ▼                                 ▼
 extractToolCalls(msgs)           extractSkillCalls(msgs)
 {Skill:3, Bash:1}                {"superpowers:brainstorming":2}
        │                                 │
        ▼                                 ▼
   toolCalls  (index)               skillCalls  (index)
        │                                 │
   ┌────┴───────────┐                     │
   ▼                ▼                      ▼
 dashboard      ccxray usage         ccxray usage
 tc['Skill'],   "Tools" section      "Skills" section
 chips, util.   (aggregate)          (per-skill invocations/loads)

Acceptance

  • A short ADR exists explaining why the two fields are separate and what NOT to do.
  • Linked from docs/normalization-map.md (tool-call extraction section).

Provider scope: why skillCalls is Anthropic-only (investigated)

繁中:經查證,這是結構性的,不是漏做。Codex 協議裡根本沒有「skill 呼叫」這個概念——skill 是 prompt 內的指令清單,不是工具呼叫,所以沒有東西可抽取。

extractSkillCalls relies on Claude Code's first-class Skill tool call:

{ type: "tool_use", name: "Skill", input: { skill: "<name>" } }   // discrete, countable, attributable

Codex (OpenAI Responses) has no such tool. Evidence from test/fixtures/codex-sessions/*.jsonl:

  • The only function_call names are exec_command (×10) and write_stdin (×2) — no Skill / skill-* tool.
  • Codex advertises skills as prompt instructions, not tools: a <skills_instructions> block lists available skills with their SKILL.md paths ("A skill is a set of local instructions ... stored in a SKILL.md file"). Every SKILL.md occurrence is in that listing; no exec_command reads one in the fixtures.

So Codex follows a skill's instructions inline — there is nothing in the wire format to attribute to "skill X was invoked". Even a cat SKILL.md would surface as a generic exec_command (Bash), not a skill call.

Implication for the ADR: record that skillCalls is intentionally Anthropic-only because the concept does not exist in the Codex protocol. If Codex per-skill stats are ever wanted, they need a different, lossy mechanism (parse <skills_instructions> for the available set, then heuristically detect usage) — out of scope for the skillCalls field.

Not for PR #94 — optional follow-up; lower priority.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions