opencode-herdr-orchestration 0.1.6 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-herdr-orchestration",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Capability-separated Herdr orchestration agents for OpenCode",
5
5
  "author": "CodingJinxx",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "scripts": {
30
30
  "test": "node --test",
31
- "check": "node --check src/plugin.js && node --check src/index.js && node --check src/agents.js && node --check src/prompts.js && node --check src/response.js && node --check bin/orchestration.js"
31
+ "check": "node --check src/plugin.js && node --check src/index.js && node --check src/agents.js && node --check src/prompts.js && node --check src/response.js && node --check src/state.js && node --check bin/orchestration.js"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=20"
package/src/agents.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
- SHEEP_BUILD_PROMPT,
3
- SHEEP_PLAN_PROMPT,
4
- SHEPHERD_BUILD_PROMPT,
5
- SHEPHERD_PLAN_PROMPT,
2
+ GRAZER_PROMPT,
3
+ SHEEPDOG_PROMPT,
4
+ SHEEP_PROMPT,
5
+ SHEPHERD_GOVERNOR_PROMPT,
6
+ SHEPHERD_PROMPT,
6
7
  SHEARER_REVIEW_PROMPT,
7
8
  } from "./prompts.js";
8
9
 
@@ -66,6 +67,112 @@ const markdownOnly = {
66
67
  "**/*.md": "allow",
67
68
  };
68
69
 
