acpus 0.5.2 → 0.6.0-alpha.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.
Files changed (140) hide show
  1. package/README.md +19 -5
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/agent-progress-format.d.ts +7 -0
  4. package/dist/agent-progress-format.d.ts.map +1 -0
  5. package/dist/agent-progress-format.js +102 -0
  6. package/dist/agent-progress-format.js.map +1 -0
  7. package/dist/authoring/core.d.ts +2 -0
  8. package/dist/authoring/core.d.ts.map +1 -0
  9. package/dist/authoring/core.js +2 -0
  10. package/dist/authoring/core.js.map +1 -0
  11. package/dist/authoring/expression.d.ts +2 -0
  12. package/dist/authoring/expression.d.ts.map +1 -0
  13. package/dist/authoring/expression.js +2 -0
  14. package/dist/authoring/expression.js.map +1 -0
  15. package/dist/authoring/tasks/git.d.ts +2 -0
  16. package/dist/authoring/tasks/git.d.ts.map +1 -0
  17. package/dist/authoring/tasks/git.js +2 -0
  18. package/dist/authoring/tasks/git.js.map +1 -0
  19. package/dist/catalog.d.ts +18 -24
  20. package/dist/catalog.d.ts.map +1 -1
  21. package/dist/catalog.js +164 -174
  22. package/dist/catalog.js.map +1 -1
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +14 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/commands/daemon.d.ts +14 -0
  28. package/dist/commands/daemon.d.ts.map +1 -0
  29. package/dist/commands/daemon.js +89 -0
  30. package/dist/commands/daemon.js.map +1 -0
  31. package/dist/commands/doctor.d.ts +11 -0
  32. package/dist/commands/doctor.d.ts.map +1 -0
  33. package/dist/commands/doctor.js +26 -0
  34. package/dist/commands/doctor.js.map +1 -0
  35. package/dist/commands/hooks.d.ts +11 -0
  36. package/dist/commands/hooks.d.ts.map +1 -0
  37. package/dist/commands/hooks.js +64 -0
  38. package/dist/commands/hooks.js.map +1 -0
  39. package/dist/commands/json.d.ts +7 -0
  40. package/dist/commands/json.d.ts.map +1 -0
  41. package/dist/commands/json.js +43 -0
  42. package/dist/commands/json.js.map +1 -0
  43. package/dist/commands/runs-picker.d.ts +21 -0
  44. package/dist/commands/runs-picker.d.ts.map +1 -0
  45. package/dist/commands/runs-picker.js +76 -0
  46. package/dist/commands/runs-picker.js.map +1 -0
  47. package/dist/commands/runs.d.ts +12 -0
  48. package/dist/commands/runs.d.ts.map +1 -0
  49. package/dist/commands/runs.js +285 -0
  50. package/dist/commands/runs.js.map +1 -0
  51. package/dist/commands/skill.d.ts +11 -0
  52. package/dist/commands/skill.d.ts.map +1 -0
  53. package/dist/commands/skill.js +221 -0
  54. package/dist/commands/skill.js.map +1 -0
  55. package/dist/commands/web.d.ts +24 -0
  56. package/dist/commands/web.d.ts.map +1 -0
  57. package/dist/commands/web.js +57 -0
  58. package/dist/commands/web.js.map +1 -0
  59. package/dist/commands/workflow.d.ts +11 -0
  60. package/dist/commands/workflow.d.ts.map +1 -0
  61. package/dist/commands/workflow.js +324 -0
  62. package/dist/commands/workflow.js.map +1 -0
  63. package/dist/daemon-entry.d.ts +3 -0
  64. package/dist/daemon-entry.d.ts.map +1 -0
  65. package/dist/daemon-entry.js +26 -0
  66. package/dist/daemon-entry.js.map +1 -0
  67. package/dist/errors.d.ts +19 -0
  68. package/dist/errors.d.ts.map +1 -0
  69. package/dist/errors.js +28 -0
  70. package/dist/errors.js.map +1 -0
  71. package/dist/output.d.ts +91 -11
  72. package/dist/output.d.ts.map +1 -1
  73. package/dist/output.js +203 -44
  74. package/dist/output.js.map +1 -1
  75. package/dist/program.d.ts +9 -0
  76. package/dist/program.d.ts.map +1 -0
  77. package/dist/program.js +85 -0
  78. package/dist/program.js.map +1 -0
  79. package/dist/run-status-surface.d.ts +12 -0
  80. package/dist/run-status-surface.d.ts.map +1 -0
  81. package/dist/run-status-surface.js +324 -0
  82. package/dist/run-status-surface.js.map +1 -0
  83. package/dist/workflow-preparation.d.ts +3 -0
  84. package/dist/workflow-preparation.d.ts.map +1 -0
  85. package/dist/workflow-preparation.js +34 -0
  86. package/dist/workflow-preparation.js.map +1 -0
  87. package/package.json +35 -35
  88. package/skills/acpus/SKILL.md +120 -0
  89. package/skills/acpus/eval/evaluations.md +47 -0
  90. package/skills/acpus/examples/hooks.example.json +17 -0
  91. package/skills/acpus/examples/workflows/adversarial-review/workflow.ts +248 -0
  92. package/skills/acpus/examples/workflows/change-approval/workflow.ts +121 -0
  93. package/skills/acpus/examples/workflows/issue-triage/tasks.ts +14 -0
  94. package/skills/acpus/examples/workflows/issue-triage/workflow.ts +146 -0
  95. package/skills/acpus/examples/workflows/multi-aspect-brainstorm/workflow.ts +304 -0
  96. package/skills/acpus/examples/workflows/worktree-tournament/workflow.ts +194 -0
  97. package/skills/acpus/references/authoring.md +205 -0
  98. package/skills/acpus/references/cli-operations.md +94 -0
  99. package/skills/acpus/references/hooks-json.md +81 -0
  100. package/skills/acpus/references/runtime-recovery.md +97 -0
  101. package/dist/agent-activity.d.ts +0 -3
  102. package/dist/agent-activity.d.ts.map +0 -1
  103. package/dist/agent-activity.js +0 -50
  104. package/dist/agent-activity.js.map +0 -1
  105. package/dist/agent-overrides.d.ts +0 -3
  106. package/dist/agent-overrides.d.ts.map +0 -1
  107. package/dist/agent-overrides.js +0 -39
  108. package/dist/agent-overrides.js.map +0 -1
  109. package/dist/follow.d.ts +0 -23
  110. package/dist/follow.d.ts.map +0 -1
  111. package/dist/follow.js +0 -164
  112. package/dist/follow.js.map +0 -1
  113. package/dist/hooks.d.ts +0 -4
  114. package/dist/hooks.d.ts.map +0 -1
  115. package/dist/hooks.js +0 -159
  116. package/dist/hooks.js.map +0 -1
  117. package/dist/index.d.ts +0 -3
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/index.js +0 -592
  120. package/dist/index.js.map +0 -1
  121. package/dist/io.d.ts +0 -3
  122. package/dist/io.d.ts.map +0 -1
  123. package/dist/io.js +0 -27
  124. package/dist/io.js.map +0 -1
  125. package/dist/observations.d.ts +0 -39
  126. package/dist/observations.d.ts.map +0 -1
  127. package/dist/observations.js +0 -83
  128. package/dist/observations.js.map +0 -1
  129. package/dist/runs-show.d.ts +0 -40
  130. package/dist/runs-show.d.ts.map +0 -1
  131. package/dist/runs-show.js +0 -296
  132. package/dist/runs-show.js.map +0 -1
  133. package/dist/supervisor-client.d.ts +0 -9
  134. package/dist/supervisor-client.d.ts.map +0 -1
  135. package/dist/supervisor-client.js +0 -8
  136. package/dist/supervisor-client.js.map +0 -1
  137. package/dist/supervisor.d.ts +0 -18
  138. package/dist/supervisor.d.ts.map +0 -1
  139. package/dist/supervisor.js +0 -24
  140. package/dist/supervisor.js.map +0 -1
