Skip to content

fix(convo): attribute harness-generated turns to the harness actor - #165

Open
akesling wants to merge 1 commit into
mainfrom
akesling/harness-generated-turns
Open

fix(convo): attribute harness-generated turns to the harness actor#165
akesling wants to merge 1 commit into
mainfrom
akesling/harness-generated-turns

Conversation

@akesling

@akesling akesling commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

A deriver bug fix. Independent of the kind work in #164 / #132 — no schema change, no kind version involved.

The bug

A harness sometimes emits an assistant message itself: an API error, a rate-limit notice, a timeout. No model call happens; the accounting is all zeros. Claude Code records a placeholder in message.model for these, and the deriver took that placeholder at face value, producing agent:<placeholder> actors and matching meta.actors entries.

The actor convention never meant that. The agent-coding-session actor table has always defined agent:<model> as "a model reply, named by the recorded model" — a harness notice is not a model reply, and a placeholder is not a recorded model. So this is the deriver contradicting the spec that was already written, not a change to it.

These are not rare: sampling real Claude Code sessions, 14 of 40 contained at least one.

The fix

Turn gains a provider-agnostic harness_generated flag (serde-defaulted, so the wire format is unchanged when false). The shared deriver attributes such turns to tool:<provider> — the same harness actor that system and other roles already take — while the turn's role stays assistant, so the message keeps its place in the transcript.

The placeholder spelling stays where the format knowledge belongs: toolpath-claude owns the constant and maps it to the flag with model: None, so the string never enters the IR. Any other provider can set the flag from whatever signal its own format offers without touching shared code. Repo-wide, the placeholder is now spelled in exactly one file.

agent:unknown is unaffected — "no model recorded" and "no model involved" stay distinct outcomes.

Testing

Full workspace suite, clippy, rustdoc, and the examples gate green. Coverage: a harness-generated turn takes the harness actor with the role preserved and a meta.actors entry carrying provider but no model; a flagged turn's stray model is ignored; an ordinary assistant turn is untouched; a turn with no recorded model still derives as agent:unknown; and the flag round-trips on the wire in both directions.

Versioning follows the deriver-behavior-fix precedent (Derive: resolve duplicate step ids, 2026-07-01): toolpath-convo takes a minor bump because a new public field on Turn breaks struct-literal construction downstream; toolpath-claude takes a patch, its public API being unchanged. No CLI bump — no kind URI changes here.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

A harness sometimes emits an assistant message itself — an API error, a
rate-limit notice, a timeout — with no model call behind it. Claude Code
marks these with a placeholder in message.model, and derive_path took
that placeholder at face value: the step landed on an agent: actor
naming a string that is not a model, and meta.actors described the
harness as if it were one. The actor convention never meant that —
agent:<model> is "a model reply, named by the recorded model".

Turn now carries a provider-agnostic harness_generated flag
(serde-optional, omitted when false, so the wire format stays
byte-compatible with producers that predate it), and derive_path keys
the tool:<provider> harness actor on it — the same actor the system and
other roles already take. The role stays assistant, so the message keeps
its place in the transcript; only the attribution changes.
agent:unknown is unaffected: "no model recorded" and "no model involved"
stay distinguishable.

toolpath-claude owns the placeholder: it maps the sentinel to the flag
and leaves Turn.model empty, so the string never reaches the shared,
provider-agnostic crate. Any provider can mark such turns from whatever
signal its own format gives it, without touching shared code.

No schema change: agent-coding-session already says agent:<model> names
a recorded model, and a placeholder is neither.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant