talking-stick 0.4.1 → 0.4.3

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.
package/README.md CHANGED
@@ -93,6 +93,7 @@ tt list — which rooms exist under a path
93
93
  tt join — join the room for this workspace
94
94
  tt leave — explicitly leave a room; deletes it when no active members remain
95
95
  tt wait — block until the stick is available, with takeover signals
96
+ tt wait --park — stay coordinated without auto-claiming idle rooms
96
97
  tt release — normal handoff to the next fair waiter, with structured Handoff
97
98
  tt assign — explicit handoff to a named agent
98
99
  tt take — deliberate claim when the prior holder is gone/stuck
@@ -106,7 +107,7 @@ tt instructions — editable collaboration prompt loaded by the skill
106
107
 
107
108
  A workspace maps to a room — usually the `git` root or nearest project marker — so two agents `cd`'d anywhere under the same repo join the same room automatically.
108
109
 
109
- The global skill tells the model when to join, wait, verify its guardian, take over, leave notes, send messages, and hand off.
110
+ The global skill tells the model when to join, wait, take over, leave notes, send messages, and hand off.
110
111
 
111
112
  ## Editable collaboration instructions
112
113
 
@@ -146,7 +147,7 @@ tt events --wait|--follow [--event TYPE[,TYPE]] [--target self|any|agent]
146
147
  - `tt msg recv --wait` exits on the next matching batch — ideal for harnesses that can launch a background command and notice when it completes; restart with `--after <last_event_seq>` to resume.
147
148
  - `tt events --wait` and `tt events --follow` default to `--target self`; pass `--target any` only for audit/debug views.
148
149
  - `wait_for_events` is observer-safe: it never mutates room state, so non-holders can use it freely without disturbing turn-fairness bookkeeping.
149
- - Event receive does not grant the stick. Agents must still use `tt wait` for ownership and verify the returned guardian before editing shared files.
150
+ - Event receive does not grant the stick. Agents must still use `tt wait` for ownership before editing shared files.
150
151
 
151
152
  **When to message vs note vs handoff.**
152
153
 
@@ -182,8 +183,8 @@ tt whoami [--explain] # show the resolved C
182
183
  tt list [path] # list rooms
183
184
  tt join [path] [--force-new] # join the room for path
184
185
  tt leave [path] # leave the room for path
185
- tt wait [path] [--timeout 110s] # block until your turn
186
- tt try [path] # non-blocking claim attempt
186
+ tt wait [path] [--timeout 110s] [--park] # block until your turn; --park disables idle auto-claim
187
+ tt try [path] [--park] # non-blocking claim attempt
187
188
  tt state [path] # full room state
188
189
  tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent] # room event log; --wait/--follow long-polls
189
190
  tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR] # send an OOB message
@@ -60,6 +60,9 @@ export function formatWaitResult(result) {
60
60
  : "";
61
61
  return `Not your turn — turn ${result.turn_id ?? "?"} is reserved for ${result.reserved_for}${deadline}.`;
62
62
  }
63
+ if (result.reason === "auto_claim_disabled") {
64
+ return "Parked — auto-claim disabled; idle room left untouched.";
65
+ }
63
66
  return "Not your turn yet.";
64
67
  }
65
68
  case "closed":
@@ -123,8 +126,8 @@ Commands:
123
126
  tt join [path] [--force-new]
124
127
  tt leave [path]
125
128
  tt kick <agent_id> [path] [--reason TEXT] [--force]
126
- tt wait [path] [--timeout 110s]
127
- tt try [path]
129
+ tt wait [path] [--timeout 110s] [--park]
130
+ tt try [path] [--park]
128
131
  tt state [path]
129
132
  tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent]
130
133
  tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR]
@@ -137,7 +137,7 @@ export const COMMAND_REGISTRY = [
137
137
  needsRuntime: true,
138
138
  startupMaintenance: true,
139
139
  internal: false,
140
- usage: "tt wait [path] [--timeout 110s]",
140
+ usage: "tt wait [path] [--timeout 110s] [--park]",
141
141
  description: "Wait until this agent can claim the stick.",
142
142
  handler: ({ runtime, parsed, cliEntryUrl }) => handleWaitCommand(requireRuntime(runtime), parsed, false, cliEntryUrl)
143
143
  },