@@ -0,0 +1,304 @@
1
+ import { defineWorkflow, z } from "acpus/core";
2
+ import { eq, ifElse, md } from "acpus/expression";
3
+
4
+ export default defineWorkflow({
5
+ name: "multi-aspect-brainstorm",
6
+ description: "Run multi-aspect brainstorm rounds and synthesize the final result.",
7
+
8
+ inputSchema: z.object({
9
+ rounds: z.number().default(1).describe(
10
+ "Number of brainstorm rounds to run. Use 1 for a first pass; increase when you want agents to iterate on the latest synthesis.",
11
+ ),
12
+ subject: z.string().describe(
13
+ "The topic, problem, product idea, design question, or decision space to brainstorm.",
14
+ ),
15
+ rubric: z.string().describe(
16
+ "The main goals for the brainstorm, such as desired qualities, success direction, audience, or constraints that should shape the ideas.",
17
+ ),
18
+ criteria: z.string().default("").describe(
19
+ "Optional comma-separated criteria or constraints, for example: cheap, fast to prototype, low operational risk.",
20
+ ),
21
+ context: z.string().default("").describe(
22
+ "Optional background information the agents should consider, such as existing decisions, user needs, project constraints, or prior attempts.",
23
+ ),
24
+ }),
25
+
26
+ agents: {
27
+ alpha: {
28
+ use: "pi",
29
+ },
30
+ beta: {
31
+ use: "pi",
32
+ },
33
+ gamma: {
34
+ use: "claude",
35
+ },
36
+ delta: {
37
+ use: "claude",
38
+ },
39
+ synthesizer: {
40
+ use: "claude",
41
+ },
42
+ },
43
+ }).build(({ input, agents, meta, step }) => {
44
+ const rounds = step("brainstorm_rounds").loop({
45
+ initial: {
46
+ round: 0,
47
+ alpha: "",
48
+ beta: "",
49
+ gamma: "",
50
+ delta: "",
51
+ synthesis: "",
52
+ },
53
+ maxIterations: input.rounds,
54
+ do: ({ iter, previous, step }) => {
55
+ const firstRound = eq(iter, 0);
56
+ const aspects = step("aspect_work").parallel({
57
+ maxConcurrency: 4,
58
+ branches: {
59
+ alpha: {
60
+ do: ({ step }) => {
61
+ const aspect = step("alpha_aspect").agent({
62
+ run: {
63
+ agent: agents.alpha,
64
+ cwd: meta.workspaceDir,
65
+ sessionKey: "alpha-brainstorm",
66
+ prompt: md`
67
+ ${ifElse(firstRound, md`
68
+ You are Alpha in a multi-aspect brainstorm. Your lens is practical solution design and execution paths.
69
+
70
+ Generate feasible directions, implementation shapes, sequencing ideas, and refinements.
71
+ Do not approve, reject, score, audit, or produce review findings.
72
+ Work independently, but use later round updates to deepen and sharpen your ideas.
73
+
74
+ Subject:
75
+ ${input.subject}
76
+
77
+ Context:
78
+ ${input.context}
79
+
80
+ Brainstorm goals:
81
+ ${input.rubric}
82
+
83
+ Comma-separated useful constraints and selection criteria:
84
+ ${input.criteria}
85
+ `, "")}
86
+
87
+ Round: ${iter}
88
+
89
+ ${ifElse(firstRound, "Produce your first independent pass.", md`
90
+ Continue from the existing Alpha session. Do not restate the setup.
91
+ Use the latest synthesis to add practical options, tighten execution paths, and refine tradeoffs.
92
+
93
+ Previous synthesis:
94
+ ${previous.synthesis}
95
+ `)}
96
+
97
+ Return concise natural-language notes with options, refinements, tradeoffs, and next experiments.
98
+ `,
99
+ },
100
+ timeout: "45m",
101
+ });
102
+
103
+ return { aspect: aspect.output };
104
+ },
105
+ },
106
+ beta: {
107
+ do: ({ step }) => {
108
+ const aspect = step("beta_aspect").agent({
109
+ run: {
110
+ agent: agents.beta,
111
+ cwd: meta.workspaceDir,
112
+ sessionKey: "beta-brainstorm",
113
+ prompt: md`
114
+ ${ifElse(firstRound, md`
115
+ You are Beta in a multi-aspect brainstorm. Your lens is option expansion, unusual alternatives, and edge-aware constraints.
116
+
117
+ Generate non-obvious directions, neglected constraints, surprising combinations, and ways to keep risk visible while expanding choices.
118
+ Do not approve, reject, score, audit, or produce review findings.
119
+ Work independently, but use later round updates to broaden and sharpen the option space.
120
+
121
+ Subject:
122
+ ${input.subject}
123
+
124
+ Context:
125
+ ${input.context}
126
+
127
+ Brainstorm goals:
128
+ ${input.rubric}
129
+
130
+ Comma-separated useful constraints and selection criteria:
131
+ ${input.criteria}
132
+ `, "")}
133
+
134
+ Round: ${iter}
135
+
136
+ ${ifElse(firstRound, "Produce your first independent pass.", md`
137
+ Continue from the existing Beta session. Do not restate the setup.
138
+ Use the latest synthesis to add alternatives, expose overlooked constraints, and refine tradeoffs.
139
+
140
+ Previous synthesis:
141
+ ${previous.synthesis}
142
+ `)}
143
+
144
+ Return concise natural-language notes with options, refinements, tradeoffs, and next experiments.
145
+ `,
146
+ },
147
+ timeout: "45m",
148
+ });
149
+
150
+ return { aspect: aspect.output };
151
+ },
152
+ },
153
+ gamma: {
154
+ do: ({ step }) => {
155
+ const aspect = step("gamma_aspect").agent({
156
+ run: {
157
+ agent: agents.gamma,
158
+ cwd: meta.workspaceDir,
159
+ sessionKey: "gamma-brainstorm",
160
+ prompt: md`
161
+ ${ifElse(firstRound, md`
162
+ You are Gamma in a multi-aspect brainstorm. Your lens is theme clustering, prioritization, and promising combinations.
163
+
164
+ Organize ideas into coherent themes, identify high-leverage priorities, and combine compatible directions into stronger concepts.
165
+ Do not approve, reject, score, audit, or produce review findings.
166
+ Work independently, but use later round updates to improve structure and synthesis opportunities.
167
+
168
+ Subject:
169
+ ${input.subject}
170
+
171
+ Context:
172
+ ${input.context}
173
+
174
+ Brainstorm goals:
175
+ ${input.rubric}
176
+
177
+ Comma-separated useful constraints and selection criteria:
178
+ ${input.criteria}
179
+ `, "")}
180
+
181
+ Round: ${iter}
182
+
183
+ ${ifElse(firstRound, "Produce your first independent pass.", md`
184
+ Continue from the existing Gamma session. Do not restate the setup.
185
+ Use the latest synthesis to cluster ideas, refine priorities, and combine promising directions.
186
+
187
+ Previous synthesis:
188
+ ${previous.synthesis}
189
+ `)}
190
+
191
+ Return concise natural-language notes with options, refinements, tradeoffs, and next experiments.
192
+ `,
193
+ },
194
+ timeout: "45m",
195
+ });
196
+
197
+ return { aspect: aspect.output };
198
+ },
199
+ },
200
+ delta: {
201
+ do: ({ step }) => {
202
+ const aspect = step("delta_aspect").agent({
203
+ run: {
204
+ agent: agents.delta,
205
+ cwd: meta.workspaceDir,
206
+ sessionKey: "delta-brainstorm",
207
+ prompt: md`
208
+ ${ifElse(firstRound, md`
209
+ You are Delta in a multi-aspect brainstorm. Your lens is assumptions, missing information, experiments, and alternate framings.
210
+
211
+ Surface assumptions, open questions, small experiments, useful evidence to gather, and reframes that could unlock better ideas.
212
+ Do not approve, reject, score, audit, or produce review findings.
213
+ Work independently, but use later round updates to enrich the question set and exploration paths.
214
+
215
+ Subject:
216
+ ${input.subject}
217
+
218
+ Context:
219
+ ${input.context}
220
+
221
+ Brainstorm goals:
222
+ ${input.rubric}
223
+
224
+ Comma-separated useful constraints and selection criteria:
225
+ ${input.criteria}
226
+ `, "")}
227
+
228
+ Round: ${iter}
229
+
230
+ ${ifElse(firstRound, "Produce your first independent pass.", md`
231
+ Continue from the existing Delta session. Do not restate the setup.
232
+ Use the latest synthesis to add assumptions, questions, experiments, and alternate framings.
233
+
234
+ Previous synthesis:
235
+ ${previous.synthesis}
236
+ `)}
237
+
238
+ Return concise natural-language notes with options, refinements, tradeoffs, and next experiments.
239
+ `,
240
+ },
241
+ timeout: "45m",
242
+ });
243
+
244
+ return { aspect: aspect.output };
245
+ },
246
+ },
247
+ },
248
+ });
249
+
250
+ const synthesis = step("synthesize_round").agent({
251
+ run: {
252
+ agent: agents.synthesizer,
253
+ cwd: meta.workspaceDir,
254
+ sessionKey: "multi-aspect-synthesizer",
255
+ prompt: md`
256
+ ${ifElse(firstRound, md`
257
+ You are the synthesizer for a multi-aspect brainstorm.
258
+ Combine independent aspect outputs into a useful idea map.
259
+ Do not approve, reject, score, audit, or produce review findings.
260
+
261
+ Subject:
262
+ ${input.subject}
263
+
264
+ Context:
265
+ ${input.context}
266
+
267
+ Brainstorm goals:
268
+ ${input.rubric}
269
+
270
+ Comma-separated useful constraints and selection criteria:
271
+ ${input.criteria}
272
+ `, "")}
273
+
274
+ Round: ${iter}
275
+
276
+ ${ifElse(firstRound, "Produce the clearest idea map for this first round.", "Continue from the existing synthesizer session. Produce the clearest idea map for this round.")}
277
+ Do not average opinions mechanically.
278
+
279
+ Current aspect outputs:
280
+ Alpha: ${aspects.output.alpha.aspect}
281
+ Beta: ${aspects.output.beta.aspect}
282
+ Gamma: ${aspects.output.gamma.aspect}
283
+ Delta: ${aspects.output.delta.aspect}
284
+
285
+ Return a concise natural-language synthesis with strongest directions, notable alternatives, tradeoffs, open questions, and next exploration steps.
286
+ `,
287
+ },
288
+ timeout: "45m",
289
+ });
290
+
291
+ return {
292
+ round: iter,
293
+ alpha: aspects.output.alpha.aspect,
294
+ beta: aspects.output.beta.aspect,
295
+ gamma: aspects.output.gamma.aspect,
296
+ delta: aspects.output.delta.aspect,
297
+ synthesis: synthesis.output,
298
+ };
299
+ },
300
+ onExhausted: "returnLast",
301
+ });
302
+
303
+ return { synthesis: rounds.output.synthesis };
304
+ });
@@ -0,0 +1,194 @@
1
+ import { defineWorkflow, z } from "acpus/core";
2
+ import { md, template } from "acpus/expression";
3
+ import { createWorktree } from "acpus/tasks/git";
4
+
5
+ export default defineWorkflow({
6
+ name: "worktree-tournament",
7
+ description: "Create competing worktree implementations and have an agent judge the best result.",
8
+ inputSchema: z.object({
9
+ repoPath: z.path().describe("Source repository path used to create candidate worktrees and run the judge."),
10
+ worktreeRoot: z.path().describe("Directory where per-run candidate worktrees should be created."),
11
+ task: z.string().describe("Implementation task each candidate agent should attempt independently."),
12
+ baseRef: z.string().default("HEAD").describe("Git ref used as the base for each candidate worktree."),
13
+ forceRemove: z.boolean().default(false).describe(
14
+ "Whether createWorktree may remove an existing candidate worktree path before recreating it.",
15
+ ),
16
+ }),
17
+ agents: {
18
+ implementer: { use: "codex" },
19
+ judge: { use: "codex" },
20
+ },
21
+ }).build(({ input, agents, meta, step }) => {
22
+ const paths = step("plan_worktrees").task({
23
+ run: {
24
+ input: { root: input.worktreeRoot, runId: meta.runId },
25
+ exec: async ({ input }) => ({
26
+ alpha: `${input.root}/${input.runId}-alpha`,
27
+ beta: `${input.root}/${input.runId}-beta`,
28
+ gamma: `${input.root}/${input.runId}-gamma`,
29
+ }),
30
+ },
31
+ });
32
+
33
+ const candidates = step("candidate_worktrees").parallel({
34
+ branches: {
35
+ alpha: {
36
+ do: ({ step }) => {
37
+ const worktree = step("create_alpha_worktree").task({
38
+ run: {
39
+ task: createWorktree,
40
+ input: {
41
+ repo: input.repoPath,
42
+ path: paths.output.alpha,
43
+ ref: input.baseRef,
44
+ forceRemove: input.forceRemove,
45
+ },
46
+ },
47
+ timeout: "2m",
48
+ });
49
+
50
+ const implementation = step("implement_alpha").agent({
51
+ run: {
52
+ agent: agents.implementer,
53
+ cwd: worktree.output.worktreePath,
54
+ prompt: md`
55
+ Implement this task in the alpha worktree.
56
+
57
+ Task: ${input.task}
58
+
59
+ Return a Markdown implementation report with:
60
+ - Changed files
61
+ - Implementation summary
62
+ - Test command run, or "not run" with the reason
63
+ `,
64
+ },
65
+ timeout: "45m",
66
+ });
67
+
68
+ return {
69
+ lane: "alpha",
70
+ worktreePath: worktree.output.worktreePath,
71
+ report: implementation.output,
72
+ };
73
+ },
74
+ },
75
+ beta: {
76
+ do: ({ step }) => {
77
+ const worktree = step("create_beta_worktree").task({
78
+ run: {
79
+ task: createWorktree,
80
+ input: {
81
+ repo: input.repoPath,
82
+ path: paths.output.beta,
83
+ ref: input.baseRef,
84
+ forceRemove: input.forceRemove,
85
+ },
86
+ },
87
+ timeout: "2m",
88
+ });
89
+
90
+ const implementation = step("implement_beta").agent({
91
+ run: {
92
+ agent: agents.implementer,
93
+ cwd: worktree.output.worktreePath,
94
+ prompt: md`
95
+ Implement this task in the beta worktree.
96
+
97
+ Task: ${input.task}
98
+
99
+ Return a Markdown implementation report with:
100
+ - Changed files
101
+ - Implementation summary
102
+ - Test command run, or "not run" with the reason
103
+ `,
104
+ },
105
+ timeout: "45m",
106
+ });
107
+
108
+ return {
109
+ lane: "beta",
110
+ worktreePath: worktree.output.worktreePath,
111
+ report: implementation.output,
112
+ };
113
+ },
114
+ },
115
+ gamma: {
116
+ do: ({ step }) => {
117
+ const worktree = step("create_gamma_worktree").task({
118
+ run: {
119
+ task: createWorktree,
120
+ input: {
121
+ repo: input.repoPath,
122
+ path: paths.output.gamma,
123
+ ref: input.baseRef,
124
+ forceRemove: input.forceRemove,
125
+ },
126
+ },
127
+ timeout: "2m",
128
+ });
129
+
130
+ const implementation = step("implement_gamma").agent({
131
+ run: {
132
+ agent: agents.implementer,
133
+ cwd: worktree.output.worktreePath,
134
+ prompt: md`
135
+ Implement this task in the gamma worktree.
136
+
137
+ Task: ${input.task}
138
+
139
+ Return a Markdown implementation report with:
140
+ - Changed files
141
+ - Implementation summary
142
+ - Test command run, or "not run" with the reason
143
+ `,
144
+ },
145
+ timeout: "45m",
146
+ });
147
+
148
+ return {
149
+ lane: "gamma",
150
+ worktreePath: worktree.output.worktreePath,
151
+ report: implementation.output,
152
+ };
153
+ },
154
+ },
155
+ },
156
+ });
157
+
158
+ const judgment = step("judge_candidates").agent({
159
+ outputSchema: z.object({
160
+ winner: z.enum(["alpha", "beta", "gamma"]),
161
+ rationale: z.string(),
162
+ }),
163
+ run: {
164
+ agent: agents.judge,
165
+ cwd: input.repoPath,
166
+ prompt: md`
167
+ Judge the best implementation for this task.
168
+
169
+ Task: ${input.task}
170
+
171
+ Each candidate includes its lane, worktree path, and Markdown implementation report.
172
+
173
+ Alpha: ${candidates.output.alpha}
174
+ Beta: ${candidates.output.beta}
175
+ Gamma: ${candidates.output.gamma}
176
+
177
+ Return the winning lane and a concise rationale.
178
+ `,
179
+ },
180
+ retry: { max: 1 },
181
+ timeout: "30m",
182
+ });
183
+
184
+ return {
185
+ runId: meta.runId,
186
+ winner: judgment.output.winner,
187
+ rationale: judgment.output.rationale,
188
+ candidates: {
189
+ alpha: candidates.output.alpha,
190
+ beta: candidates.output.beta,
191
+ gamma: candidates.output.gamma,
192
+ },
193
+ };
194
+ });
@@ -0,0 +1,205 @@
1
+ # Authoring Workflows
2
+
3
+ ## Quick Start
4
+
5
+ ### Imports
6
+
7
+ Use TypeScript workflow modules and user-facing facades only:
8
+
9
+ ```ts
10
+ import { defineWorkflow, task, secret, z } from "acpus/core";
11
+ import { template, md, and, eq, lte, coalesce, len, head, get, filter, map, where, not, or, gte, includes, ifElse, ne, lt, gt, isEmpty, startsWith, endsWith, matches, every, some, max, min, pick } from "acpus/expression";
12
+ import { createWorktree } from "acpus/tasks/git";
13
+ ```
14
+
15
+ Do not import `@acpus/*` from user workflows; those are implementation packages behind the `acpus/*` facades.
16
+
17
+ ### Minimal Workflow Skeleton
18
+
19
+ ```ts
20
+ import { defineWorkflow, z } from "acpus/core";
21
+ import { template } from "acpus/expression";
22
+
23
+ export default defineWorkflow({
24
+ name: "my-workflow",
25
+ description: "Analyze a topic in a repository.",
26
+ inputSchema: z.object({ repoPath: z.path(), topic: z.string() }),
27
+ agents: { worker: { use: "codex" } },
28
+ }).build(({ input, agents, meta, step }) => {
29
+ const result = step("work").agent({
30
+ outputSchema: z.object({ ok: z.boolean(), summary: z.string() }),
31
+ run: { agent: agents.worker, prompt: template`Analyze ${input.topic} in ${input.repoPath}.` },
32
+ });
33
+ return { runId: meta.runId, ok: result.output.ok, summary: result.output.summary };
34
+ });
35
+ ```
36
+
37
+ ## Mental Model
38
+
39
+ ### Why Expressions Exist
40
+
41
+ Acpus workflows are a TypeScript-authored DSL, not ordinary runtime TypeScript. `build` declares a durable graph before a run executes, so workflow input, metadata, and prior node outputs are graph values wrapped as `Expr<T>` tokens. Treat every run-dependent value as an expression token and use graph constructs plus `acpus/expression` helpers to combine, compare, select, and render those values. Use plain JavaScript only for authoring-time constants and task `exec` bodies.
42
+
43
+ ### Expressions And Templates
44
+
45
+ Expression helpers:
46
+
47
+ | Do not write | Write |
48
+ | --- | --- |
49
+ | `input.ready && output.ok` | `and(input.ready, output.ok)` |
50
+ | `input.kind === "release"` | `eq(input.kind, "release")` |
51
+ | `risk <= 3` | `lte(risk, 3)` |
52
+ | `input.maybe ?? "fallback"` | `coalesce(input.maybe, "fallback")` |
53
+ | `` `topic ${input.topic}` `` | `template\`topic ${input.topic}\`` |
54
+ | `input.items.length` | `len(input.items)` |
55
+ | `items[0]` | `head(items)` or `get(items, 0)` |
56
+
57
+ For collections: `filter(input.items, item => where(item, { tags: { contains: "ready" } }))`, `map(ready, item => item.id)`, `len(ready)`, `coalesce(head(readyIds), "(none)")`.
58
+
59
+ Use `template` for compact strings and `md` for multiline prompts/messages; `md` trims surrounding blank lines and common indentation while preserving expression interpolation.
60
+
61
+ ### Boundary Schemas
62
+
63
+ Boundary schemas use `z` from `acpus/core`. Use `z.path()` for filesystem paths crossing workflow boundaries.
64
+
65
+ Keep workflow input, Agent output, Signal output, and reusable task input JSON-compatible and durable. Avoid transforms, functions, promises, maps, sets, dates, bigint, symbol, `undefined`, `void`, `never`, non-finite numbers, sparse arrays, cycles, and class instances in graph-boundary values or runtime outputs.
66
+
67
+ ## Workflow Building Blocks
68
+
69
+ ### Tasks
70
+
71
+ Task nodes run deterministic local glue, write artifacts, and infer output from `exec`:
72
+
73
+ ```ts
74
+ const prepared = step("prepare").task({
75
+ run: {
76
+ input: { repoPath: input.repoPath },
77
+ cwd: input.repoPath,
78
+ exec: async ({ input, $, artifact, abortSignal }) => {
79
+ const status = await $`git status --short`;
80
+ return { dirty: status.stdout.trim().length > 0, statusFile: await artifact.writeText("status.txt", status.stdout, { mediaType: "text/plain" }) };
81
+ },
82
+ },
83
+ timeout: "2m",
84
+ });
85
+ ```
86
+
87
+ Reusable tasks own their input schema and executable body:
88
+
89
+ ```ts
90
+ // tasks/normalize-name.ts
91
+ import { task, z } from "acpus/core";
92
+ import slugify from "slugify";
93
+
94
+ export const normalizeName = task.define({
95
+ inputSchema: z.object({ name: z.string() }),
96
+ exec: async ({ input }) => ({ slug: slugify(input.name, { lower: true }) }),
97
+ });
98
+ ```
99
+
100
+ ```ts
101
+ import { normalizeName } from "./tasks/normalize-name.js";
102
+ const normalized = step("normalize").task({ run: { task: normalizeName, input: { name: input.name } } });
103
+ ```
104
+
105
+ Inline task source is embedded in frozen IR. Pass workflow values through `run.input` and use only task context (`input`, `$`, `artifact`, `env`, `abortSignal`) inside `exec`.
106
+
107
+ ### Agents
108
+
109
+ Agent nodes handle judgment, synthesis, planning, and review:
110
+
111
+ ```ts
112
+ const review = step("review").agent({
113
+ outputSchema: z.object({ ready: z.boolean(), summary: z.string() }),
114
+ run: { agent: agents.reviewer, prompt: template`Review artifact ${prepared.output.statusFile}.`, cwd: input.repoPath },
115
+ retry: { max: 2 },
116
+ timeout: "30m",
117
+ });
118
+ ```
119
+
120
+ Top-level agent definitions use either `use` or `command`, never both. Agent nodes without `outputSchema` return `Expr<string>`. The default `permissionMode` is write-capable.
121
+
122
+ ### Agent Selection
123
+
124
+ Native acpx `use` names include `pi`, `codex`, `claude`, `gemini`, `cursor`, `copilot`, `droid`, `fast-agent`, `grok-build`, `iflow`, `kilocode`, `kimi`, `kiro`, `mux`, `opencode`, `qoder`, `qwen`, and `trae`; for the current list, see https://github.com/openclaw/acpx/tree/main/agents.
125
+
126
+ For agents not natively supported by acpx, use `command: "<acp command>"`, for example `{ command: "npx pi-acp" }`.
127
+
128
+ ### Signals And Asserts
129
+
130
+ Signal nodes wait for operator input:
131
+
132
+ ```ts
133
+ const approval = step("approval").signal({
134
+ outputSchema: z.object({ approved: z.boolean(), notes: z.string().default("") }),
135
+ run: { prompt: template`Approve the result: ${review.output.summary}` },
136
+ timeout: "24h",
137
+ onTimeout: { action: "fail", message: "Approval timed out" },
138
+ });
139
+ ```
140
+
141
+ Assert nodes fail the run when an expression condition is false:
142
+
143
+ ```ts
144
+ step("require_approval").assert({ condition: approval.output.approved, message: template`Approval denied: ${approval.output.notes}` });
145
+ ```
146
+
147
+ ### Composites
148
+
149
+ Graph-level composites:
150
+
151
+ - `if`: conditional branch; both branches should return compatible object shapes.
152
+ - `switch`: case list plus required `default` branch.
153
+ - `parallel`: static named branches; `strategy` defaults to `"all"`, with `"race"` available.
154
+ - `fanout`: runtime array expansion; output is an array. `strategy` defaults to `"all"`, with `"quorum"` available.
155
+ - `loop`: seeded pre-check loop. `maxIterations` counts body executions only; `stopWhen` checks before each body execution.
156
+
157
+ Composite callbacks receive `{ step }` plus composite-specific values such as `item`, `iter`, and `previous`. Return a plain object to declare composite output; do not add `outputSchema` to composites. For `parallel({ strategy: "race" })`, output is `{ winner, result }`, not a branch-keyed object. For `fanout({ strategy: "quorum", count })`, output is the accepted item array, not an envelope.
158
+
159
+ ## Best Practices
160
+
161
+ ### Structured Boundaries
162
+
163
+ - Use the smallest structured boundary that the workflow actually needs.
164
+ - Omit `outputSchema` when natural-language text is enough.
165
+ - Add schemas only for values the workflow must branch on, fan out over, assert, or expose as machine-readable final output.
166
+ - Let agents exchange Markdown when later agents can read the result directly.
167
+
168
+ ### Agent Sessions And Permissions
169
+
170
+ - When the user has no agent preference, ask which agent to use.
171
+ - Check local availability with `command -v <binary>` before recommending a local agent.
172
+ - Use `acpus runs retry` for control-plane retry after a failed run.
173
+ - Omit `sessionKey` by default; set it only when a multi-turn loop explicitly needs the agent to reuse one session.
174
+ - Omit `permissionMode` for normal authoring; use `permissionMode: "approve-reads"` only for agents that are explicitly not allowed to write, such as audit-only inspection.
175
+
176
+ ### Task Boundaries
177
+
178
+ - Put reusable tasks in separate task modules when they need shared code or third-party packages installed with the workflow package.
179
+ - Keep inline tasks self-contained: no third-party imports, module-scope environment reads, or module-scope captures.
180
+
181
+ ### Expression And Schema Hygiene
182
+
183
+ - Start from the examples; inspect declarations only when the examples do not answer the API question.
184
+ - Use graph-level composites instead of JavaScript control flow over expression values.
185
+ - Use expression helpers instead of native operators/properties over expression values.
186
+ - Use `template` for compact strings and `md` for multiline prompts/messages.
187
+ - Use signal nodes only when the workflow needs external control.
188
+ - Keep graph-boundary schema values JSON-compatible and durable.
189
+
190
+ ## Validation And API Lookup
191
+
192
+ ### Workflow Check
193
+
194
+ `acpus workflow check <workflow.ts-or-catalog>` prepares the workflow in memory and catches TypeScript diagnostics plus Acpus authoring diagnostics such as Expr truthiness, native operators over Expr values, dynamic node ids, task callsites that cannot be joined to task metadata, inline task captures, and non-admissible outputs.
195
+
196
+ Run `acpus workflow check <workflow.ts-or-catalog>` before admitting a run.
197
+
198
+ ### Declaration Lookup
199
+
200
+ When exact API usage is unclear, inspect installed declarations instead of copied reference prose:
201
+
202
+ 1. Start from the global install: `$(npm root -g)/acpus/dist/authoring/`.
203
+ 2. Read facade declarations there: `core.d.ts`, `expression.d.ts`, and `tasks/git.d.ts`.
204
+ 3. Follow re-exports into sibling global packages: `$(npm root -g)/@acpus/core/dist/*.d.ts`, `@acpus/expression/dist/*.d.ts`, and `@acpus/tasks/dist/*.d.ts`.
205
+ 4. Retrieve only the relevant symbol plus nearby doc comment/signature; do not dump full declaration files.