@cmgmyr/hive 0.1.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.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/claude-plugin/.claude-plugin/plugin.json +7 -0
- package/claude-plugin/README.md +47 -0
- package/claude-plugin/hooks/hooks.json +14 -0
- package/claude-plugin/kickoff.mjs +41 -0
- package/claude-plugin/skills/cleanup/SKILL.md +60 -0
- package/dist/abi.js +220 -0
- package/dist/abiProbe.js +9 -0
- package/dist/backgroundTasks.js +51 -0
- package/dist/backup.js +378 -0
- package/dist/brief.js +113 -0
- package/dist/build-info.json +1 -0
- package/dist/claudeDir.js +5 -0
- package/dist/cli.js +2901 -0
- package/dist/codexHome.js +297 -0
- package/dist/config.js +72 -0
- package/dist/context.js +256 -0
- package/dist/contextCheckpoint.js +55 -0
- package/dist/dashboard.js +1158 -0
- package/dist/dataDir.js +89 -0
- package/dist/db.js +421 -0
- package/dist/dispatcher.js +150 -0
- package/dist/firstPrompt.js +6 -0
- package/dist/harnesses.js +173 -0
- package/dist/help.js +353 -0
- package/dist/hook.js +197 -0
- package/dist/hooks.js +50 -0
- package/dist/index.js +34 -0
- package/dist/kickoff.js +191 -0
- package/dist/leadMessage.js +98 -0
- package/dist/mcpConfig.js +121 -0
- package/dist/processes.js +161 -0
- package/dist/profiles.js +278 -0
- package/dist/projectYml.js +254 -0
- package/dist/prompts.js +18 -0
- package/dist/ptys.js +146 -0
- package/dist/result.js +53 -0
- package/dist/scheduler.js +1622 -0
- package/dist/sessionProbe.js +131 -0
- package/dist/slug.js +29 -0
- package/dist/spawn.js +370 -0
- package/dist/stateProvenance.js +97 -0
- package/dist/statusline.js +82 -0
- package/dist/strictInput.js +52 -0
- package/dist/teardown.js +89 -0
- package/dist/tmux.js +1295 -0
- package/dist/tools/agents.js +1246 -0
- package/dist/tools/kv.js +89 -0
- package/dist/tools/leases.js +87 -0
- package/dist/tools/meta.js +183 -0
- package/dist/tools/pads.js +279 -0
- package/dist/tools/params.js +14 -0
- package/dist/tools/todos.js +382 -0
- package/dist/tools/wakes.js +466 -0
- package/dist/transcript.js +123 -0
- package/dist/version.js +59 -0
- package/package.json +62 -0
- package/profiles/orchestration/posture.md +27 -0
- package/profiles/orchestration/runbook.md +72 -0
- package/profiles/orchestration/worker.md +38 -0
- package/profiles/simple/posture.md +14 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { codexInputBoxState, codexPaneChoiceCheck, codexPaneHasInputBox, inputBoxState, paneChoiceCheck, paneHasInputBox, } from "./tmux.js";
|
|
2
|
+
export function commandHead(command) {
|
|
3
|
+
return command.trim().split(/\s+/)[0] ?? "";
|
|
4
|
+
}
|
|
5
|
+
export function carriesNameFlag(tokens) {
|
|
6
|
+
return tokens.some((arg) => arg === "--name" || arg.startsWith("--name=") || (arg.startsWith("-n") && !arg.startsWith("--")));
|
|
7
|
+
}
|
|
8
|
+
// Stripped before comparing basenames - never a wrapper's own flags. Negative controls live in
|
|
9
|
+
// test/harness-wrapper-matching.test.mjs.
|
|
10
|
+
const ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
11
|
+
const KNOWN_WRAPPERS = new Set(["env", "nice", "arch", "time"]);
|
|
12
|
+
function basenameOf(token) {
|
|
13
|
+
return token.split("/").pop() ?? "";
|
|
14
|
+
}
|
|
15
|
+
function resolvedCommand(command) {
|
|
16
|
+
const tokens = command.trim().split(/\s+/).filter(Boolean);
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < tokens.length) {
|
|
19
|
+
if (ENV_ASSIGNMENT.test(tokens[i])) {
|
|
20
|
+
i++;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (KNOWN_WRAPPERS.has(basenameOf(tokens[i])) && i + 1 < tokens.length) {
|
|
24
|
+
i++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
return { prefix: tokens.slice(0, i + 1).join(" "), basename: basenameOf(tokens[i] ?? "") };
|
|
30
|
+
}
|
|
31
|
+
function resolvedCommandBasename(command) {
|
|
32
|
+
return resolvedCommand(command).basename;
|
|
33
|
+
}
|
|
34
|
+
// For a caller rebuilding an invocation over a recorded command that may carry stale flags
|
|
35
|
+
// (agent_resume). Reuses matches()'s own walk rather than a second parser.
|
|
36
|
+
export function resolvedCommandPrefix(command) {
|
|
37
|
+
return resolvedCommand(command).prefix;
|
|
38
|
+
}
|
|
39
|
+
const claudeHarness = {
|
|
40
|
+
name: "claude",
|
|
41
|
+
matches: (command) => resolvedCommandBasename(command) === "claude",
|
|
42
|
+
argsFor: ({ displayName, namedByCaller }) => displayName && !namedByCaller ? ["--name", displayName] : [],
|
|
43
|
+
briefDelivery: {
|
|
44
|
+
settingsArgs: (path) => ["--settings", path],
|
|
45
|
+
systemPromptArgs: (path) => ["--append-system-prompt-file", path],
|
|
46
|
+
},
|
|
47
|
+
stateSource: true,
|
|
48
|
+
transcriptDir: true,
|
|
49
|
+
contextTokens: true,
|
|
50
|
+
contextRecord: "claude",
|
|
51
|
+
mintsSessionId: true,
|
|
52
|
+
supportsResume: true,
|
|
53
|
+
supportsRename: true,
|
|
54
|
+
classifiesPaneScreen: true,
|
|
55
|
+
paneClassifier: { choiceCheck: paneChoiceCheck, inputBoxState, hasInputBox: paneHasInputBox },
|
|
56
|
+
hasScopes: true,
|
|
57
|
+
needsHome: false,
|
|
58
|
+
initialPromptArgs: null,
|
|
59
|
+
// clear, other, and an unobserved value all stop nothing (todo 765; test/fixtures/hook-payloads/
|
|
60
|
+
// README.md). Only these two are what Claude Code documents as the session being over for good.
|
|
61
|
+
terminalSessionEndReasons: ["prompt_input_exit", "logout"],
|
|
62
|
+
};
|
|
63
|
+
export const codexHarness = {
|
|
64
|
+
name: "codex",
|
|
65
|
+
matches: (command) => resolvedCommandBasename(command) === "codex",
|
|
66
|
+
argsFor: () => [],
|
|
67
|
+
briefDelivery: null,
|
|
68
|
+
stateSource: true,
|
|
69
|
+
transcriptDir: false,
|
|
70
|
+
contextTokens: false,
|
|
71
|
+
contextRecord: "codex",
|
|
72
|
+
// `codex resume <SESSION_ID>` is a positional; no --session-id flag exists, so codex mints its
|
|
73
|
+
// own. This pair disagreeing is why they are two fields.
|
|
74
|
+
mintsSessionId: false,
|
|
75
|
+
supportsResume: true,
|
|
76
|
+
supportsRename: false,
|
|
77
|
+
classifiesPaneScreen: true,
|
|
78
|
+
paneClassifier: {
|
|
79
|
+
choiceCheck: codexPaneChoiceCheck,
|
|
80
|
+
inputBoxState: codexInputBoxState,
|
|
81
|
+
hasInputBox: codexPaneHasInputBox,
|
|
82
|
+
},
|
|
83
|
+
hasScopes: false,
|
|
84
|
+
needsHome: true,
|
|
85
|
+
// Auto-submits with no Enter; verified live on v0.149.0.
|
|
86
|
+
initialPromptArgs: (message) => [message],
|
|
87
|
+
// Measured live on 0.151.0 (todo 782 S1): both `/quit` and Ctrl-C twice fire SessionEnd with
|
|
88
|
+
// reason "other" - never claude's `prompt_input_exit` or `logout`. Codex's vocabulary is
|
|
89
|
+
// disjoint from claude's, not a superset of it, so it gets its own set rather than joining
|
|
90
|
+
// claude's.
|
|
91
|
+
terminalSessionEndReasons: ["other"],
|
|
92
|
+
};
|
|
93
|
+
const unknownHarness = {
|
|
94
|
+
name: "unknown",
|
|
95
|
+
matches: () => false,
|
|
96
|
+
argsFor: () => [],
|
|
97
|
+
briefDelivery: null,
|
|
98
|
+
stateSource: false,
|
|
99
|
+
transcriptDir: false,
|
|
100
|
+
contextTokens: false,
|
|
101
|
+
contextRecord: null,
|
|
102
|
+
mintsSessionId: false,
|
|
103
|
+
supportsResume: false,
|
|
104
|
+
supportsRename: false,
|
|
105
|
+
classifiesPaneScreen: false,
|
|
106
|
+
paneClassifier: null,
|
|
107
|
+
hasScopes: false,
|
|
108
|
+
needsHome: false,
|
|
109
|
+
initialPromptArgs: null,
|
|
110
|
+
// An unrecognized command's vocabulary is unknown, so it stops nothing rather than guessing.
|
|
111
|
+
terminalSessionEndReasons: [],
|
|
112
|
+
};
|
|
113
|
+
const HARNESSES = [claudeHarness, codexHarness];
|
|
114
|
+
export function harnessFor(command) {
|
|
115
|
+
return HARNESSES.find((harness) => harness.matches(command)) ?? unknownHarness;
|
|
116
|
+
}
|
|
117
|
+
// The one source of known harness names; hive.yml's `agents:` and agent_spawn both validate here.
|
|
118
|
+
export function harnessNames() {
|
|
119
|
+
return HARNESSES.map((harness) => harness.name);
|
|
120
|
+
}
|
|
121
|
+
export function registerHarness(harness) {
|
|
122
|
+
if (HARNESSES.some((h) => h.name === harness.name)) {
|
|
123
|
+
throw new Error(`a harness named "${harness.name}" is already registered`);
|
|
124
|
+
}
|
|
125
|
+
if ((harness.paneClassifier != null) !== harness.classifiesPaneScreen) {
|
|
126
|
+
throw new Error(`harness "${harness.name}" sets classifiesPaneScreen to ${harness.classifiesPaneScreen} but ` +
|
|
127
|
+
`paneClassifier is ${harness.paneClassifier == null ? "missing" : "set"}. Every call site that reads ` +
|
|
128
|
+
"classifiesPaneScreen assumes paneClassifier is present whenever it is true, and never consulted " +
|
|
129
|
+
"when it is false; the two must agree.");
|
|
130
|
+
}
|
|
131
|
+
if (harness.supportsRename && !harness.classifiesPaneScreen) {
|
|
132
|
+
throw new Error(`harness "${harness.name}" sets supportsRename without classifiesPaneScreen. agent_rename PASTES ` +
|
|
133
|
+
"AND SUBMITS /rename into a live pane, and the guards that decide whether that is safe (a dialog " +
|
|
134
|
+
"on screen, unsubmitted human text in the box) can only read a screen this table says hive can " +
|
|
135
|
+
"classify. Set classifiesPaneScreen, or leave supportsRename off.");
|
|
136
|
+
}
|
|
137
|
+
// Defends a JS caller (a test stub, or a future third-party registration with no TypeScript
|
|
138
|
+
// check behind it) that omits this field: src/hook.ts calls .includes() on it unconditionally,
|
|
139
|
+
// and undefined.includes would throw, silently swallowed by the try/catch around that call -
|
|
140
|
+
// the exact silent-no-op failure mode this whole capability exists to prevent, just for the
|
|
141
|
+
// registering harness instead of a shipped one.
|
|
142
|
+
HARNESSES.push({ ...harness, terminalSessionEndReasons: harness.terminalSessionEndReasons ?? [] });
|
|
143
|
+
}
|
|
144
|
+
export function unregisterHarness(name) {
|
|
145
|
+
const index = HARNESSES.findIndex((harness) => harness.name === name);
|
|
146
|
+
if (index !== -1)
|
|
147
|
+
HARNESSES.splice(index, 1);
|
|
148
|
+
}
|
|
149
|
+
export function isClaudeCommand(command) {
|
|
150
|
+
return harnessFor(command).name === "claude";
|
|
151
|
+
}
|
|
152
|
+
// An empty command is "no fact recorded", never "unclassifiable" - reading it the other way stops
|
|
153
|
+
// every wake a plain session ever set for itself.
|
|
154
|
+
export function screenClassifiable(command) {
|
|
155
|
+
if (command.trim() === "")
|
|
156
|
+
return true;
|
|
157
|
+
return harnessFor(command).classifiesPaneScreen;
|
|
158
|
+
}
|
|
159
|
+
// Defaults to claude's, not null: otherwise screenClassifiable's "true" above becomes a lie the
|
|
160
|
+
// first time anything reads the pane it was supposed to gate.
|
|
161
|
+
export function paneClassifierFor(command) {
|
|
162
|
+
if (command.trim() === "")
|
|
163
|
+
return claudeHarness.paneClassifier;
|
|
164
|
+
return harnessFor(command).paneClassifier;
|
|
165
|
+
}
|
|
166
|
+
export function transcriptDirFor(command) {
|
|
167
|
+
return harnessFor(command).transcriptDir;
|
|
168
|
+
}
|
|
169
|
+
// ANY transcript to corroborate a stall against: a resolvable directory, or a path the harness
|
|
170
|
+
// reported through its own hook payload. One predicate so both call sites admit the same rows.
|
|
171
|
+
export function hasTranscriptSignal(row) {
|
|
172
|
+
return transcriptDirFor(row.command) || row.transcript_path !== "";
|
|
173
|
+
}
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
export function helpOverview() {
|
|
2
|
+
return `HIVE — shared memory and coordination across Claude Code sessions
|
|
3
|
+
|
|
4
|
+
Many workers, one shared memory. Multiple Claude sessions (and humans) share
|
|
5
|
+
one project-scoped state store: pads, todos, kv, leases.
|
|
6
|
+
|
|
7
|
+
Call help(topic="<name>") for details. Topics: ${Object.keys(HELP_TOPICS).join(", ")}.
|
|
8
|
+
|
|
9
|
+
Scope resolution for project-scoped tools:
|
|
10
|
+
pass project_id for a one-off override
|
|
11
|
+
otherwise hive uses the project selected in this session
|
|
12
|
+
otherwise it auto-detects from the working directory, or auto-selects a
|
|
13
|
+
single registered project
|
|
14
|
+
|
|
15
|
+
Quick start:
|
|
16
|
+
1. whoami — see your actor id and effective project
|
|
17
|
+
2. pad_list / todo_list — see what other sessions have shared
|
|
18
|
+
3. help(topic="workflow") — the lead/worker operating pattern
|
|
19
|
+
|
|
20
|
+
PROMPTS — invokable prompts (slash commands in MCP clients)
|
|
21
|
+
runbook — load this project's own standing process
|
|
22
|
+
|
|
23
|
+
AGENTS — spawn and drive worker sessions in tmux
|
|
24
|
+
agent_spawn, agent_list, agent_status, agent_send, agent_output,
|
|
25
|
+
agent_message_get, agent_rename, agent_park, agent_resume, agent_close
|
|
26
|
+
|
|
27
|
+
WAKE-UPS — scheduled nudges instead of polling
|
|
28
|
+
wake_set, wake_when_idle, wake_get, wake_update, wake_cancel, wake_list
|
|
29
|
+
|
|
30
|
+
PADS — shared documents (plans, findings, handoffs)
|
|
31
|
+
pad_write, pad_read, pad_append, pad_edit, pad_list, pad_archive, pad_delete
|
|
32
|
+
|
|
33
|
+
TODOS — shared task tracking with dependencies
|
|
34
|
+
todo_create, todo_list, todo_get, todo_update, todo_archive,
|
|
35
|
+
todo_complete, todo_comment, todo_block, todo_unblock
|
|
36
|
+
|
|
37
|
+
KV — small shared status values (with optional TTL)
|
|
38
|
+
kv_set, kv_get, kv_list, kv_delete
|
|
39
|
+
|
|
40
|
+
LEASES — advisory expiring claims on shared work areas
|
|
41
|
+
lease_acquire, lease_release
|
|
42
|
+
|
|
43
|
+
PROJECTS — scope management
|
|
44
|
+
project_list, project_add, project_select, project_prune
|
|
45
|
+
|
|
46
|
+
ACTORS — who is writing
|
|
47
|
+
actor_prune`;
|
|
48
|
+
}
|
|
49
|
+
export const HELP_TOPICS = {
|
|
50
|
+
workflow: `WORKFLOW — lead/worker orchestration
|
|
51
|
+
|
|
52
|
+
One session acts as the lead. It spawns and drives workers with the agent
|
|
53
|
+
tools; each worker is its own agent CLI session in a tmux window, locked to
|
|
54
|
+
this project.
|
|
55
|
+
|
|
56
|
+
Read the standing process first: run \`hive runbook\` in a shell. It prints
|
|
57
|
+
the project's profile runbook, or its runbook pad when the project has no
|
|
58
|
+
profile, with hive.yml vars resolved. Whatever it says takes precedence over
|
|
59
|
+
this generic pattern.
|
|
60
|
+
|
|
61
|
+
The operating pattern:
|
|
62
|
+
1. Interview the human until you can write a real plan.
|
|
63
|
+
2. Write the plan to a pad: pad_write(name="plan", ...). Include the goal,
|
|
64
|
+
code paths, constraints, parallel lanes, and verification steps. The
|
|
65
|
+
pad is the shared memory for the run. Keep it current.
|
|
66
|
+
3. Convert the plan into todos. One todo per lane. Name the files each
|
|
67
|
+
lane owns and its acceptance criteria in the body.
|
|
68
|
+
4. Encode ordering with blockers: todo_block(todo_id, blocker_id). Workers
|
|
69
|
+
pick up work with todo_list(is_blocked=false, status="open").
|
|
70
|
+
5. Spawn one worker per unblocked lane: agent_spawn(name="api-worker").
|
|
71
|
+
For parallel file edits, give each worker its own git worktree via cwd.
|
|
72
|
+
A claude worker briefs itself: hive loads the brief into its system
|
|
73
|
+
prompt. Send the lane's objective, its pad/todo ids, and file ownership
|
|
74
|
+
directly. A worker whose screen hive cannot classify is refused on the
|
|
75
|
+
text path; its brief is written to the receipt's brief_path instead.
|
|
76
|
+
6. Workers set status="in_progress", do the work, then todo_comment the
|
|
77
|
+
handoff: changed files, tests run, remaining risk. Then todo_complete.
|
|
78
|
+
Completing returns newly_unblocked todo ids.
|
|
79
|
+
7. Do not poll. Running MORE THAN ONE worker, set
|
|
80
|
+
wake_when_idle(scope="project", body="...") ONCE and go quiet: it is a
|
|
81
|
+
STANDING watch over the crew you spawn, it reports each worker as it
|
|
82
|
+
finishes, it covers workers you spawn later, and you never re-arm it.
|
|
83
|
+
wake_when_idle(agents=[...], body="...") is the one-shot: it fires on
|
|
84
|
+
the first finish and STOPS WATCHING the rest, so at three or more
|
|
85
|
+
workers it will lose one. Either way, read REAL output (agent_output)
|
|
86
|
+
before declaring a lane done. The human can watch live with
|
|
87
|
+
tmux attach -t hive-main.
|
|
88
|
+
8. The lead reviews real diffs and output, not just summaries, then
|
|
89
|
+
integrates one lane at a time.
|
|
90
|
+
9. Capture handoffs in pads/todo comments BEFORE agent_close; terminal
|
|
91
|
+
output is not retained.
|
|
92
|
+
|
|
93
|
+
Manual workers still work too: a human can open a window and run
|
|
94
|
+
HIVE_AGENT_ID=worker-1 HIVE_AGENT_NAME="API worker" claude
|
|
95
|
+
|
|
96
|
+
Rules that keep this sane:
|
|
97
|
+
- Keep worker prompts self-contained: include pad and todo ids.
|
|
98
|
+
- Record every decision in the pad or a todo comment. Sessions die;
|
|
99
|
+
the store survives.
|
|
100
|
+
- Take a lease (lease_acquire) before editing a shared file area; leases
|
|
101
|
+
expire so a dead session never wedges anyone.`,
|
|
102
|
+
profiles: `PROFILES — standing instructions shared across projects
|
|
103
|
+
|
|
104
|
+
A profile is a named set of instructions this project runs under, named by
|
|
105
|
+
"profile:" in hive.yml. hive resolves each file from ~/.hive/profiles first,
|
|
106
|
+
then its own defaults, so a file the human forked is theirs and the rest
|
|
107
|
+
track hive.
|
|
108
|
+
|
|
109
|
+
posture.md already in your system prompt if the session started with
|
|
110
|
+
hive lead, with this project's vars resolved. You do not need
|
|
111
|
+
to read it; \`hive posture\` shows the human what you were given.
|
|
112
|
+
runbook.md the project's process. Read it with \`hive runbook\` (a shell
|
|
113
|
+
command, not a tool). It has hive.yml vars substituted.
|
|
114
|
+
worker.md what agent_spawn puts in a worker's system prompt.
|
|
115
|
+
|
|
116
|
+
hive runbook the process, ready to read
|
|
117
|
+
hive posture the posture text you were started with
|
|
118
|
+
hive profile list what exists and where each file comes from
|
|
119
|
+
hive profile fork <name> copy a default into ~/.hive so the human can edit
|
|
120
|
+
|
|
121
|
+
A project with a profile usually has NO "runbook" pad; the profile replaced
|
|
122
|
+
it. A project on "profile: none" keeps the pad, and \`hive runbook\` prints
|
|
123
|
+
that instead, so the one command is right either way.
|
|
124
|
+
|
|
125
|
+
If the human asks to change how work runs here, that is an edit to the
|
|
126
|
+
runbook (fork it first), not a pad write.`,
|
|
127
|
+
agents: `AGENTS — spawn and drive worker sessions in tmux
|
|
128
|
+
|
|
129
|
+
agent_spawn(name?, model?, command?, extra_args?, cwd?, placement?, layout?) —
|
|
130
|
+
start a worker (default command: claude) in session hive-main.
|
|
131
|
+
placement="split" (default) tiles the worker as a pane in the lead's
|
|
132
|
+
window so the whole crew shares one screen; placement="window" gives it
|
|
133
|
+
its own tmux window (an iTerm tab under control mode). layout picks how split panes are
|
|
134
|
+
arranged: tiled (default), main-vertical (lead takes the left half,
|
|
135
|
+
workers stack on the right), main-horizontal, even-horizontal,
|
|
136
|
+
even-vertical. hive re-applies it when a worker closes, so the
|
|
137
|
+
arrangement survives crew changes. Projects can set a default placement
|
|
138
|
+
and layout in hive.yml; an explicit argument overrides it. cwd defaults to the project root; pass
|
|
139
|
+
a git worktree path to isolate parallel file edits. A claude worker gets
|
|
140
|
+
its brief in the system prompt; other commands return instructions to
|
|
141
|
+
PREPEND to the first prompt.
|
|
142
|
+
Workers run with HIVE_PROJECT_LOCK=1.
|
|
143
|
+
agent_send(name|agent_id, text?, keys?, submit?, wait_ms?) — type into the
|
|
144
|
+
worker's terminal. Multi-line text pastes safely; keys sends tmux key
|
|
145
|
+
names like Escape or C-c. wait_ms returns the terminal tail after.
|
|
146
|
+
Text is prefixed with the sender tag.
|
|
147
|
+
Text over 300 chars sent to a LEAD by anyone but that lead is stored and
|
|
148
|
+
delivered as a one-line pointer, so the human's own pane stays readable;
|
|
149
|
+
the receipt says shortened and names the message id.
|
|
150
|
+
agent_message_get(message_id) — the full text behind a pointer line. Kept
|
|
151
|
+
7 days; a lookup for a pruned id says it expired, not that it is missing.
|
|
152
|
+
agent_output(name|agent_id, lines?) — read the rendered terminal.
|
|
153
|
+
agent_status(name|agent_id, include_brief?) — liveness, current command,
|
|
154
|
+
short tail, and the path to the brief this worker was given
|
|
155
|
+
(include_brief=true returns its text; no transcript records it).
|
|
156
|
+
agent_list(include_closed?) — all agents with live status.
|
|
157
|
+
agent_rename(name|agent_id, new_name) — change the display name. actor_id
|
|
158
|
+
stays agent:N, so older pad writes and todo comments still point here.
|
|
159
|
+
A live claude worker is told to retitle its own session, which arrives as
|
|
160
|
+
a user turn: rename between assignments, not mid-task.
|
|
161
|
+
agent_park(name|agent_id) — END OF DAY. Kill the pane, mark the row PARKED
|
|
162
|
+
rather than plain closed, record the branch, and hand back a board line
|
|
163
|
+
plus the one call that brings the lane back. Use this instead of
|
|
164
|
+
agent_close whenever the lane is PAUSED rather than finished: "closed"
|
|
165
|
+
alone means both, and a next-morning lead cannot tell them apart.
|
|
166
|
+
agent_resume(name|agent_id) — NEXT MORNING. Reopen a closed or parked
|
|
167
|
+
claude or codex worker on a fresh pane from its recorded session id,
|
|
168
|
+
with the same actor_id and its full prior context. It does not send
|
|
169
|
+
the assignment; agent_send it afterwards. Read the pane before
|
|
170
|
+
believing any wake about a worker you just resumed.
|
|
171
|
+
agent_close(name|agent_id) — kill the worker's pane and mark closed. Capture
|
|
172
|
+
handoffs first; output is not retained. Self-close needs confirm_self.
|
|
173
|
+
On a PARKED row (by agent_id) it releases the park instead, which is how
|
|
174
|
+
you abandon a lane you have decided not to resume.
|
|
175
|
+
|
|
176
|
+
Address a worker by its name, not its id: agent_send(name="impl", ...). A
|
|
177
|
+
partial name works when it matches one running worker, so name="123" finds
|
|
178
|
+
DEVX-123. The name is the handle you chose and the one shown in its pane.
|
|
179
|
+
|
|
180
|
+
Visibility is automatic: if nothing is attached to the project session when
|
|
181
|
+
a worker spawns, hive pops open iTerm (control mode by default; see
|
|
182
|
+
hive setup --attach) or Terminal attached to it, so the human sees every
|
|
183
|
+
worker as a native window and can type into any of them. The human usually
|
|
184
|
+
starts the day with the CLI: hive lead.
|
|
185
|
+
Manual attach also works: tmux attach -t hive-main, or
|
|
186
|
+
tmux -CC attach -t hive-main for iTerm's native windows.
|
|
187
|
+
|
|
188
|
+
Worker state (agent_state on list/status) comes from Claude Code hooks that
|
|
189
|
+
agent_spawn wires automatically: working (prompt submitted), idle (finished
|
|
190
|
+
its turn), waiting (needs permission or input). Non-claude commands show
|
|
191
|
+
"unknown".`,
|
|
192
|
+
wakes: `WAKE-UPS — scheduled nudges instead of polling
|
|
193
|
+
|
|
194
|
+
wake_set(delay_seconds, body, deliver_to?, repeat_every_seconds?) —
|
|
195
|
+
one-shot or repeating wake-up
|
|
196
|
+
wake_when_idle(agents | scope, body, mode?, max_wait_seconds?,
|
|
197
|
+
deliver_to?) — fire when workers go idle. Pass exactly one of:
|
|
198
|
+
scope="project" — a STANDING watch over the crew you spawn in this project.
|
|
199
|
+
It reports EACH worker as it finishes or its window dies, covers
|
|
200
|
+
workers spawned after you set it, and keeps watching until
|
|
201
|
+
max_wait_seconds (default 4 hours) or wake_cancel. Workers already
|
|
202
|
+
idle DO count, deliberately. Use this whenever more than one worker
|
|
203
|
+
is running. ONE PER PROJECT: setting a second is refused and names
|
|
204
|
+
the one already running, so calling twice costs nothing.
|
|
205
|
+
agents=[...] — a ONE-SHOT over a named list. mode=any (default) fires
|
|
206
|
+
on the first fresh idle transition and then STOPS WATCHING the
|
|
207
|
+
others; mode=all fires when every watched agent is idle and returns
|
|
208
|
+
already_satisfied instead of scheduling if they already are.
|
|
209
|
+
Agents already idle when it was set do not count.
|
|
210
|
+
wake_get(wake_id) — read one wake-up by id, with its untruncated body
|
|
211
|
+
(wake_list truncates at 120 chars)
|
|
212
|
+
wake_update(wake_id, delay_seconds?, body?, repeat_every_seconds?) — edit
|
|
213
|
+
a pending wake-up you own in place, without minting a new id.
|
|
214
|
+
delay_seconds is relative to now; repeat_every_seconds changes only the
|
|
215
|
+
interval used for firings after this one. Only body can be edited on an
|
|
216
|
+
idle wake (from wake_when_idle); delay_seconds/repeat_every_seconds
|
|
217
|
+
apply to a delay wake (from wake_set) only.
|
|
218
|
+
wake_cancel(wake_id) — cancel a pending wake-up you own
|
|
219
|
+
wake_list() — pending wake-ups in this project
|
|
220
|
+
|
|
221
|
+
Delivery contract:
|
|
222
|
+
When a wake-up fires, its body is typed into the target session's terminal
|
|
223
|
+
as a fresh user turn, prefixed with [hive wake #N]. The receiving agent
|
|
224
|
+
gets it cold, so write bodies self-contained: agent ids, pad/todo ids,
|
|
225
|
+
and the next action. Plain English, no markup.
|
|
226
|
+
Waking your OWN lead pane is the exception: it is not cold, so carry the
|
|
227
|
+
action and the ids and point at where the detail lives.
|
|
228
|
+
|
|
229
|
+
Receiving wake-ups:
|
|
230
|
+
Spawned workers can always receive (deliver_to their name or id).
|
|
231
|
+
A lead session can receive only if it runs inside tmux; start it with
|
|
232
|
+
tmux, then claude (watch with tmux attach, or tmux -CC attach for iTerm's
|
|
233
|
+
native windows). Otherwise wake_set without deliver_to fails with guidance.
|
|
234
|
+
|
|
235
|
+
Idle detection is exact, not heuristic: Claude Code hooks in each spawned
|
|
236
|
+
worker report working/idle/waiting into the shared store the moment they
|
|
237
|
+
happen. Cancel wake-ups when the monitored work is done.`,
|
|
238
|
+
projects: `PROJECTS — scope management
|
|
239
|
+
|
|
240
|
+
Every pad, todo, kv entry, and lease belongs to one project (a directory).
|
|
241
|
+
|
|
242
|
+
project_list — all registered projects plus the current selection
|
|
243
|
+
project_add(path?, name?) — register a directory (defaults to cwd)
|
|
244
|
+
project_select(project_id) — set this session's default scope
|
|
245
|
+
project_prune() — delete every registered project that owns no rows
|
|
246
|
+
anywhere in the store, verified individually; never your own; refuses
|
|
247
|
+
under HIVE_PROJECT_LOCK=1 since it sweeps every project, not just yours
|
|
248
|
+
|
|
249
|
+
Resolution order: explicit project_id argument, then session selection, then
|
|
250
|
+
working-directory auto-detection. When the working directory matches no
|
|
251
|
+
registered project, it is registered as a new project automatically; hive
|
|
252
|
+
never falls back to an unrelated project.
|
|
253
|
+
|
|
254
|
+
Git worktrees and subdirectories resolve to the primary checkout's project,
|
|
255
|
+
so a worker session in a worktree shares the main repo's pads and todos.
|
|
256
|
+
Register a worktree path explicitly with project_add to make it a separate
|
|
257
|
+
project on purpose.
|
|
258
|
+
|
|
259
|
+
Cross-project access is intentional-only. An empty result means there is
|
|
260
|
+
nothing in this project; report that and stop. Reach into another project
|
|
261
|
+
(project_select or a project_id override) only when the user explicitly
|
|
262
|
+
names it. Set HIVE_PROJECT_LOCK=1 in a session's environment to reject
|
|
263
|
+
cross-project access entirely; use it for worker sessions.`,
|
|
264
|
+
identity: `IDENTITY — who is writing
|
|
265
|
+
|
|
266
|
+
Every write records an actor. Identity comes from environment variables read
|
|
267
|
+
at session start:
|
|
268
|
+
|
|
269
|
+
HIVE_AGENT_ID stable actor id, e.g. "worker-1" or "lead"
|
|
270
|
+
HIVE_AGENT_NAME display name, e.g. "API worker"
|
|
271
|
+
|
|
272
|
+
Without HIVE_AGENT_ID you are "user:<username>" (kind: human). Launch worker
|
|
273
|
+
sessions with distinct ids so handoffs and leases are attributable:
|
|
274
|
+
|
|
275
|
+
HIVE_AGENT_ID=worker-1 claude
|
|
276
|
+
|
|
277
|
+
whoami shows your actor id, kind, and effective project.
|
|
278
|
+
|
|
279
|
+
See help(topic="actors") for actor_prune.`,
|
|
280
|
+
actors: `ACTORS — pruning stale identities
|
|
281
|
+
|
|
282
|
+
actor_prune() — delete every actor that owns no rows anywhere in the store
|
|
283
|
+
and has not been active in the last minute, checked globally across
|
|
284
|
+
every project since actors carry no project_id; never your own;
|
|
285
|
+
refuses under HIVE_PROJECT_LOCK=1 since it sweeps the whole store
|
|
286
|
+
|
|
287
|
+
See help(topic="identity") for how an actor id is assigned in the first
|
|
288
|
+
place.`,
|
|
289
|
+
pads: `PADS — shared documents
|
|
290
|
+
|
|
291
|
+
pad_write(name, content, tags?) — create a pad; names are unique per project
|
|
292
|
+
pad_write(pad_id, name, content, expected_revision) — full overwrite; the
|
|
293
|
+
revision guard makes concurrent writers safe
|
|
294
|
+
pad_read(pad_id | name) — content plus revision and metadata
|
|
295
|
+
pad_append(pad_id, content, expected_revision?) — add to the end
|
|
296
|
+
pad_edit(pad_id, old_text, new_text, expected_revision?) — replace one
|
|
297
|
+
unique literal occurrence; include enough context to disambiguate
|
|
298
|
+
pad_list(query?, tags?, include_archived?) — summaries without content;
|
|
299
|
+
query matches names and content and returns a snippet
|
|
300
|
+
pad_archive(pad_id, archived?) — archive frees the name for a new active
|
|
301
|
+
pad; content stays readable by pad_id. archived=false unarchives.
|
|
302
|
+
pad_delete(pad_id, expected_revision?) — permanent; prefer pad_archive
|
|
303
|
+
|
|
304
|
+
Convention: read before you write. Reads return the revision; pass it back
|
|
305
|
+
as expected_revision so a concurrent edit fails loudly instead of clobbering.
|
|
306
|
+
|
|
307
|
+
Lifecycle: durable pads (a runbook, a daily board) get updated in place.
|
|
308
|
+
Temporary pads (a lane plan, a findings dump) get archived when their work
|
|
309
|
+
completes, like todos. Rotate a daily board by archiving yesterday's pad
|
|
310
|
+
and writing a fresh one under the same name; keep the live pad small,
|
|
311
|
+
since every read pays for its full length.`,
|
|
312
|
+
todos: `TODOS — shared task tracking
|
|
313
|
+
|
|
314
|
+
todo_create(title, body?, priority?, tags?, slug?, blocked_by?) — priority
|
|
315
|
+
is high|medium|low; slug is a short label (~3-5 words) so this todo
|
|
316
|
+
reads the same way everywhere it's referenced by id
|
|
317
|
+
todo_list(status?, is_blocked?, priority?, query?, tags?, include_archived?, limit?, offset?)
|
|
318
|
+
— status is open|in_progress|backlog|completed; is_blocked=false finds
|
|
319
|
+
dispatchable work; archived todos are excluded unless include_archived
|
|
320
|
+
todo_get(todo_id, include_comments?) — full body, blockers, comments;
|
|
321
|
+
always reaches an archived todo too, by id
|
|
322
|
+
todo_update(todo_id, title?, body?, priority?, status?, tags?, slug?)
|
|
323
|
+
todo_archive(todo_id, archived?) — retires a todo but keeps it readable
|
|
324
|
+
by id; archived=false reverses it. Refuses if this todo still blocks
|
|
325
|
+
a non-completed todo, unless this todo is itself completed
|
|
326
|
+
todo_complete(todo_id, completed?) — returns newly_unblocked todo ids;
|
|
327
|
+
completed=false reopens
|
|
328
|
+
todo_comment(todo_id, body) — handoffs, decisions, findings
|
|
329
|
+
todo_block(todo_id, blocker_id) / todo_unblock(todo_id, blocker_id)
|
|
330
|
+
|
|
331
|
+
Blockers form a dependency graph (cycles are rejected). A todo is blocked
|
|
332
|
+
while any of its blockers is not completed. Archived and completed are
|
|
333
|
+
independent axes: archiving hides a closed lane's scaffolding from
|
|
334
|
+
todo_list without marking anything done.`,
|
|
335
|
+
kv: `KV — small shared status values
|
|
336
|
+
|
|
337
|
+
kv_set(key, value, ttl_seconds?) — value is any JSON; TTL optional
|
|
338
|
+
kv_get(key)
|
|
339
|
+
kv_list(prefix?)
|
|
340
|
+
kv_delete(key)
|
|
341
|
+
|
|
342
|
+
Use kv for values another session needs to discover: current branch, a port,
|
|
343
|
+
a phase flag. Use pads for anything longer than a line or two.`,
|
|
344
|
+
leases: `LEASES — advisory expiring claims on shared work areas
|
|
345
|
+
|
|
346
|
+
lease_acquire(key, ttl_seconds) — non-blocking; returns the holder if
|
|
347
|
+
already taken; re-taking your own lease extends it
|
|
348
|
+
lease_release(key)
|
|
349
|
+
|
|
350
|
+
Keys are project-scoped and free-form; keep them stable and specific, like
|
|
351
|
+
"file:src/api/routes.ts" or "todo:42". Leases expire on their own, so a
|
|
352
|
+
crashed session never wedges the team.`,
|
|
353
|
+
};
|