@@ -146,7 +146,7 @@ export const COMMAND_REGISTRY = [
146
146
  needsRuntime: true,
147
147
  startupMaintenance: true,
148
148
  internal: false,
149
- usage: "tt try [path]",
149
+ usage: "tt try [path] [--park]",
150
150
  description: "Check turn availability without waiting.",
151
151
  handler: ({ runtime, parsed, cliEntryUrl }) => handleWaitCommand(requireRuntime(runtime), parsed, true, cliEntryUrl)
152
152
  },
@@ -2,28 +2,31 @@ import { clearCliSessionLease, createSystemProcessInspector, findCliSessionByRoo
2
2
  import { checkGuardianLiveness, spawnGuardian, stopGuardian } from "./guardian.js";
3
3
  import { resolveHandoff } from "./handoff.js";
4
4
  import { deriveCliIdentity, resolveTakeoverReason, shouldUseOperatorOverride } from "./identity.js";
5
- import { parseWaitTimeout } from "./parser.js";
5
+ import { hasOption, normalizeBooleanFlag, parseWaitTimeout } from "./parser.js";
6
6
  import { formatWaitResult, printResult } from "./output.js";
7
7
  import { requireLeaseSession, upsertSessionFromJoin } from "./session.js";
8
8
  export async function handleWaitCommand(runtime, parsed, isTry, cliEntryUrl) {
9
+ normalizeBooleanFlag(parsed, "park");
10
+ const park = hasOption(parsed, "park");
9
11
  const contextPath = parsed.positionals[0] ?? process.cwd();
10
12
  const identity = deriveCliIdentity(parsed);
11
13
  const joined = runtime.commands.joinPath(identity, { context_path: contextPath });
12
14
  upsertSessionFromJoin(identity, joined);
13
15
  const waitResult = await runtime.commands.waitForTurn(identity, {
14
16
  room_id: joined.room_id,
15
- max_wait_ms: isTry ? 0 : parseWaitTimeout(parsed)
17
+ max_wait_ms: isTry ? 0 : parseWaitTimeout(parsed),
18
+ auto_claim: park ? false : undefined
16
19
  });
17
20
  if (waitResult.status === "your_turn") {
18
21
  if (waitResult.reason === "already_owner") {
19
22
  const sessionPath = resolveCliSessionPath();
20
23
  const existing = findCliSessionByRoom(sessionPath, identity.agent_id, joined.room_id);
21
- const liveness = existing
24
+ const liveness = existing?.guardian_pid
22
25
  ? checkGuardianLiveness({
23
26
  pid: existing.guardian_pid,
24
27
  process_started_at: existing.guardian_process_started_at
25
28
  }, createSystemProcessInspector())
26
- : "unknown";
29
+ : "gone";
27
30
  if (liveness === "gone") {
28
31
  const replacement = await spawnGuardian({
29
32
  agentId: identity.agent_id,
@@ -44,7 +47,12 @@ export async function handleWaitCommand(runtime, parsed, isTry, cliEntryUrl) {
44
47
  guardian_process_started_at: replacement.process_started_at,
45
48
  updated_at: new Date().toISOString()
46
49
  });
47
- printResult(parsed, { ...waitResult, guardian_pid: replacement.pid }, () => `Already holding the stick (turn ${waitResult.turn_id}). Prior guardian was gone; spawned replacement ${replacement.pid}.`);
50
+ printResult(parsed, { ...waitResult, guardian_pid: replacement.pid }, () => {
51
+ const reason = existing?.guardian_pid
52
+ ? "Prior guardian was gone"
53
+ : "No guardian was recorded";
54
+ return `Already holding the stick (turn ${waitResult.turn_id}). ${reason}; spawned replacement ${replacement.pid}.`;
55
+ });
48
56
  return;
49
57
  }
50
58
  const guardianPid = existing?.guardian_pid;
package/dist/commands.js CHANGED
@@ -37,7 +37,8 @@ export class TalkingStickCommands {
37
37
  return this.service.waitForTurn({
38
38
  agent_id: identity.agent_id,
39
39
  room_id: input.room_id,
40
- max_wait_ms: input.max_wait_ms
40
+ max_wait_ms: input.max_wait_ms,
41
+ auto_claim: input.auto_claim
41
42
  });
42
43
  }
43
44
  heartbeat(identity, input) {
@@ -6,7 +6,9 @@ import { resolveContextPath } from "./path-resolution.js";
6
6
  export const DEFAULT_MAX_INSTRUCTION_FILE_BYTES = 256 * 1024;
7
7
  export const DEFAULT_INSTRUCTIONS_MARKDOWN = `# Talking Stick collaboration instructions
8
8
 
9
- Keep using Talking Stick until the shared task is done. After releasing or handing off, re-enter the wait loop by default. Prefer continued action unless the task is complete or the operator explicitly redirects or stops the room. If you are the only active member of the room, stop polling after a clear handoff rather than churning release/reclaim turns.
9
+ Keep using Talking Stick until the shared task is done. After releasing or handing off, re-enter the wait loop by default. Prefer continued action unless the task is complete or the operator explicitly redirects or stops the room. If you are the only active member of the room, stop polling after a clear handoff rather than churning release/reclaim turns. If you have no expected work and are blocked on operator input or an external signal, use \`tt wait --park --json\` so you stay coordinated without auto-claiming idle turns.
10
+
11
+ On freshly invoked multi-agent tasks, give peers a short window to join before deciding you are alone. Use a normal wait timeout or spend about a minute on read-only repo orientation while other harnesses appear.
10
12
 
11
13
  Use phase names in handoffs when they clarify the work: draft, adversarial review, convergence, implementation, implementation review, test review, and release. These phases are vocabulary, not protocol state.
12
14
 
package/dist/service.js CHANGED
@@ -656,6 +656,19 @@ export class TalkingStickService {
656
656
  };
657
657
  }
658
658
  if (!room.owner && !room.reserved_for) {
659
+ const autoClaim = input.auto_claim ?? true;
660
+ if (!autoClaim) {
661
+ return {
662
+ status: "not_yet",
663
+ room_state: inspection.state,
664
+ turn_id: room.turn_id,
665
+ current_owner: room.owner ?? undefined,
666
+ reserved_for: room.reserved_for ?? undefined,
667
+ lease_expires_at: room.lease_expires_at ?? undefined,
668
+ claim_expires_at: room.claim_expires_at ?? undefined,
669
+ reason: "auto_claim_disabled"
670
+ };
671
+ }
659
672
  if (this.shouldDeferIdleClaim(room, input.agent_id, now)) {
660
673
  return {
661
674
  status: "not_yet",
@@ -0,0 +1,137 @@
1
+ # Park mode for `tt wait`
2
+
3
+ **Status:** Implemented in working tree (claude:b8175be6 + codex:0c293df7). Original checklist retained as design context.
4
+
5
+ **Origin:** Coordination churn observed during the guardian-contract session on 2026-05-10. After a no-work release, the just-released agent's next `tt wait` auto-claimed the idle room because the existing `shouldDeferIdleClaim` cooldown is gated on `hasOtherActiveRoomMember`, which was false when the other agent went briefly inactive. Sequence: claim → release → claim → release with no work in between (turns 247–248 in this room; turns 1205–1291 earlier with claude:b2c853ee). Independently surfaced from both sides; design draft in room note `722adc99-1f1e-4e83-a950-5176dce3ae1c`.
6
+
7
+ ## Problem
8
+
9
+ `tt wait` is overloaded as two operations:
10
+
11
+ 1. Wait for a turn that will be handed to me (reserved_for me, pass/assign to me, takeover).
12
+ 2. Claim the room if it is idle (auto-claim).
13
+
14
+ The second operation is correct when the caller has work. When the caller has no work but is staying coordinated (waiting on operator input or other external signal), it produces the churn pattern. The existing `priorOwnerReleaseCooldownMs` heuristic helps but can't encode operator-wait intent — only the caller knows whether they have work.
15
+
16
+ ## Design
17
+
18
+ Add a protocol-level opt-out for the idle auto-claim. The two operations split cleanly:
19
+
20
+ - **`tt wait`** (current behavior, unchanged): caller is willing to take the stick now. Auto-claim is on.
21
+ - **`tt wait --park`**: caller wants to stay coordinated but only act on explicit signals. Auto-claim is off. Already-owner, reserved-to-me, pass-to-me, and takeover-available still return `your_turn` / `takeover_available` normally.
22
+
23
+ **Protocol field:** `WaitForTurnInput.auto_claim?: boolean` (default `true`). The CLI flag `--park` sets `auto_claim: false`. Naming separation is deliberate — `auto_claim` is the precise protocol invariant; `--park` is the UX vocabulary.
24
+
25
+ **Filtered branches under `auto_claim: false`:**
26
+
27
+ - `!room.owner && !room.reserved_for` → `grantTurn` (service.ts:1107) is the **only** auto-claim path. Park returns `not_yet` with reason `auto_claim_disabled` here.
28
+
29
+ **Preserved branches:**
30
+
31
+ - already_owner (service.ts:1090) — true signal, return your_turn.
32
+ - reserved_for === caller (service.ts:1138) — explicit pass, return your_turn.
33
+ - recipient_gone / owner_gone / stale_owner / claim_timeout (service.ts:1122, 1146, 1162, 1173) — return takeover_available. Park = no automatic ownership, not blind recovery.
34
+ - closed (service.ts:1086) — unchanged.
35
+ - default not_yet (service.ts:1184) — unchanged.
36
+
37
+ **Fair routing:** parked agents stay eligible for `tt assign next`. Park opts out of automatic claim, not explicit routing.
38
+
39
+ **Cooldown:** `shouldDeferIdleClaim` is unchanged. Park is the explicit-intent mechanism; the cooldown is a heuristic for the no-park case. Tightening the cooldown to fire when alone would be a silent semantic change for plain `tt wait` callers and is the wrong tool.
40
+
41
+ ## Implementation
42
+
43
+ ### `src/service.ts`
44
+
45
+ Add `auto_claim?: boolean` to `WaitForTurnInput`. In `waitForTurnOnce` (line 1078), gate the idle branch:
46
+
47
+ ```ts
48
+ if (!room.owner && !room.reserved_for) {
49
+ const autoClaim = input.auto_claim ?? true;
50
+ if (!autoClaim) {
51
+ return {
52
+ status: "not_yet",
53
+ room_state: inspection.state,
54
+ turn_id: room.turn_id,
55
+ reason: "auto_claim_disabled"
56
+ };
57
+ }
58
+ if (this.shouldDeferIdleClaim(room, input.agent_id, now)) {
59
+ return { status: "not_yet", /* existing fields */ };
60
+ }
61
+ return this.grantTurn(room, input.agent_id, now);
62
+ }
63
+ ```
64
+
65
+ Add `auto_claim_disabled` to the `not_yet` reason union.
66
+
67
+ ### MCP surface
68
+
69
+ No MCP schema change is needed in the current CLI-only implementation. The older `src/mcp-server.ts` surface no longer exists; `src/commands.ts` carries the command-level `auto_claim` field through to the service.
70
+
71
+ ### `src/cli/parser.ts`
72
+
73
+ Use `normalizeBooleanFlag(parsed, "park")` in the wait/try handler so `--park` can appear before or after the optional path. The generic parser still consumes the next non-`--` token by default; normalization restores that consumed token as a positional for this boolean flag.
74
+
75
+ ### `src/cli/turn-commands.ts`
76
+
77
+ In `handleWaitCommand`:
78
+
79
+ ```ts
80
+ normalizeBooleanFlag(parsed, "park");
81
+ const park = hasOption(parsed, "park");
82
+ const waitResult = await runtime.commands.waitForTurn(identity, {
83
+ room_id: joined.room_id,
84
+ max_wait_ms: isTry ? 0 : parseWaitTimeout(parsed),
85
+ auto_claim: park ? false : undefined
86
+ });
87
+ ```
88
+
89
+ Update `formatWaitResult` to print `Parked — auto-claim disabled; idle room left untouched.` when `status: "not_yet"` and `reason: "auto_claim_disabled"`.
90
+
91
+ ### `skills/talking-stick/SKILL.md`
92
+
93
+ In §8 "After Release, Stay In The Loop", add after the "stop polling if only active member" rule:
94
+
95
+ > If you have no expected work and are blocked on operator input or external signal, use `tt wait --park` instead of `tt wait` to stay coordinated without claiming idle turns. Park returns `your_turn` only for explicit signals (reserved-to-me, pass/assign to me, takeover-available); it never auto-claims an idle room. Switch back to plain `tt wait` once you have work to do.
96
+
97
+ Add `tt wait --park` to the CLI list in §1.
98
+
99
+ ### `README.md`
100
+
101
+ Add `--park` to the `tt wait` line in the CLI cheat sheet (~line 100). One-line description under it: "Stay coordinated without auto-claiming idle turns."
102
+
103
+ ### `CHANGELOG.md`
104
+
105
+ Unreleased Added entry:
106
+
107
+ ```
108
+ - **`tt wait --park`.** New flag opts out of idle-room auto-claim while keeping the agent coordinated for explicit passes, assignments, and takeover signals. Use when waiting on operator input without intent to take the next idle turn. Protocol-level field is `wait_for_turn.auto_claim` (default true).
109
+ ```
110
+
111
+ ## Tests
112
+
113
+ Add to `tests/talking-stick.test.ts` and `tests/cli.test.ts`:
114
+
115
+ - **service**: `waitForTurn with auto_claim=false on an idle room returns not_yet with reason auto_claim_disabled` (regression for this session's churn).
116
+ - **service**: `waitForTurn with auto_claim=false when reserved_for == caller returns your_turn`.
117
+ - **service**: `waitForTurn with auto_claim=false when caller is already owner returns your_turn`.
118
+ - **service**: `waitForTurn with auto_claim=false surfaces takeover_available for stale owner` (and for claim_timeout, recipient_gone, owner_gone).
119
+ - **service**: `waitForTurn with auto_claim=false returns not_yet when another agent owns the stick`.
120
+ - **service**: plain `waitForTurn` (auto_claim default true) still claims idle rooms — pin no-regression.
121
+ - **CLI**: `tt wait --park` against an idle room exits with not_yet, no guardian spawned, no claim event in the log.
122
+ - **CLI**: `tt wait --park` against a reservation-to-me returns your_turn with a live guardian.
123
+
124
+ ## Out of scope
125
+
126
+ - No change to `shouldDeferIdleClaim` (line 2066) or `priorOwnerReleaseCooldownMs`.
127
+ - No new `tt park` verb. `--park` flag is the only surface in v1.
128
+ - No change to how parked agents are heartbeat-tracked; they remain active members.
129
+ - No change to fair-routing eligibility; parked agents stay in the `tt assign next` pool.
130
+
131
+ ## Verification
132
+
133
+ `npm run typecheck && npm test && npm run build`. Manual smoke: in a two-agent room, agent A releases with no work, runs `tt wait --park --timeout 5s` — expect `not_yet, reason: auto_claim_disabled` and no claim event. Then agent B does `tt assign next` — agent A's next park wait should return `your_turn`. Then agent A releases and runs plain `tt wait --timeout 5s` — expect your_turn (auto-claim restored).
134
+
135
+ ## Commit message
136
+
137
+ `Add tt wait --park for non-claiming coordination`
@@ -26,7 +26,7 @@
26
26
  ## Consumer Responsibilities
27
27
 
28
28
  - Keep `wait_for_turn` / `tt wait` running separately. Receive processes do not claim or grant the stick, even when they return pass, release, or assignment events.
29
- - Treat an event wake as a prompt to read, reply, or retry `tt wait`. It is not permission to mutate shared files; only a `your_turn` wait result with a live guardian grants ownership.
29
+ - Treat an event wake as a prompt to read, reply, or retry `tt wait`. It is not permission to mutate shared files; only a `your_turn` wait result grants ownership.
30
30
  - Decide how to surface `delivery_hint=interrupt`; the server only records the hint.
31
31
  - Dedupe on `event_id` if restart replay is possible.
32
32
  - Treat message bodies as room-visible text, not private data.
@@ -0,0 +1,41 @@
1
+ # Talking Stick 0.4.2
2
+
3
+ Date: 2026-05-10
4
+
5
+ This patch moves guardian liveness back into the CLI contract. Agents should not
6
+ manually inspect guardian PIDs after `tt wait`; a `your_turn` result means the
7
+ CLI has confirmed or spawned the guardian it needs, and guardian setup failures
8
+ surface as command failures.
9
+
10
+ ## Fixed
11
+
12
+ ### Guardian ownership contract
13
+
14
+ `tt wait` now repairs an already-owned turn when the local CLI session has no
15
+ recorded guardian PID. This can happen after local session-file loss or partial
16
+ state cleanup: the room still knows the caller owns the turn, but the foreground
17
+ CLI no longer has a guardian process recorded. Instead of returning
18
+ `your_turn` with `guardian_pid: null`, the CLI now starts a replacement guardian,
19
+ records it, and returns that PID.
20
+
21
+ Known-uncertain liveness with an existing PID is still left alone. That avoids
22
+ respawning guardians repeatedly on platforms or process-start formats where
23
+ exact liveness cannot be proven.
24
+
25
+ ### Harness guidance
26
+
27
+ The bundled skill, README, and receive-consumer contract now describe
28
+ `tt wait`/`tt take` as the ownership gate. Event streams remain observer-only:
29
+ an event wake can tell an agent to read, reply, or retry `tt wait`, but it does
30
+ not grant workspace write authority.
31
+
32
+ ## Verification
33
+
34
+ ```bash
35
+ npm run typecheck
36
+ npx vitest run tests/cli.test.ts -t guardian
37
+ npm test
38
+ npm run build
39
+ node dist/cli.js --help
40
+ git diff --check
41
+ ```
@@ -0,0 +1,48 @@
1
+ # Talking Stick 0.4.3
2
+
3
+ Date: 2026-05-11
4
+
5
+ This patch adds a parked wait mode for agents that need to remain coordinated
6
+ without taking an idle turn. It addresses release/reclaim churn during pauses
7
+ for operator input while preserving the existing `tt wait` behavior for agents
8
+ that are ready to work.
9
+
10
+ ## Added
11
+
12
+ ### `tt wait --park`
13
+
14
+ `tt wait --park` and `tt try --park` now opt out of idle-room auto-claim. The
15
+ underlying service/command field is `auto_claim`, which defaults to true for
16
+ the existing behavior. When false, an idle room returns `not_yet` with
17
+ `reason: "auto_claim_disabled"` and does not mint a claim event or start a
18
+ guardian.
19
+
20
+ Parked waits still return actionable signals:
21
+
22
+ - already-owned turns remain `your_turn`
23
+ - explicit passes or assignments to the caller still become `your_turn`
24
+ - takeover availability is still surfaced
25
+
26
+ Plain `tt wait` is unchanged and still auto-claims idle rooms.
27
+
28
+ ## Changed
29
+
30
+ ### Coordination guidance
31
+
32
+ The bundled skill and default editable instructions now tell agents to use
33
+ `tt wait --park --json` when they have no expected work and are blocked on
34
+ operator input or an external signal. They also remind freshly invoked agents
35
+ to give peers a short window to join before concluding they are alone, using
36
+ normal waits or read-only repo orientation while other harnesses appear.
37
+
38
+ ## Verification
39
+
40
+ ```bash
41
+ npm run typecheck
42
+ npx vitest run tests/talking-stick.test.ts tests/cli.test.ts -t "auto_claim=false|tt wait --park|auto_claim default"
43
+ npm test
44
+ npm run build
45
+ node dist/cli.js --help
46
+ git diff --check
47
+ npm pack --dry-run
48
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talking-stick",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "CLI coordination tool for path-scoped agent handoffs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@ Useful commands:
32
32
  - `tt whoami --json`
33
33
  - `tt join --json`
34
34
  - `tt wait --json`
35
+ - `tt wait --park --json`
35
36
  - `tt try --json`
36
37
  - `tt state --json`
37
38
  - `tt events --after N --target any --json`
@@ -60,6 +61,8 @@ tt join --json
60
61
 
61
62
  Keep the returned room id and canonical path in mind. The current working directory is the implicit path for normal commands; pass an explicit path only when coordinating a different directory or intentionally selecting a nested room.
62
63
 
64
+ On freshly invoked multi-agent tasks, give peers a short window to join before deciding you are alone. Use a normal wait timeout or spend about a minute on read-only repo orientation while other harnesses appear.
65
+
63
66
  After joining, load editable collaboration instructions once:
64
67
 
65
68
  ```sh
@@ -95,7 +98,7 @@ Possible outcomes:
95
98
  - `takeover_available`: surface the reason and make takeover explicit
96
99
  - `closed`: stop and explain that the room is closed
97
100
 
98
- A successful `tt wait` or `tt take` starts an internal `tt guard` lease guardian and returns `guardian_pid` in JSON. Verify the field is present and the pid is alive before you start a long edit; the guardian is what keeps your lease from expiring after the foreground `tt wait` process exits. If `guardian_pid` is missing or the pid is gone, stop, run `tt wait` again to repair the guardian (it will detect the existing ownership and respawn the guardian), and only then continue. Do not kill that guardian.
101
+ A successful `tt wait` or `tt take` starts an internal `tt guard` lease guardian and returns `guardian_pid` in JSON. Trust `tt wait`: a `your_turn` result means the CLI confirmed or spawned a guardian, and if it could not, the command would have failed. Do not kill that guardian.
99
102
 
100
103
  ### 4. While Waiting
101
104
 
@@ -103,7 +106,7 @@ Prefer to run `tt wait` in the background if your harness supports background co
103
106
 
104
107
  Prefer wait cycles over scheduled wakeups. A direct long-poll stays aligned with other agents and usually notices a released stick within the same cycle. Use scheduled wakeups only when your harness cannot keep a wait running in the background.
105
108
 
106
- Do not replace `tt wait` with an event receiver. `tt events --wait` is only a wake channel for messages and handoff/reservation events. If it exits with a pass, release, assignment, or message, process the event, then run or continue `tt wait --json`; do not touch shared files unless that wait returns `your_turn` and a live `guardian_pid`.
109
+ Do not replace `tt wait` with an event receiver. `tt events --wait` is only a wake channel for messages and handoff/reservation events. If it exits with a pass, release, assignment, or message, process the event, then run or continue `tt wait --json`; do not touch shared files unless that wait returns `your_turn`.
107
110
 
108
111
  If you do not have the stick:
109
112
 
@@ -226,6 +229,8 @@ Exit the wait loop only when one of these is true:
226
229
 
227
230
  In every other case, after `tt release` or `tt assign`, go straight back into `tt wait --json`. If you are the only active member of the room, stop polling after a clear handoff. Treat "only active" as no other member that `tt state --json` reports active or that has been seen in the last hour; if liveness is ambiguous, run one more normal wait cycle instead of churning. Other agents going briefly quiet is not enough to declare yourself alone.
228
231
 
232
+ If you have no expected work and are blocked on operator input or an external signal, use `tt wait --park --json` instead of `tt wait --json` to stay coordinated without claiming idle turns. Park still surfaces explicit passes, assignments, and takeover availability; it never auto-claims an idle room. Switch back to plain `tt wait --json` once you have work to do.
233
+
229
234
  If the operator tells you to drop out of coordination, run `tt leave --json`. Rooms with no active members are deleted instead of kept as history, and long-idle rooms may be purged on later invocations.
230
235
 
231
236
  If the room state shows ghost members from past sessions whose processes are gone, run `tt kick <agent_id> --json` to evict them. Use `--force` only when the operator explicitly tells you to remove a still-active member.