infiquetra/agent-launcher
Create one verified coding-agent session through the agents wrapper and Herdr. Orchestrate consumes this shared launch surface.
Changelog
[1.7.3] - 2026-10-14
The roles library moves to infiquetra-sdlc revision f8d0994 (X1a: the lifecycle records saga's
new review model). The snapshot gains the targeted_reviewer role, implementation-result gains
the builder_record field, and the four review findings descriptions point at saga's finding
record. The plan-reviewer, issue-reviewer, and planner prompts follow the new finding format; the
three implementer prompts name builder_record; and a minimal targeted-reviewer.md wrapper lands
for C8 to extend.
Added
- Issue #162: the implementer, standard-repair implementer, and expert-repair implementer prompts
tell the builder to read
plugins/saga/references/question-banks/policy-questions.jsonand store one declaration per question withbuilder_record.py writebefore the build loop. - The targeted reviewer, started headless.
launcher.py reviewstarts saga's targeted reviewer on its staffed vendor with that vendor's normal configuration (instruction files, plugins, hooks and the subscription sign-in), in a fresh export of the head commit with no.git. For Claude it runsclaude -pwith a per-launch--settingsfile that turns on the command sandbox and fails closed: writes outside the scratch copy, every temp root, the network and credential files are denied, and credential-named environment variables are hidden from sandboxed commands. It removes API-key variables, so the session never uses an API key, and returns the answer plus a result with the session's usage, the vendor and its version, the requested and resolved model, the prompt and schema fingerprints, a fingerprint of the instruction files and enabled plugins, and the scratch copy's changes. The reviewed change is untrusted, so the copy is written from git's object store (its.gitattributescannot shape it) and withholds every.claude/directory and.mcp.json(Claude would run their hooks and servers outside the sandbox) in any letter case, symlinks leaving the copy, and instruction files with an@import that is not a plain relative path; a head whose paths differ only by case is refused, and no export write passes through a symlink; the result lists what was withheld. The launch refuses when user or managed settings would widen the sandbox, lists any symlink the session makes apart, which saga's check refuses, and counts a file the session writes under.claude/as a change. Every other vendor is refused by name until it has a sandbox recipe that passeslauncher.py reviewer-probe, which runs the recipe live and checks each denial from outside the session; a probe whosecurlcould not run fails rather than passing as a network denial. roles/targeted-reviewer.md, a thin wrapper naming saga's prompt. The roster plans it one seat, never one per lens, and leaves it tolauncher.py reviewrather than opening a pane.- Candidate reviewer recipes for Grok and Muse, admitted only on a live probe (issue #223).
REVIEWER_CANDIDATESholds recipes thatreviewer-proberuns andreviewnever does. Each recipe was built from that CLI's own help: Grok runs headless under a per-launchstrict-based profile, and Muse runsmuse execwith its sandbox on and--sandbox-network restricted. Each removes its own API-key variables (XAI_API_KEY,GROK_DEPLOYMENT_KEY;META_API_KEY) and withholds its own project files. A candidate reviews only whenREVIEWER_ADMISSIONSholds a complete probe record for its platform and executable. The launcher runs the vendor's native binary, never a wrapper script. Its bytes are hashed and compared with the record before anything runs it, then copied into the launch directory and checked again, and its version is read from that verified copy. Both candidates failed their probes on macOS on 2026-10-11 and stay refused; the journal records each failing check. reviewer-probechecks more, and can record what it saw (issue #223).--record <file>writes the vendor, platform, executable identity, sign-in path and every verdict, with no path in it. New checks cover the vendor's own state directory (read and write), its sign-in files, a secret exported by shell start-up files (every vendor, Claude included since issue #225), a parsed answer and a zero exit.--modelnow defaults to the recipe's probe model.
Changed
-
Presentation metadata (author, repository, license, and keywords) now lives in the portable
plugin.json. The Claude manifest keepsauthorand its paths, and no longer repeatsrepositoryorkeywords. -
The Muse reviewer candidate's probe model is now
muse-wonder-full-1.0, and its probe record was re-recorded live against it. The verdicts are unchanged: the same six checks fail, so Muse stays refused as a reviewer. -
A Claude reviewer launch requires
--max-budget-usd(issue #223).launcher.py reviewpasses the ceiling toclaude -pand records it asceiling_usdinresult.json. A Claude launch without one, or with an amount that is not a positive figure with at most two decimal places, is refused (exit 2) before anything is created. A vendor with no dollar ceiling refuses the flag. The probe's own Claude launch carriesREVIEWER_PROBE_CEILING_USD(1.00). Measured: Claude stops at the first turn boundary past the ceiling, so one turn can overshoot it. -
The probe's network check proves sandbox denial (issue #223). It now holds only when the same address was reachable from outside the session before and after it, and the session's curl failed in a way that means the connection never left (6, 7, or 56 with a refused proxy tunnel). A timeout, a certificate failure or an offline machine no longer counts as denial.
-
Claude's sandboxed commands no longer see variables exported by shell start-up files (issue #225). The shell around each command read its start-up files before the command ran, and the sandbox denies only names the launch environment had, so an export from a start-up file reached the code under review.
launcher.py reviewnow writes a per-launchCLAUDE_CODE_SHELL_PREFIXbeside the copy and names it in--settingsand the session environment. Inside the sandbox it runs each command with only the allow-listed variables, in a bash that reads no start-up file; hooks and MCP servers, which run outside the sandbox, keep their environment. It tells the two apart by reading a sentinel file the settings deny, so a failed read always filters. The sandbox's proxy variables no longer reach commands either; the network stays denied (curl now exits 6). A user, local or managed settings file that setsenv.CLAUDE_CODE_SHELL_PREFIXrefuses the launch, because managed settings would replace the filter. The Claude probe now makes the start-up check too, and the live probe holds all 19 checks.
Fixed
-
The targeted reviewer's sandbox reads only what the review needs (issue #189). Claude's command sandbox now blocks reads of the whole home directory and allows back only the scratch copy, the review packet, and the
bin,libandpyvenv.cfgof the toolchain installpython3ornodeon the session'sPATHresolves to under home (never the install whole). A path that would re-open home, a shared root such as~/.config, or a credential path is never allowed back; for the copy or packet the launch refuses. The result'ssandbox_readsnames each allowed path with its reason. Sandboxed commands receive only the variables onREVIEWER_ENVIRONMENT_ALLOWED, so a secret in an ordinarily named variable no longer passes, and git is pointed at/dev/nullfor its global configuration. The credential path list gains~/.git-credentials,~/.pypirc,~/.cargo/credentials*,~/.terraform.d,~/.vault-token,~/.config/hub,~/.local/share/keyringsand~/.npmrc.reviewer-probealso checks that a canary planted in the home directory is unreadable, that the packet is readable, that a real Python test and git run, and that an ordinary-named variable is hidden. -
A Muse launch no longer stops when Herdr names the session
maki. Muse Code is the agent Herdr detects asmaki, so the identity check now accepts either kind formuse(HERDR_KIND_ALIASES,accepted_herdr_kinds). Every other vendor still needs Herdr to report its own name. Found on 6 October 2026 when two Muse launches for issue #147 stopped with "herdr reports agent 'maki', requested 'muse'".
[1.7.2] - 2026-10-04
Re-pins the roles library to the infiquetra-sdlc revision that requires combined-branch functional testing, and makes roster writes lock-safe.
Changed
- The roles library is re-pinned to infiquetra-sdlc revision
e5a2be10(issue #97), the merge of infiquetra-sdlc#174, which requires functional testing of the combined branch before code review.roles/lifecycle-snapshot.jsonis regenerated from the lifecycle at that revision: theimplementation-resultcontract gains the required fieldcombined_branch_functional_run, and four field descriptions change. The prompts follow the lifecycle there. The three implementer prompts name the new field, and the Initial Implementation Worker's stop rule drops the per-unit branch preview for the combined-branch run. The Delivery Manager records the repository's declared functional-test environment or its waiver. The Review Controller checks the run before a cycle starts. The Planner and the Plan Reviewer carry the run-level waiver rule.test_changelog_names_the_pinnow reads the[Unreleased]entry when there is one, because a pin move lands there before the release that bumps the version. roster.pywrites the run record'srosterthrough saga'srun_record.update(issue 95): it takes the record's lock, re-reads the record and replaces onlyrosteron that fresh copy, so a unit row or usage entry written since the roster was read survives. With a saga older than issue 95, which has noupdate, it falls back to the old unlocked write.roster.STAFFING_ROLE_TO_ROLE_IDis now read at run time by saga'srole_agent_types.py(issue 106); its comment says so. No behaviour changed.
[1.7.1] - 2026-09-22
1.7.1 — imported from infiquetra-claude-plugins@acc99fe71e25ad8a898eace7033051a7f1c3079e (upstream 1.7.0); authored here from this commit; no provenance manifest from now on.
The roles library is unchanged and still written against infiquetra-sdlc revision 5efc869f.
Changed
- Removed the Claude plugin-cache fallback (
~/.claude/plugins/cache/*/agent-launcher/) fromSKILL.mdand this README. That ladder resolved a Claude install instead of this package. Claude still reaches the scripts through$CLAUDE_PLUGIN_ROOT/skills/..., because skills stay at the package root. Every other harness runspython3 skills/agent-launcher/scripts/launcher.pyfrom the package root. - Removed the personal keychain service name and the home-lab Ansible pointer from the skill. Credentials are named only as environment variables.
- The three manifests agree on 1.7.1. The patch bump marks the authored cut of upstream 1.7.0.
- The slug example in
claude_project_slugand the statusline example inpane_account_labeluse inert names. Account-label behaviour is unchanged: the label is read from the statusline beside whateverUSERthe process has.
Dropped tests
These came from upstream and their premise is the upstream repository (the Orchestrate plugin beside this one, that repository's journal, or its marketplace), so they are not carried:
test_orchestrate_has_no_private_launcher_copytest_degraded_path_binds_settle_and_review_namestest_orchestrate_subprocess_cli_is_not_the_launcher_clitest_orchestrate_ingests_this_scripttest_orchestrate_sorts_launcher_cache_versions_numericallytest_orchestrate_rejects_a_launcher_below_its_declared_floortest_orchestrate_declares_agent_launcher_dependency_and_breaking_versiontest_shipped_launcher_defines_every_name_orchestrate_requirestest_skill_bound_name_list_matches_required_launcher_namestest_journal_43_pane_citations_are_named_not_reproducible- The Orchestrate half of
test_every_pane_write_goes_through_the_one_writerandtest_forcing_the_guard_off_at_each_write_site_is_observed(the launcher half stays) - The journal and Orchestrate assertions inside
test_release_and_journal_record_the_composer_contract(the changelog, skill, andcomposer.pyassertions stay) - From upstream
tests/test_agent_launcher_plugin.py, every test whose subject is Orchestrate's install, ingest, floor, or marketplace registration. Kept, rewritten underplugins/agent-launcher/tests/test_plugin_surface.py:test_agent_launcher_packaged_files,test_standalone_launcher_missing_composer_is_a_named_stop,test_standalone_launcher_broken_composer_is_a_named_stop. Dropped by name:test_agent_launcher_metadata_is_marketplace_registered,test_installed_layout_names_cache_dirs_from_the_manifest,test_orchestrate_declares_agent_launcher_dependency_in_metadata,test_orchestrate_skill_matches_the_deferred_floor_failure_contract,test_orchestrate_readme_and_command_agree_the_launcher_floor_is_enforced,test_orchestrate_skill_states_the_staged_input_recovery_runbook,test_installed_orchestrate_roster_fails_fast_without_launcher,test_installed_orchestrate_expand_and_go_fail_before_worktree_creation,test_installed_orchestrate_with_discoverable_launcher_passes_preflight,test_read_only_help_and_roster_survive_a_stale_launcher_while_go_enforces_the_floor,test_a_launcher_root_that_lacks_the_bound_names_is_the_named_companion_fault,test_missing_composer_is_deferred_and_reported_without_a_traceback,test_ingested_launcher_broken_composer_uses_the_same_named_contract,test_internal_launcher_failure_uses_the_same_deferred_named_contract,test_installed_cache_discovery_selects_and_validates_the_highest_numeric_version,test_the_companion_floor_matrix,test_plugin_root_discovery_selects_the_highest_numeric_version,test_bad_agent_launcher_root_override_exits_with_the_named_message,test_malformed_floor_requirements_exit_with_the_named_message,test_each_companion_fault_names_its_own_cause_and_remedy,test_a_launcher_that_fails_mid_file_binds_nothing,test_installed_orchestrate_help_describes_orchestrate,test_the_degraded_stub_roster_covers_every_referenced_launcher_name,test_the_four_shared_constants_agree_between_orchestrate_and_the_launcher,test_every_sibling_plugin_path_goes_through_the_layout_helper,test_a_name_only_stub_is_not_a_usable_companion,test_a_launcher_root_that_lacks_a_read_path_bound_name_degrades_status,test_check_does_not_agree_when_liveness_was_not_performed,test_highest_cache_version_that_dropped_a_bound_name_is_not_a_write_companion,test_orchestrate_install_sentence_names_the_agent_launcher_floor,test_write_gate_comments_name_the_live_write_path,test_live_docs_do_not_present_deleted_say_or_used_pane_as_current.
tests/test_roster.py is carried. A test that needs saga's run_record.py or Fleet Core's staffing.json skips when that file is not in this checkout, because those packages are imported on other branches. It does not fail for their absence.
[1.7.0] - 2026-09-19
Bumped from 1.6.0, the agent-launcher version on the integration branch origin/parent/1018
when this card branched; saga's 0.164.0 entry names that same head by commit. The roles library this
helper briefs its sessions from is unchanged and still written against infiquetra-sdlc revision
5efc869f.
Added
skills/agent-launcher/scripts/roster.py: the roster helper (issue #1024).upreads an issue's run record, resolves every role its staffing plan names to a vendor, model, effort and prompt from the roles library, and creates one named herdr pane per role throughlauncher.py, so every pane carries an ownership receipt.waitwaits for those panes under a caller timeout.downcloses exactly the panes the run record says this helper created, and nothing else.- A
roster_entry.v1row per created pane in the run record'srosterarray, carrying the role, the pane, tab and workspace identifiers, and the absolute path of the launch receipt that proves ownership. The receipts live beside the run record, outside every worktree, so removing a unit's worktree cannot strand a pane no one can prove the right to close. --account company|personalonup, because the staffing plan carries tiers and an account is not one: without it the helper appends no account flag and the wrapper's own default applies, which is the personal account. A staffing row carrying its ownaccountoverrides the flag for that role. Found by running the helper against the live herdr server rather than by reading it.A whole roster from a run recordinSKILL.md: the helper's contract, its refusals, and the rule thatdownis the only teardown.
Guards
upis idempotent per role, so re-running it after an interruption repairs the roster rather than doubling it;downnever readsherdr agent listto decide what to close, skips any row whose ownership receipt is gone or whosecreated_byis not this helper, and refuses the pane it is itself running in; a blocked role is reported with its output tail, never answered; every wait carries a timeout and takes herdr's own settled-state default; every subcommand refuses outside a herdr pane with exit 4; and a staffing role the roles library has no prompt for —merging-workeris the one that exists today — is a named refusal rather than an invented briefing.
[1.6.0] - 2026-09-19
Added
- A roles library at
roles/, one reusable prompt per lifecycle role (#1022). Fourteen prompts plus a README stating the contract each follows: the role and its authority boundary, the inputs it reads from the run record, the handoff contract it posts, and its stop rule. Written in the vocabulary ofinfiquetra-sdlcat revision5efc869f, read at that pin withgit showso a checkout on a moving default branch still serves the pinned documents. - Fourteen is the lifecycle's fifteen roles minus the Human Operator, who is a person rather than a session. The two historical role identifiers are carried in frontmatter rather than in filenames: the Architect's identifier is
orchestratorand the Delivery Manager's iscontroller. roles/lens-reviewer.mdcarries a shared reviewer half plus one section per lens, keyed to the fifteen identifiers in the lifecycle's lens catalogue. It states no threshold of its own; the catalogue owns the strictness ladder.tests/test_roles_library.py, which reads the expected role set from the README's map and the lens, contract and role identifiers from the sibling lifecycle checkout when one resolves.
Notes
- Nothing here spawns, orders, gates or aggregates a role; the roster helper and the run chain consume these prompts later.
- The
team-executionplugin's 25 agent prompts and two criteria documents were the source material and are unchanged by this release. The README accounts for where each one's substance went.
[1.5.2] - 2026-09-16
Changed
- SKILL.md's bound-name list names
AccountMismatchErrorand matches Orchestrate'sREQUIRED_LAUNCHER_NAMES, includingComposerState(#1003). Removing or renaming one remains a major change that moves Orchestrate's declared floor.
[1.5.1] - 2026-09-16
Changed
- A blank-separated empty marker under a staged draft is a decoy, not an empty live box (#961).
inspect_composerwalks trailing empty markers and returns the earlier staged inspection when every row between them is blank or another empty decoy. A content row between an echoed prompt and a last empty box still reads empty (CORR-05). - The structural pane-write detector reports the enumerated evasion shapes (#972).
_raw_door_callsfollows argv through lists, tuples, concatenation, assigned constants, f-strings, aliases, keywordargs=, andsubprocess.run, plus._raw/._typeandw.write. The snippet test drives that helper with no special-case returns.
[1.5.0] - 2026-09-16
Changed
- Composer classification no longer treats a quoted Claude continuation as empty, and an adjacent painted empty marker cannot authorize a write (#1002). A still-empty Claude box whose next row begins with another vendor glyph is that draft. An immediately adjacent empty marker under a staged block returns the staged inspection. A blank or content row between blocks still lets last-block-wins keep an empty live box empty.
doneis a started session.redeliverwill not send into one (#1002).NEVER_STARTED_STATUSESis(None, "idle", "unknown"). A receipt that already delivered, or that is missingunit_name,pane,tab_id,owned, oragent_name, is refused at exit 2.PaneWriter.writeowns both Herdr doors as nested functions;_rawand_typeare gone (#1002). A failed or timed-out composer read refuses the write. The structural test is a net for enumerated AST shapes, not a proof of impossibility.- OpenCode picker options are the effort ladder; the typed-token echo is not session confirmation (#1002). Notes and stops no longer interpolate scraped tokens.
picker_menu_onlydoes not write a verified note. herdr workspace listis bounded, account labels are read from the visible tail, inspect windows have both a row cap and a 131072-byte cap, and task-file writes refuse symlink escape (#1002). Empty-box receipts drop a staleinput_box_text_chars.
[1.4.0] - 2026-09-02
Changed
- Every line that enters a session goes through one door,
PaneWriter.write, and that door owns the inspection (#907). Both raw Herdr write calls exist only inside the class; the OpenCode picker's two keystrokes, each setup slash command, the task, each resend, and every prompt Orchestrate sends later are calls on the same writer, which inspects before each write from its own record of having written. An uninspected write can no longer be introduced by forgetting a flag.say()is removed. On an owned OpenCode launch the picker opening is the one exempt first write; the variant selection and the task are inspected. redeliverretries an undelivered prompt as well as a staged-input stop, refuses by exit code, and reads the session's status in the same vocabulary as delivery (#907). A receipt withprompt_delivered: falseis retryable. Refusals -- an empty--prompt, another task's receipt, neither retryable marker, no pane -- exit 2 before any Herdr call; a retry whose prompt was not observed to be taken exits 1; success exits 0. A session reportingdone,unknownor no status has not started and is retried; only a visibly started one is refused. The deadpane_idalias is gone; the README documents the receipt's key set.- The composer parser is handed at most
PANE_INSPECT_MAX_LINESrows, never a byte cut (#907). A byte cut could land inside the marker row and turn a staged draft intonot_found. - The picker refusal reports the count of options, not the scraped tokens (#907).
- The OpenCode variant confirmation records where the token was seen (#907).
variant_confirmed_fromissessionorpicker_menu_only; the preflight confirms the variant only in the first case. - The pane-typing door's timeout names the ambiguity like the prompt door (#907).
- The delivery warning names the recovery (#907).
[1.3.0] - 2026-09-02
Added
redeliversubcommand (#907). The standalone retry for a staged-input stop. It takes the tab, pane and ownership from the receipt the stop wrote and the task from the same flagslaunchtakes; it refuses a receipt written for another task, one that does not record a staged-input stop, or one with no pane; it never runs the wrapper create; and it exits nonzero when the prompt was not observed to be taken. Before, the only visible recovery was a secondlaunch, which created a second session over the first owned tab.
Changed
- Every pane write after the first is inspected, whatever the ownership (#907). The write
half of the guard predicate now records that the launcher wrote into the session, not which
door carried the line. Before, a successful
herdr agent promptleft it false, so an owned session's two resends -- and every write of a redelivery -- went out with no composer inspection. The rule has one owner,should_guard_pane_write, which Orchestrate's later senders call too. The 1.2.2 line below describing the resend rule as "unowned or the launcher already typed into it" described the defective predicate. In this release the OpenCode picker's two writes sat outside the rule and never set the write flag, so an owned OpenCode launch still made three uninspected writes; 1.4.0 closes that by construction. redeliver()refuses a session that has left idle (#907). It inherits the resend loop's own precondition: a working, blocked or gone session may already hold the task, so the retry route closes asprompt_undeliveredfor the operator to check instead of risking a second delivery.- Both pane writes carry a timeout,
PANE_WRITE_SECONDS(#907). They were the only Herdr calls with no bound. A prompt that times out is a named stop and never falls through to the pane door. - The composer parser is linear on unterminated OSC sequences, and is handed at most
PANE_INSPECT_MAX_CHARScharacters from the tail of the pane (#907). say()andsend()return nothing;pane_input_text()is removed (#907). No caller was left that could use either safely.
[1.2.2] - 2026-09-02
Fixed
-
A broken composer parser is one named stop in both entry modes (#907). A parser file that raises on import stops
launchwith the exception type and message, standalone and ingested by Orchestrate, instead of escaping as a traceback. -
A failed tab close records its note once (#907).
close_run_sessionis the single writer of the close-failure note and tests membership on the whole note, so repeated failures no longer stack copies. -
The composer row rule is one classification per physical row (#907). A row continues an open block when it is bordered or when it is unbordered and indented past the marker column; a blank, a horizontal rule, a marker, or a row at or left of the marker ends the block.
-
A resend inspects when the pane is unowned or the launcher already typed into it (#907). The inspection sits immediately before the write it authorises.
-
Adjacent glyph-led rows stay
unclassifiablewhen the viewport cannot prove a new box (#907). -
input_box_text_charsis the visible length of the absorbed draft (#907). One definition, recorded only when the box is staged.
[1.2.1] - 2026-08-31
Fixed
- Composer geometry no longer turns pane chrome into a draft or an ambiguous draft into empty
(#907). Paired borders are structural, blank and merely indented rows no longer become input
continuations, and adjacent glyph-led rows produce
unclassifiablewhen the viewport cannot prove whether they are a new box. Marker detection remains anchored at the row prefix. - Every unowned-pane resend performs a fresh input inspection (#907). A successful agent-prompt send no longer lets a later pane fallback bypass the staged-input guard.
- The composer loader owns its source location and produces a named stop (#907). Standalone and
Orchestrate-ingested launchers resolve
composer.pyfrom the compiled launcher path without a caller-injected global; missing or unreadable parser files no longer escape as tracebacks. - The serialized input-box receipt is now documented as a complete contract (#907). The skill
and README enumerate every
input_boxvalue and the conditional redactedinput_box_text_charsfield.
[1.2.0] - 2026-08-30
Fixed
- The unowned-pane guard no longer mistakes scrollback for the live composer (#907). Composer
parsing now lives in a bounded terminal parser, selects the last block positionally, terminates
it at the first non-continuation row, understands bordered composers and per-attribute terminal
resets, and covers the complete vendor roster. Claude, Codex, Grok, Agy, and Qwen have verified
glyphs; Muse and OpenCode are explicit unsupported cases. Receipts distinguish
unclassifiable,not_found,unsupported_vendor,read_failed, andread_timeout; an unambiguous staged draft still stops with onlyinput_box_text_chars, never the text. - Launch and teardown failures retain recoverable session identity (#907). A create timeout reconciles the target workspace's tab set into a minimal receipt, a genuine wrapper exit 124 is no longer confused with a synthesized timeout, and an already-absent owned tab is an idempotent successful close. Pane reads and close calls are bounded.
- Receipt evidence now says exactly which checks ran (#907). One receipt shape is completed in place, empty permission-token lists no longer claim argv confirmation, malformed receipt files produce a named recovery stop, and transcript files removed during preflight are skipped.
[1.1.0] - 2026-08-30
Fixed
- A hanging session create is a named stop, not a blocked launch (#890).
launchruns the wrapper under an explicit deadline (LAUNCH_CREATE_SECONDS, 120 seconds — larger than every other deadline because it may reach another machine and cold-start a vendor CLI) and stops with a named message when the create times out or exits nonzero, instead of blocking without bound. - A pane this launch did not create is inspected before it is prompted (#897). A session
whose tab already existed may hold text somebody staged in its input box, and a prompt typed
behind it can submit that text. The guard reads the box with styling intact and stops on
unambiguous staged text, recorded only by character count and never cleared. Fully styled text
can be byte-indistinguishable from a placeholder; version 1.2.0 records that case separately
instead of claiming a distinction. The guard keys on tab ownership, not the wrapper's
reusedbit, which names the workspace and would inspect the ordinary create path. - A declared permission is honoured and confirmed, never silently downgraded (#896). A
permission value outside the vendor's map stops with a named message instead of falling back to
the auto flags, and preflight confirms the declared posture against the launch argv, recording
permission_resolvedin the receipt distinctly from the requested value. - A stale transcript can no longer certify a launch's account (#889).
launchcaptures the create instant and passes it as a recency floor, so a statusline-silent session is confirmed only from a transcript written at or after that instant (one second of mtime slack absorbs filesystem granularity). The receipt recordsaccount_evidence— statusline, transcript, or none — beside the requested account. - A failing owned close is surfaced instead of swallowed (#888).
close_run_sessionreturns the Herdr result and records a failing close (returncode and error text) on the unit note;close_owned_session, the CLI-facing variant, exits nonzero on the same failure. Unowned sessions still close nothing and report nothing. - A missing receipt path stops with a cause and a recovery, not a traceback (#887).
close --receipt-jsonon an argument that is neither an existing file nor inline JSON raises aSystemExitnaming the path and the remedy. Inline JSON — object or array — keeps its existing behaviour, including thereceipt must be a JSON objectstop. - The skill stops overselling
--dry-run(#880). The guidance now names what dry-run confirms (resolved working directory, Herdr workspace, flag ordering, exact command) and what it does not validate (model, reasoning effort, account), and documents the real preflight — a bounded live launch with a read-back — under an ordering rule with secret-safety safeguards.
[1.0.0] - 2026-08-25
Added
- Portable single-session launch contract (#777). New plugin owns create-via-
agents, verify-via-Herdr, prompt delivery, and owned cleanup. An ordinary session can launch one verified agent without starting an Orchestrate run. Orchestrate consumes the same module (skills/agent-launcher/scripts/launcher.py) and no longer keeps a private copy of the launcher seam. Explicit dependency on the canonicalherdrskill for every interaction after the session exists; this plugin does not duplicate it. The Agent Plugins port is tracked at infiquetra-agent-plugins#22 and does not gate this release.