tickmarkr 1.74.0 → 1.75.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.
@@ -1,4 +1,5 @@
1
- import { type AuthHealth, type WorkerAdapter } from "./types.js";
1
+ import { type AuthHealth, type TrustDialog, type WorkerAdapter } from "./types.js";
2
+ export declare const CLAUDE_TRUST_DIALOG: TrustDialog;
2
3
  export declare function claudeSlug(real: string): string;
3
4
  export declare function probeVersion(bin: string): AuthHealth;
4
5
  export declare const claudeCode: WorkerAdapter;
@@ -20,6 +20,12 @@ import { channelsFromConfig, shq, TokenUsageSchema } from "./types.js";
20
20
  // Phase 18's operator-price × tokens derivation, not a CLI claim.
21
21
  const MAX_SESSION_FILES = 20; // newest-first; a long-lived project dir can hold many sessions
22
22
  const MAX_SESSION_BYTES = 8_000_000; // per-file cap; a runaway JSONL cannot make the read unbounded
23
+ // v1.75 T2 / OBS-137: current Claude Code workspace-trust prompt (2.1.218). The full question
24
+ // distinguishes this startup gate from routine agent text; Enter accepts the selected trust option.
25
+ export const CLAUDE_TRUST_DIALOG = {
26
+ fingerprint: "Quick safety check: Is this a project you created or one you trust?",
27
+ key: "Enter",
28
+ };
23
29
  export function claudeSlug(real) {
24
30
  return real.replace(/[^A-Za-z0-9]/g, "-");
25
31
  }
