Skip to content

refine spawns a stray agent session named literally voro-{task_id} - #113

Merged
MJohnson459 merged 1 commit into
mainfrom
launch-identity
Aug 1, 2026
Merged

refine spawns a stray agent session named literally voro-{task_id}#113
MJohnson459 merged 1 commit into
mainfrom
launch-identity

Conversation

@MJohnson459

Copy link
Copy Markdown
Contributor

Give every launch one identity, rendered in one place.

What changed

A note-driven refine borrowed the dispatch template and never substituted {task_id}, so every refine of every task launched a backgrounded session called, literally, voro-{task_id} — one stray name shared by all of them. Underneath sat three renderers, each computing a launch's identity ad hoc and each replacing a different set of placeholders.

One launch identity. Launch (voro-core, agent.rs) names a Dispatch, a Refine or a Plan once; its session_name(), slug() and task_id() feed the session name, the prompt/log filenames and the launch-log label together. File slugs are byte-identical to what the three paths computed before (task-<id>, refine-<id>, plan-<p>), so no prompt or log filename moved.

{session_name}, and one place that renders a command. The built-in claude dispatch and plan now name their session from {session_name} instead of spelling voro-{task_id}: a dispatch stays voro-<id> (the published contract), a refine is voro-<id>-refine, a planning session voro-plan-<project>. claude --help confirms --name is not a --bg-only flag, so the foreground plan verb carries it too. {session_name} is permitted on dispatch/plan and refused on the session verbs — the rule {model} already followed — and {task_id} is now refused on plan, whose target may be a project with no task id to bind. ResolvedAgent::dispatch_command/plan_command are replaced by launch_command/plan_launch_command, which take a LaunchSpec and return a fully rendered command line, so the voro crate never calls .replace on one again; shell_quote moved to voro-core and is re-exported from dispatch.rs, leaving app.rs untouched.

No expand verb. Refine keeps using the dispatch template and passes a different name. The roster stays dispatch/sessions/attach/resume/plan, so a third-party agent defining only dispatch/cmd refines exactly as before. Refine also still opens no session row, unchanged, and stays fire-and-forget.

One substitution routine. voro_core::render(template, bindings) makes a single left-to-right pass, emits each bound value verbatim and never re-scans it; unknown {…} is copied through. Chained String::replace could not promise that — whichever untrusted value went in last, the ones before it were scanned — so a task body, branch name, document title or project name discussing {task_id} or {db} was rewritten before the agent read it. That is the defect that corrupted this task's own body. render_refine_prompt, render_preamble and render_planning_prompt now compose innermost-block-first through it.

refine()'s summary reports the session name as well as the log, since the launcher exits at birth and the log holds only its banner: task 1 sent for refinement by 'claude' as voro-1-refine (pid …) — log ….

Docs

DESIGN.md §5 (placeholder inventory), §6 (refine launches a named session and still opens no row; Expansion inherits its identity from Launch), §8 (the naming invariant beside the {model} rule; the one-renderer and one-identity paragraph; why no verb was added). docs/agent-integration.md: the built-in TOML block, a new {session_name} bullet, the reworked {task_id} and plan bullets. STARTER_HEADER prose. CHANGELOG Changed entry — this moves the published agent contract.

Tests

New: Launch naming and slugs; the built-in claude rendering distinct names for dispatch/refine/plan with no { surviving; prompt-file quoting; validate_agent refusing {session_name}/{task_id} on the session verbs and {task_id} on plan, and accepting both on dispatch/plan; the renderer itself in voro-core (a value containing another placeholder emitted verbatim in either binding order, longest-match wins); a refine and a dispatch of one task rendering distinct names asserted through markers and launches.log; a refine of a task whose body and note are full of {task_id}/{db} reaching the agent unchanged; a dispatch whose branch name and linked-doc title contain {task_id}/{db} reaching the agent unchanged. The existing refine and {task_id}-dispatch tests pass unchanged.

Verification

cargo test --workspace (566 tests), cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --all — all clean. Plus an end-to-end run against a scratch database with a stub agent: refining task 1 launched voro-1-refine, dispatching the same task launched voro-1, both with every placeholder bound, and the prompt carried the flag is {db}, literally and keep {task_id} as written verbatim while voro set 1 --db '…' --body-file still resolved.

Not done

Not pushed — the operator's standing rule is that no background session writes to a remote. The branch launch-identity is committed locally in the worktree at .claude/worktrees/launch-identity.

A note-driven refine borrowed the dispatch template and never substituted
`{task_id}`, so every refine of every task launched a backgrounded session
called, literally, `voro-{task_id}` — one stray name shared by all of them.
Underneath sat three places that each computed a launch's identity ad hoc and
each replaced a different set of placeholders.

A `Launch` value in voro-core now names a dispatch, a refine or a planning
session once, and its session name, prompt/log file slug and launch-log label
all come from it. Templates gain `{session_name}`, which the built-in claude
verbs use in place of spelling `voro-{task_id}` themselves; a dispatch is still
`voro-<id>`, a refine is `voro-<id>-refine`, a planning session
`voro-plan-<project>`. `{session_name}` is honoured on dispatch and plan and
refused elsewhere, the rule `{model}` already followed, and `{task_id}` is now
refused on plan, whose target may be a project with no task. No verb was added:
an agent defining only `dispatch` refines exactly as before.

`ResolvedAgent::launch_command`/`plan_launch_command` return a fully rendered
command line, so the TUI crate no longer calls `.replace` on one, and every
template — commands, dispatch preamble, planning and refine prompts — goes
through a single-pass renderer that emits each bound value verbatim. Chained
replaces could not: a task body, branch name, document title or project name
discussing `{task_id}` or `{db}` was rewritten before the agent read it, which
is what corrupted this task's own body.

Verified with `cargo test --workspace` and `cargo clippy --workspace
--all-targets -- -D warnings`, plus an end-to-end run against a scratch
database: refining then dispatching one task launches `voro-1-refine` and
`voro-1`, with the placeholder-laden body and note reaching the agent
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxRm8eUG7HDnuZrJAs7niu
@MJohnson459
MJohnson459 merged commit 642a7b5 into main Aug 1, 2026
6 checks passed
@MJohnson459
MJohnson459 deleted the launch-identity branch August 1, 2026 20:05
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