@softspark/ai-toolkit 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ # Concise Mode
2
+
3
+ Active when project sets `output-mode: concise` or user invokes `/brand-voice concise`.
4
+
5
+ ## Targets
6
+
7
+ - Token output ≤60% of default
8
+ - No response longer than 12 lines unless data requires it (file list, test failures, code)
9
+ - No response shorter than 1 sentence — never reply with just "Done." or "OK"
10
+
11
+ ## Hard Rules
12
+
13
+ 1. **No preamble.** Start with the answer or the action result, not "I'll now..." or "Let me explain..."
14
+ 2. **No closing summary.** Don't restate what you just did unless asked.
15
+ 3. **Bullets over prose** for any list ≥3 items. Each bullet = one fact, ≤80 chars.
16
+ 4. **One paragraph max** per topic. If you need two, split into bullets.
17
+ 5. **No filler transitions:** drop "Additionally," "Moreover," "It's worth noting," "Importantly."
18
+ 6. **No hedging stacks:** "I think we could potentially maybe try" → "Try X."
19
+ 7. **Quote facts, not framing:** show file paths, line numbers, exact error text. Cut the explanation around them if the user can read them.
20
+
21
+ ## What to Cut First
22
+
23
+ | Pattern | Action |
24
+ |---------|--------|
25
+ | "Let me check..." / "I'll look at..." | Delete — just do the action |
26
+ | "It looks like..." / "It seems..." | Delete and state the fact directly |
27
+ | "As you can see..." | Delete — the user can see |
28
+ | "Now that we've ... let's ..." | Delete — start the next action |
29
+ | "Great question!" / "Good point!" | Delete always |
30
+ | Restating the user's question | Delete — answer it |
31
+ | "In summary," / "Overall," | Delete the summary entirely |
32
+
33
+ ## What to Keep
34
+
35
+ - Concrete file paths, line numbers, command output
36
+ - Exact error messages and stack frames
37
+ - Trade-offs when the user has to decide
38
+ - Warnings about destructive actions
39
+ - Numbers (timings, sizes, counts, percentages)
40
+
41
+ ## Examples
42
+
43
+ **Bad (verbose):**
44
+
45
+ ```
46
+ Let me check the brand-voice skill for you. I'll read the SKILL.md file
47
+ to understand its current structure and then look at how it integrates
48
+ with the rest of the system. Now that I've examined it, I can see that
49
+ the skill currently has 130 lines and uses YAML frontmatter. As you can
50
+ see, it has a clear structure with sections for anti-tropes, voice
51
+ principles, and rules. In summary, the skill is well-organized.
52
+ ```
53
+
54
+ **Good (concise):**
55
+
56
+ ```
57
+ brand-voice/SKILL.md: 130 lines, YAML frontmatter, sections for anti-tropes,
58
+ voice principles, rules. Structure is clean.
59
+ ```
60
+
61
+ Token ratio: 76 → 25 = 33%.
62
+
63
+ ## Boundary Behavior
64
+
65
+ - If a question genuinely needs 30 lines to answer, write 30. Don't truncate facts to hit the budget.
66
+ - If the user asks "explain", give one solid paragraph, not a one-liner.
67
+ - If unsure whether the user wants depth, ask. One question is cheaper than a wrong-length response.
@@ -0,0 +1,91 @@
1
+ # Strict Mode
2
+
3
+ Active when project sets `output-mode: strict` or user invokes `/brand-voice strict`. More aggressive than `concise`. Use for long sessions, expensive models, or batch operations where every token costs.
4
+
5
+ ## Targets
6
+
7
+ - Token output ≤40% of default
8
+ - No prose blocks — only lists, tables, code, exact strings
9
+ - No response longer than 8 lines unless data requires it
10
+
11
+ ## Hard Rules
12
+
13
+ 1. **No prose paragraphs.** Replace prose with bullet lists or tables.
14
+ 2. **No connective tissue.** Drop "because", "so", "therefore" unless the causal link is the answer.
15
+ 3. **Sentence fragments allowed** for non-data answers: "Done." "Yes." "Already exists at <path>." are valid responses.
16
+ 4. **Tables for any comparison ≥2 items.** Two-column min: `key | value`.
17
+ 5. **No examples unless asked.** Strict mode assumes the user knows what good output looks like.
18
+ 6. **No qualifiers.** Drop "approximately", "roughly", "around", "about" — give exact numbers or say "unknown".
19
+ 7. **No second-person framing.** "You should X" → "Do X." or "X is required."
20
+
21
+ ## Mandatory Cuts
22
+
23
+ | Pattern | Replacement |
24
+ |---------|-------------|
25
+ | Any paragraph >2 sentences | Bullet list |
26
+ | "There are N reasons..." | Numbered list directly |
27
+ | "Let me know if..." | (delete) |
28
+ | "Hope this helps" | (delete) |
29
+ | "Feel free to..." | (delete) |
30
+ | Repeating the question | (delete) |
31
+ | Re-explaining what was just done | (delete) |
32
+ | "Now that..." / "After that..." | (delete) |
33
+ | "It's important to note..." | State the fact, drop the framing |
34
+ | "In other words..." | Pick one phrasing, drop the other |
35
+
36
+ ## What Stays Full-Length
37
+
38
+ - Code blocks (never elide)
39
+ - File paths, error messages, stack traces (never truncate)
40
+ - Command output the user must see
41
+ - Test failure listings
42
+ - Security findings (CVE IDs, severity, file:line)
43
+ - Diff context lines
44
+
45
+ ## Format Skeleton
46
+
47
+ For most strict-mode responses, use this skeleton:
48
+
49
+ ```
50
+ <one-line answer or status>
51
+
52
+ <table or bullets if data>
53
+
54
+ <next action if applicable, one line>
55
+ ```
56
+
57
+ That's the whole response. No intro, no outro, no transitions.
58
+
59
+ ## Examples
60
+
61
+ **User asks: "is the test passing?"**
62
+
63
+ Default: ~40 tokens of explanation. Strict: `Yes. tests/test_brand_voice.bats: 5/5 passing in 0.3s.`
64
+
65
+ **User asks: "what changed in this commit?"**
66
+
67
+ Default: prose summary + diff highlights. Strict:
68
+
69
+ ```
70
+ Changed: app/skills/brand-voice/SKILL.md (+23 lines)
71
+ Added: app/skills/brand-voice/modes/concise.md
72
+ Added: app/skills/brand-voice/modes/strict.md
73
+ ```
74
+
75
+ **User asks: "should I use Postgres or MySQL?"**
76
+
77
+ Default: paragraph weighing options. Strict:
78
+
79
+ | Factor | Postgres | MySQL |
80
+ |--------|----------|-------|
81
+ | JSONB | yes | partial |
82
+ | Replication | logical+phys | logical+phys |
83
+ | Default for this stack | yes | no |
84
+
85
+ Recommend: Postgres.
86
+
87
+ ## Boundary Behavior
88
+
89
+ - Strict mode does NOT mean wrong. If a fact requires 5 lines to be correct, write 5 lines. Cut framing, never substance.
90
+ - Strict mode does NOT mean rude. Drop pleasantries, not respect.
91
+ - If the user explicitly asks for explanation, switch to concise mode for that response. Strict is the default, not a gag.
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env python3
2
+ """Measure token reduction of brand-voice output modes against fixtures.
3
+
4
+ Reads each fixture directory containing default.md, concise.md, strict.md,
5
+ counts tokens with a deterministic whitespace heuristic, and reports per-fixture
6
+ and aggregate ratios. Asserts:
7
+
8
+ concise <= CONCISE_BUDGET (default 0.60)
9
+ strict <= STRICT_BUDGET (default 0.40)
10
+
11
+ Also asserts fact preservation: every file path, identifier, line:number, and
12
+ fenced code block from default.md must appear in concise.md and strict.md.
13
+
14
+ Usage:
15
+ python3 app/skills/brand-voice/scripts/measure.py \\
16
+ --fixtures tests/fixtures/output-modes/
17
+
18
+ Exit codes:
19
+ 0 all fixtures pass budgets and fact preservation
20
+ 1 one or more fixtures violate budget or drop facts
21
+ 2 invalid fixtures dir or missing files
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import json
27
+ import re
28
+ import sys
29
+ from pathlib import Path
30
+
31
+ CONCISE_BUDGET_DEFAULT = 0.60
32
+ STRICT_BUDGET_DEFAULT = 0.40
33
+
34
+ PATH_RE = re.compile(r"\b[\w/.-]+\.[a-zA-Z]{1,5}\b")
35
+ LINE_REF_SUFFIX_RE = re.compile(r":\d+$")
36
+ CODE_BLOCK_RE = re.compile(r"```[\w]*\n.*?```", re.DOTALL)
37
+ NUMBER_WITH_UNIT_RE = re.compile(r"\b\d+(?:\.\d+)?(?:ms|s|kb|mb|gb|%)\b", re.IGNORECASE)
38
+ IDENT_BACKTICK_RE = re.compile(r"`([^`\n]+)`")
39
+
40
+
41
+ def count_tokens(text: str) -> int:
42
+ """Whitespace-based token estimate. Deterministic and offline."""
43
+ return len(text.split())
44
+
45
+
46
+ def normalize_path(value: str) -> str:
47
+ """Strip `:NN` line-number suffix so `foo.ts` and `foo.ts:42` compare equal."""
48
+ return LINE_REF_SUFFIX_RE.sub("", value).strip("`")
49
+
50
+
51
+ def extract_facts(text: str) -> set[str]:
52
+ """Extract load-bearing facts: file paths, numbers with units, backtick identifiers.
53
+
54
+ Does NOT extract code-block bodies. A mode response legitimately shows
55
+ different code (e.g. the fix) than the default response (e.g. the bug),
56
+ and treating each code line as a required fact produces false positives.
57
+ """
58
+ text_no_code = CODE_BLOCK_RE.sub("", text)
59
+ facts: set[str] = set()
60
+
61
+ for match in PATH_RE.findall(text_no_code):
62
+ normalized = normalize_path(match)
63
+ if "/" in normalized:
64
+ facts.add(normalized)
65
+
66
+ for match in IDENT_BACKTICK_RE.findall(text_no_code):
67
+ cleaned = match.strip()
68
+ if len(cleaned) >= 3 and not cleaned.startswith("```"):
69
+ facts.add(normalize_path(cleaned))
70
+
71
+ for match in NUMBER_WITH_UNIT_RE.findall(text_no_code):
72
+ facts.add(match.lower())
73
+
74
+ return facts
75
+
76
+
77
+ def load_must_contain(fixture_dir: Path) -> list[str]:
78
+ """Optional explicit must-contain assertions, one per line."""
79
+ must_contain_file = fixture_dir / "must_contain.txt"
80
+ if not must_contain_file.exists():
81
+ return []
82
+ return [
83
+ line.strip()
84
+ for line in must_contain_file.read_text().splitlines()
85
+ if line.strip() and not line.strip().startswith("#")
86
+ ]
87
+
88
+
89
+ def evaluate_fixture(
90
+ fixture_dir: Path,
91
+ concise_budget: float,
92
+ strict_budget: float,
93
+ ) -> dict:
94
+ default_file = fixture_dir / "default.md"
95
+ concise_file = fixture_dir / "concise.md"
96
+ strict_file = fixture_dir / "strict.md"
97
+
98
+ for path in (default_file, concise_file, strict_file):
99
+ if not path.exists():
100
+ return {
101
+ "fixture": fixture_dir.name,
102
+ "ok": False,
103
+ "error": f"missing file: {path.name}",
104
+ }
105
+
106
+ default_text = default_file.read_text()
107
+ concise_text = concise_file.read_text()
108
+ strict_text = strict_file.read_text()
109
+
110
+ default_tokens = count_tokens(default_text)
111
+ concise_tokens = count_tokens(concise_text)
112
+ strict_tokens = count_tokens(strict_text)
113
+
114
+ if default_tokens == 0:
115
+ return {
116
+ "fixture": fixture_dir.name,
117
+ "ok": False,
118
+ "error": "default.md empty",
119
+ }
120
+
121
+ concise_ratio = concise_tokens / default_tokens
122
+ strict_ratio = strict_tokens / default_tokens
123
+
124
+ default_facts = extract_facts(default_text)
125
+ concise_facts = extract_facts(concise_text)
126
+ strict_facts = extract_facts(strict_text)
127
+
128
+ concise_missing = sorted(default_facts - concise_facts)
129
+ strict_missing = sorted(default_facts - strict_facts)
130
+
131
+ must_contain = load_must_contain(fixture_dir)
132
+ concise_required_missing = [
133
+ item for item in must_contain if item not in concise_text
134
+ ]
135
+ strict_required_missing = [
136
+ item for item in must_contain if item not in strict_text
137
+ ]
138
+
139
+ budget_ok_concise = concise_ratio <= concise_budget
140
+ budget_ok_strict = strict_ratio <= strict_budget
141
+ facts_ok_concise = not concise_required_missing
142
+ facts_ok_strict = not strict_required_missing
143
+
144
+ return {
145
+ "fixture": fixture_dir.name,
146
+ "ok": budget_ok_concise
147
+ and budget_ok_strict
148
+ and facts_ok_concise
149
+ and facts_ok_strict,
150
+ "default_tokens": default_tokens,
151
+ "concise_tokens": concise_tokens,
152
+ "strict_tokens": strict_tokens,
153
+ "concise_ratio": round(concise_ratio, 3),
154
+ "strict_ratio": round(strict_ratio, 3),
155
+ "concise_budget_ok": budget_ok_concise,
156
+ "strict_budget_ok": budget_ok_strict,
157
+ "concise_advisory_missing": concise_missing,
158
+ "strict_advisory_missing": strict_missing,
159
+ "concise_required_missing": concise_required_missing,
160
+ "strict_required_missing": strict_required_missing,
161
+ }
162
+
163
+
164
+ def main() -> int:
165
+ parser = argparse.ArgumentParser(description=__doc__)
166
+ parser.add_argument(
167
+ "--fixtures",
168
+ type=Path,
169
+ default=Path("tests/fixtures/output-modes"),
170
+ help="Path to fixtures directory",
171
+ )
172
+ parser.add_argument(
173
+ "--concise-budget",
174
+ type=float,
175
+ default=CONCISE_BUDGET_DEFAULT,
176
+ help=f"Max concise/default token ratio (default {CONCISE_BUDGET_DEFAULT})",
177
+ )
178
+ parser.add_argument(
179
+ "--strict-budget",
180
+ type=float,
181
+ default=STRICT_BUDGET_DEFAULT,
182
+ help=f"Max strict/default token ratio (default {STRICT_BUDGET_DEFAULT})",
183
+ )
184
+ parser.add_argument(
185
+ "--json",
186
+ action="store_true",
187
+ help="Emit JSON report instead of human format",
188
+ )
189
+ args = parser.parse_args()
190
+
191
+ if not args.fixtures.is_dir():
192
+ print(f"error: fixtures dir not found: {args.fixtures}", file=sys.stderr)
193
+ return 2
194
+
195
+ fixture_dirs = sorted(
196
+ d for d in args.fixtures.iterdir() if d.is_dir() and (d / "default.md").exists()
197
+ )
198
+
199
+ if not fixture_dirs:
200
+ print(f"error: no fixtures found under {args.fixtures}", file=sys.stderr)
201
+ return 2
202
+
203
+ results = [
204
+ evaluate_fixture(d, args.concise_budget, args.strict_budget)
205
+ for d in fixture_dirs
206
+ ]
207
+
208
+ total_default = sum(r.get("default_tokens", 0) for r in results)
209
+ total_concise = sum(r.get("concise_tokens", 0) for r in results)
210
+ total_strict = sum(r.get("strict_tokens", 0) for r in results)
211
+ aggregate = {
212
+ "concise_ratio": round(total_concise / total_default, 3) if total_default else 0,
213
+ "strict_ratio": round(total_strict / total_default, 3) if total_default else 0,
214
+ "fixtures": len(results),
215
+ "passed": sum(1 for r in results if r.get("ok")),
216
+ }
217
+
218
+ if args.json:
219
+ print(json.dumps({"results": results, "aggregate": aggregate}, indent=2))
220
+ else:
221
+ print(f"{'fixture':<28} {'default':>8} {'concise':>8} {'strict':>8} {'c%':>6} {'s%':>6} {'ok':>4}")
222
+ print("-" * 76)
223
+ for r in results:
224
+ if "error" in r:
225
+ print(f"{r['fixture']:<28} ERROR: {r['error']}")
226
+ continue
227
+ mark = "yes" if r["ok"] else "NO"
228
+ print(
229
+ f"{r['fixture']:<28} {r['default_tokens']:>8} "
230
+ f"{r['concise_tokens']:>8} {r['strict_tokens']:>8} "
231
+ f"{int(r['concise_ratio'] * 100):>5}% "
232
+ f"{int(r['strict_ratio'] * 100):>5}% {mark:>4}"
233
+ )
234
+ print("-" * 76)
235
+ print(
236
+ f"aggregate: concise={int(aggregate['concise_ratio'] * 100)}% "
237
+ f"strict={int(aggregate['strict_ratio'] * 100)}% "
238
+ f"passed={aggregate['passed']}/{aggregate['fixtures']}"
239
+ )
240
+
241
+ all_ok = all(r.get("ok") for r in results)
242
+ return 0 if all_ok else 1
243
+
244
+
245
+ if __name__ == "__main__":
246
+ sys.exit(main())
@@ -18,6 +18,8 @@ Triggers the Chief of Staff to generate an executive summary.
18
18
  /briefing [period]
19
19
  # Example: /briefing today
20
20
  # Example: /briefing week
21
+ /briefing --tokens [--since 7d|24h|30m]
22
+ # Reports real session token usage from Claude Code JSONL.
21
23
  ```
22
24
 
23
25
  ## Protocol
@@ -58,6 +60,65 @@ Triggers the Chief of Staff to generate an executive summary.
58
60
  - "Recent runs" without an explicit time bound defaults to **everything** on some log backends. Always pass `--since` or a date filter, or you will read a week into yesterday's memory.
59
61
  - Successful runs outnumber interesting runs by an order of magnitude. Aggressively filter green/noop entries — they are the signal's noise floor.
60
62
 
63
+ ## Token Receipts
64
+
65
+ The `--tokens` flag reports real token usage parsed from Claude Code session JSONL — not estimates. Useful for:
66
+
67
+ - Verifying `output-mode: concise` actually reduces tokens vs default sessions
68
+ - Spotting expensive runs before they show up on the bill
69
+ - Capturing a baseline before changing prompts or skills
70
+
71
+ Underlying script: `scripts/session_token_stats.py`.
72
+
73
+ ```bash
74
+ # Aggregate current session
75
+ python3 scripts/session_token_stats.py --json
76
+
77
+ # Statusline-friendly one-line output
78
+ python3 scripts/session_token_stats.py --statusline
79
+
80
+ # Trend vs baseline
81
+ python3 scripts/session_token_stats.py --statusline --baseline ~/.softspark/ai-toolkit/baseline.json
82
+ ```
83
+
84
+ ### Status line (installed by default in v3.2.0+)
85
+
86
+ `ai-toolkit install` wires `~/.claude/settings.json` to `app/hooks/ai-toolkit-statusline.sh`. The hook reads native Claude Code statusLine stdin (no session JSONL parsing) and renders one line:
87
+
88
+ ```
89
+ ➜ <dir> git:(branch) ✗ ████░░░░░░ 43% ↑6.5k ↓252k effort:xhigh <model>
90
+ ```
91
+
92
+ Segments left to right:
93
+
94
+ - `➜ <dir>` — current directory basename
95
+ - `git:(branch) ✗` — git branch + dirty marker
96
+ - 10-cell **progress bar** for context-window usage. Color tiers: green `<70%`, orange `70–89%`, red `≥90%`
97
+ - `↑in ↓out` — token arrows. Green up = input (upload), red down = output (download). Both cumulative across the session.
98
+ - `effort:level` — Claude Code effort level (low / medium / high / xhigh)
99
+ - model name
100
+
101
+ Custom statusLine entries you set yourself (without the `_source: ai-toolkit` tag) are preserved untouched on install.
102
+
103
+ Opt-outs (no reinstall required):
104
+
105
+ - `AI_TOOLKIT_STATUSLINE_DISABLE=1` — silence the line entirely
106
+ - `AI_TOOLKIT_STATUSLINE_NO_TOKENS=1` — hide token arrows segment
107
+ - `AI_TOOLKIT_STATUSLINE_NO_GIT=1` — hide git segment
108
+ - `AI_TOOLKIT_STATUSLINE_NO_EFFORT=1` — hide effort level segment
109
+ - `AI_TOOLKIT_STATUSLINE_NO_COLOR=1` — disable ANSI colors
110
+ - `AI_TOOLKIT_STATUSLINE_SHOW_COST=1` — append Claude Code's reported cost (`cost.total_cost_usd`)
111
+ - `AI_TOOLKIT_STATUSLINE_DUMP=1` — write received stdin to `/tmp/cc-statusline-input.json` (debug)
112
+
113
+ ### Save a baseline
114
+
115
+ ```bash
116
+ python3 scripts/session_token_stats.py --json | jq '.totals' > ~/.softspark/ai-toolkit/baseline.json
117
+ export AI_TOOLKIT_STATUSLINE_BASELINE=~/.softspark/ai-toolkit/baseline.json
118
+ ```
119
+
120
+ The statusline then renders trend arrows (↑ / ↓) against that baseline.
121
+
61
122
  ## When NOT to Use
62
123
 
63
124
  - For a specific production incident — use `/workflow incident-response`
@@ -3,7 +3,7 @@ name: swarm
3
3
  description: "Execute tasks via Map-Reduce, Consensus, or Relay swarms"
4
4
  user-invocable: true
5
5
  effort: max
6
- argument-hint: "[map-reduce|consensus|relay] [task]"
6
+ argument-hint: "[map-reduce|consensus|relay] [--with-kb] [--worktree] [task]"
7
7
  context: fork
8
8
  agent: orchestrator
9
9
  model: opus
@@ -79,3 +79,85 @@ Agent(
79
79
  2. De-duplicate identical findings
80
80
  3. Synthesize unique insights
81
81
  4. Generate final swarm report
82
+
83
+ ## KB-First Mode (`--with-kb`)
84
+
85
+ When `$ARGUMENTS` contains `--with-kb`, every spawned agent MUST receive KB context grounded in the project knowledge base.
86
+
87
+ ### Required pre-flight (run BEFORE spawning agents)
88
+
89
+ 1. Call `mcp__rag-mcp__smart_query` with the original task as `query`. Use `use_multi_hop=true` if the task spans 2+ concepts.
90
+ 2. Capture `results[*].kb_id`, `title`, `content`, and `source_documents_used`.
91
+ 3. Build a `[KB CONTEXT]` block (max 10 entries, pruned to top scores).
92
+
93
+ ### Per-agent prompt template (mandatory under `--with-kb`)
94
+
95
+ ```
96
+ [KB CONTEXT — from rag-mcp smart_query, ground all decisions in these]
97
+ - {kb_id}: {title}
98
+ {content excerpt, ≤300 chars}
99
+ - ...
100
+
101
+ [YOUR SUB-TASK]
102
+ {specific sub-task, owned files, success criteria}
103
+
104
+ [RULES]
105
+ - Cite KB entries as [PATH: kb_id] when you rely on them.
106
+ - If KB is silent on a decision, state that explicitly — do NOT invent.
107
+ - After producing your output, call mcp__rag-mcp__verify_answer with your answer + the cited kb_ids; include the verdict in your final report.
108
+ ```
109
+
110
+ ### Aggregation under `--with-kb`
111
+
112
+ The synthesis step MUST include a `## KB Coverage` section listing which `kb_id`s were actually cited and any agent that returned `verdict: unsupported`.
113
+
114
+ ### When to skip `--with-kb`
115
+
116
+ - Pure code-mechanical tasks (rename, format, dependency bump) — KB adds noise.
117
+ - Tasks already scoped to one file with no cross-cutting concerns.
118
+
119
+ ## Isolated Worktrees Mode (`--worktree`)
120
+
121
+ When `$ARGUMENTS` contains `--worktree`, every spawned agent in **Map-Reduce** mode runs in its own git worktree on a throwaway branch. Aggregation merges or copies the changes back into the lead workspace.
122
+
123
+ ### Why
124
+
125
+ - Agents touching adjacent files (same module, different functions) can race.
126
+ - Writing to disjoint paths is not enough — file-locking, formatter cache, IDE indexers, and `.git/index.lock` all leak.
127
+ - Worktrees give each agent a real filesystem-level boundary plus a named branch for review.
128
+
129
+ ### How (mandatory under `--worktree`)
130
+
131
+ Pass `isolation: "worktree"` to every `Agent` call:
132
+
133
+ ```
134
+ Agent(
135
+ subagent_type="...",
136
+ description="...",
137
+ prompt="...",
138
+ isolation="worktree"
139
+ )
140
+ ```
141
+
142
+ The Agent tool returns the worktree path and branch name on completion. **Empty worktrees are auto-cleaned** by the runtime when the agent made no changes — you don't have to.
143
+
144
+ ### Aggregation under `--worktree`
145
+
146
+ After all agents return:
147
+
148
+ 1. List the returned `(path, branch)` pairs.
149
+ 2. For each non-empty result: `cd <main repo> && git merge --no-ff <branch>` (or cherry-pick the commits if the agent didn't commit).
150
+ 3. If any merge conflicts → escalate, do NOT auto-resolve. Cite which two agents touched the same hunk.
151
+ 4. After successful merge → delete the worktree: `git worktree remove <path>` and the throwaway branch.
152
+
153
+ ### When `--worktree` is mandatory (not optional)
154
+
155
+ - Map-Reduce with N≥3 agents touching the same module tree
156
+ - Any task that runs the project formatter or codegen
157
+ - Any task that mutates lockfiles, migrations, or generated artifacts
158
+
159
+ ### When to skip `--worktree`
160
+
161
+ - Consensus mode — agents return analysis text, not file changes.
162
+ - Relay mode — sequential by design, next agent reads prior agent's commit.
163
+ - Single-agent fallback or KB-only research swarms.