Skip to content

fix(agent-publish): accept the in-place agent.kdl.candidate spec source - #354

Merged
schickling merged 1 commit into
mainfrom
schickling-assistant/2026-08-26-fb460-candidate-spec-name
Aug 28, 2026
Merged

fix(agent-publish): accept the in-place agent.kdl.candidate spec source#354
schickling merged 1 commit into
mainfrom
schickling-assistant/2026-08-26-fb460-candidate-spec-name

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What

st2 agent digest --spec and st2 agent publish --spec gated the source file on
path.extension() == "kdl" and reported the refusal as published spec must be canonical KDL.

That message names the wrong cause, and the requirement contradicts the prescribed authoring
workflow: axe agent check requires the candidate in place at
<catalog>/agents/<host>/<identity>/agent.kdl.candidate, whose extension is candidate. So the
same bytes that satisfy the check step could not be digested or published, and the workaround was
to keep a second copy of them outside the catalog — the drift hazard the candidate workflow exists
to prevent.

This accepts agent.kdl.candidate alongside *.kdl, and makes the error name the rejected path
and the accepted names. The gate still keeps legacy TOML/JSON declarations out of publication, and
the bytes are still strictly parsed as canonical KDL immediately afterwards.

Feedback row: FB-460 (friction, 4 occurrences) —
https://app.notion.com/p/st2-agent-digest-publish-reject-any-spec-file-not-named-literally-agent-kdl-with-the-message-publis-3b0e3d41f4a38124bc8ef3f73eb25d5f

Changed lines: 80 (72 insertions, 8 deletions) across 2 filessrc/agent_publish.rs,
tests/agent_publish.rs.

Reproduction — before (main @ 19485cf)

One spec, four file names, identical bytes:

$ st2 --version
st2 0.1.0 — running from local source (19485cf, 16 minutes ago)

=== same bytes in both places? ===
identical bytes

=== A. digest the candidate staged where 'axe agent check' requires it ===
Error: published spec must be canonical KDL
rc=1

=== B. digest the SAME BYTES under a .kdl name ===
1413f11245bb4d2d73c1996dafd3dc81678dad3dfc16833ab9790b4eb2d0e822
rc=0

=== C. same bytes, .kdl name, INSIDE the catalog agent dir ===
1413f11245bb4d2d73c1996dafd3dc81678dad3dfc16833ab9790b4eb2d0e822
rc=0

=== D. same bytes, non-.kdl name, OUTSIDE the catalog ===
Error: published spec must be canonical KDL
rc=1

C and D are controls. They show the discriminator is the file extension alone — not the location.
FB-460's occurrence-4 note concluded the rule was "location-conditional"; that reading is wrong,
because every passing example in it happened to end in .kdl.

Reproduction — after (this branch)

$ st2 --version
st2 0.1.0 — running from local source (19485cf, 25 minutes ago, dirty)

=== A. digest the candidate staged where 'axe agent check' requires it ===
1413f11245bb4d2d73c1996dafd3dc81678dad3dfc16833ab9790b4eb2d0e822
rc=0

=== B. same bytes under a .kdl name ===
1413f11245bb4d2d73c1996dafd3dc81678dad3dfc16833ab9790b4eb2d0e822
rc=0

=== E. a genuinely non-KDL source is still refused, and the message names the file ===
Error: spec source must be named `*.kdl` or `agent.kdl.candidate`, found /tmp/fb460-spec.candidate
rc=1

Same digest for the candidate and the .kdl copy, so the candidate is now a first-class source.

Tests

Two regression tests were added and both were confirmed failure-capable: with the tests in place
and src/agent_publish.rs reverted to main, both fail with the old error.

Without the fix:

test an_in_place_candidate_is_a_publishable_spec_source ... FAILED
thread 'an_in_place_candidate_is_a_publishable_spec_source' panicked at tests/agent_publish.rs:46:5:
Error: published spec must be canonical KDL
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 21 filtered out

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 21 filtered out
---- a_rejected_spec_source_names_the_filename_it_rejected stdout ----
stderr: Error: published spec must be canonical KDL

With the fix:

running 1 test
test an_in_place_candidate_is_a_publishable_spec_source ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out

running 1 test
test a_rejected_spec_source_names_the_filename_it_rejected ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out

cargo fmt --check reports no diff in either file this PR touches.

Pre-existing failure, not introduced here

The full --test agent_publish suite has one unrelated failure that also reproduces on a clean
main @ 19485cf (run twice, same result):

thread 'incomplete_apply_marker_blocks_declarations_but_not_the_state_plane' panicked at
tests/agent_publish.rs:391:5: resident supervisor restart-stormed on an incomplete apply
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 19 filtered out

(19 filtered out on clean main vs 21 on this branch is this PR's two added tests.)

Deliberately out of scope

FB-460's occurrence 4 also notes that st2 agent publish --bundle refuses when the target directory
exists without an agent.kdl. That refusal is a deliberate create-only guard in publish()
(bundle target directory already exists without agent.kdl), not the same root cause, and relaxing
it is a design decision rather than a bug fix. Left untouched.

CI attribution

main at 19485cf is green in CI (Nix workflow, run for
feat(otel): tracing facade …, conclusion success). The failure above therefore reproduces on
clean main on the machine this branch was developed on, not in CI — treat it as
environment-specific to that host rather than as a broken upstream test. nix flake check also does
not gate cargo fmt --check or clippy (flake.nix:258), so the pre-existing formatting drift on
main is not a CI concern either.

Posted on behalf of @schickling
field value
agent_identity dev3.dotfiles.fb-batch1.st2.worker
agent_persona worker
agent_supervisor dev3.dotfiles-lead
agent_tool Claude Code
agent_tool_version 2.1.237
agent_runtime Claude Code 2.1.237
tooling_profile dotfiles@e78784a

…urce

`st2 agent digest --spec` and `st2 agent publish --spec` gated the source on
`path.extension() == "kdl"` and reported the refusal as "published spec must be
canonical KDL". Two problems follow from that.

The message names the wrong cause. It points at KDL syntax, so a caller with a
perfectly canonical declaration re-reads the KDL looking for a formatting fault
that does not exist. FB-460 records that costing a full bisection per seat.

The requirement also contradicts the prescribed authoring workflow. `axe agent
check` requires the candidate in place at
`<catalog>/agents/<host>/<identity>/agent.kdl.candidate`, whose extension is
`candidate`, so the same bytes that satisfy the check step cannot be digested or
published. The workaround was to keep a second copy of the same bytes outside the
catalog, which is exactly the drift hazard the candidate workflow exists to
prevent.

Accept `agent.kdl.candidate` alongside `*.kdl`, and name the rejected path and
the accepted names in the error. The gate still keeps legacy TOML/JSON
declarations out of publication, and the bytes are still strictly parsed as
canonical KDL immediately afterwards.

agent-identity: dev3.dotfiles.fb-batch1.st2.worker
agent-persona: worker
agent-supervisor: dev3.dotfiles-lead
agent-tool: Claude Code
agent-tool-version: 2.1.237
agent-runtime: Claude Code 2.1.237
tooling-profile: dotfiles@e78784a
@schickling-assistant
schickling-assistant marked this pull request as ready for review August 26, 2026 20:25
@schickling
schickling merged commit 4c0c760 into main Aug 28, 2026
1 check passed
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.

2 participants