Goal
Implement the agent spec as convoy's declaration contract: one file per agent, agent-level fields as intent, child pty blocks as tasks, discovered by slurping the catalog.
Today convoy reads a TOML catalog at <net>/catalog/<identity>.toml and writes a separate .convoy/pty.toml. The spec unifies those into one file and adds the pieces that make declared agents durable.
This is a tracking issue for the work; individual pieces can land separately.
Why now
Two properties we want depend on this, and neither is reachable with the current shape.
Declared, stable identity. Names generated per launch resolve to fresh, empty context storage on every restart. Anything that externalizes state to context/now.md can therefore never read its own predecessor's writing. A declared identity in a catalog file is what makes cold-boot continuity possible at all.
Restart convergence. Once identity is stable and context is external, a session can be killed at any moment and a fresh one converges on the same goal. That turns crash recovery into a testable property rather than an aspiration.
Work
1. Catalog in the spec format
Read agent specs per the spec — KDL, TOML, and JSON with identical semantics. Discovery slurps catalog/**/*.{kdl,toml,json} and keeps whatever is an agent spec; identity comes from content, with path segments supplying defaults and a path↔content mismatch a warning.
Replaces the split between the catalog entry and .convoy/pty.toml.
2. Identity validation at declare time
convoy add currently checks only truthiness and bypasses both the parser and preflight, so nothing validates identity on the way in. This matters because convoy's accepted charset and smalltalk's disagree: worker_fodfix passes convoy's launch check and is rejected by the bus. A name that declares cleanly and then cannot be written is the worst failure ordering.
Validate at declare time against one grammar — charset, length, reserved words, uniqueness scope. Length has a real bound: pty keys its socket on ${name}.sock, so identity length is constrained by socket path limits, not just aesthetics.
3. convoy rename
Rename must move the catalog entry and the whole bus folder — context/, context/decisions/, archive/, inbox/, status — and leave a redirect tombstone at the old identity so in-flight messages and stale peer references resolve rather than failing.
This is load-bearing rather than convenience. If identity must be a meaningful declared name, the cost of choosing one is only acceptable when a wrong choice is cheaply correctable. Without rename-preserving-continuity, changing a name orphans everything the agent externalized.
Tombstones accumulate and want a retention rule.
4. Launch through an external command
Convoy currently execs the harness binary directly (exec claude …). Deployments that wrap their harness — for credential selection, persona projection, policy gates, telemetry — are bypassed entirely, so a convoy-managed session runs outside the boundary every other session in that deployment runs inside.
An optional bin on the agent spec, used in place of the bare harness name when deriving the command, resolves this without convoy needing to know what the wrapper does. It also sidesteps the closed Harness = "claude" | "codex" union for deployments running other harnesses.
5. up --once should pre-trust
convoy up --once does not pre-trust the agent directory, so the harness sits at its trust prompt forever and the one-shot path is not usable headlessly. Hosted convoy up does pre-trust. Worth aligning.
6. Refuse context/ under counter-named identities
A <role>-<n> counter discriminator looks safe but isn't: the counter re-derives only within one parent's lifetime, so a restart renumbers peers and an agent can silently inherit a stranger's context/now.md. Reading another agent's durable state as your own is a bad failure to leave available.
Suggest convoy refuse to create context/ under a counter-named folder, making the unsafe case structurally impossible rather than discouraged. Meaningful discriminators stay fine — in practice most already are.
7. convoy run
There is no convoy run today, so there's no supported path for an ad-hoc session that isn't a declared catalog member. Deployments retiring their own per-account launcher aliases need somewhere for one-off sessions to go.
Related
Items 1–3 are the spine; 4 unblocks wrapped deployments; 5–7 are smaller and independent. Happy to take any of them, and would rather agree the shape of 2 and 3 before writing code, since both change existing behaviour.
Goal
Implement the agent spec as convoy's declaration contract: one file per agent, agent-level fields as intent, child
ptyblocks as tasks, discovered by slurping the catalog.Today convoy reads a TOML catalog at
<net>/catalog/<identity>.tomland writes a separate.convoy/pty.toml. The spec unifies those into one file and adds the pieces that make declared agents durable.This is a tracking issue for the work; individual pieces can land separately.
Why now
Two properties we want depend on this, and neither is reachable with the current shape.
Declared, stable identity. Names generated per launch resolve to fresh, empty context storage on every restart. Anything that externalizes state to
context/now.mdcan therefore never read its own predecessor's writing. A declared identity in a catalog file is what makes cold-boot continuity possible at all.Restart convergence. Once identity is stable and context is external, a session can be killed at any moment and a fresh one converges on the same goal. That turns crash recovery into a testable property rather than an aspiration.
Work
1. Catalog in the spec format
Read agent specs per the spec — KDL, TOML, and JSON with identical semantics. Discovery slurps
catalog/**/*.{kdl,toml,json}and keeps whatever is an agent spec; identity comes from content, with path segments supplying defaults and a path↔content mismatch a warning.Replaces the split between the catalog entry and
.convoy/pty.toml.2. Identity validation at declare time
convoy addcurrently checks only truthiness and bypasses both the parser and preflight, so nothing validates identity on the way in. This matters because convoy's accepted charset and smalltalk's disagree:worker_fodfixpasses convoy's launch check and is rejected by the bus. A name that declares cleanly and then cannot be written is the worst failure ordering.Validate at declare time against one grammar — charset, length, reserved words, uniqueness scope. Length has a real bound: pty keys its socket on
${name}.sock, so identity length is constrained by socket path limits, not just aesthetics.3.
convoy renameRename must move the catalog entry and the whole bus folder —
context/,context/decisions/,archive/,inbox/,status— and leave a redirect tombstone at the old identity so in-flight messages and stale peer references resolve rather than failing.This is load-bearing rather than convenience. If identity must be a meaningful declared name, the cost of choosing one is only acceptable when a wrong choice is cheaply correctable. Without rename-preserving-continuity, changing a name orphans everything the agent externalized.
Tombstones accumulate and want a retention rule.
4. Launch through an external command
Convoy currently execs the harness binary directly (
exec claude …). Deployments that wrap their harness — for credential selection, persona projection, policy gates, telemetry — are bypassed entirely, so a convoy-managed session runs outside the boundary every other session in that deployment runs inside.An optional
binon the agent spec, used in place of the bare harness name when deriving the command, resolves this without convoy needing to know what the wrapper does. It also sidesteps the closedHarness = "claude" | "codex"union for deployments running other harnesses.5.
up --onceshould pre-trustconvoy up --oncedoes not pre-trust the agent directory, so the harness sits at its trust prompt forever and the one-shot path is not usable headlessly. Hostedconvoy updoes pre-trust. Worth aligning.6. Refuse
context/under counter-named identitiesA
<role>-<n>counter discriminator looks safe but isn't: the counter re-derives only within one parent's lifetime, so a restart renumbers peers and an agent can silently inherit a stranger'scontext/now.md. Reading another agent's durable state as your own is a bad failure to leave available.Suggest convoy refuse to create
context/under a counter-named folder, making the unsafe case structurally impossible rather than discouraged. Meaningful discriminators stay fine — in practice most already are.7.
convoy runThere is no
convoy runtoday, so there's no supported path for an ad-hoc session that isn't a declared catalog member. Deployments retiring their own per-account launcher aliases need somewhere for one-off sessions to go.Related
st agentshas no freshness window — reports stale status as live smalltalk#102 —st agentshas no freshness windowItems 1–3 are the spine; 4 unblocks wrapped deployments; 5–7 are smaller and independent. Happy to take any of them, and would rather agree the shape of 2 and 3 before writing code, since both change existing behaviour.