@@ -54,13 +60,11 @@ export const claudeCode = {
54
60
  // and --mcp-config is VARIADIC — a positional after it is eaten as a config-file path, so another
55
61
  // flag must always follow the value, never the prompt.
56
62
  headlessCommand: (promptFile, model) => `claude -p "$(cat ${shq(promptFile)})" --model ${shq(model)} --permission-mode bypassPermissions --strict-mcp-config --mcp-config '{"mcpServers":{}}' --output-format text`,
57
- // HYG-03: the residual first-entry dialog on an interactive TUI is the workspace TRUST dialog (not MCP
58
- // config loading) — CLI-imposed, no flag to pre-accept, only store is claude's global last-writer-wins
59
- // ~/.claude.json keyed on the exact path. Closed WON'T-FIX (decision B, 2026-07-10): tickmarkr writes nothing
60
- // to that file (a seed races claude's own writes, nondeterministically). Amortizes to one operator dismissal
61
- // per stable worktree path; blocked-pane paging surfaces it. Do NOT change this command to "fix" the dialog —
62
- // see .planning/REQUIREMENTS.md HYG-03 and 21-02-LIVE-CHECK.md. Revisit if upstream ships a --trust flag.
63
+ // HYG-03 / OBS-137: the residual first-entry dialog is workspace trust, not MCP config loading.
64
+ // Claude's only store is global last-writer-wins ~/.claude.json, so tickmarkr still does not seed it;
65
+ // the daemon safely answers only the exact adapter-declared dialog once per slot.
63
66
  interactiveCommand: (promptFile, model) => `claude --model ${shq(model)} --strict-mcp-config --mcp-config '{"mcpServers":{}}' --permission-mode bypassPermissions "$(cat ${shq(promptFile)})"`,
67
+ trustDialog: CLAUDE_TRUST_DIALOG,
64
68
  resumeCommand: (sessionId, promptFile, model) => `claude -r ${shq(sessionId)} --model ${shq(model)} --strict-mcp-config --mcp-config '{"mcpServers":{}}' --permission-mode bypassPermissions "$(cat ${shq(promptFile)})"`,
65
69
  invoke(task, _cwd, a, ctx) {
66
70
  return { command: this.headlessCommand(ctx.promptFile, a.model) };
@@ -1,8 +1,9 @@
1
- import { type TrustVerdict, type WorkerAdapter } from "./types.js";
1
+ import { type TrustDialog, type TrustVerdict, type WorkerAdapter } from "./types.js";
2
2
  export declare function readCodexModelsCache(path?: string): {
3
3
  models: string[];
4
4
  fetchedAt?: string;
5
5
  };
6
+ export declare const CODEX_TRUST_DIALOG: TrustDialog;
6
7
  export declare function seedCodexTrust(repoRoot: string, configPath?: string): TrustVerdict;
7
8
  export declare function hasCodexTrustedProject(text: string, root: string): boolean;
8
9
  export declare function codexConfigMcpServerNames(configPath?: string): string[];
@@ -89,6 +89,12 @@ const GITDIR_WRITABLE = `-c "sandbox_workspace_write.writable_roots=[\\"$(git re
89
89
  // -s/--sandbox workspace-write sandbox (deliberately NOT --dangerously-bypass-approvals-and-sandbox,
90
90
  // which would drop the sandbox). Listed by `codex --help` and `codex exec --help` (verified 2026-07-23).
91
91
  const CODEX_HOOK_TRUST = "--dangerously-bypass-hook-trust";
92
+ // v1.75 T2 / OBS-137: current Codex workspace-trust prompt (0.144.6). The exact heading
93
+ // is distinct from normal agent output; Enter accepts the selected "Yes, continue" option.
94
+ export const CODEX_TRUST_DIALOG = {
95
+ fingerprint: "Do you trust the contents of this directory?",
96
+ key: "Enter",
97
+ };
92
98
  // v1.22 T5 / OBS-16: codex keys trust on absolute path under [projects."<root>"] trust_level="trusted"
93
99
  // in ~/.codex/config.toml (CODEX_HOME relocates the dir). Worktrees inherit parent-project trust when
94
100
  // the REPO ROOT is trusted — seed the root once, cover every future worktree. Idempotent: a second
@@ -196,6 +202,7 @@ export const codex = {
196
202
  // v1.22 T5: seed [projects."<repoRoot>"] trust_level="trusted" so fresh worktrees never stall on
197
203
  // "Do you trust this directory?" (OBS-16). doctor-only side effect.
198
204
  trust: (repoRoot) => seedCodexTrust(repoRoot),
205
+ trustDialog: CODEX_TRUST_DIALOG,
199
206
  // v1.5 MODEL-01: file read only (no `codex models` subcommand exists, verified 2026-07-10).
200
207
  // Already fails OPEN to [] internally — advisory detection, unlike gates' fail-closed.
201
208
  listModels: async () => readCodexModelsCache().models,
@@ -20,6 +20,7 @@ export interface KimiInteractiveSeedResult {
20
20
  seedError?: string;
21
21
  sessionId?: string;
22
22
  }
23
+ export declare const KIMI_INPUT_BOX: import("./types.js").InputBox;
23
24
  export declare function runKimiInteractiveSeed(opts: {
24
25
  driver: Pick<ExecutorDriver, "run" | "waitOutput" | "read">;
25
26
  slot: Slot;
@@ -5,7 +5,7 @@ import { probeVersion } from "./claude-code.js";
5
5
  import { parseWorkerResult } from "./prompt.js";
6
6
  import { runInteractiveSeed } from "../run/interactive-seed.js";
7
7
  import { sh } from "../run/git.js";
8
- import { channelsFromConfig, MODEL_ID_RE, shq, TokenUsageSchema } from "./types.js";
8
+ import { channelsFromConfig, declareInputBox, MODEL_ID_RE, shq, TokenUsageSchema } from "./types.js";
9
9
  // KIMI-03 → v1.58 T5: the "no harness-readable counter" block (research F-6, 2026-07-17) is
10
10
  // LIFTED for collectUsage — kimi 0.27.0 writes a wire journal per agent at
11
11
  // ~/.kimi-code/sessions/<wd>/session_<uuid>/agents/<agent>/wire.jsonl, and ~/.kimi-code/
@@ -118,6 +118,11 @@ export function confirmKimiSeedBanner(banner, assignedModel) {
118
118
  }
119
119
  return { ok: true, sessionId: kimiBannerSessionId(banner) };
120
120
  }
121
+ // v1.75 T1 / OBS-136: this readiness line is rendered inside Kimi Code's bordered steady-state
122
+ // input box. The adapter owns the fingerprint; the driver only consults declarations generically.
123
+ export const KIMI_INPUT_BOX = declareInputBox("kimi", {
124
+ fingerprint: "Send /help for help information.",
125
+ });
121
126
  // Shared launch-then-seed surface (T6) + banner confirm (T7/T2). One definition so the adapter
122
127
  // property and the daemon's generic runInteractiveSeed path cannot drift.
123
128
  const KIMI_SEED = {
@@ -165,6 +170,7 @@ export const kimi = {
165
170
  // prompt as one user turn. Banner model/session confirmation runs on the daemon's generic
166
171
  // runInteractiveSeed path via confirmBanner on KIMI_SEED (not a separate dispatch helper).
167
172
  interactiveSeed: KIMI_SEED,
173
+ inputBox: KIMI_INPUT_BOX,
168
174
  // v1.53 T3 resume — live-probed 2026-07-18: `-p` + `-S <id>` compose cleanly (no OBS-67-class
169
175
  // flag rejection) and the resumed session carries prior conversation state. `-S <id>` is the
170
176
  // deterministic form; `-c` rejected as primary — cwd-keyed, nondeterministic under worktree
@@ -82,6 +82,12 @@ export interface TrustDialog {
82
82
  key: string;
83
83
  }
84
84
  export declare function matchesTrustDialog(paneText: string, dialog: TrustDialog): boolean;
85
+ export interface InputBox {
86
+ fingerprint: string;
87
+ }
88
+ export declare function declareInputBox(adapterId: string, inputBox: InputBox): InputBox;
89
+ export declare function declaredInputBoxForWorkerName(workerName: string): InputBox | undefined;
90
+ export declare function matchesInputBox(paneText: string, inputBox: InputBox): boolean;
85
91
  export interface WorkerAdapter {
86
92
  id: string;
87
93
  vendor: string;
@@ -105,6 +111,7 @@ export interface WorkerAdapter {
105
111
  contextUsage?(session: SessionRef): ContextUsage | null;
106
112
  trust?(repoRoot: string): TrustVerdict;
107
113
  trustDialog?: TrustDialog;
114
+ inputBox?: InputBox;
108
115
  hardcodedFlags?: {
109
116
  binary: string;
110
117
  flags: string[];
@@ -30,6 +30,18 @@ export function modelAuthed(health, model, allowUnverifiedModels = false) {
30
30
  export function matchesTrustDialog(paneText, dialog) {
31
31
  return paneText.includes(dialog.fingerprint);
32
32
  }
33
+ const inputBoxes = new Map();
34
+ export function declareInputBox(adapterId, inputBox) {
35
+ inputBoxes.set(adapterId, inputBox);
36
+ return inputBox;
37
+ }
38
+ export function declaredInputBoxForWorkerName(workerName) {
39
+ const adapterId = /^.+-worker-(.+)-a\d+-.+$/.exec(workerName)?.[1];
40
+ return adapterId === undefined ? undefined : inputBoxes.get(adapterId);
41
+ }
42
+ export function matchesInputBox(paneText, inputBox) {
43
+ return paneText.includes(inputBox.fingerprint);
44
+ }
33
45
  export function channelsFromConfig(adapterId, cfg) {
34
46
  const e = cfg.tiers[adapterId];
35
47
  if (!e)
@@ -57,6 +57,14 @@ function withModeLine(yaml, mode) {
57
57
  return yaml.replace(/^routing:$/m, `routing:\n mode: ${mode}`);
58
58
  return `routing:\n mode: ${mode}\n${yaml}`;
59
59
  }
60
+ function formatFleetSteering(cfg) {
61
+ const blocks = [];
62
+ if (cfg.review.prefer?.length)
63
+ blocks.push(`review:\n prefer: ${JSON.stringify(cfg.review.prefer)}`);
64
+ if (cfg.consult.prefer?.length)
65
+ blocks.push(`consult:\n prefer: ${JSON.stringify(cfg.consult.prefer)}`);
66
+ return blocks.length ? `${blocks.join("\n")}\n` : "";
67
+ }
60
68
  // v1.51 T4: one gloss per routing mode on the fleet mode screen — mirrors the preset compiler.
61
69
  const MODE_GLOSS = {
62
70
  "partner-led": "every shape frontier · explore off",
@@ -83,7 +91,9 @@ export async function fleet(argv, cwd = process.cwd(), adapters = allAdapters(),
83
91
  const rm = resolveRunMode(cwd, { globalDir });
84
92
  const body = formatFleetPrint(cwd, { globalDir });
85
93
  const nl = body.indexOf("\n");
86
- return `${body.slice(0, nl)}\n# mode: ${rm.mode.mode} (${rm.source})${body.slice(nl)}`;
94
+ // Steering comes from the same resolved config snapshot the editor consumes below,
95
+ // not from another parse of either raw overlay.
96
+ return `${body.slice(0, nl)}\n# mode: ${rm.mode.mode} (${rm.source})${body.slice(nl)}${formatFleetSteering(rm.cfg)}`;
87
97
  }
88
98
  if (!interactive)
89
99
  return { out: NON_TTY_MSG, code: 1 };
@@ -14,6 +14,7 @@ export declare class HerdrDriver implements ExecutorDriver {
14
14
  private deliverySerial;
15
15
  private dispatchLeases;
16
16
  private deliveredPanes;
17
+ private inputBoxes;
17
18
  private ws;
18
19
  private callerPane;
19
20
  private watches;
@@ -1,4 +1,4 @@
1
- import { shq } from "../adapters/types.js";
1
+ import { declaredInputBoxForWorkerName, matchesInputBox, shq } from "../adapters/types.js";
2
2
  import { PANE_IDENTITY_ENV, paneIdentityLine } from "../brand.js";
3
3
  import { createWorktree, sh } from "../run/git.js";
4
4
  import { herdrSealShellPrefix } from "./subprocess.js";
@@ -32,6 +32,7 @@ export class HerdrDriver {
32
32
  deliverySerial = Promise.resolve();
33
33
  dispatchLeases = new WeakMap();
34
34
  deliveredPanes = new WeakMap();
35
+ inputBoxes = new WeakMap();
35
36
  // VIS-10: the run's workspace id, captured once at construction (the daemon inherits it from the
36
37
  // operator's env before the driver is built). Required at slot() time, never in the constructor —
37
38
  // pickDriver and its unit test construct HerdrDriver without env, so slot() is the trust gate.
@@ -142,6 +143,7 @@ export class HerdrDriver {
142
143
  }
143
144
  }
144
145
  async slot(cwd, name, opts) {
146
+ const inputBox = declaredInputBoxForWorkerName(name);
145
147
  // T1 ownership contract: `opts.owned` (T2 call sites) names the pane canonically —
146
148
  // tickmarkr:<role>:<taskId>:<attempt>:<runId>. Without it, `name` passes through byte-identical
147
149
  // (today's legacy daemon/gates/consult shapes) — canonicalizeLegacyName (types.ts) is what lets
@@ -154,12 +156,13 @@ export class HerdrDriver {
154
156
  // Production dispatch names are canonical even when the gate call site supplies the already-
155
157
  // formatted name rather than SlotOpts.owned. Hold one lease across slot() → run(); legacy/manual
156
158
  // slots retain their existing allocation-only semantics for compatibility.
157
- if (parseOwnedName(resolved))
158
- return this.reserveDispatch(allocate);
159
+ const slot = parseOwnedName(resolved) ? await this.reserveDispatch(allocate) : await allocate();
160
+ if (inputBox)
161
+ this.inputBoxes.set(slot, inputBox);
159
162
  // group wins if both are set (a group tab is already stage-labeled; passing both is a caller bug).
160
163
  // label (without group) → dedicated labeled tab via tabSlot's third param: no groups-map entry, no
161
164
  // refcount, no groupSerial, no degrade latch — dedicated tabs have no shared state to guard (SUP-01).
162
- return allocate(); // label undefined → defaults to name (today's behavior)
165
+ return slot; // label undefined → defaults to name (today's behavior)
163
166
  }
164
167
  // today's per-slot tab path, plus the VIS-04 orphan reap
165
168
  // label defaults to the slot name; group tabs pass the STAGE name instead — a first-member label
@@ -397,16 +400,19 @@ export class HerdrDriver {
397
400
  // line only after two consecutive pane reads agree; an already-stable frame returns on the
398
401
  // first fresh read without a timer. A changing pane is bounded and preserves OBS-85's
399
402
  // fail-closed error instead of guessing from an adapter fingerprint.
400
- const settled = await this.settleDeliveryLine(pane, slot.cwd, transcript);
403
+ const settled = await this.settleDeliveryLine(pane, slot.cwd, transcript, this.inputBoxes.get(slot));
401
404
  transcript = settled.transcript;
402
405
  if (!settled.ok) {
403
406
  throw new Error(`herdr delivery clear failed — refusing to retype onto a corrupted line (OBS-85); pane transcript:\n${transcript}`);
404
407
  }
405
- // Clear the corrupted input line before retyping; a failed clear must NOT be retyped onto —
406
- // corrupt-prefix + clean-retype would concatenate and false-verify by containment.
407
- const cleared = await this.herdr(`pane send-keys ${shq(pane)} C-u`, slot.cwd);
408
- if (cleared.code !== 0)
409
- throw new Error(`herdr delivery clear failed — refusing to retype onto a corrupted line (OBS-85); pane transcript:\n${transcript}`);
408
+ if (!settled.recognizedInputBox) {
409
+ // Clear the corrupted shell input line before retyping; a failed clear must NOT be retyped
410
+ // onto — corrupt-prefix + clean-retype would concatenate and false-verify by containment.
411
+ // A stable adapter-declared input box is already an empty legitimate delivery target.
412
+ const cleared = await this.herdr(`pane send-keys ${shq(pane)} C-u`, slot.cwd);
413
+ if (cleared.code !== 0)
414
+ throw new Error(`herdr delivery clear failed — refusing to retype onto a corrupted line (OBS-85); pane transcript:\n${transcript}`);
415
+ }
410
416
  }
411
417
  const typed = await this.herdr(`pane send-text ${shq(pane)} ${shq(cmd)}`, slot.cwd);
412
418
  if (typed.code !== 0)
@@ -423,20 +429,25 @@ export class HerdrDriver {
423
429
  }
424
430
  throw new Error(`herdr delivery corrupted after ${DELIVERY_ATTEMPTS} attempts — enter never pressed (OBS-85); pane transcript:\n${transcript}`);
425
431
  }
426
- async settleDeliveryLine(pane, cwd, initialTranscript) {
432
+ async settleDeliveryLine(pane, cwd, initialTranscript, inputBox) {
427
433
  let transcript = initialTranscript;
428
434
  for (let readAttempt = 0; readAttempt < DELIVERY_SETTLE_READ_ATTEMPTS; readAttempt++) {
429
435
  const read = await this.herdr(`pane read ${shq(pane)} --source recent-unwrapped --lines ${DELIVERY_READ_LINES}`, cwd);
430
436
  if (read.code !== 0)
431
- return { ok: false, transcript: read.stdout || transcript };
432
- if (read.stdout === transcript)
433
- return { ok: true, transcript };
437
+ return { ok: false, transcript: read.stdout || transcript, recognizedInputBox: false };
438
+ if (read.stdout === transcript) {
439
+ return {
440
+ ok: true,
441
+ transcript,
442
+ recognizedInputBox: inputBox !== undefined && matchesInputBox(transcript, inputBox),
443
+ };
444
+ }
434
445
  transcript = read.stdout;
435
446
  if (readAttempt < DELIVERY_SETTLE_READ_ATTEMPTS - 1) {
436
447
  await new Promise((resolve) => setTimeout(resolve, DELIVERY_SETTLE_POLL_MS));
437
448
  }
438
449
  }
439
- return { ok: false, transcript };
450
+ return { ok: false, transcript, recognizedInputBox: false };
440
451
  }
441
452
  async deliveryReadMatches(pane, cmd, cwd) {
442
453
  const read = await this.herdr(`pane read ${shq(pane)} --source recent-unwrapped --lines ${DELIVERY_READ_LINES}`, cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "1.74.0",
3
+ "version": "1.75.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,8 +15,9 @@ When working in a multi-agent terminal environment, decide your role before star
15
15
  - **Orchestrator:** your session was started to execute the mission. Rename your own tab/pane `ORCH · <version>` (short labels: ≤20 chars, `ROLE · token`) and run the loop below.
16
16
  - **Supervisor with a live orchestrator:** do not start a second run. Relay the mission to the existing orchestrator with a [verified handoff](#verified-handoffs-agent-to-agent-messaging), then supervise it as OVERSEER.
17
17
  - **Primary session without an orchestrator:** rename your own tab `OVERSEER · <version>` and your agent `overseer`, spawn one child orchestration session with your host's launch form, label its tab `ORCH · <version>` and name its agent, give it the mission and these rules verbatim, then supervise it. Do not drive a duplicate single-tier run yourself. Before spawning, confirm any PREVIOUS orchestrator has stood down (monitors stopped, input box empty — dim ghost-text suggestions are UI, not queued input; ANSI-verify before alarming) and close its tab — the journal, records, and ledger hold the story; scrollback is disposable.
18
- - **Claude Code:** `herdr agent start orchestrator --cwd <repo> --no-focus -- claude --permission-mode bypassPermissions`
19
- - **Codex:** `herdr agent start orchestrator --cwd <repo> --no-focus -- codex --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
18
+ - **Spawning on current herdr is two-step** — the one-shot `agent start --cwd/--tab/--no-focus` form was removed in the herdr CLI redesign and now fails with `unknown option` (OBS-138). First create the pane: `herdr tab create --workspace <ws> --cwd <repo> --label "ORCH · <version>"` (tab create does not steal focus unless `--focus` is passed; parse `result.root_pane.pane_id` from its JSON), then start the agent in it:
19
+ - **Claude Code:** `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions`
20
+ - **Codex:** `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
20
21
  - **Auxiliary agents you spawn (consultants, reviewers, scouts) follow the same forms.** Never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions` (tickmarkr's own adapter uses exactly this for workers, judges, and consults). A read-only codex consultant may use `--sandbox read-only`; any codex session that must touch git needs the unsandboxed flag above.
21
22
 
22
23
  Outside a multi-agent terminal environment, run the loop directly.
@@ -14,8 +14,9 @@ When working in a multi-agent terminal environment, decide your role before star
14
14
  - **Orchestrator:** your session was started to execute the mission. Rename your own tab/pane `ORCH · <version>` (short labels: ≤20 chars, `ROLE · token`) and run the loop below.
15
15
  - **Supervisor with a live orchestrator:** do not start a second run. Relay the mission to the existing orchestrator with a [verified handoff](#verified-handoffs-agent-to-agent-messaging), then supervise it as OVERSEER.
16
16
  - **Primary session without an orchestrator:** rename your own tab `OVERSEER · <version>` and your agent `overseer`, spawn one child orchestration session with your host's launch form, label its tab `ORCH · <version>` and name its agent, give it the mission and these rules verbatim, then supervise it. Do not drive a duplicate single-tier run yourself. Before spawning, confirm any PREVIOUS orchestrator has [stood down](#stand-down-mission-end-and-retirement) and close its tab.
17
- - **Claude Code:** `herdr agent start orchestrator --cwd <repo> --no-focus -- claude --permission-mode bypassPermissions`
18
- - **Codex:** `herdr agent start orchestrator --cwd <repo> --no-focus -- codex --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
17
+ - **Spawning on current herdr is two-step** — the one-shot `agent start --cwd/--tab/--no-focus` form was removed in the herdr CLI redesign and now fails with `unknown option` (OBS-138). First create the pane: `herdr tab create --workspace <ws> --cwd <repo> --label "ORCH · <version>"` (tab create does not steal focus unless `--focus` is passed; parse `result.root_pane.pane_id` from its JSON), then start the agent in it:
18
+ - **Claude Code:** `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions`
19
+ - **Codex:** `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
19
20
  - **Auxiliary agents you spawn (consultants, reviewers, scouts) follow the same forms.** Never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions` (tickmarkr's own adapter uses exactly this for workers, judges, and consults). A read-only codex consultant may use `--sandbox read-only`; any codex session that must touch git needs the unsandboxed flag above.
20
21
 
21
22
  Outside a multi-agent terminal environment, run the loop directly.
@@ -29,7 +29,7 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
29
29
  main name plus at most ONE hot-state token. Vocabulary: ORCH carries the milestone and progress
30
30
  fraction (`ORCH · v1.19 4/5`, updated on every task-done); WORKERS carries the task token (tickmarkr
31
31
  updates it). Never long context strings or ✓-chains.
32
- 2. **Orchestrator**: Launch the orchestrator with your agent host. For Claude Code, use `herdr agent start orchestrator --cwd <repo> --no-focus -- claude --permission-mode bypassPermissions` (pin a strong model with `--model <m>` if the operator has a policy). For Codex, use `herdr agent start orchestrator --cwd <repo> --no-focus -- codex --dangerously-bypass-approvals-and-sandbox` (add `--model <m>` to specify the model). The unsandboxed flag is REQUIRED: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation — do not downgrade it. Workers you never spawn — tickmarkr spawns its own visible worker panes. Auxiliary agents you do spawn (consultants, reviewers, scouts) follow the same forms: never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions`, and a read-only codex consultant may use `--sandbox read-only`.
32
+ 2. **Orchestrator**: Launch the orchestrator with your agent host. Spawning on current herdr is two-step — the one-shot `agent start --cwd` form was removed in the herdr CLI redesign and now fails with `unknown option` (OBS-138): first create the pane with `herdr tab create --workspace <ws> --cwd <repo> --label "ORCH · <version>"` and parse `result.root_pane.pane_id` from its JSON, then start the agent in it. For Claude Code, use `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions` (append `--model <m>` after the `--` if the operator has a policy). For Codex, use `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` (add `--model <m>` to specify the model). The unsandboxed flag is REQUIRED: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation — do not downgrade it. Workers you never spawn — tickmarkr spawns its own visible worker panes. Auxiliary agents you do spawn (consultants, reviewers, scouts) follow the same forms: never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions`, and a read-only codex consultant may use `--sandbox read-only`.
33
33
  3. **Standing instructions travel as a brief FILE, never as pane text** — PTY input truncates at ~1024B and a
34
34
  truncated brief silently drops policy. Write the full brief to `<repo>/.tickmarkr/overseer/ORCH-BRIEF.md`
35
35
  (inside the tickmarkr state dir — already self-gitignored, no exclude step needed), then send one line: