Skip to content
Open
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
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,43 @@

All notable changes to the Toolpath workspace are documented here.

## Model on the turn payload + kind v1.2.0 — 2026-07-16

Derived agent-coding-session documents never wrote the model into the
`conversation.append` structural payload: it rode only the step actor
string (`agent:<model>`) and `meta.actors[…].model`, so consumers
reading `structural.model` got null and had to parse the actor string
(reported by Pathbase, pathbase#225).

Kind `agent-coding-session` **v1.2.0** adds an optional `model` to the
turn payload — the model that produced the turn, verbatim from the
source. The append is now self-describing about attribution.

- The shared `toolpath_convo::derive_path` emits `model` when
`Turn.model` is set, which covers all seven providers (claude,
gemini, codex, opencode, cursor, pi, copilot) — they derive through
it.
- When the source records no model, the key is **omitted entirely** —
the `agent:unknown` actor sentinel never appears in the payload.
- The actor string and `meta.actors` are unchanged; the payload `model`
is the same value they attribute.
- Compat: the field is additive and optional, so v1.2.0 documents are
structurally valid v1.1.0 documents; the new version exists so
consumers can rely on the payload carrying the model when the source
records one. `PATH_KIND_AGENT_CODING_SESSION` now points at v1.2.0;
`…_V1_1_0` and `…_V1_0_0` name the superseded URIs. `path p validate`
bundles all three schemas; `path kind` resolves the bare name to
v1.2.0.

Crates bumped (every crate that depends on `toolpath`, matching the
v1.1.0 precedent since the emitted kind URI changes): `toolpath` 0.8.0,
`toolpath-convo` 0.12.0, `toolpath-git` 0.7.0, `toolpath-github` 0.7.0,
`toolpath-claude` 0.13.0, `toolpath-gemini` 0.7.0, `toolpath-codex`
0.7.0, `toolpath-copilot` 0.2.0, `toolpath-opencode` 0.6.0,
`toolpath-cursor` 0.3.0, `toolpath-pi` 0.7.0, `toolpath-dot` 0.6.0,
`toolpath-md` 0.8.0, `path-cli` 0.16.0, `toolpath-cli` 0.16.0.
`pathbase-client` is unaffected.

## Derive: resolve duplicate step ids — 2026-07-01

- **`toolpath-convo`** (0.11.1): `derive_path` now guarantees the derived
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ When changing a crate's public API (new types, new trait impls, new public metho

The `toolpath-cli` shim lives **outside** the workspace (`exclude = ["crates/toolpath-cli"]` in the root `Cargo.toml`). Both `toolpath-cli` and `path-cli` produce a binary literally named `path`, and cargo can't write two bin targets to the same workspace `target/debug/path` — so the shim opts out and gets its own `crates/toolpath-cli/target/` (covered by the `crates/*/target` line in `.gitignore`). Practical consequences: `cargo build --workspace`, `cargo test --workspace`, and `cargo run -p toolpath-cli` from the repo root **do not** include the shim. To touch it, use `--manifest-path crates/toolpath-cli/Cargo.toml`. The release script special-cases the shim in `get_version` and `publish` so the workflow is otherwise unchanged.

Build the site after changes: `cd site && pnpm run build` (should produce 11 pages).
Build the site after changes: `cd site && pnpm run build` (should produce 12 pages).

## Things to know

Expand All @@ -262,9 +262,10 @@ Build the site after changes: `cd site && pnpm run build` (should produce 11 pag
- `toolpath-gemini` treats main file + sibling sub-agent UUID dir as one conversation. Sub-agent files are folded into `DelegatedWork` with populated `turns` (unlike `toolpath-claude`, whose sub-agent turns live in separate session files and stay empty). See `docs/agents/formats/gemini.md` for the full format reference.
- Provider-specific extras convention: `Turn.extra` and `WatcherEvent::Progress.data` use provider-namespaced keys (e.g. `extra["claude"]`, `extra["gemini"]`). `toolpath-claude` populates `Turn.extra["claude"]` from `ConversationEntry.extra`; `toolpath-gemini` populates `Turn.extra["gemini"]` with the full `tokens` struct, per-thought metadata, and tool-call status. This lets trait-only consumers access provider metadata without importing provider types.
- Shared derivation: `toolpath-convo` provides a provider-agnostic `ConversationView → Path` mapping via `toolpath_convo::derive_path`. New conversation providers should build on it rather than re-implementing the mapping.
- Path kinds: `toolpath::v1::PathMeta.kind` is an optional URI naming a hosted kind spec; URIs are immutable and semver-versioned. The only one defined so far is `https://toolpath.net/kinds/agent-coding-session/v1.1.0` (constant `toolpath::v1::PATH_KIND_AGENT_CODING_SESSION`; `…_V1_0_0` names the superseded URI); every conversation → `Path` derivation sets it via the shared `toolpath_convo::derive_path` or each provider crate's own. Carried through the JSONL form via `PathOpen.meta` and `PathMeta` patch lines. Spec sources live in `site/kinds/<name>/<version>/{index.md,schema.json}` (schema.json is a symlink into `crates/path-cli/kinds/`, which `path p validate` bundles — both versions) and publish under `https://toolpath.net/kinds/`; the registry index is `site/kinds/index.md`. RFC: "Document Kind". JSON Schema: `$defs/pathMeta`.
- Path kinds: `toolpath::v1::PathMeta.kind` is an optional URI naming a hosted kind spec; URIs are immutable and semver-versioned. The only one defined so far is `https://toolpath.net/kinds/agent-coding-session/v1.2.0` (constant `toolpath::v1::PATH_KIND_AGENT_CODING_SESSION`; `…_V1_1_0` and `…_V1_0_0` name the superseded URIs); every conversation → `Path` derivation sets it via the shared `toolpath_convo::derive_path` or each provider crate's own. Carried through the JSONL form via `PathOpen.meta` and `PathMeta` patch lines. Spec sources live in `site/kinds/<name>/<version>/{index.md,schema.json}` (schema.json is a symlink into `crates/path-cli/kinds/`, which `path p validate` bundles — all versions) and publish under `https://toolpath.net/kinds/`; the registry index is `site/kinds/index.md`. RFC: "Document Kind". JSON Schema: `$defs/pathMeta`.
- Token accounting (kind v1.1.0): two keys on `conversation.append`/`Turn`, both optional. `token_usage` = "the total for a message" (on the group's final step; `Σ` over a path = session total). `attributed_token_usage` = "this step's own attributed spend", populated only where the source genuinely reports per-step spend (its own key, so the sum is unaffected; remainder = group total − Σ attributed, computed not stored). One provider message can span several steps (Claude writes one JSONL line per content block); `Turn.group_id` groups them. `toolpath-claude` fills `group_id` from `message.id` and takes the **field-wise-max** group total (line order not trusted). Claude's per-line `usage` is a cumulative *streaming snapshot* (Anthropic streaming API: `message_start` seeds output near 0, `message_delta` is cumulative), NOT a per-block cost — so Claude emits no `attributed_token_usage`; the projector re-expands the total onto every line. `toolpath-codex` differences the cumulative `total_token_usage` (dedup-safe: never sum `last_token_usage` — Codex re-emits it stale; openai/codex #14489), attributes each per-call delta to the step it follows, and derives the round total from those attributions. pi/opencode decode all-zero wire counters as `None`. Never stamp a cumulative counter, a repeated message total, or zero-filled placeholders onto a step; never derive attribution from Claude's streaming snapshots.
- Token usage `breakdowns` (kind v1.1.0, additive): an optional third key on `TokenUsage` — a decomposition of a top-level class into named sub-classes, keyed by class (e.g. `"output"`), inner map sub-class → tokens (e.g. `breakdowns["output"]["reasoning"] = 243`). INFORMATIONAL ONLY: **never summed into any total** (the parent class already counts those tokens, so the session-total guarantee is untouched); invariant `Σ(inner) ≤ parent`; omitted when empty; rides both `token_usage` and `attributed_token_usage`. Per-provider reality: **Gemini** reports `thoughts` (reasoning) as an additive sibling that the derivation used to **drop** (under-counting output) — it's now folded into `output_tokens` *and* recorded as `breakdowns["output"]["reasoning"]`, with the projector un-folding it on the reverse path for a lossless round-trip (`Some(0)` preserved as a real Gemini-3 zero-reasoning signal). **OpenCode** folds `reasoning` into output and records the same breakdown. **Codex** differences `reasoning_output_tokens` (⊆ output, cumulative) into `breakdowns["output"]["reasoning"]` on both per-step `attributed_token_usage` and per-round `token_usage`. **Claude** records no breakdown (its JSONL `usage` doesn't itemize thinking tokens).
- Model attribution (kind v1.2.0): an assistant `conversation.append` payload carries an optional `model` — the model that produced the turn, verbatim from the source — so consumers read `structural.model` instead of parsing the actor string. Emitted by the shared `derive_path` when `Turn.model` is `Some`; **omitted entirely when the source records none** (the `agent:unknown` actor sentinel never appears in the payload). The actor string (`agent:<model>`) and `meta.actors[…].model` are unchanged and carry the same value.
- Pi provider: `toolpath-pi` reads Pi session JSONL from `~/.pi/agent/sessions/`. Sessions use a tree (id/parentId) in a single file, and may link to a parent file via `parentSession` in the header. The tree is preserved as a DAG in the derived `Path`.
- Codex provider: `toolpath-codex` reads Codex CLI rollout files from `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`. Sessions are date-bucketed (not project-keyed). File-change fidelity is excellent — Codex's `patch_apply_end` events carry either the unified diff (for updates) or the full file content (for adds), so the derived `Path` gets a real `raw` perspective on every file artifact. See `docs/agents/formats/codex.md` for the full format reference.
- Copilot provider (**preview, schema reverse-engineered**): `toolpath-copilot` reads the GitHub Copilot CLI (`@github/copilot`) `events.jsonl` stream from `~/.copilot/session-state/<id>/` (root overridable via `COPILOT_HOME`; also globs the legacy `history-session-state/`). Sessions are global (id-keyed dirs), not project-keyed. The `events.jsonl` schema is **undocumented** but was **verified against a first-hand capture at `copilotVersion` 1.0.67**: envelope is `{type, data, id, timestamp, parentId}`; cwd + git (branch/remote/`headCommit`) live under `session.start`'s `data.context` (primary; `workspace.yaml` is the fallback, parsed by a tolerant key-scan with no YAML dep); tool calls carry `toolCallId`/`toolName`/`arguments` with results under `result.content`; `assistant.message` carries `reasoningText` (→ thinking) and per-message `outputTokens` (summed for the session total). The reader stays deliberately tolerant (payload inline / under `data` / under `payload`; multiple key spellings; unknown events preserved as `ConversationEvent`s). `subagent.*` and `session.shutdown` (`tokenDetails`) are now observed via a real feature-elicit capture at 1.0.68; event types not yet observed (`skill.invoked`, `hook.*`, `abort`, compaction) and the `checkpoints/` format remain unverified. Wired into the CLI both directions — forward (`path p import/list/show copilot`, `path share`) and reverse via `CopilotProjector` (`path p export copilot`, `path resume`; the projector emits `events.jsonl` in the observed shape, remaps foreign tool names via `native_name`, and — for resume — writes `session-state/<id>/` + a `session-store.db` `sessions` row, INSERTing only a fresh id). **Verified in copilot 1.0.67**: a projected session loads and resumes in the real `copilot --resume`. Reaching that mapped the loader's writer contract (UUID `id`/`parentId`, offset-ISO timestamps on every event, `turnId`/`messageId` on turn-scoped events, non-empty `toolCallId`, full `session.start` shape, and `subagent.*` fields `agentName`/`agentDisplayName`/`agentDescription`/`toolCallId`) — documented with verbatim rejection messages in `docs/agents/formats/copilot-cli/writing-compatible.md`; verified on a small session and a 5817-event sub-agent session. Also validated in isolation by the cross-harness conformance matrix (`crates/path-cli/tests/cross_harness_matrix.rs`, fixture `test-fixtures/copilot/`) + a round-trip test. File fidelity is **Codex-grade**: `edit`/`create` completes embed the real file-state diff inline (`result.detailedContent`), which upgrades the arg-derived `FileMutation.raw_diff`; sub-agents are `task` tool calls with thin `subagent.*` markers sharing the `toolCallId` (delegations pair by it). Session token totals take `output` from the summed per-message `outputTokens` and merge input/cache totals from `session.shutdown`'s `tokenDetails` (Copilot reports only `output` per-message, so input+cache would otherwise be dropped); no per-turn attribution. Full format reference + verification checklist: `docs/agents/formats/copilot-cli/`.
Expand Down
28 changes: 14 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 14 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ edition = "2024"
license = "Apache-2.0"

[workspace.dependencies]
toolpath = { version = "0.7.0", path = "crates/toolpath" }
toolpath-convo = { version = "0.11.1", path = "crates/toolpath-convo" }
toolpath-git = { version = "0.6.0", path = "crates/toolpath-git" }
toolpath-claude = { version = "0.12.0", path = "crates/toolpath-claude", default-features = false }
toolpath-gemini = { version = "0.6.0", path = "crates/toolpath-gemini", default-features = false }
toolpath-codex = { version = "0.6.0", path = "crates/toolpath-codex" }
toolpath-copilot = { version = "0.1.0", path = "crates/toolpath-copilot" }
toolpath-opencode = { version = "0.5.0", path = "crates/toolpath-opencode" }
toolpath-cursor = { version = "0.2.0", path = "crates/toolpath-cursor" }
toolpath-github = { version = "0.6.0", path = "crates/toolpath-github" }
toolpath-dot = { version = "0.5.0", path = "crates/toolpath-dot" }
toolpath-md = { version = "0.7.0", path = "crates/toolpath-md" }
toolpath-pi = { version = "0.6.1", path = "crates/toolpath-pi" }
path-cli = { version = "0.15.0", path = "crates/path-cli" }
toolpath = { version = "0.8.0", path = "crates/toolpath" }
toolpath-convo = { version = "0.12.0", path = "crates/toolpath-convo" }
toolpath-git = { version = "0.7.0", path = "crates/toolpath-git" }
toolpath-claude = { version = "0.13.0", path = "crates/toolpath-claude", default-features = false }
toolpath-gemini = { version = "0.7.0", path = "crates/toolpath-gemini", default-features = false }
toolpath-codex = { version = "0.7.0", path = "crates/toolpath-codex" }
toolpath-copilot = { version = "0.2.0", path = "crates/toolpath-copilot" }
toolpath-opencode = { version = "0.6.0", path = "crates/toolpath-opencode" }
toolpath-cursor = { version = "0.3.0", path = "crates/toolpath-cursor" }
toolpath-github = { version = "0.7.0", path = "crates/toolpath-github" }
toolpath-dot = { version = "0.6.0", path = "crates/toolpath-dot" }
toolpath-md = { version = "0.8.0", path = "crates/toolpath-md" }
toolpath-pi = { version = "0.7.0", path = "crates/toolpath-pi" }
path-cli = { version = "0.16.0", path = "crates/path-cli" }
pathbase-client = { version = "0.2.0", path = "crates/pathbase-client" }

reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/path-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "path-cli"
version = "0.15.0"
version = "0.16.0"
edition.workspace = true
license.workspace = true
repository = "https://github.com/empathic/toolpath"
Expand Down
Loading
Loading