70
+ // Constrained orchestration state tools. The planning shepherd authors plan
71
+ // artifacts; shepherd-governor and sheepdog read the authoritative plan, and
72
+ // sheepdog records and reads execution artifacts. This matrix is the single
73
+ // source of truth for both the plugin-level tool enforcement (src/index.js)
74
+ // and the per-agent permission entries below.
75
+ export const STATE_TOOLS = Object.freeze({
76
+ planWrite: "herdr_plan_write",
77
+ planRead: "herdr_plan_read",
78
+ executionWrite: "herdr_execution_write",
79
+ executionRead: "herdr_execution_read",
80
+ });
81
+
82
+ export const STATE_TOOL_ACCESS = Object.freeze(
83
+ new Map([
84
+ [STATE_TOOLS.planWrite, new Set(["shepherd"])],
85
+ [STATE_TOOLS.planRead, new Set(["shepherd", "shepherd-governor", "sheepdog"])],
86
+ [STATE_TOOLS.executionWrite, new Set(["sheepdog"])],
87
+ [STATE_TOOLS.executionRead, new Set(["sheepdog"])],
88
+ ]),
89
+ );
90
+
91
+ const ALL_STATE_TOOLS = Object.freeze(Object.values(STATE_TOOLS));
92
+
93
+ function stateToolPermissions(allowedTools) {
94
+ return Object.fromEntries(
95
+ ALL_STATE_TOOLS.map((name) => [name, allowedTools.includes(name) ? "allow" : "deny"]),
96
+ );
97
+ }
98
+
99
+ const SHEPHERD_STATE_TOOLS = [STATE_TOOLS.planWrite, STATE_TOOLS.planRead];
100
+ const GOVERNOR_STATE_TOOLS = [STATE_TOOLS.planRead];
101
+ const SHEEPDOG_STATE_TOOLS = [
102
+ STATE_TOOLS.planRead,
103
+ STATE_TOOLS.executionWrite,
104
+ STATE_TOOLS.executionRead,
105
+ ];
106
+
107
+ const SHEPHERD_SPAWNABLE_AGENTS = ["grazer"];
108
+ const GOVERNOR_SPAWNABLE_AGENTS = ["grazer", "sheepdog"];
109
+ const SHEEPDOG_SPAWNABLE_AGENTS = ["grazer", "sheep", "shearer-low", "shearer-medium"];
110
+
111
+ const SHEEPDOG_LIFECYCLE_ALLOWS = {
112
+ "git merge --ff-only*": "allow",
113
+ "git merge --no-ff --no-edit*": "allow",
114
+ "git merge --continue": "allow",
115
+ "git merge --abort": "allow",
116
+ "git merge --quit": "allow",
117
+ "git cherry-pick*": "allow",
118
+ "git cherry-pick --continue": "allow",
119
+ "git cherry-pick --skip": "allow",
120
+ "git cherry-pick --abort": "allow",
121
+ "git cherry-pick --quit": "allow",
122
+ "git commit*": "allow",
123
+ };
124
+
125
+ const SHEEPDOG_DENIALS = {
126
+ "git push*": "deny",
127
+ "git pull*": "deny",
128
+ "git fetch*": "deny",
129
+ "git remote*": "deny",
130
+ "git rebase*": "deny",
131
+ "git reset*": "deny",
132
+ "git revert*": "deny",
133
+ "git checkout*": "deny",
134
+ "git switch*": "deny",
135
+ "git stash*": "deny",
136
+ "git clean*": "deny",
137
+ "git worktree*": "deny",
138
+ "git branch -D*": "deny",
139
+ "git branch -d*": "deny",
140
+ "git commit --no-verify*": "deny",
141
+ "git commit * --no-verify*": "deny",
142
+ "git commit --amend*": "deny",
143
+ "git commit * --amend*": "deny",
144
+ "git merge *--no-verify*": "deny",
145
+ };
146
+
147
+ const SHEEP_DENIALS = {
148
+ "herdr*": "deny",
149
+ "gh*": "deny",
150
+ "npm publish*": "deny",
151
+ "git push*": "deny",
152
+ "git pull*": "deny",
153
+ "git fetch*": "deny",
154
+ "git remote*": "deny",
155
+ "git merge*": "deny",
156
+ "git cherry-pick*": "deny",
157
+ "git rebase*": "deny",
158
+ "git reset*": "deny",
159
+ "git revert*": "deny",
160
+ "git checkout*": "deny",
161
+ "git switch*": "deny",
162
+ "git stash*": "deny",
163
+ "git clean*": "deny",
164
+ "git worktree*": "deny",
165
+ "git tag*": "deny",
166
+ "git apply*": "deny",
167
+ "git am*": "deny",
168
+ "git branch -D*": "deny",
169
+ "git branch -d*": "deny",
170
+ "git commit --no-verify*": "deny",
171
+ "git commit *--no-verify*": "deny",
172
+ "git commit --amend*": "deny",
173
+ "git commit *--amend*": "deny",
174
+ };
175
+
69
176
  function mergeRecord(base, override) {
70
177
  if (!base || typeof base !== "object" || Array.isArray(base)) return override ?? base;
71
178
  if (!override || typeof override !== "object" || Array.isArray(override)) return override ?? base;
@@ -80,30 +187,41 @@ export function mergeAgent(defaults, override) {
80
187
  return mergeRecord(defaults, override);
81
188
  }
82
189
 
190
+ function spawnMatrix(agents) {
191
+ return Object.fromEntries(
192
+ agents.map((agent) => [`herdr agent start * --kind opencode --pane * -- --agent ${agent}`, "allow"]),
193
+ );
194
+ }
195
+
83
196
  export function createAgents(options = {}) {
84
197
  const shepherdModel = options.shepherdModel;
85
198
  const workerModel = options.workerModel ?? "litellm/glm-5.3-flash";
86
199
  const workerVariant = options.workerVariant;
200
+ const grazerVariant = options.grazerVariant ?? workerVariant;
201
+ const sheepdogModel = options.sheepdogModel ?? "litellm/glm-5.3-flash";
202
+ const sheepdogVariant = options.sheepdogVariant;
87
203
  const reviewerModel = options.reviewerModel ?? "litellm-responses/gpt-5.6-terra";
88
- const shepherdBuildPermissions = options.shepherdBuildPermissions ?? {};
89
- const shepherdBuildPrompt = appendPrompt(SHEPHERD_BUILD_PROMPT, options.shepherdBuildPromptAppend);
204
+ const shepherdPermissions = options.shepherdPermissions ?? {};
205
+ const shepherdPrompt = appendPrompt(SHEPHERD_PROMPT, options.shepherdPromptAppend);
90
206
 
91
207
  return {
92
- "shepherd-plan": {
208
+ shepherd: {
93
209
  mode: "primary",
94
210
  ...(shepherdModel ? { model: shepherdModel } : {}),
95
- description: "Researches and presents implementation-ready plans through sheep-plan workers without implementing them.",
96
- prompt: SHEPHERD_PLAN_PROMPT,
211
+ description:
212
+ "Plans through grazer research and presents implementation-ready plans; never implements, integrates, or delivers.",
213
+ prompt: shepherdPrompt,
97
214
  permission: {
98
215
  grep: "allow",
99
216
  todowrite: "allow",
100
217
  edit: markdownOnly,
101
218
  apply_patch: markdownOnly,
102
219
  herdr_agent_response: "allow",
220
+ ...stateToolPermissions(SHEPHERD_STATE_TOOLS),
103
221
  bash: {
104
222
  "*": "deny",
105
223
  ...herdrInspection,
106
- "herdr agent start * --kind opencode --pane * -- --agent sheep-plan": "allow",
224
+ ...spawnMatrix(SHEPHERD_SPAWNABLE_AGENTS),
107
225
  "git status*": "allow",
108
226
  "git diff*": "allow",
109
227
  "git log*": "allow",
@@ -115,44 +233,38 @@ export function createAgents(options = {}) {
115
233
  "git add *.md": "allow",
116
234
  "git add **/*.md": "allow",
117
235
  "git commit*": "allow",
118
- "git push origin HEAD": "allow",
119
- "git push -u origin HEAD": "allow",
120
- "git push --set-upstream origin HEAD": "allow",
236
+ "git push*": "deny",
121
237
  "git commit --no-verify*": "deny",
122
238
  "git commit * --no-verify*": "deny",
123
- "git push *main*": "deny",
124
- "git push *master*": "deny",
125
- "git push *:*": "deny",
126
- "git push *--no-verify*": "deny",
127
- "git push *--force*": "deny",
128
- "git push *-f*": "deny",
129
- "git push *--delete*": "deny",
239
+ "git commit --amend*": "deny",
240
+ "git commit * --amend*": "deny",
130
241
  "git branch -D*": "deny",
131
242
  "git branch -d*": "deny",
132
243
  "git worktree remove * --force*": "deny",
133
244
  ...separatorDenials,
134
245
  },
135
246
  task: "deny",
247
+ ...shepherdPermissions,
136
248
  },
137
249
  },
138
250
 
139
- "shepherd-build": {
251
+ "shepherd-governor": {
140
252
  mode: "primary",
141
253
  ...(shepherdModel ? { model: shepherdModel } : {}),
142
- description: "Executes approved plans through planning, implementation, and independent review workers.",
143
- prompt: shepherdBuildPrompt,
254
+ description:
255
+ "Executes approved plans through sheepdog squads, verifies results, and owns final delivery: pushes, merges, PRs.",
256
+ prompt: SHEPHERD_GOVERNOR_PROMPT,
144
257
  permission: {
145
258
  grep: "allow",
259
+ todowrite: "allow",
146
260
  edit: markdownOnly,
147
261
  apply_patch: markdownOnly,
148
262
  herdr_agent_response: "allow",
263
+ ...stateToolPermissions(GOVERNOR_STATE_TOOLS),
149
264
  bash: {
150
265
  "*": "deny",
151
266
  ...herdrInspection,
152
- "herdr agent start * --kind opencode --pane * -- --agent sheep-plan": "allow",
153
- "herdr agent start * --kind opencode --pane * -- --agent sheep-build": "allow",
154
- "herdr agent start * --kind opencode --pane * -- --agent shearer-review-low": "allow",
155
- "herdr agent start * --kind opencode --pane * -- --agent shearer-review-medium": "allow",
267
+ ...spawnMatrix(GOVERNOR_SPAWNABLE_AGENTS),
156
268
  "git status*": "allow",
157
269
  "git diff*": "allow",
158
270
  "git log*": "allow",
@@ -160,7 +272,7 @@ export function createAgents(options = {}) {
160
272
  "git branch*": "allow",
161
273
  "git rev-parse*": "allow",
162
274
  "git merge-base*": "allow",
163
- "git worktree*": "allow",
275
+ "git worktree list*": "allow",
164
276
  "git add *.md": "allow",
165
277
  "git add **/*.md": "allow",
166
278
  "git commit*": "allow",
@@ -175,26 +287,64 @@ export function createAgents(options = {}) {
175
287
  "git push *--delete*": "deny",
176
288
  "git push *--no-verify*": "deny",
177
289
  "git commit *--no-verify*": "deny",
290
+ "git commit --amend*": "deny",
291
+ "git commit *--amend*": "deny",
178
292
  "git merge *--no-verify*": "deny",
179
293
  "git branch -D*": "deny",
180
294
  "git branch -d*": "deny",
295
+ "git worktree add*": "deny",
296
+ "git worktree remove*": "deny",
297
+ "git worktree move*": "deny",
298
+ "git worktree prune*": "deny",
181
299
  "git worktree remove * --force*": "deny",
300
+ "herdr worktree create*": "deny",
301
+ "herdr worktree open*": "deny",
302
+ "herdr worktree remove*": "deny",
182
303
  "gh pr create*": "allow",
183
304
  "gh pr view*": "allow",
184
305
  "gh pr checks*": "allow",
185
306
  ...separatorDenials,
186
307
  },
187
308
  task: "deny",
188
- ...shepherdBuildPermissions,
189
309
  },
190
310
  },
191
311
 
192
- "sheep-plan": {
312
+ sheepdog: {
313
+ mode: "primary",
314
+ model: sheepdogModel,
315
+ ...(sheepdogVariant ? { variant: sheepdogVariant } : {}),
316
+ description:
317
+ "Leads execution squads of grazer, sheep, and shearers, prepares worker branches and worktrees, owns validation, review tiers, retries, and conflict recovery, and performs clean local integration with merge and cherry-pick lifecycle commands only.",
318
+ prompt: SHEEPDOG_PROMPT,
319
+ permission: {
320
+ read: "allow",
321
+ glob: "allow",
322
+ grep: "allow",
323
+ list: "allow",
324
+ todowrite: "allow",
325
+ edit: "deny",
326
+ apply_patch: "deny",
327
+ herdr_agent_response: "allow",
328
+ ...stateToolPermissions(SHEEPDOG_STATE_TOOLS),
329
+ bash: {
330
+ "*": "deny",
331
+ ...safeGitInspection,
332
+ ...herdrInspection,
333
+ ...spawnMatrix(SHEEPDOG_SPAWNABLE_AGENTS),
334
+ ...SHEEPDOG_LIFECYCLE_ALLOWS,
335
+ ...SHEEPDOG_DENIALS,
336
+ ...separatorDenials,
337
+ },
338
+ task: "deny",
339
+ },
340
+ },
341
+
342
+ grazer: {
193
343
  mode: "primary",
194
344
  model: workerModel,
195
- ...(workerVariant ? { variant: workerVariant } : {}),
196
- description: "Performs read-only repository research for a shepherd.",
197
- prompt: SHEEP_PLAN_PROMPT,
345
+ ...(grazerVariant ? { variant: grazerVariant } : {}),
346
+ description: "Performs read-only repository research for the shepherd, shepherd-governor, or sheepdog.",
347
+ prompt: GRAZER_PROMPT,
198
348
  permission: {
199
349
  read: "allow",
200
350
  glob: "allow",
@@ -204,51 +354,41 @@ export function createAgents(options = {}) {
204
354
  edit: "deny",
205
355
  apply_patch: "deny",
206
356
  herdr_agent_response: "deny",
357
+ ...stateToolPermissions([]),
207
358
  bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
208
359
  task: "deny",
209
360
  },
210
361
  },
211
362
 
212
- "sheep-build": {
363
+ sheep: {
213
364
  mode: "primary",
214
365
  model: workerModel,
215
366
  ...(workerVariant ? { variant: workerVariant } : {}),
216
- description: "Implements a bounded task, verifies it, and hands a local commit to shepherd-build.",
217
- prompt: SHEEP_BUILD_PROMPT,
367
+ description:
368
+ "Implements a bounded task, verifies it, and hands a local commit to sheepdog.",
369
+ prompt: SHEEP_PROMPT,
218
370
  permission: {
219
371
  grep: "allow",
220
372
  herdr_agent_response: "deny",
373
+ ...stateToolPermissions([]),
221
374
  bash: {
222
375
  "*": "allow",
223
- "git push*": "deny",
224
- "git merge*": "deny",
225
- "git pull*": "deny",
226
- "git branch -D*": "deny",
227
- "git branch -d*": "deny",
228
- "git rebase*": "deny",
229
- "git reset*": "deny",
230
- "git commit *--no-verify*": "deny",
231
- "git commit --no-verify*": "deny",
232
- "git *;*": "deny",
233
- "git *&&*": "deny",
234
- "git *||*": "deny",
235
- "git *|*": "deny",
236
- "git *>*": "deny",
237
- "git *<*": "deny",
376
+ ...SHEEP_DENIALS,
377
+ ...separatorDenials,
238
378
  },
239
379
  task: "deny",
240
380
  },
241
381
  },
242
382
 
243
- "shearer-review-low": reviewerAgent(reviewerModel, "low"),
244
- "shearer-review-medium": reviewerAgent(reviewerModel, "medium"),
383
+ "shearer-low": reviewerAgent(reviewerModel, "low"),
384
+ "shearer-medium": reviewerAgent(reviewerModel, "medium"),
245
385
  };
246
386
  }
247
387
 
248
388
  function appendPrompt(prompt, addition) {
249
389
  if (addition === undefined || addition === "") return prompt;
250
390
  if (typeof addition !== "string") {
251
- throw new TypeError("shepherdBuildPromptAppend must be a string.");
391
+ throw new TypeError("shepherdPromptAppend must be a string.");
252
392
  }
253
393
  return `${prompt.trimEnd()}\n\n${addition.trim()}`;
254
394
  }
@@ -269,6 +409,7 @@ function reviewerAgent(model, variant) {
269
409
  apply_patch: "deny",
270
410
  todowrite: "deny",
271
411
  herdr_agent_response: "deny",
412
+ ...stateToolPermissions([]),
272
413
  bash: { "*": "deny", ...safeGitInspection, ...separatorDenials },
273
414
  task: "deny",
274
415
  },
package/src/index.js CHANGED
@@ -1,20 +1,112 @@
1
- import { createAgents, mergeAgent } from "./agents.js";
1
+ import { tool } from "@opencode-ai/plugin";
2
+
3
+ import { createAgents, mergeAgent, STATE_TOOL_ACCESS, STATE_TOOLS } from "./agents.js";
2
4
  import { createResponseTool } from "./response.js";
5
+ import { createStateService } from "./state.js";
3
6
 
4
7
  const SESSION_MODES = new Map();
5
8
 
6
9
  function modeForAgent(agent) {
7
- if (agent === "shepherd-plan") return "plan";
8
- if (agent === "shepherd-build") return "build";
9
- if (agent === "shearer-review-low" || agent === "shearer-review-medium") return "review";
10
- if (agent === "sheep-plan") return "sheep-plan";
11
- if (agent === "sheep-build") return "sheep-build";
10
+ if (agent === "shepherd") return "shepherd";
11
+ if (agent === "shepherd-governor") return "governor";
12
+ if (agent === "sheepdog") return "sheepdog";
13
+ if (agent === "grazer") return "grazer";
14
+ if (agent === "sheep") return "sheep";
15
+ if (agent === "shearer-low" || agent === "shearer-medium") return "shearer";
12
16
  return "none";
13
17
  }
14
18
 
19
+ function stateError(code, message, retryable = false) {
20
+ return { ok: false, error: { code, message, retryable } };
21
+ }
22
+
23
+ // Exposes the constrained orchestration state API (src/state.js) as plugin
24
+ // tools. Enforcement mirrors STATE_TOOL_ACCESS in src/agents.js: the planning
25
+ // shepherd writes and reads plan artifacts; shepherd-governor and sheepdog
26
+ // read the authoritative plan; sheepdog writes and reads execution artifacts.
27
+ // Artifacts are durable Markdown files under the repository's shared Git
28
+ // common directory, so linked worktrees share state while separate clones
29
+ // never do. The state service invokes only read-only `git rev-parse`; it
30
+ // never writes arbitrary Git metadata.
31
+ export function createStateTools(stateOptions = {}) {
32
+ const state = createStateService(stateOptions);
33
+
34
+ const definitions = [
35
+ {
36
+ name: STATE_TOOLS.planWrite,
37
+ description:
38
+ "Durably store a plan Markdown artifact under the repository's shared Git common directory, keyed by plan ID. Planning shepherd only.",
39
+ write: true,
40
+ run: (args) => state.writePlan(args),
41
+ },
42
+ {
43
+ name: STATE_TOOLS.planRead,
44
+ description:
45
+ "Read a stored plan Markdown artifact by plan ID. Shepherd, shepherd-governor, and sheepdog only.",
46
+ write: false,
47
+ run: (args) => state.readPlan(args.planId),
48
+ },
49
+ {
50
+ name: STATE_TOOLS.executionWrite,
51
+ description:
52
+ "Durably store an execution Markdown artifact for a plan under the repository's shared Git common directory, keyed by plan ID. Sheepdog only.",
53
+ write: true,
54
+ run: (args) => state.writeExecution(args),
55
+ },
56
+ {
57
+ name: STATE_TOOLS.executionRead,
58
+ description:
59
+ "Read a stored execution Markdown artifact by plan ID. Sheepdog only.",
60
+ write: false,
61
+ run: (args) => state.readExecution(args.planId),
62
+ },
63
+ ];
64
+
65
+ const tools = {};
66
+ for (const definition of definitions) {
67
+ tools[definition.name] = tool({
68
+ description: definition.description,
69
+ args: {
70
+ planId: tool.schema
71
+ .string()
72
+ .describe("Plan ID keying the artifact; 1-64 characters of letters, digits, dot, underscore, or hyphen."),
73
+ ...(definition.write
74
+ ? {
75
+ markdown: tool.schema
76
+ .string()
77
+ .min(1)
78
+ .describe("Complete Markdown body to store as the artifact content."),
79
+ }
80
+ : {}),
81
+ },
82
+ async execute(args, context) {
83
+ const allowed = STATE_TOOL_ACCESS.get(definition.name);
84
+ if (!allowed?.has(context?.agent)) {
85
+ return JSON.stringify(
86
+ stateError(
87
+ "UNAUTHORIZED_AGENT",
88
+ `Agent ${context?.agent ?? "unknown"} may not use ${definition.name}.`,
89
+ ),
90
+ );
91
+ }
92
+ const result = await definition.run(args);
93
+ context.metadata({
94
+ title: result.ok ? `${definition.name}: ${args.planId}` : `${definition.name}: ${result.error.code}`,
95
+ metadata: result.ok
96
+ ? { planId: args.planId, path: result.artifact.path }
97
+ : { error: result.error.code },
98
+ });
99
+ return JSON.stringify(result);
100
+ },
101
+ });
102
+ }
103
+ return tools;
104
+ }
105
+
15
106
  export const HerdrOrchestrationPlugin = async (_input, options = {}) => ({
16
107
  tool: {
17
108
  herdr_agent_response: createResponseTool(options.response),
109
+ ...createStateTools(options.state),
18
110
  },
19
111
  config(config) {
20
112
  config.agent ??= {};
@@ -43,4 +135,4 @@ export const HerdrOrchestrationPlugin = async (_input, options = {}) => ({
43
135
  });
44
136
 
45
137
  export default HerdrOrchestrationPlugin;
46
- export { createAgents, modeForAgent };
138
+ export { createAgents, mergeAgent, modeForAgent, STATE_TOOL_ACCESS, STATE_TOOLS };