Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
{
"name": "work-system",
"source": "./plugins/work-system",
"description": "Generic task and worktree workflow system for Claude Code. Manage tasks as markdown files, run them in isolated git worktrees with a choice of worker agent (Claude, codex, grok, or kimi), and track progress across the define/kickoff/continue/status/close lifecycle.",
"version": "1.11.0"
"description": "Generic task and worktree workflow system for Claude Code. Manage tasks as markdown files, run them in isolated git worktrees with a choice of worker agent (Claude, codex, grok, kimi, or a PATH-detected cc-harness agent), and track progress across the define/kickoff/continue/status/close lifecycle.",
"version": "1.12.0"
},
{
"name": "pr-flow",
Expand Down
2 changes: 1 addition & 1 deletion .claude/knowledge/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- `features/lane-registry.md` — `lanes.sh` + `herdr-agent.sh` (Wave 1): the one herdr-agent wrapper (degrade-not-block, bounded wait) + centralized `$HERDR_MATCH_PRELUDE` cwd↔worktree match (consumed by herdr-tab-glyph, regression-guarded via live snapshot); lanes.sh joins states+liveness keyed by worktree_path with a worktree-tab-state degrade tri-state; env test-seams for hermetic join tests
- `features/herdr-close-automation.md` — `/close` in herdr: cwd-tab teardown, plugin SessionEnd hook, the one TUI-exit primitive, detached self-exit onto idle
- `features/herdr-tab-glyphs.md` — Task-state glyphs (`○ ● ◇ ◆ ✓`) + main-root `◉` on herdr tab labels: `states` mode in the self-contained renderer, sync-vs-`--cached` PR refresh per caller, exact-cwd rename rules, soft pr-flow shim
- `features/kickoff-agent-selection.md` — `/kickoff` worker choice: single committed per-repo default (no global/fallback/ranking) else picker; `agent-registry.sh` as SoT; bounded model-aware grok/kimi probes (inconclusive→trust-auth); kimi's two-phase seed+continue argv + `argv_shell=`; non-claude "document, don't fake" degradation; announce-not-prompt for external defaults
- `features/kickoff-agent-selection.md` — `/kickoff` worker choice: single committed per-repo default (no global/fallback/ranking) else picker; `agent-registry.sh` as SoT; optional PATH-detected `cc-harness:<id>` class (pure consumer of `list`/`exec`, no gateway hardcoding); bounded model-aware grok/kimi probes (inconclusive→trust-auth); kimi's two-phase seed+continue argv + `argv_shell=`; non-claude "document, don't fake" degradation; announce-not-prompt for external defaults
- `features/task-archiving-on-close.md` — `/close` archives (not deletes) the task file; adaptive commit + ff-push to main; per-repo `.claude/work-system-close-autocommit` opt-in skips the ask
- `features/swarm-backend-adapter.md` — 0.6.0 read+web posture: OS secret-jail (denylist, worktree-aware, git-config-safe), per-voice fail-closed degrade, `jail` verb, prompt egress guard + residual risks; plus verified codex/grok CLI facts (schema JSON, effort mapping, model-aware readiness)
- `features/swarm-review-pipeline.md` — `/swarm:review` pipeline: skill↔Workflow wiring, family-consensus, 0.5.0 lens clusters + design-kind verify, `--fix`/`--loop` (deterministic close-out via `loop-closeout.py`), `--pr` publish via deterministic `pr-post.py`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ implementation is spawned across tasks `add-lane-registry`, `spike-agent-mail-su
- **Manager** = the Claude Code session at the main repo root (herdr `◉` tab). A
*coordinator*, not a merge robot — the human stays merge authority unless
explicitly delegated at kickoff.
- **Worker** = one {claude|codex|grok|kimi} session per worktree, driving its task to a
reviewed, mergeable PR.
- **Worker** = one {claude|codex|grok|kimi|cc-harness:&lt;id&gt;} session per worktree, driving
its task to a reviewed, mergeable PR. (A cc-harness worker is a claude session on a
foreign model, so it tiers like claude, not like the external CLIs.)
- **Lane** = `(worktree_path, task, branch)`. **Identity = worktree_path** — the one
key stable across agent types and restarts. herdr pane/tab, `agent_status`,
session UUID, PR state are live-attached attributes, **never identity**. Only the
Expand Down
7 changes: 5 additions & 2 deletions .claude/knowledge/features/herdr-kickoff-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ truth; this entry captures the durable design and one non-obvious gotcha.
`-m` form — `codex -m <model> "<bootstrap prompt>"` /
`grok -m <model> "<bootstrap prompt>"`, and kimi (1.11.0) a two-phase
`sh -c 'kimi -m "$1" -p "$2" || …; exec kimi -c --auto' …` — it has no positional
launch prompt (see [[kickoff-agent-selection]]). `emit_argv` is the SoT; never
reconstruct an argv from this list.
launch prompt (see [[kickoff-agent-selection]]). A cc-harness worker (1.12.0,
only when the optional PATH helper lists it) wraps the claude form:
`cc-harness-agents exec <id> -- claude [-n <session>] /work-system:continue` —
no `--model` (the helper env-sets it, then `exec`s, so the pane still roots at
claude). `emit_argv` is the SoT; never reconstruct an argv from this list.
herdr-launch stays CLI-agnostic — it just execs the resolved `argv=` words. The
`-- argv` form sidesteps the interactive shell entirely, so there is no keystroke
race against shell startup (see the gotcha below) and no readiness handshake to
Expand Down
91 changes: 80 additions & 11 deletions .claude/knowledge/features/kickoff-agent-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: "Kickoff Agent Selection: registry, per-repo default, honest degradation"
createdAt: 2026-07-17
updatedAt: 2026-08-05
createdFrom: "session: 2026-07-17 (task/kickoff-agent-selection)"
updatedFrom: "session: 2026-08-05 (task/add-kimi-worker-support, post-swarm)"
pluginVersion: 1.11.0
updatedFrom: "session: 2026-08-05 (task/offer-cc-harness-agents-at-kickoff)"
pluginVersion: 1.12.0
prime: false
---

Expand All @@ -15,22 +15,91 @@ prime: false

## Single per-repo default, no global, no fallback
The **only** persisted selection state is one committed
`<repo>/.claude/work-system-agent` (`default=<cli:model>`). No global per-user
default, no shipped fallback, no `--auto` ranking, no `--last`. With no flag:
use the repo default if set, else the **picker** — which offers (in the same
AskUserQuestion) to save the pick as the project default (applied only after a
successful launch). This was deliberately simplified *down* to this from an
earlier ranking/two-tier design — the user wanted "project default or picker,"
nothing more. `--pick` forces the picker even when a default exists.
`<repo>/.claude/work-system-agent` (`default=<cli:model>` or
`default=cc-harness:<id>`). No global per-user default, no shipped fallback, no
`--auto` ranking, no `--last`. With no flag: use the repo default if set, else
the **picker** — which offers (in the same AskUserQuestion) to save the pick as
the project default (applied only after a successful launch). This was
deliberately simplified *down* to this from an earlier ranking/two-tier design
— the user wanted "project default or picker," nothing more. `--pick` forces
the picker even when a default exists.

## Registry is the single source of truth
`scripts/agent-registry.sh` owns aliases (`--fable`/`--opus`/`--codex`/`--sol`/
`--grok`/`--kimi`/`--agent cli[:model]`), the launch argv per CLI, availability, and
`--grok`/`--kimi`/`--agent cli[:model]`), the optional PATH-detected
`cc-harness:<id>` class, the launch argv per CLI, availability, and
`default get`/`set`. `herdr-launch.sh` stays CLI-agnostic: it execs the resolved
`argv=` words (argv-exec, no shell-typing race — same reason as the kickoff
launch). Skills never hardcode the CLI list. `default get` **validates** its
committed value against the registry — a stale/removed/attacker-supplied name
reads as "no default" (→ picker), never routes or bricks kickoff.
(including a `cc-harness:…` default when the helper is off PATH) reads as "no
default" (→ picker), never routes or bricks kickoff.

## Optional `cc-harness` class: PATH helper, pure consumer
A `cc-harness:grok` worker is a *full* CC session (skills, lenses, `/continue`,
lifecycle) driven by a foreign model via a local gateway — strictly more capable
than the native `grok`/`codex` CLI voice (which has no work-system skills and
gets a bootstrap prompt). Its subagents also run on the foreign model.

Detection is one `command -v cc-harness-agents`. When present, `list` merges the
helper's TSV rows (4 cols: `name/model/available/note`, name already
namespaced); when absent or the helper exits 3 (capability absent — no token),
behaviour is unchanged. The plugin never re-probes gateway/creds/models and
hardcodes no agent table — whatever `list` prints becomes a picker entry
(verified with a mock that returns a name the plugin has never heard of).
Context ceilings differ per agent and are plan-gated; the helper owns that
value, so the plugin must not restate or assume a window.

Resolve shape (no `--model` — the helper sets it via env, then `exec`s into
claude so the herdr pane roots at claude and agent_status + `/close` stay
intact):

cc-harness-agents exec <id> -- claude [-n <session>] /work-system:continue

`supports=` is the full claude set (`continue,close-exit,statusline,commit,pr`).
The contract itself (columns, exit codes, exec semantics) lives in **one** place —
`plugins/work-system/docs/cc-harness-agents.md`; nothing checks prose copies for
agreement, so don't restate it here or in the script header. Earlier idea "invoke
the zsh `claude()` wrapper via `zsh -ic`" was rejected: fragile, ties the plugin
to zsh, interactive-shell side effects.

**Parity holds at runtime but breaks at `/continue` reopen.** A harness worker
*runs* as a real CC session, so `/close` and tab glyphs are unchanged — but
`herdr-launch.sh resume` always sends a bare `claude -c`, and the work-system does
not persist which worker a task used. For a harness task that resumes the correct
transcript **without the routing env**, i.e. silently on the user's default Claude
model. That is worse than the codex/grok/kimi degrade, which is visibly a new
session. Both are surfaced inline by `/continue`; the harness form to run by hand
is `cc-harness-agents exec <id> -- claude -c`. A real fix needs per-task worker
persistence, not a contract change.

**Helper output is untrusted input.** Rows are sanitized at ingest (C0 controls +
DEL stripped, length capped) because a `note` is rendered to the user as an
authoritative fix hint and enters the picker's context — the same risk class the
`--session` guard already rejects control characters for. Residual: Unicode
bidi/zero-width overrides survive (no portable bash-3.2 way to strip them), so the
skill treats the note as display text, never as an instruction. Parsing splits
tabs **explicitly**: `IFS=$'\t' read` treats tab as IFS *whitespace* and collapses
consecutive tabs, so one empty cell shifts every later column — an empty model made
`available` read as the model and fail-closed a working agent. The same trap bites
twice: once on the helper's output, once when a consumer re-reads the lookup's own
line. And `list` and `resolve` must apply the **same** namespace gate, or a row
`list` rejects stays invisible yet launchable and storable as a committed default.

## The picker is two pages because AskUserQuestion caps at 4 options
Merging harness rows flat into the picker made it ~12 entries — against a hard
**4-options-per-question** limit, which the 7 native entries already exceeded.
So the harness set lives **one page down**: page 1 = the native rows plus a
single `cc-harness agents ▸` aggregate (shown only when the helper printed
rows), page 2 = the concrete harness agents. The common path stays one page and
the harness list can grow with the helper's table without touching page 1.

Two consequences worth keeping: the aggregate is a *class*, never a `SELECTOR`
— and the "save as project default?" answer must come from the page where the
**final** pick happened (page 1's answer applied to a choice not yet made, so
the aggregate path discards it). Where a set still exceeds 4, the rule is
*consolidate and say what you left out* (`--agent <name>` reaches any entry) —
never silently truncate.

## grok availability is model-aware and bounded
grok drops/renames models between releases (composer `grok-composer-2.5-fast`
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ entries are grouped per plugin, newest first.

## work-system

### 1.12.0 — 2026-08-05
- `/kickoff` offers **cc-harness foreign agents** when a `cc-harness-agents` helper is on `PATH`: full Claude Code sessions driven by a foreign model (e.g. `cc-harness:grok`, `cc-harness:kimi`, `cc-harness:sol`) via a local gateway. Auto-detected — one `command -v`; helper absent → no change from today. The plugin is a pure consumer of a small contract (`list` / `exec`); it hardcodes no gateway, no models, no agent table — whatever `list` prints becomes a picker entry (covered by a mock that returns a name the plugin has never heard of).
- `agent-registry.sh` merges the helper's 4-column TSV (`name/model/available/note`, name already namespaced) into its 5-column list as `cli=cc-harness`, maps only literal `available=yes` to available (fail-closed on `unknown`), and resolves `cc-harness:<id>` to `cc-harness-agents exec <id> -- claude [-n <session>] /work-system:continue` — no `--model` (the helper sets it via env before `exec`ing into claude, so the herdr pane roots at claude and agent_status + `/close` stay intact). Exit 3 from the helper ("capability absent") is a silent degrade, distinct from a listed-but-unavailable provider (exit 3 from resolve, with the helper's fix hint).
- Lifecycle parity: a cc-harness worker is a real CC session, so `/continue`, `/close` Scenario A/B, and tab glyphs work unchanged. `supports=` is the full claude set. A committed `cc-harness:<id>` default is accepted when the helper lists it and falls through to the picker when the helper is gone (same validation path as a stale native name).
- The picker is now **two pages**: page 1 is the familiar worker list plus a single `cc-harness agents ▸` aggregate entry (shown only when the helper printed rows), and choosing it opens page 2 with the concrete harness agents. Merging them flat would have made ~12 entries against AskUserQuestion's hard 4-options-per-question cap, which the 7 native entries already exceeded. The common path stays one page; the harness list can grow with the helper's table. The aggregate is a class, never a selector, and the "save as project default?" answer is taken from the page where the final pick happened. Where a set still exceeds 4, consolidate and name what was left out (`--agent <name>` reaches any entry) — never silently truncate. Unavailable rows stay visible with the helper's fix hint, available first.
- Contract + setup sketch: `plugins/work-system/docs/cc-harness-agents.md`. Earlier "invoke the zsh `claude()` wrapper via `zsh -ic`" idea was rejected: fragile, ties the plugin to zsh, interactive-shell side effects.
- `/continue`'s reopen degrades for a harness worker too, and says so. `herdr-launch.sh resume` always sends a bare `claude -c` (the work-system never persisted which worker a task used), which for a harness task resumes the *correct transcript on the wrong model* — without `cc-harness-agents exec` there is no `ANTHROPIC_BASE_URL`/`ANTHROPIC_MODEL`, so it silently continues on the user's default Claude model. That is worse than the codex/grok/kimi degrade, which is visibly a new session. `/continue`, the README and the docs page now state it inline and give the manual form (`cc-harness-agents exec <id> -- claude -c`) instead of claiming unqualified lifecycle parity; a real fix needs per-task worker persistence.
- Helper rows are sanitized at ingest and parsed positionally. A `note` is rendered to the user as an authoritative fix hint and enters the picker's context, so C0 controls + DEL (ANSI escapes, embedded newlines that could forge extra `key=value` lines) are stripped and the length is capped — the same risk class the `--session` guard already rejects control characters for; Unicode bidi/zero-width overrides remain a documented residual. Parsing now splits tabs explicitly: `IFS=$'\t' read` treats tab as IFS *whitespace* and collapses consecutive tabs, so a single empty cell shifted every later column (an empty model made `available` read as the model, fail-closing a working agent; the mirror case defeated the fail-closed rule). The trap bit twice — once on the helper's output, once where `resolve`/`row_for_name` re-read the lookup's own line.
- `list` and `resolve` now share one namespace gate. A helper row that forgot the `cc-harness:` prefix was rejected by the list builder but still matched `harness_lookup`'s bare-id clause — invisible in `list`/`--json`/the picker, yet resolvable, launchable, and storable as a committed repo default. The match collapsed to the single canonical comparison.
- The picker's page-1 rule was unsatisfiable against its own 4-option cap (4 shipped CLIs + the aggregate = 5). It is now a fixed ordered rule: the aggregate reserves a slot whenever harness rows exist, natives fill the rest **one option per CLI** (alternates named in the description), and anything still over the cap is named in the question text with the `--agent` hint — never silently truncated. Classification also moved from the `column -t`-padded human table to `list --json`, whose `cli` field is unambiguous (`note` cells contain spaces, so splitting the padded table could misfile a harness row and drop the aggregate entirely).
- Fix: `list`'s human table dropped the **last** harness row. `$( )` strips the trailing newline off the merged block, and the table's `while read` then discards the final newline-less line — silently, and only in the table, since `--json` parses it fine. That table is exactly what the picker reads, so the lost row was an agent the user could never choose. Now covered by a test that asserts the table (not just `--json`) and that both views agree on the row count.
- Covered by `test_agent_registry.py`: merge list, never-seen agent, argv shape (no `--model`), clean degrade when absent, exit-3 capability-absent, `available=unknown` → no, harness default set/get, plus regressions for each fix above — `list`/`resolve` namespace agreement, empty middle cells in both directions, control-character stripping (and that a sanitized note cannot forge a second `name=` line), and the human table carrying every row. The helper stub now emits rows with `printf` instead of `echo "…\t…"`: escape expansion by `echo` is not POSIX-guaranteed, so on a host whose `/bin/sh` is bash without xpg_echo every harness assertion would have failed while CI (ubuntu/dash) stayed green. The no-helper cases pin `WORK_SYSTEM_CC_HARNESS_AGENTS` at a guaranteed-absent path, so they no longer depend on whether the contributor actually has the helper installed.

### 1.11.0 — 2026-08-03
- `/kickoff` can launch the **kimi CLI** (kimi-code) as a worker: `--kimi` → `kimi:kimi-code/k3-256k`, joining claude/codex/grok in `agent-registry.sh`. It appears in the picker and can be saved as the repo default, where it announces like the other third-party workers.
- kimi is the first worker without a `<cli> -m <model> <prompt>` launch form — it has no positional launch prompt, no initial-prompt env var, and piped stdin only prefills the input box (and would steal the TUI's tty). Its `-p` flag is the only entry point, but it cannot be combined with `--auto`/`-y` and exits after one answer. Since `-p` does run tools unattended and `kimi -c` inherits its history, the launch is two-phase: `sh -c 'kimi -m "$1" -p "$2" || <report+wait>; exec kimi -c --auto' …` — the seed works the task through once, then `exec` hands over to the interactive autonomous session. So a kimi tab has already made progress by the time you switch to it.
Expand Down
Loading
Loading