v0.99.20
Two months of work between v0.99.17 (2026-05-02) and the move to bot-owned release
pull requests. Entries are condensed by theme; per-change detail is in the linked
issues and in git log v0.99.17..v0.99.20.
Added
- Multi-provider execution: a built-in
codexprovider alongside Claude, per-step provider routing (llm_provideras a scalar or an ordered array, with per-step overrides), and provider-aware model, effort and retry-escalation defaults (#902, #927, #1089). - tmux-supervised daemons with a full operator surface:
conduct-ts daemon start|stop|restart|connect|debug|status|logs, pause/resume, park/unpark, a startup state dashboard, an install-freshness guard, and auto-restart when the running engine goes stale (#215, #307, #486). - Rate-limit episode coordination: session- and usage-limit signals are classified as rate limiting beyond HTTP 429, provider reset times are parsed into absolute deadlines, and workers share one abortable, jittered wait instead of each backing off independently. New feature dispatch pauses while an episode is active and halted features are re-kicked when it clears.
- Retry-as-escalation: a step retry now escalates effort and then model tier rather than repeating an identical attempt, deep-step retry budgets drop from 5 to 3, and unavailable models fall back down a ladder instead of halting (#186, #188).
wiring_check— a gating step betweenbuild_reviewandmanual_testthat verifies every production surface the feature declares is actually reachable from a production entry point.build_review— a fresh-session judged gate at the build seam whose completeness rubric is the authoritative build-completion signal, with deterministic BUILD/REMEDIATE routing on FAIL (#757, #817, #984).- Per-feature cost, token and wall-clock accounting: shipped records carry
## Costand## Timeblocks and anengine_versionstamp,conduct-ts kpirenderscost=/time=/engine=per feature, and an opt-in OpenTelemetry exporter publishes conductor run traces (#537, #1090, #1196). - GitHub-issues intake for the engineer, with bidirectional write-back, issue-to-PR linkage and auto-close on merge, required priority/size/dependency labels at every capture surface, and a host-wide
conduct-ts brain start|stop|statusintake loop with desktop notifications (#490, #695). - Owner gating: a daemon builds only the merged specs it owns, resolving a machine-scoped operator identity and failing closed when no identity resolves, with write-back onto the gated spec's PR and originating issue (#184).
- Engineer worktree isolation and full DECIDE authoring — each idea is authored, landed and handed off inside its own worktree, running the complete explore → complexity → prd → architecture → stories → conflict-check → plan set (#142).
- Self-host guardrails for the harness building itself: a live-checkout boundary guard, a write-fence sandbox, a daemon-owned build-auth token separate from operator OAuth, a version-freeze approval gate, and a release gate that accepts a committed waiver (#174, #261, #354, #380).
- SHIP automation: the implementation PR is opened as a draft at the start of SHIP, halt PRs are rehabilitated at finish, irrecoverable halts leave a labeled
needs-remediationdraft PR, a mergeable sweep auto-resolves conflicts, andci_watchdrives bounded automatic fixes for red ships (#271, #274, #439, #499). - Parallel SHIP validation:
manual_test,prd_auditandarchitecture_review_as_builtfan out as a concurrent group in auto-mode runs, bounded by a newvalidation_concurrencyconfig key (#469). - Autonomous gap remediation — a blocking
prd_audit, as-built review or finish gate is routed through/remediateand repaired in place instead of halting for a human (#115). - New skills:
verify-claims(a cross-cutting correctness and assumption gate),intake,daemon-triage,rebaseandcoherence-check, plus anoperator_onlySKILL.md frontmatter field for skills invoked from outside a run. - Mermaid rendering at the architecture approval gates, and
conduct render-diagrams --checkas an authoring-time syntax gate that fails on a broken diagram (#810). - The
docs/tree is published as a browsable GitHub Pages site with a landing page and site-wide navigation (#1224). - CI runs the harness integrity suite, the conductor build and vitest suite,
tsc --noEmit, ESLint and ShellCheck on every pull request; documentation-only PRs skip the heavy jobs (#789, #802, #1040). - New
conduct-tssurface:kpi,finish-record,overlap-scan,build-auth-status,halt-issues sweep,evidence,memory setup, an--effort <level>global override mirroring--model, and--interactivefor conversational steps (#1027). bin/update— a standalone self-update and channel CLI, invoked automatically as a one-shot subprocess atconduct-tsstartup.- Deterministic project-config scaffolding for new and existing repositories, auto-discovered skill linking into both
~/.claude/skills/and~/.agents/skills/, and abin/install --allow-worktree-rootoverride for the worktree-root guard (#1169).
Changed
- The CLI is verb-first: the inline pipeline is
conduct-ts inline "<feature>"and the daemon isconduct-ts daemon …, replacing the bare-argument and--daemonforms. - Every step dispatch starts a fresh provider session, including within-step retries; Codex declares no session-resume capability, so each Codex dispatch is a cold start carrying its context through committed artifacts and the retry prompt (#325, #1110).
- Model selection is right-sized across the funnel and generated into HARNESS.md from
model-table-metadata.tsrather than hand-edited, with front-of-funnel DECIDE steps and recovery steps defaulting to Fable (#190). manual_testis a gating step whose enforcement is locked, with an S-tier SKIP sentinel that satisfies downstream prerequisites; judged gate verdicts are re-validated against current code state rather than accepted on record (#367, #817).- Post-rebase gate re-verification is delta-aware:
build_review,wiring_check,manual_test,prd_auditandarchitecture_review_as_builtverdicts are preserved rather than re-run when the rebase did not affect them (#655). - Daemon scheduling honors GitHub issue dependencies and priority labels, including a new
criticalband abovehigh; the root checkout fast-forwards on each idle poll and feature worktrees are cut fromorigin/<default>. - Daemon output is timestamped, colorized and transition-only, attributes each completed dispatch to the provider and model that ran it, and keeps routine skip notices behind
daemon_verbose: true. .memory/is a symlink to a shared canonical store rather than a tracked in-project directory; ADRs are namedadr-YYYY-MM-DD-<kebab-slug>.mdinstead of sequentially numbered; run-specific SHIP artifacts moved from tracked.docs/to gitignored.pipeline/.- Configuration resolution deep-merges project
.ai-conductor/config.ymlover~/.ai-conductor/config.ymlat every entry point, so user-level values survive keys a project omits (#1031, #1199). - README reference material moved into
docs/, leaving a landing page that points at the documentation tree.
Fixed
- Daemon reliability across the run lifecycle: halts carry a machine-readable
needs-human/mechanicalclass so the re-kick sweep cannot wipe one needing a human, a step-heartbeat watchdog detects stalls, a no-verdict backstop records where the run stopped, park markers anchor to the main repository root, park/unpark resolve that root from any cwd, restart uses a single-generation handoff, and clearing aHALTmarker re-dispatches the feature immediately (#302, #353, #374, #400, #486, #1070, #1148). - Build-completion accuracy: the gate resolves the plan scoped to the current feature when several are in flight, accepts the full task-id grammar, sources expected paths from each task's
**Files:**line, writestask-status.jsonatomically, and recovers finished work after a worktree is removed and recreated (#407, #417, #424, #425, #497, #1088, #1102). - Rebase handling: conflicts route through a bounded, gated
/rebaseresolution loop, the rebase step uses--autostashso a dirty tree no longer mis-parks as a conflict, protected-artifact seals rebaseline safely when the base branch changes another feature's DECIDE artifact, and engine-owned rebases translate sha-anchored evidence citations instead of orphaning them (#300, #976, #1121). - Self-host builds no longer halt on the harness's own bookkeeping: the live-boundary guard skips
.git/,.daemon/,.pipeline/,node_modulescaches and provider-state caches, tolerates concurrent operator edits to tracked files, and names the differing paths when it does halt (#985, #1115, #1158). - SHIP correctness: a shipped marker requires verified PR evidence, ship-readiness fails while the recorded PR is still a draft, discovery dedups on a shipped record committed to the feature's own branch, parked-feature reconciliation completes cleanup for squash-merged features, and a feature's worktree is retained until its ship is recorded (#337, #439, #1146, #1157, #1185).
- Test-suite hygiene: the suite no longer leaks real
cc-daemon-*tmux sessions, temp directories,.pipeline/artifacts, engineer-signal writes or undrained child processes into the operator's environment, and is deterministic under forked workers (#252, #257, #377, #437, #573, #861). bin/installnever hard-fails on a missing optional dependency, refuses global-mutating modes from a worktree-rooted checkout, builds theconduct-tsbundle itself, discovers skills instead of using a hardcoded list, and exits non-zero on drift under--check(#363).- CLI correctness:
--helprenders the full recursive command reference,daemon logs --followactually tails and--lines Nis parsed,--from <step>validates against the resolved step registry, unknownbin/conductsubcommands fail loudly instead of launching the pipeline, and label mutations survive GitHub's Projects-classic sunset (#178, #1027).
Removed
- RTK ("Rust Token Killer") from the install path.
- Serena semantic-search integration from the install path, bootstrap MCP registration,
HARNESS.mdguidance and the gitignore skeleton (#682, #728, #753). - The
--daemonflag, replaced by theconduct-ts daemonsubcommand, and the--output/--step <step>flags, neither of which ever affected run behavior (#1013). - The per-task evidence gate and the semantic attribution judge lane, including the
attribution_enforcement_cutoverandattribution_judge_cutoverconfig keys — per-task commit attribution is telemetry only (#773, #1211). check_harness_config(the consumer CLAUDE.md to HARNESS.md auto-upgrade) frombin/conduct; detection is retained by the session-start context hook (#226).
Migration
for f in .ai-conductor/config.yml ~/.ai-conductor/config.yml; do
[ -f "$f" ] || continue
if grep -Eq '^(attribution_enforcement_cutover|attribution_judge_cutover):' "$f"; then
sed -i.bak -E '/^(attribution_enforcement_cutover|attribution_judge_cutover):/d' "$f"
echo "Removed retired attribution cutover keys from $f (backup: $f.bak)."
else
echo "$f has no retired attribution cutover keys — nothing to do."
fi
done# Reconcile the installed harness catalogs; foreign links and files are preserved.
"${HARNESS_DIR:?HARNESS_DIR must be set by bin/migrate}/bin/install" --update
# Find shell scripts/aliases/CI configs in the current directory tree that still pass the
# removed --output or --step flags to conduct-ts, so they can be edited by hand (nothing here
# is auto-rewritten — these are external, not tracked in this repo).
grep -rEn '\bconduct-ts\b.*(--output\b|--step\b)' \
--include='*.sh' --include='*.yml' --include='*.yaml' . 2>/dev/null \
|| echo "No conduct-ts invocations with --output/--step found."# Install the user-scoped HARNESS.md links consumed by generated CLAUDE.md and AGENTS.md.
"${HARNESS_DIR:?HARNESS_DIR must be set by bin/migrate}/bin/install" --update# Unregister the harness-registered user-scope Serena MCP server (no-op if absent)
if command -v claude >/dev/null 2>&1 && claude mcp get serena >/dev/null 2>&1; then
claude mcp remove --scope user serena
echo "Removed user-scope 'serena' MCP registration."
else
echo "No user-scope 'serena' MCP registration found — nothing to do."
fi
echo "Optional manual cleanup (NOT run automatically):"
echo " uv tool uninstall serena-agent # if you don't use Serena elsewhere"
echo " pkill -f 'serena start-mcp-server' # stop stray servers from old sessions"
echo " rm -rf <project>/.serena/ # per-project semantic-index caches"# Re-run the harness installer's settings merge so the new docs-guard.sh
# PreToolUse hook (Edit|Write|NotebookEdit matcher) is added to
# ~/.claude/settings.json. Safe to re-run: bin/install's settings merge is
# idempotent (matches on hook command path, does not duplicate entries).
cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
"${HARNESS_DIR:?HARNESS_DIR must be set by bin/migrate}/bin/install" --update
echo "docs-guard.sh is now wired as a PreToolUse hook in ~/.claude/settings.json."
echo "Daemon-provisioned worktrees pick this up automatically on next"
echo "worktree-prepare handles newly provisioned worktrees automatically."
echo "If a running daemon needs new provisioning behavior, restart it yourself after this update."# To use the attribution cutover, add attribution_judge_cutover and optionally
# attribution_audit_sample_pct to .ai-conductor/config.yml. Restart a daemon
# or conductor yourself after editing if it needs to read the new configuration.
echo "Configure attribution_judge_cutover in .ai-conductor/config.yml when ready."# Link the new intake skill into ~/.claude/skills.
"${HARNESS_DIR:?HARNESS_DIR must be set by bin/migrate}/bin/install"# Only needed if your project config disables manual_test.
# Remove the `manual_test:` disabled block from .ai-conductor/config.yml, e.g.:
if [ -f .ai-conductor/config.yml ] && grep -qE 'manual_test:' .ai-conductor/config.yml; then
echo "manual_test step config found in .ai-conductor/config.yml —"
echo "if it sets 'disabled: true', delete that line (gating steps cannot be disabled)."
grep -n -A2 'manual_test:' .ai-conductor/config.yml
else
echo "No manual_test step config found — nothing to do."
fi# Set up daemon build-token if not already present
BUILD_AUTH_TOKEN_PATH="${HOME}/.ai-conductor/build-auth"
# Only mint if the file doesn't exist (no clobber of operator's existing token)
if [ ! -f "$BUILD_AUTH_TOKEN_PATH" ]; then
echo "Setting up daemon build-auth token…"
claude setup-token
chmod 600 "$BUILD_AUTH_TOKEN_PATH"
else
echo "Build-auth token already present at $BUILD_AUTH_TOKEN_PATH — no action needed."
fi
# Then configure the path in your harness config (.ai-conductor/config.yml):
echo "Configure in your harness config:"
echo " harness_self_host:"
echo " build_auth:"
echo " token_path: $BUILD_AUTH_TOKEN_PATH"# Existing worktrees keep their current hooks until they are re-provisioned.
# If you run a daemon, use your normal operator procedure to restart it after
# this update so future worktrees pick up the new provisioning code.
echo "Existing worktrees retain their hooks until re-provisioned."
echo "Enforcement stays OFF until attribution_enforcement_cutover is set in .ai-conductor/config.yml."# Daemon lifecycle remains operator-controlled. If this project has an old
# detached daemon, stop it with your normal operator procedure before adopting
# the tmux-hosted daemon management workflow.
echo "Review any running daemon and choose its lifecycle action yourself."# Rebuild the conductor engine (dist is no longer shipped in git).
cd "${HARNESS_DIR:?}/src/conductor" \
&& npm install --no-audit --no-fund \
&& npm run build# bin/conduct now rejects unknown commands instead of silently launching the
# SDLC pipeline, and forwards conduct-ts verbs to conduct-ts. Verify conduct-ts
# is installed so forwarding works:
if command -v conduct-ts >/dev/null 2>&1; then
echo "conduct-ts found: bin/conduct verb forwarding will work."
else
echo "WARNING: conduct-ts not on PATH — forwarded verbs will exit 127."
echo "Re-run bin/install to build and link conduct-ts."
fi
# Reminder: bare single-word feature descriptions are now rejected — quote
# multi-word descriptions instead, e.g.: conduct "add user auth"rm -f .claude/hooks/claude/post-commit-pipeline-sync.sh
# Install the new fast-feedback derive hook in your project's .git/hooks:
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo '.')"
if [ -d "$PROJECT_ROOT/.git" ] && [ -f "${HARNESS_DIR:?}/hooks/claude/post-commit-derive-feedback.sh" ]; then
cp "${HARNESS_DIR:?}/hooks/claude/post-commit-derive-feedback.sh" "$PROJECT_ROOT/.git/hooks/post-commit"
chmod +x "$PROJECT_ROOT/.git/hooks/post-commit"
echo "Installed fast-feedback post-commit hook"
fi# Ensure the daemon state dir is gitignored (pidfile + daemon.log live here).
if [ -f .gitignore ]; then
grep -qxF '.daemon/' .gitignore || printf '.daemon/\n' >> .gitignore
else
printf '.daemon/\n' > .gitignore
fi
echo "ensured .daemon/ is in .gitignore"# Migrate .memory/ to the canonical shared store under ~/.ai-conductor/memory/.
# No-op if .memory/ is already a symlink. Run from the project root.
_dir="$(pwd)"
_link="${_dir}/.memory"
if [ -L "${_link}" ]; then
echo ".memory/ is already a symlink — no migration needed."
elif [ -d "${_link}" ]; then
if command -v conduct-ts >/dev/null 2>&1; then
conduct-ts memory setup "${_dir}"
elif [ -x "${HARNESS_DIR:-}/bin/conduct-ts" ]; then
"${HARNESS_DIR}/bin/conduct-ts" memory setup "${_dir}"
else
echo "conduct-ts not found — please run 'conduct-ts memory setup ${_dir}' after updating." >&2
exit 1
fi
echo "Migration complete. .memory/ is now a symlink to the canonical store."
else
echo ".memory/ does not exist — it will be created automatically on next 'conduct' run."
fi# Opt in to the semantic attribution verification lane by adding the cutover
# key to .ai-conductor/config.yml. No-op / prints guidance if the file is
# missing or the key is already present.
CONFIG_FILE=".ai-conductor/config.yml"
if [ ! -f "$CONFIG_FILE" ]; then
echo "No $CONFIG_FILE found — nothing to migrate. Create one and add" \
"attribution_judge_cutover to opt in when ready."
elif grep -qF '# attribution_judge_cutover: "2026-07-11T08:30:00Z"' "$CONFIG_FILE" 2>/dev/null; then
echo "$CONFIG_FILE already contains the attribution configuration template — no migration needed."
else
cat <<'EOF' >> "$CONFIG_FILE"
# Semantic attribution judgment gate (opt-in; absent = disabled).
# Uncomment and set a past ISO-8601 instant to activate the judge lane for
# unresolved evidence-gate residue. Restart the daemon/conductor to apply.
# attribution_judge_cutover: "2026-07-11T08:30:00Z"
# attribution_audit_sample_pct: 10
EOF
echo "Appended commented-out attribution_judge_cutover / attribution_audit_sample_pct" \
"template to $CONFIG_FILE. Uncomment and set a cutover instant to opt in;" \
"restart your daemon after editing if it needs to reload configuration."
fi# Refresh an existing worktree's commit-msg hook to the post-#773 (advisory,
# non-rejecting) version. Safe to run repeatedly; writeGitHooks() always
# overwrites both hook scripts with the current generated content.
WORKTREE_ROOT="${1:-.}"
HOOKS_DIR="$WORKTREE_ROOT/.pipeline/git-hooks"
if [ ! -d "$WORKTREE_ROOT/.pipeline" ]; then
echo "No .pipeline/ found at $WORKTREE_ROOT — not a prepared conductor worktree;" \
"nothing to migrate."
elif [ -f "$HOOKS_DIR/commit-msg" ]; then
rm -f "$HOOKS_DIR/commit-msg" "$HOOKS_DIR/prepare-commit-msg"
echo "Removed stale hook scripts at $HOOKS_DIR. Re-run 'conduct' (or restart the" \
"daemon build step) for this worktree to have worktree-prepare regenerate" \
"them with the post-#773 advisory (non-rejecting) commit-msg hook."
else
echo "No commit-msg hook found at $HOOKS_DIR — nothing to migrate; it will be" \
"generated fresh (already advisory) the next time this worktree is prepared."
fi