talking-stick 0.3.0 → 0.4.1
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 +21 -3
- package/dist/cli/install-commands.js +7 -0
- package/dist/cli/instructions-commands.js +113 -0
- package/dist/cli/output.js +3 -0
- package/dist/cli/registry.js +10 -0
- package/dist/index.js +1 -0
- package/dist/instructions.js +256 -0
- package/dist/service.js +11 -7
- package/docs/plans/2026-05-06-harness-instructions-v6-converged.md +220 -0
- package/docs/releases/0.4.0.md +71 -0
- package/docs/releases/0.4.1.md +38 -0
- package/package.json +1 -1
- package/skills/talking-stick/SKILL.md +9 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI coordination tool that lets multiple AI coding agents share a single workspace without stepping on each other. One agent holds the stick at a time; handoffs carry structured context so the next agent doesn't have to re-derive it.
|
|
4
4
|
|
|
5
|
-
**Version:** 0.
|
|
5
|
+
**Version:** 0.4.1. Multi-process-safe (SQLite WAL), liveness-aware, no daemon. Supports Claude Code, Codex CLI, Gemini CLI, and OpenCode out of the box. Two agents in the same room can also chat out-of-band — without passing the stick — via `tt msg send/recv`.
|
|
6
6
|
|
|
7
7
|
## Quickstart
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ npm i -g talking-stick
|
|
|
20
20
|
tt install --all
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Restart any harness that was already running so it loads the updated skill. The skill teaches agents to coordinate by running `tt` CLI commands from the workspace.
|
|
23
|
+
Restart any harness that was already running so it loads the updated skill. The skill teaches agents to coordinate by running `tt` CLI commands from the workspace. To tune the default collaboration prompt without editing installed package files, run `tt instructions edit`.
|
|
24
24
|
|
|
25
25
|
### 3. Try it: two agents, one repo
|
|
26
26
|
|
|
@@ -46,7 +46,7 @@ That's the whole workflow. They negotiate turns automatically, hand off structur
|
|
|
46
46
|
|
|
47
47
|
| Method | Command | Notes |
|
|
48
48
|
|---|---|---|
|
|
49
|
-
| **From npm** | `npm i -g talking-stick` | Published as `0.
|
|
49
|
+
| **From npm** | `npm i -g talking-stick` | Published as `0.4.1`. Requires Node ≥ 22. |
|
|
50
50
|
| **From GitHub** | `npm i -g github:mostlydev/talking-stick` | Tracks the `master` branch; builds on install via the `prepare` hook. |
|
|
51
51
|
| **From source** | `git clone … && npm install && npm link` | For contributors. |
|
|
52
52
|
|
|
@@ -101,12 +101,27 @@ tt state — authoritative state projection
|
|
|
101
101
|
tt events — audit log and long-poll stream of turn transitions/messages
|
|
102
102
|
tt notes add/list — durable async observations for the room
|
|
103
103
|
tt msg send/recv — out-of-band chat into the room event log
|
|
104
|
+
tt instructions — editable collaboration prompt loaded by the skill
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
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.
|
|
107
108
|
|
|
108
109
|
The global skill tells the model when to join, wait, verify its guardian, take over, leave notes, send messages, and hand off.
|
|
109
110
|
|
|
111
|
+
## Editable collaboration instructions
|
|
112
|
+
|
|
113
|
+
The bundled skill is the safety floor. It is intentionally small and package-managed. Local collaboration preferences live in editable Markdown files that `tt instructions` shows to agents after they join.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
tt instructions show # effective prompt for the detected harness
|
|
117
|
+
tt instructions show --harness codex # view one harness's effective prompt
|
|
118
|
+
tt instructions edit # edit user defaults
|
|
119
|
+
tt instructions edit --project # edit this repo's overrides
|
|
120
|
+
tt instructions reset --project # remove this repo's override
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Effective instructions are layered in this order: bundled defaults, user defaults at `${TALKING_STICK_DATA_DIR}/instructions.md` (normally `~/.local/share/talking-stick/instructions.md`), then project overrides at `.talking-stick/instructions.md` in the workspace root. User and project files are created lazily on first edit, so installing `tt` does not litter repositories or harness config directories.
|
|
124
|
+
|
|
110
125
|
## Non-owner notes
|
|
111
126
|
|
|
112
127
|
While you wait your turn you may still need to flag something to the current owner: a subtle invariant, a related bug, a pointer to a doc. Non-owner notes give you a durable channel without interrupting the turn.
|
|
@@ -173,6 +188,9 @@ tt state [path] # full room state
|
|
|
173
188
|
tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent] # room event log; --wait/--follow long-polls
|
|
174
189
|
tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR] # send an OOB message
|
|
175
190
|
tt msg recv [--wait|--follow] [--from agent] [--after N] [--target self|any|agent] [--path DIR] # receive OOB messages
|
|
191
|
+
tt instructions show [path] [--harness claude|codex|gemini|opencode|all] [--scope effective|bundled|user|project] # show collaboration prompt
|
|
192
|
+
tt instructions edit [path] [--user|--project] # edit user or project prompt
|
|
193
|
+
tt instructions reset [path] (--user|--project) # delete a user or project prompt
|
|
176
194
|
tt release [path] --status TEXT --next-action TEXT # normal handoff
|
|
177
195
|
tt pass [path] --status TEXT --next-action TEXT # pass/end your turn
|
|
178
196
|
tt assign <target|next> [path] --status TEXT --next-action TEXT # explicit handoff
|
|
@@ -29,6 +29,7 @@ export async function runInstallCommand(parsed) {
|
|
|
29
29
|
const results = (await Promise.all(harnesses.map((harness) => runSkillInstall(harness, installOptions)))).flat();
|
|
30
30
|
reportInstallResults(results, "install");
|
|
31
31
|
reportCleanupResults(await runCleanup(harnesses, "manual", installOptions), "install");
|
|
32
|
+
printInstructionHint(results);
|
|
32
33
|
}
|
|
33
34
|
export async function runUninstallCommand(parsed) {
|
|
34
35
|
normalizeBooleanFlag(parsed, "print");
|
|
@@ -222,6 +223,12 @@ function reportInstallResults(results, mode) {
|
|
|
222
223
|
throw new Error(`${mode} completed with failures.`);
|
|
223
224
|
}
|
|
224
225
|
}
|
|
226
|
+
function printInstructionHint(results) {
|
|
227
|
+
if (!results.some((result) => result.ok && !result.skipped)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
process.stdout.write("Customize collaboration instructions with: tt instructions edit\n");
|
|
231
|
+
}
|
|
225
232
|
function reportCleanupResults(results, mode) {
|
|
226
233
|
let anyFailed = false;
|
|
227
234
|
for (const result of results) {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { editInstructions, parseInstructionScope, resetInstructions, showInstructions } from "../instructions.js";
|
|
2
|
+
import { deriveCliIdentity } from "./identity.js";
|
|
3
|
+
import { getStringOption, hasOption } from "./parser.js";
|
|
4
|
+
import { printResult } from "./output.js";
|
|
5
|
+
export async function handleInstructionsCommand(parsed) {
|
|
6
|
+
const [subcommand = "show", ...rest] = parsed.positionals;
|
|
7
|
+
const subParsed = {
|
|
8
|
+
name: `instructions ${subcommand}`,
|
|
9
|
+
positionals: rest,
|
|
10
|
+
options: parsed.options
|
|
11
|
+
};
|
|
12
|
+
switch (subcommand) {
|
|
13
|
+
case "show":
|
|
14
|
+
handleInstructionsShowCommand(subParsed);
|
|
15
|
+
return;
|
|
16
|
+
case "edit":
|
|
17
|
+
await handleInstructionsEditCommand(subParsed);
|
|
18
|
+
return;
|
|
19
|
+
case "reset":
|
|
20
|
+
handleInstructionsResetCommand(subParsed);
|
|
21
|
+
return;
|
|
22
|
+
default:
|
|
23
|
+
throw new Error(`Unknown instructions subcommand: ${subcommand}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function handleInstructionsShowCommand(parsed) {
|
|
27
|
+
repairBooleanFlag(parsed, "json", 0);
|
|
28
|
+
repairBooleanFlag(parsed, "text", 0);
|
|
29
|
+
const contextPath = resolveContextPathArg(parsed);
|
|
30
|
+
const scope = parseInstructionScope(getStringOption(parsed, "scope"));
|
|
31
|
+
const identity = deriveCliIdentity(parsed);
|
|
32
|
+
const result = showInstructions({
|
|
33
|
+
harness: getStringOption(parsed, "harness"),
|
|
34
|
+
scope,
|
|
35
|
+
options: {
|
|
36
|
+
contextPath,
|
|
37
|
+
identity
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
printResult(parsed, result, () => {
|
|
41
|
+
if (result.text.trim().length > 0) {
|
|
42
|
+
return result.text;
|
|
43
|
+
}
|
|
44
|
+
return `No ${result.scope} instructions found for ${result.harness}.`;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function handleInstructionsEditCommand(parsed) {
|
|
48
|
+
repairBooleanFlag(parsed, "json", 0);
|
|
49
|
+
repairBooleanFlag(parsed, "text", 0);
|
|
50
|
+
repairBooleanFlag(parsed, "user", 0);
|
|
51
|
+
repairBooleanFlag(parsed, "project", 0);
|
|
52
|
+
const contextPath = resolveContextPathArg(parsed);
|
|
53
|
+
const scope = resolveEditableScope(parsed, false);
|
|
54
|
+
const result = await editInstructions({
|
|
55
|
+
scope,
|
|
56
|
+
options: { contextPath }
|
|
57
|
+
});
|
|
58
|
+
printResult(parsed, result, () => {
|
|
59
|
+
if (result.opened) {
|
|
60
|
+
return `Opened ${result.scope} instructions: ${result.path}`;
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
`Instructions file ready: ${result.path}`,
|
|
64
|
+
"Set $VISUAL or $EDITOR to edit it from this command."
|
|
65
|
+
].join("\n");
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function handleInstructionsResetCommand(parsed) {
|
|
69
|
+
repairBooleanFlag(parsed, "json", 0);
|
|
70
|
+
repairBooleanFlag(parsed, "text", 0);
|
|
71
|
+
repairBooleanFlag(parsed, "user", 0);
|
|
72
|
+
repairBooleanFlag(parsed, "project", 0);
|
|
73
|
+
const contextPath = resolveContextPathArg(parsed);
|
|
74
|
+
const scope = resolveEditableScope(parsed, true);
|
|
75
|
+
const result = resetInstructions({
|
|
76
|
+
scope,
|
|
77
|
+
options: { contextPath }
|
|
78
|
+
});
|
|
79
|
+
printResult(parsed, result, () => {
|
|
80
|
+
if (result.removed) {
|
|
81
|
+
return `Removed ${result.scope} instructions: ${result.path}`;
|
|
82
|
+
}
|
|
83
|
+
return `No ${result.scope} instructions file at ${result.path}`;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function resolveEditableScope(parsed, requireExplicit) {
|
|
87
|
+
const wantsUser = hasOption(parsed, "user");
|
|
88
|
+
const wantsProject = hasOption(parsed, "project");
|
|
89
|
+
if (wantsUser && wantsProject) {
|
|
90
|
+
throw new Error("Pass only one of --user or --project.");
|
|
91
|
+
}
|
|
92
|
+
if (wantsProject) {
|
|
93
|
+
return "project";
|
|
94
|
+
}
|
|
95
|
+
if (wantsUser || !requireExplicit) {
|
|
96
|
+
return "user";
|
|
97
|
+
}
|
|
98
|
+
throw new Error("Pass --user or --project to choose which instructions to reset.");
|
|
99
|
+
}
|
|
100
|
+
function resolveContextPathArg(parsed) {
|
|
101
|
+
const pathOption = parsed.options.get("path");
|
|
102
|
+
if (pathOption === true) {
|
|
103
|
+
throw new Error("--path requires a value.");
|
|
104
|
+
}
|
|
105
|
+
return pathOption ?? parsed.positionals[0] ?? process.cwd();
|
|
106
|
+
}
|
|
107
|
+
function repairBooleanFlag(parsed, key, insertAt) {
|
|
108
|
+
const value = parsed.options.get(key);
|
|
109
|
+
if (typeof value === "string") {
|
|
110
|
+
parsed.positionals.splice(insertAt, 0, value);
|
|
111
|
+
parsed.options.set(key, true);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/cli/output.js
CHANGED
|
@@ -129,6 +129,9 @@ Commands:
|
|
|
129
129
|
tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent]
|
|
130
130
|
tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR]
|
|
131
131
|
tt msg recv [--wait|--follow] [--from agent] [--after N] [--target self|any|agent] [--path DIR]
|
|
132
|
+
tt instructions show [path] [--harness claude|codex|gemini|opencode|all] [--scope effective|bundled|user|project]
|
|
133
|
+
tt instructions edit [path] [--user|--project]
|
|
134
|
+
tt instructions reset [path] (--user|--project)
|
|
132
135
|
tt release [path] (--status TEXT --next-action TEXT | --stdin)
|
|
133
136
|
tt pass [path] (--status TEXT --next-action TEXT | --stdin)
|
|
134
137
|
tt assign <target|next> [path] (--status TEXT --next-action TEXT | --stdin)
|
package/dist/cli/registry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runGuardCommand } from "./guardian.js";
|
|
2
2
|
import { runInstallCommand, runMcpMigrationCommand, runSelfUpdateCommand, runUninstallCommand } from "./install-commands.js";
|
|
3
|
+
import { handleInstructionsCommand } from "./instructions-commands.js";
|
|
3
4
|
import { handleMsgCommand } from "./msg-commands.js";
|
|
4
5
|
import { handleNotesCommand } from "./notes-commands.js";
|
|
5
6
|
import { handleEventsCommand, handleJoinCommand, handleKickCommand, handleLeaveCommand, handleListCommand, handleStateCommand, handleWhoAmICommand } from "./room-commands.js";
|
|
@@ -50,6 +51,15 @@ export const COMMAND_REGISTRY = [
|
|
|
50
51
|
description: "Remove stale Talking Stick MCP registrations.",
|
|
51
52
|
handler: ({ parsed }) => runMcpMigrationCommand(parsed)
|
|
52
53
|
},
|
|
54
|
+
{
|
|
55
|
+
name: "instructions",
|
|
56
|
+
needsRuntime: false,
|
|
57
|
+
startupMaintenance: true,
|
|
58
|
+
internal: false,
|
|
59
|
+
usage: "tt instructions [show|edit|reset] [--harness NAME] [--scope SCOPE]",
|
|
60
|
+
description: "Show, edit, or reset editable collaboration instructions.",
|
|
61
|
+
handler: ({ parsed }) => handleInstructionsCommand(parsed)
|
|
62
|
+
},
|
|
53
63
|
{
|
|
54
64
|
name: "whoami",
|
|
55
65
|
needsRuntime: false,
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { applyPragmas, assertLocalFilesystem, detectFilesystemType, migrate, ope
|
|
|
4
4
|
export { ProtocolError, isProtocolError } from "./errors.js";
|
|
5
5
|
export { deriveHarnessCliIdentity, deriveHumanCliIdentity, deriveMcpHarnessIdentity } from "./identity.js";
|
|
6
6
|
export { ancestorPaths, canonicalizeContextPath, resolveContextPath, resolveWorkspaceRoot } from "./path-resolution.js";
|
|
7
|
+
export { DEFAULT_MAX_INSTRUCTION_FILE_BYTES, DEFAULT_INSTRUCTIONS_MARKDOWN, editInstructions, extractHarnessInstructions, normalizeInstructionHarness, parseInstructionScope, resetInstructions, resolveInstructionHarness, resolveInstructionPaths, showInstructions } from "./instructions.js";
|
|
7
8
|
export { SUPPORTED_HARNESSES, MissingHarnessError, detectHarness, parseHarnessList, planUninstall, resolveHarnessConfigDir, resolveOpencodeConfigDir, resolveOpencodeConfigPath, runAction, skipAction } from "./install.js";
|
|
8
9
|
export { DEFAULT_SKILL_NAME, planSkillInstall, planSkillUninstall, resolveBundledSkillPath, resolveSkillTargetPath, syncInstalledSkills } from "./skill-install.js";
|
|
9
10
|
export { readPackageVersion, readUpdateMigrationState, resolveUpdateMigrationStatePath, runFirstRunMcpMigration, runStaleMcpCleanup, writeUpdateMigrationState } from "./update-migration.js";
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { resolveDataDir } from "./config.js";
|
|
5
|
+
import { resolveContextPath } from "./path-resolution.js";
|
|
6
|
+
export const DEFAULT_MAX_INSTRUCTION_FILE_BYTES = 256 * 1024;
|
|
7
|
+
export const DEFAULT_INSTRUCTIONS_MARKDOWN = `# Talking Stick collaboration instructions
|
|
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.
|
|
10
|
+
|
|
11
|
+
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
|
+
|
|
13
|
+
Typical fits are advisory. Claude is usually strong at prose, first-pass synthesis, tool-running, implementation review, and test review. Codex is usually strong at adversarial review, convergence, implementation, edge cases, and release mechanics after operator approval. Gemini and OpenCode start with conservative local guidance until project dogfood says otherwise.
|
|
14
|
+
|
|
15
|
+
For multi-agent design work, prefer independent read-only drafts first, then adversarial review and convergence. Do not impose a draft file structure on the workspace by default. If scratch draft files are useful, delete superseded pre-convergence drafts after the converged plan exists unless the operator asks to keep them.
|
|
16
|
+
|
|
17
|
+
Default to normal release handoffs. Use named assignment only when a specific member must go next because of unique context, credentials, capability, or direct operator routing.
|
|
18
|
+
|
|
19
|
+
## Claude
|
|
20
|
+
|
|
21
|
+
Lean into drafting, synthesis, tool-running, implementation review, and test review. Watch for scope creep and messy first-pass artifacts. When implementation belongs elsewhere, make the next phase explicit in the handoff.
|
|
22
|
+
|
|
23
|
+
## Codex
|
|
24
|
+
|
|
25
|
+
Lean into adversarial review, convergence, precise implementation, edge-case sweeps, and release mechanics after operator approval. Watch for over-indexing on mechanics when the operator still needs to decide direction.
|
|
26
|
+
|
|
27
|
+
## Gemini
|
|
28
|
+
|
|
29
|
+
Use broad context review and exploration conservatively until the project has stronger Gemini-specific dogfood. Keep handoffs concrete and do not assume responsibility that the operator assigned to another harness.
|
|
30
|
+
|
|
31
|
+
## OpenCode
|
|
32
|
+
|
|
33
|
+
Use terminal-native local exploration and implementation conservatively until the project has stronger OpenCode-specific dogfood. Keep coordination safety ahead of speed.
|
|
34
|
+
`;
|
|
35
|
+
const HARNESS_ALIASES = {
|
|
36
|
+
all: "all",
|
|
37
|
+
base: "all",
|
|
38
|
+
claude: "claude",
|
|
39
|
+
"claude-code": "claude",
|
|
40
|
+
codex: "codex",
|
|
41
|
+
gemini: "gemini",
|
|
42
|
+
opencode: "opencode"
|
|
43
|
+
};
|
|
44
|
+
export function resolveInstructionPaths(options = {}) {
|
|
45
|
+
const contextPath = options.contextPath ?? process.cwd();
|
|
46
|
+
const workspaceRoot = resolveContextPath(contextPath).workspace_root;
|
|
47
|
+
return {
|
|
48
|
+
user: path.join(resolveDataDir(options), "instructions.md"),
|
|
49
|
+
project: path.join(workspaceRoot, ".talking-stick", "instructions.md")
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function showInstructions(input = {}) {
|
|
53
|
+
const options = input.options ?? {};
|
|
54
|
+
const harness = resolveInstructionHarness(input.harness, options.identity);
|
|
55
|
+
const scope = input.scope ?? "effective";
|
|
56
|
+
const paths = resolveInstructionPaths(options);
|
|
57
|
+
const layers = readInstructionLayers(paths, options.maxInstructionFileBytes ?? DEFAULT_MAX_INSTRUCTION_FILE_BYTES);
|
|
58
|
+
const selectedLayers = selectLayers(scope, layers);
|
|
59
|
+
const text = joinInstructionTexts(selectedLayers.map((layer) => extractHarnessInstructions(layer.text, harness)));
|
|
60
|
+
return {
|
|
61
|
+
harness,
|
|
62
|
+
scope,
|
|
63
|
+
text,
|
|
64
|
+
sources: selectedLayers.map((layer) => ({
|
|
65
|
+
scope: layer.scope,
|
|
66
|
+
path: layer.path
|
|
67
|
+
})),
|
|
68
|
+
paths
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export async function editInstructions(input = {}) {
|
|
72
|
+
const scope = input.scope ?? "user";
|
|
73
|
+
const options = input.options ?? {};
|
|
74
|
+
const paths = resolveInstructionPaths(options);
|
|
75
|
+
const filePath = paths[scope];
|
|
76
|
+
const created = ensureInstructionFile(filePath);
|
|
77
|
+
const editor = chooseEditor(options);
|
|
78
|
+
if (!editor) {
|
|
79
|
+
return { scope, path: filePath, created, opened: false, editor: null };
|
|
80
|
+
}
|
|
81
|
+
await runEditor(editor, filePath);
|
|
82
|
+
return { scope, path: filePath, created, opened: true, editor };
|
|
83
|
+
}
|
|
84
|
+
export function resetInstructions(input) {
|
|
85
|
+
const paths = resolveInstructionPaths(input.options ?? {});
|
|
86
|
+
const filePath = paths[input.scope];
|
|
87
|
+
const removed = fs.existsSync(filePath);
|
|
88
|
+
if (removed) {
|
|
89
|
+
fs.rmSync(filePath, { force: true });
|
|
90
|
+
}
|
|
91
|
+
return { scope: input.scope, path: filePath, removed };
|
|
92
|
+
}
|
|
93
|
+
export function resolveInstructionHarness(explicitHarness, identity) {
|
|
94
|
+
if (explicitHarness) {
|
|
95
|
+
return normalizeInstructionHarness(explicitHarness);
|
|
96
|
+
}
|
|
97
|
+
const displayName = identity?.process_metadata.display_name ?? undefined;
|
|
98
|
+
const fromDisplay = displayName ? HARNESS_ALIASES[normalizeKey(displayName)] : undefined;
|
|
99
|
+
if (fromDisplay) {
|
|
100
|
+
return fromDisplay;
|
|
101
|
+
}
|
|
102
|
+
const prefix = identity?.agent_id.split(":")[0];
|
|
103
|
+
const fromPrefix = prefix ? HARNESS_ALIASES[normalizeKey(prefix)] : undefined;
|
|
104
|
+
return fromPrefix ?? "all";
|
|
105
|
+
}
|
|
106
|
+
export function normalizeInstructionHarness(value) {
|
|
107
|
+
const normalized = HARNESS_ALIASES[normalizeKey(value)];
|
|
108
|
+
if (!normalized) {
|
|
109
|
+
throw new Error(`--harness must be one of claude, codex, gemini, opencode, all (got ${value}).`);
|
|
110
|
+
}
|
|
111
|
+
return normalized;
|
|
112
|
+
}
|
|
113
|
+
export function parseInstructionScope(value) {
|
|
114
|
+
if (!value) {
|
|
115
|
+
return "effective";
|
|
116
|
+
}
|
|
117
|
+
if (value === "effective" ||
|
|
118
|
+
value === "bundled" ||
|
|
119
|
+
value === "user" ||
|
|
120
|
+
value === "project") {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`--scope must be one of effective, bundled, user, project (got ${value}).`);
|
|
124
|
+
}
|
|
125
|
+
export function extractHarnessInstructions(markdown, harness) {
|
|
126
|
+
const trimmed = markdown.trim();
|
|
127
|
+
if (!trimmed || harness === "all") {
|
|
128
|
+
return trimmed;
|
|
129
|
+
}
|
|
130
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
131
|
+
const shared = [];
|
|
132
|
+
const sections = new Map();
|
|
133
|
+
let current = null;
|
|
134
|
+
let sawSection = false;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const header = parseHarnessHeader(line);
|
|
137
|
+
if (header) {
|
|
138
|
+
sawSection = true;
|
|
139
|
+
current = header;
|
|
140
|
+
if (!sections.has(current)) {
|
|
141
|
+
sections.set(current, []);
|
|
142
|
+
}
|
|
143
|
+
sections.get(current)?.push(line);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!sawSection) {
|
|
147
|
+
shared.push(line);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (current) {
|
|
151
|
+
sections.get(current)?.push(line);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return joinInstructionTexts([
|
|
155
|
+
shared.join("\n").trim(),
|
|
156
|
+
sections.get(harness)?.join("\n").trim() ?? ""
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
function readInstructionLayers(paths, maxInstructionFileBytes) {
|
|
160
|
+
const layers = [
|
|
161
|
+
{ scope: "bundled", path: null, text: DEFAULT_INSTRUCTIONS_MARKDOWN }
|
|
162
|
+
];
|
|
163
|
+
for (const scope of ["user", "project"]) {
|
|
164
|
+
const filePath = paths[scope];
|
|
165
|
+
if (!fs.existsSync(filePath)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const stat = fs.statSync(filePath);
|
|
169
|
+
if (stat.size > maxInstructionFileBytes) {
|
|
170
|
+
throw new Error(`${scope} instructions file is too large (${stat.size} bytes, max ${maxInstructionFileBytes}): ${filePath}`);
|
|
171
|
+
}
|
|
172
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
173
|
+
layers.push({ scope, path: filePath, text });
|
|
174
|
+
}
|
|
175
|
+
return layers;
|
|
176
|
+
}
|
|
177
|
+
function selectLayers(scope, layers) {
|
|
178
|
+
if (scope === "effective") {
|
|
179
|
+
return layers;
|
|
180
|
+
}
|
|
181
|
+
return layers.filter((layer) => layer.scope === scope);
|
|
182
|
+
}
|
|
183
|
+
function joinInstructionTexts(parts) {
|
|
184
|
+
return parts
|
|
185
|
+
.map((part) => part.trim())
|
|
186
|
+
.filter((part) => part.length > 0)
|
|
187
|
+
.join("\n\n");
|
|
188
|
+
}
|
|
189
|
+
function ensureInstructionFile(filePath) {
|
|
190
|
+
if (fs.existsSync(filePath)) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
194
|
+
fs.writeFileSync(filePath, DEFAULT_INSTRUCTIONS_MARKDOWN);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
function parseHarnessHeader(line) {
|
|
198
|
+
const match = line.match(/^##\s+(.+?)\s*$/);
|
|
199
|
+
if (!match) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const key = normalizeKey(match[1]);
|
|
203
|
+
if (key.startsWith("claude"))
|
|
204
|
+
return "claude";
|
|
205
|
+
if (key.startsWith("codex"))
|
|
206
|
+
return "codex";
|
|
207
|
+
if (key.startsWith("gemini"))
|
|
208
|
+
return "gemini";
|
|
209
|
+
if (key.startsWith("opencode"))
|
|
210
|
+
return "opencode";
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
function chooseEditor(options) {
|
|
214
|
+
const env = options.env ?? process.env;
|
|
215
|
+
const explicit = env.VISUAL?.trim() || env.EDITOR?.trim();
|
|
216
|
+
if (explicit) {
|
|
217
|
+
return explicit;
|
|
218
|
+
}
|
|
219
|
+
switch (options.platform ?? process.platform) {
|
|
220
|
+
case "darwin":
|
|
221
|
+
return "open -t";
|
|
222
|
+
case "win32":
|
|
223
|
+
return "notepad.exe";
|
|
224
|
+
default:
|
|
225
|
+
if (env.DISPLAY || env.WAYLAND_DISPLAY) {
|
|
226
|
+
return "xdg-open";
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function runEditor(editor, filePath) {
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
const child = spawn(`${editor} ${shellQuote(filePath)}`, {
|
|
234
|
+
stdio: "inherit",
|
|
235
|
+
shell: true
|
|
236
|
+
});
|
|
237
|
+
child.on("error", reject);
|
|
238
|
+
child.on("close", (code) => {
|
|
239
|
+
if (code === 0) {
|
|
240
|
+
resolve();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
reject(new Error(`${editor} exited with code ${code}.`));
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function shellQuote(value) {
|
|
248
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
249
|
+
}
|
|
250
|
+
function normalizeKey(value) {
|
|
251
|
+
return value
|
|
252
|
+
.trim()
|
|
253
|
+
.toLowerCase()
|
|
254
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
255
|
+
.replace(/^-+|-+$/g, "");
|
|
256
|
+
}
|
package/dist/service.js
CHANGED
|
@@ -1247,20 +1247,24 @@ export class TalkingStickService {
|
|
|
1247
1247
|
if (!room.pending_handoff_event_seq) {
|
|
1248
1248
|
return false;
|
|
1249
1249
|
}
|
|
1250
|
-
|
|
1251
|
-
return false;
|
|
1252
|
-
}
|
|
1250
|
+
const handoffAgeMs = now.getTime() - Date.parse(room.updated_at);
|
|
1253
1251
|
const pendingEvent = this.getEventBySeq(room.pending_handoff_event_seq);
|
|
1254
1252
|
const priorOwner = pendingEvent?.from_agent_id ?? null;
|
|
1255
1253
|
if (priorOwner === agentId &&
|
|
1256
|
-
this.
|
|
1257
|
-
return
|
|
1254
|
+
this.hasOtherActiveRoomMember(room.room_id, agentId, now)) {
|
|
1255
|
+
return handoffAgeMs < this.priorOwnerReleaseCooldownMs();
|
|
1256
|
+
}
|
|
1257
|
+
if (handoffAgeMs >= this.policy.waiterGraceMs) {
|
|
1258
|
+
return false;
|
|
1258
1259
|
}
|
|
1259
1260
|
const bestKnownMember = this.findBestFairKnownMember(room.room_id, priorOwner, now);
|
|
1260
1261
|
return bestKnownMember !== null && bestKnownMember.agent_id !== agentId;
|
|
1261
1262
|
}
|
|
1262
|
-
|
|
1263
|
-
return this.getMembers(roomId).some((member) => member.agent_id !== agentId);
|
|
1263
|
+
hasOtherActiveRoomMember(roomId, agentId, now) {
|
|
1264
|
+
return this.getMembers(roomId).some((member) => member.agent_id !== agentId && this.isMemberActive(member, now));
|
|
1265
|
+
}
|
|
1266
|
+
priorOwnerReleaseCooldownMs() {
|
|
1267
|
+
return Math.max(this.policy.waiterGraceMs * 6, 60_000);
|
|
1264
1268
|
}
|
|
1265
1269
|
inspectRoom(room, now) {
|
|
1266
1270
|
const members = this.getMembers(room.room_id);
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# v6 converged design — harness instructions
|
|
2
|
+
|
|
3
|
+
> **Status:** converged claude + codex design.
|
|
4
|
+
>
|
|
5
|
+
> **Convergence path:** independent drafts written in parallel, then debated
|
|
6
|
+
> by cross-reference. Outcomes: claude conceded load-mechanism (separate
|
|
7
|
+
> `tt instructions show` call, not piggyback on `tt join`); codex conceded
|
|
8
|
+
> file-structure (single file per layer, not per-harness) and materialization
|
|
9
|
+
> (lazy on edit, never on install). Naming, CLI surface, project-file scope,
|
|
10
|
+
> and bundled-skill scope were already aligned.
|
|
11
|
+
>
|
|
12
|
+
> The pre-debate drafts (`v6-claude.md`, `v6-codex.md`) were deleted after
|
|
13
|
+
> convergence per Wojtek: "if they write something they need to delete the
|
|
14
|
+
> pre-debate versions after convergence."
|
|
15
|
+
|
|
16
|
+
## Problem
|
|
17
|
+
|
|
18
|
+
v5 collapsed the phase guidance into bundled `SKILL.md`. Wojtek's pushback:
|
|
19
|
+
*"The skill ships with the binary, which means people won't be able to edit. I
|
|
20
|
+
want them to be able to edit, but I still want it to be simple."* `tt install`
|
|
21
|
+
links the bundled skill by default and startup sync can refresh copied
|
|
22
|
+
installs — there is no durable user-edit affordance.
|
|
23
|
+
|
|
24
|
+
v6 fixes this with editable Markdown instructions, seeded lazily, opened with
|
|
25
|
+
`$EDITOR`. Not JSON profiles. Not a config subsystem.
|
|
26
|
+
|
|
27
|
+
## Shape
|
|
28
|
+
|
|
29
|
+
Two layers of concern, kept separate by design:
|
|
30
|
+
|
|
31
|
+
### 1. Bundled skill floor (`skills/talking-stick/SKILL.md`)
|
|
32
|
+
|
|
33
|
+
Immutable, package-owned. Covers join, wait, guardian checks, release/handoff,
|
|
34
|
+
event-wake safety, takeover, notes, messages, and the "keep using the stick
|
|
35
|
+
until done" rule. Tells agents how to load the editable collaboration
|
|
36
|
+
instructions. **Does not include phase vocabulary or typical fits** — those
|
|
37
|
+
live in the editable layer with bundled defaults as fallback inside the
|
|
38
|
+
instructions code path, not duplicated into the safety contract.
|
|
39
|
+
|
|
40
|
+
### 2. Editable collaboration instructions
|
|
41
|
+
|
|
42
|
+
Single Markdown file per layer, with optional per-harness sections inside:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
~/.local/share/talking-stick/instructions.md # user override (optional, XDG-aware)
|
|
46
|
+
<repo>/.talking-stick/instructions.md # project override (optional)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Bundled defaults live as text constants in `src/instructions.ts` — they are
|
|
50
|
+
the seed text and the always-available fallback if no editable file exists.
|
|
51
|
+
|
|
52
|
+
The file uses `## <Harness>` section headers. The effective text for a given
|
|
53
|
+
harness is: shared preamble (everything before the first `##`) + the matching
|
|
54
|
+
harness section.
|
|
55
|
+
|
|
56
|
+
## Effective-text resolution
|
|
57
|
+
|
|
58
|
+
Additive concatenation in layer order: bundled → user → project. Later layers
|
|
59
|
+
add or override by plain language; the model weighs the concatenated text. No
|
|
60
|
+
merge semantics, no structured fields, no precedence rules beyond "later text
|
|
61
|
+
typically wins by recency." Bundled safety floor (the SKILL.md content)
|
|
62
|
+
remains higher than editable content always — operators cannot use
|
|
63
|
+
instructions to override the coordination safety contract.
|
|
64
|
+
|
|
65
|
+
## Loading
|
|
66
|
+
|
|
67
|
+
Agents call `tt instructions show --json` once after `tt join` on the first
|
|
68
|
+
substantial task in a room. The command infers the caller's harness from
|
|
69
|
+
identity detection (no `--harness` flag needed). The JSON result includes the
|
|
70
|
+
effective Markdown text and the file paths that contributed to it.
|
|
71
|
+
|
|
72
|
+
If the command fails, agents continue with the bundled `SKILL.md` guidance
|
|
73
|
+
alone. Coordination must never depend on instructions loading successfully.
|
|
74
|
+
|
|
75
|
+
`tt join` returns membership/coordination state only; instructions are
|
|
76
|
+
deliberately separate.
|
|
77
|
+
|
|
78
|
+
## CLI surface
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
tt instructions show [--harness <name>] [--scope effective|user|project|bundled] [--json]
|
|
82
|
+
tt instructions edit [--user|--project]
|
|
83
|
+
tt instructions reset [--user|--project]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `show` defaults to `--scope effective` and the detected current harness.
|
|
87
|
+
- `edit` opens `$VISUAL`, then `$EDITOR`, then a platform default if available;
|
|
88
|
+
prints the file path if no editor is found. Defaults to `--user`.
|
|
89
|
+
- `edit` materializes the seed text on first use, then opens the editor.
|
|
90
|
+
Subsequent edits open the existing file in place.
|
|
91
|
+
- `reset` requires explicit scope (no implicit `--user`); deletes the file at
|
|
92
|
+
the chosen scope so the layer below wins again.
|
|
93
|
+
|
|
94
|
+
**Cut from both drafts:** `set`/`append`/`init`/`diff`. Editor-only for v1.
|
|
95
|
+
Agents holding the stick can write to `<repo>/.talking-stick/instructions.md`
|
|
96
|
+
directly via filesystem.
|
|
97
|
+
|
|
98
|
+
## Materialization
|
|
99
|
+
|
|
100
|
+
Lazy on first `tt instructions edit`, never on `tt install`. Reasoning:
|
|
101
|
+
- Operators who never customize stay on shipped defaults forever — package
|
|
102
|
+
updates flow through automatically with no stale local copies.
|
|
103
|
+
- Operators who customize get a one-time materialization at the moment they
|
|
104
|
+
show intent (running `edit`).
|
|
105
|
+
- `tt install` stays fast and scriptable; no extra files written.
|
|
106
|
+
|
|
107
|
+
`tt install` final output gains one hint:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Customize collaboration instructions with: tt instructions edit
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
No interactive prompt.
|
|
114
|
+
|
|
115
|
+
## Bundled defaults (content)
|
|
116
|
+
|
|
117
|
+
Text constants in `src/instructions.ts`. Short. Cover:
|
|
118
|
+
|
|
119
|
+
- the "keep using Talking Stick until the shared task is done" rule,
|
|
120
|
+
including "prefer continued action unless the task is complete or the
|
|
121
|
+
operator explicitly redirects/stops",
|
|
122
|
+
- the phase vocabulary: draft, adversarial review, convergence,
|
|
123
|
+
implementation, implementation review, test review, release,
|
|
124
|
+
- typical fits (advisory, not enforced):
|
|
125
|
+
- Claude: prose, first-pass synthesis, tool-running, implementation review,
|
|
126
|
+
test review.
|
|
127
|
+
- Codex: adversarial review, convergence, implementation, edge cases,
|
|
128
|
+
release mechanics after operator approval.
|
|
129
|
+
- Gemini/OpenCode: conservative starter guidance until dogfood gives
|
|
130
|
+
evidence.
|
|
131
|
+
|
|
132
|
+
These are defaults, not a scheduler. Talking Stick does not auto-route turns
|
|
133
|
+
based on them.
|
|
134
|
+
|
|
135
|
+
## Parallel drafting without workspace clutter
|
|
136
|
+
|
|
137
|
+
Wojtek's target default is independent read-only planning first, then debate
|
|
138
|
+
until convergence. His concern was not the independent thinking; it was
|
|
139
|
+
workspace clutter and imposed file structure: *"I'm not sure I want to default
|
|
140
|
+
to the models writing drafts or imposing a file structure on the workspace. I
|
|
141
|
+
guess maybe if they write something they need to delete the pre-debate versions
|
|
142
|
+
after convergence."*
|
|
143
|
+
|
|
144
|
+
So the default is **read-only independent drafts**, not repo draft files.
|
|
145
|
+
Rules when this happens:
|
|
146
|
+
|
|
147
|
+
- **Drafts go in handoff `status` text or room notes by default**, not in
|
|
148
|
+
workspace files. Coordination channels are the natural place for read-only
|
|
149
|
+
position exchange.
|
|
150
|
+
- **If files are unavoidable** (drafts too long for handoffs/notes), use
|
|
151
|
+
files in `docs/plans/` with clear `*-<harness>-draft.md` naming, and
|
|
152
|
+
**delete them after convergence**. Only the converged artifact remains.
|
|
153
|
+
- Single-agent rooms and tiny tasks can skip parallel drafting.
|
|
154
|
+
- The default for larger multi-agent planning is: draft independently, debate,
|
|
155
|
+
converge, then write only the converged artifact if a workspace file is
|
|
156
|
+
useful.
|
|
157
|
+
|
|
158
|
+
This is still prompt guidance, not protocol. Talking Stick does not create
|
|
159
|
+
draft files, track phases, or auto-route turns based on this pattern.
|
|
160
|
+
|
|
161
|
+
## What we explicitly do not build
|
|
162
|
+
|
|
163
|
+
- No JSON config, no schema, no validator beyond a soft size cap on read.
|
|
164
|
+
- No structured merge semantics. Plain Markdown concatenation.
|
|
165
|
+
- No `set`/`append`/`init`/`diff` commands.
|
|
166
|
+
- No materialization on install.
|
|
167
|
+
- No phase tracking in the protocol. No `tt phase set`. No `phase` field in
|
|
168
|
+
handoff types.
|
|
169
|
+
- No `tt state` rendering changes.
|
|
170
|
+
- No model overlays.
|
|
171
|
+
- No auto-routing by harness.
|
|
172
|
+
- No instruction-text override of the bundled safety floor.
|
|
173
|
+
|
|
174
|
+
## Implementation order
|
|
175
|
+
|
|
176
|
+
1. `src/instructions.ts` — bundled default text constants, layer resolver
|
|
177
|
+
(bundled → user → project, additive concatenation), XDG-aware path
|
|
178
|
+
resolution, `## <Harness>` section parser, soft size cap.
|
|
179
|
+
2. Add `tt instructions show` (CLI + service plumbing).
|
|
180
|
+
3. Add `tt instructions edit` with `$VISUAL` / `$EDITOR` / platform fallback.
|
|
181
|
+
Materializes seed on first use.
|
|
182
|
+
4. Add `tt instructions reset`.
|
|
183
|
+
5. Edit bundled `SKILL.md`: keep current safety contract, add one short line
|
|
184
|
+
pointing agents at `tt instructions show --json` after first `tt join`.
|
|
185
|
+
6. Update `tt install` final output with the customize hint.
|
|
186
|
+
7. Tests: `tests/instructions.test.ts` for layer resolution, missing files,
|
|
187
|
+
materialization-only-on-edit, section parsing, additive concatenation,
|
|
188
|
+
bundled-fallback when commands fail. Extend `tests/cli.test.ts` for the
|
|
189
|
+
new commands.
|
|
190
|
+
8. README: replace `tt install-skill` references with `tt install`; add a
|
|
191
|
+
short paragraph on `tt instructions edit` for customization.
|
|
192
|
+
9. Release notes entry.
|
|
193
|
+
|
|
194
|
+
## Acceptance criteria
|
|
195
|
+
|
|
196
|
+
- A fresh user can install with `npm i -g talking-stick && tt install --all`
|
|
197
|
+
and get useful defaults without answering prompts.
|
|
198
|
+
- The same user can run `tt instructions edit` and customize collaboration
|
|
199
|
+
behavior in `$EDITOR`.
|
|
200
|
+
- A package update never overwrites the user's instruction file.
|
|
201
|
+
- An agent runs one command after join (`tt instructions show --json`) and
|
|
202
|
+
receives effective instruction text + source paths.
|
|
203
|
+
- If instruction loading fails, the bundled `SKILL.md` still gives enough
|
|
204
|
+
guidance to coordinate safely (agents will lack phase vocabulary but
|
|
205
|
+
coordination/safety remains intact).
|
|
206
|
+
- No new protocol state, no role enforcement, and no auto-routing are added.
|
|
207
|
+
|
|
208
|
+
## Process notes (meta)
|
|
209
|
+
|
|
210
|
+
This convergence used parallel-drafting one-off. Each harness wrote an
|
|
211
|
+
independent v6 draft without reading the other's first. Both landed near
|
|
212
|
+
the same shape but differed on three points (file structure, load
|
|
213
|
+
mechanism, materialization). Reading both drafts side by side made the
|
|
214
|
+
real trade-offs visible quickly; concessions went both ways within one
|
|
215
|
+
round.
|
|
216
|
+
|
|
217
|
+
The pre-debate draft files were deleted after convergence per Wojtek. The
|
|
218
|
+
parallel-drafting workflow is captured above as an *optional* operator-
|
|
219
|
+
driven pattern, not a shipped default — to avoid imposing file structure
|
|
220
|
+
on workspaces by default.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Talking Stick 0.4.0
|
|
2
|
+
|
|
3
|
+
Date: 2026-05-10
|
|
4
|
+
|
|
5
|
+
This release adds editable collaboration instructions for harness roles and
|
|
6
|
+
phase preferences while keeping the bundled Talking Stick skill as the
|
|
7
|
+
coordination safety floor.
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
### Editable collaboration instructions
|
|
12
|
+
|
|
13
|
+
New CLI surface:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
tt instructions show
|
|
17
|
+
tt instructions edit
|
|
18
|
+
tt instructions reset
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`show` returns the effective Markdown prompt for the detected harness. Effective
|
|
22
|
+
instructions are layered as bundled defaults, user defaults, and project
|
|
23
|
+
overrides. User instructions live under the Talking Stick data directory
|
|
24
|
+
(`instructions.md`); project instructions live at
|
|
25
|
+
`.talking-stick/instructions.md` in the workspace root.
|
|
26
|
+
|
|
27
|
+
`edit` lazily materializes the default Markdown on first use and opens
|
|
28
|
+
`$VISUAL`, `$EDITOR`, or a platform editor fallback. `reset` deletes the chosen
|
|
29
|
+
user or project file so lower layers apply again.
|
|
30
|
+
|
|
31
|
+
### Bundled defaults
|
|
32
|
+
|
|
33
|
+
The shipped defaults describe the shared phase vocabulary:
|
|
34
|
+
|
|
35
|
+
- draft
|
|
36
|
+
- adversarial review
|
|
37
|
+
- convergence
|
|
38
|
+
- implementation
|
|
39
|
+
- implementation review
|
|
40
|
+
- test review
|
|
41
|
+
- release
|
|
42
|
+
|
|
43
|
+
They also include advisory harness fits for Claude, Codex, Gemini, and
|
|
44
|
+
OpenCode. These defaults are prompt guidance only; Talking Stick does not track
|
|
45
|
+
phase state or auto-route turns.
|
|
46
|
+
|
|
47
|
+
### Skill loading
|
|
48
|
+
|
|
49
|
+
The bundled `talking-stick` skill now tells agents to run
|
|
50
|
+
`tt instructions show --json` after joining a room. If the command fails, agents
|
|
51
|
+
continue with the bundled skill. Editable instructions can add local
|
|
52
|
+
preferences, but they cannot override the core safety rules around joining,
|
|
53
|
+
waiting, guardian checks, handoffs, event wakes, or takeover.
|
|
54
|
+
|
|
55
|
+
## Changed
|
|
56
|
+
|
|
57
|
+
- `tt install` prints a short customization hint after a successful skill
|
|
58
|
+
install: `Customize collaboration instructions with: tt instructions edit`.
|
|
59
|
+
- The skill's re-entry guidance now says to prefer continued action until the
|
|
60
|
+
task is complete or the operator explicitly redirects or stops the room.
|
|
61
|
+
|
|
62
|
+
## Verification
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run typecheck
|
|
66
|
+
npm run build
|
|
67
|
+
npm test
|
|
68
|
+
git diff --check
|
|
69
|
+
node dist/cli.js instructions show --harness codex --scope bundled --json
|
|
70
|
+
npm pack --dry-run
|
|
71
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Talking Stick 0.4.1
|
|
2
|
+
|
|
3
|
+
Date: 2026-05-10
|
|
4
|
+
|
|
5
|
+
This patch fixes release/reclaim churn discovered while dogfooding the 0.4.0
|
|
6
|
+
collaboration instructions. A holder could release with "no work", immediately
|
|
7
|
+
re-enter `tt wait`, and reclaim the stick before another active harness had a
|
|
8
|
+
real chance to claim.
|
|
9
|
+
|
|
10
|
+
## Fixed
|
|
11
|
+
|
|
12
|
+
### Prior-owner release cooldown
|
|
13
|
+
|
|
14
|
+
When a room is idle after a release, the release's prior owner now waits through
|
|
15
|
+
a bounded cooldown before reclaiming if another active member exists. The
|
|
16
|
+
cooldown defaults to `max(6 * waiterGraceMs, 60_000)`, which is 60 seconds with
|
|
17
|
+
the stock policy.
|
|
18
|
+
|
|
19
|
+
Other members can still claim immediately, and the prior owner can continue
|
|
20
|
+
immediately when he is genuinely alone. Stale members and audit-only
|
|
21
|
+
`target=any` event reads do not force a cooldown.
|
|
22
|
+
|
|
23
|
+
### Solo-polling guidance
|
|
24
|
+
|
|
25
|
+
The bundled skill and default collaboration instructions now clarify the escape
|
|
26
|
+
hatch for one-agent rooms: if you are the only active member, stop polling after
|
|
27
|
+
a clear handoff instead of churning release/reclaim turns. Brief quiet periods
|
|
28
|
+
from another active agent are not enough to declare yourself alone.
|
|
29
|
+
|
|
30
|
+
## Verification
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run typecheck
|
|
34
|
+
npm run build
|
|
35
|
+
npm test
|
|
36
|
+
git diff --check
|
|
37
|
+
npm pack --dry-run
|
|
38
|
+
```
|
package/package.json
CHANGED
|
@@ -60,6 +60,14 @@ tt join --json
|
|
|
60
60
|
|
|
61
61
|
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
62
|
|
|
63
|
+
After joining, load editable collaboration instructions once:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
tt instructions show --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If that command fails, continue with this bundled skill. Editable instructions can add local preferences, but they do not override the safety rules in this skill.
|
|
70
|
+
|
|
63
71
|
Right after joining, start a background ambient receiver so direct messages and turn passes/reservations surface as soon as they happen instead of waiting for the next time you poll:
|
|
64
72
|
|
|
65
73
|
```sh
|
|
@@ -214,10 +222,9 @@ The default after `tt release` or `tt assign` is to re-enter the wait loop and k
|
|
|
214
222
|
Exit the wait loop only when one of these is true:
|
|
215
223
|
|
|
216
224
|
- the shared task is explicitly finished
|
|
217
|
-
- you are the only active member and there is no one to hand off to
|
|
218
225
|
- the operator gives a direct redirect or stop
|
|
219
226
|
|
|
220
|
-
In every other case, after `tt release` or `tt assign`, go straight back into `tt wait --json`.
|
|
227
|
+
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.
|
|
221
228
|
|
|
222
229
|
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.
|
|
223
230
|
|