@bastani/atomic 0.5.0 → 0.5.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/.atomic/workflows/hello/claude/index.ts +10 -11
- package/.atomic/workflows/hello/copilot/index.ts +11 -26
- package/.atomic/workflows/hello/opencode/index.ts +11 -17
- package/.atomic/workflows/hello-parallel/claude/index.ts +20 -23
- package/.atomic/workflows/hello-parallel/copilot/index.ts +21 -42
- package/.atomic/workflows/hello-parallel/opencode/index.ts +21 -31
- package/.atomic/workflows/ralph/claude/index.ts +42 -53
- package/.atomic/workflows/ralph/copilot/index.ts +36 -79
- package/.atomic/workflows/ralph/opencode/index.ts +36 -59
- package/README.md +391 -166
- package/package.json +1 -1
- package/src/sdk/define-workflow.ts +35 -19
- package/src/sdk/index.ts +4 -0
- package/src/sdk/providers/claude.ts +103 -10
- package/src/sdk/providers/copilot.ts +16 -23
- package/src/sdk/providers/opencode.ts +15 -22
- package/src/sdk/runtime/executor.ts +138 -55
- package/src/sdk/runtime/graph-inference.ts +50 -0
- package/src/sdk/types.ts +113 -38
- package/src/sdk/workflows.ts +14 -1
- package/src/services/system/workflows.ts +136 -1
|
@@ -8,32 +8,31 @@
|
|
|
8
8
|
* Run: atomic workflow -n hello -a claude "describe this project"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { defineWorkflow
|
|
11
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
12
|
|
|
13
|
-
export default defineWorkflow({
|
|
13
|
+
export default defineWorkflow<"claude">({
|
|
14
14
|
name: "hello",
|
|
15
15
|
description: "Two-session Claude demo: describe → summarize",
|
|
16
16
|
})
|
|
17
17
|
.run(async (ctx) => {
|
|
18
|
-
const describe = await ctx.
|
|
18
|
+
const describe = await ctx.stage(
|
|
19
19
|
{ name: "describe", description: "Ask Claude to describe the project" },
|
|
20
|
+
{},
|
|
21
|
+
{},
|
|
20
22
|
async (s) => {
|
|
21
|
-
await
|
|
22
|
-
await claudeQuery({ paneId: s.paneId, prompt: s.userPrompt });
|
|
23
|
+
await s.session.query(s.userPrompt);
|
|
23
24
|
s.save(s.sessionId);
|
|
24
25
|
},
|
|
25
26
|
);
|
|
26
27
|
|
|
27
|
-
await ctx.
|
|
28
|
+
await ctx.stage(
|
|
28
29
|
{ name: "summarize", description: "Summarize the previous session's output" },
|
|
30
|
+
{},
|
|
31
|
+
{},
|
|
29
32
|
async (s) => {
|
|
30
|
-
await createClaudeSession({ paneId: s.paneId });
|
|
31
33
|
const research = await s.transcript(describe);
|
|
32
34
|
|
|
33
|
-
await
|
|
34
|
-
paneId: s.paneId,
|
|
35
|
-
prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
36
|
-
});
|
|
35
|
+
await s.session.query(`Read ${research.path} and summarize it in 2-3 bullet points.`);
|
|
37
36
|
s.save(s.sessionId);
|
|
38
37
|
},
|
|
39
38
|
);
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
11
|
-
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* `CopilotSession.sendAndWait` defaults to a 60s timeout and THROWS on
|
|
@@ -17,51 +16,37 @@ import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
|
17
16
|
*/
|
|
18
17
|
const SEND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
19
18
|
|
|
20
|
-
export default defineWorkflow({
|
|
19
|
+
export default defineWorkflow<"copilot">({
|
|
21
20
|
name: "hello",
|
|
22
21
|
description: "Two-session Copilot demo: describe → summarize",
|
|
23
22
|
})
|
|
24
23
|
.run(async (ctx) => {
|
|
25
|
-
const describe = await ctx.
|
|
24
|
+
const describe = await ctx.stage(
|
|
26
25
|
{ name: "describe", description: "Ask the agent to describe the project" },
|
|
26
|
+
{},
|
|
27
|
+
{},
|
|
27
28
|
async (s) => {
|
|
28
|
-
|
|
29
|
-
await client.start();
|
|
29
|
+
await s.session.sendAndWait({ prompt: s.userPrompt }, SEND_TIMEOUT_MS);
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
33
|
-
|
|
34
|
-
await session.sendAndWait({ prompt: s.userPrompt }, SEND_TIMEOUT_MS);
|
|
35
|
-
|
|
36
|
-
s.save(await session.getMessages());
|
|
37
|
-
|
|
38
|
-
await session.disconnect();
|
|
39
|
-
await client.stop();
|
|
31
|
+
s.save(await s.session.getMessages());
|
|
40
32
|
},
|
|
41
33
|
);
|
|
42
34
|
|
|
43
|
-
await ctx.
|
|
35
|
+
await ctx.stage(
|
|
44
36
|
{ name: "summarize", description: "Summarize the previous session's output" },
|
|
37
|
+
{},
|
|
38
|
+
{},
|
|
45
39
|
async (s) => {
|
|
46
40
|
const research = await s.transcript(describe);
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
await client.start();
|
|
50
|
-
|
|
51
|
-
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
52
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
53
|
-
|
|
54
|
-
await session.sendAndWait(
|
|
42
|
+
await s.session.sendAndWait(
|
|
55
43
|
{
|
|
56
44
|
prompt: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
57
45
|
},
|
|
58
46
|
SEND_TIMEOUT_MS,
|
|
59
47
|
);
|
|
60
48
|
|
|
61
|
-
s.save(await session.getMessages());
|
|
62
|
-
|
|
63
|
-
await session.disconnect();
|
|
64
|
-
await client.stop();
|
|
49
|
+
s.save(await s.session.getMessages());
|
|
65
50
|
},
|
|
66
51
|
);
|
|
67
52
|
})
|
|
@@ -8,23 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
11
|
-
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
12
11
|
|
|
13
|
-
export default defineWorkflow({
|
|
12
|
+
export default defineWorkflow<"opencode">({
|
|
14
13
|
name: "hello",
|
|
15
14
|
description: "Two-session OpenCode demo: describe → summarize",
|
|
16
15
|
})
|
|
17
16
|
.run(async (ctx) => {
|
|
18
|
-
const describe = await ctx.
|
|
17
|
+
const describe = await ctx.stage(
|
|
19
18
|
{ name: "describe", description: "Ask the agent to describe the project" },
|
|
19
|
+
{},
|
|
20
|
+
{ title: "describe" },
|
|
20
21
|
async (s) => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const session = await client.session.create({ title: "describe" });
|
|
24
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
25
|
-
|
|
26
|
-
const result = await client.session.prompt({
|
|
27
|
-
sessionID: session.data!.id,
|
|
22
|
+
const result = await s.client.session.prompt({
|
|
23
|
+
sessionID: s.session.id,
|
|
28
24
|
parts: [{ type: "text", text: s.userPrompt }],
|
|
29
25
|
});
|
|
30
26
|
|
|
@@ -32,17 +28,15 @@ export default defineWorkflow({
|
|
|
32
28
|
},
|
|
33
29
|
);
|
|
34
30
|
|
|
35
|
-
await ctx.
|
|
31
|
+
await ctx.stage(
|
|
36
32
|
{ name: "summarize", description: "Summarize the previous session's output" },
|
|
33
|
+
{},
|
|
34
|
+
{ title: "summarize" },
|
|
37
35
|
async (s) => {
|
|
38
36
|
const research = await s.transcript(describe);
|
|
39
|
-
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
40
|
-
|
|
41
|
-
const session = await client.session.create({ title: "summarize" });
|
|
42
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
43
37
|
|
|
44
|
-
const result = await client.session.prompt({
|
|
45
|
-
sessionID: session.
|
|
38
|
+
const result = await s.client.session.prompt({
|
|
39
|
+
sessionID: s.session.id,
|
|
46
40
|
parts: [
|
|
47
41
|
{
|
|
48
42
|
type: "text",
|
|
@@ -8,58 +8,55 @@
|
|
|
8
8
|
* Run: atomic workflow -n hello-parallel -a claude "describe this project"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { defineWorkflow
|
|
11
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
12
|
|
|
13
|
-
export default defineWorkflow({
|
|
13
|
+
export default defineWorkflow<"claude">({
|
|
14
14
|
name: "hello-parallel",
|
|
15
15
|
description: "Parallel Claude demo: describe → [summarize-a, summarize-b] → merge",
|
|
16
16
|
})
|
|
17
17
|
.run(async (ctx) => {
|
|
18
|
-
const describe = await ctx.
|
|
18
|
+
const describe = await ctx.stage(
|
|
19
19
|
{ name: "describe", description: "Ask Claude to describe the project" },
|
|
20
|
+
{},
|
|
21
|
+
{},
|
|
20
22
|
async (s) => {
|
|
21
|
-
await
|
|
22
|
-
await claudeQuery({ paneId: s.paneId, prompt: s.userPrompt });
|
|
23
|
+
await s.session.query(s.userPrompt);
|
|
23
24
|
s.save(s.sessionId);
|
|
24
25
|
},
|
|
25
26
|
);
|
|
26
27
|
|
|
27
28
|
const [summarizeA, summarizeB] = await Promise.all([
|
|
28
|
-
ctx.
|
|
29
|
+
ctx.stage(
|
|
29
30
|
{ name: "summarize-a", description: "Summarize the description as bullet points" },
|
|
31
|
+
{},
|
|
32
|
+
{},
|
|
30
33
|
async (s) => {
|
|
31
34
|
const research = await s.transcript(describe);
|
|
32
|
-
await
|
|
33
|
-
await claudeQuery({
|
|
34
|
-
paneId: s.paneId,
|
|
35
|
-
prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
36
|
-
});
|
|
35
|
+
await s.session.query(`Read ${research.path} and summarize it in 2-3 bullet points.`);
|
|
37
36
|
s.save(s.sessionId);
|
|
38
37
|
},
|
|
39
38
|
),
|
|
40
|
-
ctx.
|
|
39
|
+
ctx.stage(
|
|
41
40
|
{ name: "summarize-b", description: "Summarize the description as a one-liner" },
|
|
41
|
+
{},
|
|
42
|
+
{},
|
|
42
43
|
async (s) => {
|
|
43
44
|
const research = await s.transcript(describe);
|
|
44
|
-
await
|
|
45
|
-
await claudeQuery({
|
|
46
|
-
paneId: s.paneId,
|
|
47
|
-
prompt: `Read ${research.path} and summarize it in a single sentence.`,
|
|
48
|
-
});
|
|
45
|
+
await s.session.query(`Read ${research.path} and summarize it in a single sentence.`);
|
|
49
46
|
s.save(s.sessionId);
|
|
50
47
|
},
|
|
51
48
|
),
|
|
52
49
|
]);
|
|
53
50
|
|
|
54
|
-
await ctx.
|
|
51
|
+
await ctx.stage(
|
|
55
52
|
{ name: "merge", description: "Merge both summaries into a final output" },
|
|
53
|
+
{},
|
|
54
|
+
{},
|
|
56
55
|
async (s) => {
|
|
57
56
|
const bullets = await s.transcript(summarizeA);
|
|
58
57
|
const oneliner = await s.transcript(summarizeB);
|
|
59
|
-
await
|
|
60
|
-
|
|
61
|
-
paneId: s.paneId,
|
|
62
|
-
prompt: [
|
|
58
|
+
await s.session.query(
|
|
59
|
+
[
|
|
63
60
|
"Combine the following two summaries into one concise paragraph:",
|
|
64
61
|
"",
|
|
65
62
|
"## Bullet points",
|
|
@@ -68,7 +65,7 @@ export default defineWorkflow({
|
|
|
68
65
|
"## One-liner",
|
|
69
66
|
oneliner.content,
|
|
70
67
|
].join("\n"),
|
|
71
|
-
|
|
68
|
+
);
|
|
72
69
|
s.save(s.sessionId);
|
|
73
70
|
},
|
|
74
71
|
);
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
|
-
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* `CopilotSession.sendAndWait` defaults to a 60s timeout and THROWS on
|
|
@@ -18,89 +17,71 @@ import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
|
18
17
|
*/
|
|
19
18
|
const SEND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
20
19
|
|
|
21
|
-
export default defineWorkflow({
|
|
20
|
+
export default defineWorkflow<"copilot">({
|
|
22
21
|
name: "hello-parallel",
|
|
23
22
|
description: "Parallel Copilot demo: describe → [summarize-a, summarize-b] → merge",
|
|
24
23
|
})
|
|
25
24
|
.run(async (ctx) => {
|
|
26
25
|
// Sequential: describe
|
|
27
|
-
const describe = await ctx.
|
|
26
|
+
const describe = await ctx.stage(
|
|
28
27
|
{ name: "describe", description: "Ask the agent to describe the project" },
|
|
28
|
+
{},
|
|
29
|
+
{},
|
|
29
30
|
async (s) => {
|
|
30
|
-
|
|
31
|
-
await client.start();
|
|
31
|
+
await s.session.sendAndWait({ prompt: s.userPrompt }, SEND_TIMEOUT_MS);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
35
|
-
await session.sendAndWait({ prompt: s.userPrompt }, SEND_TIMEOUT_MS);
|
|
36
|
-
|
|
37
|
-
s.save(await session.getMessages());
|
|
38
|
-
await session.disconnect();
|
|
39
|
-
await client.stop();
|
|
33
|
+
s.save(await s.session.getMessages());
|
|
40
34
|
},
|
|
41
35
|
);
|
|
42
36
|
|
|
43
37
|
// Parallel: summarize-a + summarize-b
|
|
44
38
|
const [summarizeA, summarizeB] = await Promise.all([
|
|
45
|
-
ctx.
|
|
39
|
+
ctx.stage(
|
|
46
40
|
{ name: "summarize-a", description: "Summarize the description as bullet points" },
|
|
41
|
+
{},
|
|
42
|
+
{},
|
|
47
43
|
async (s) => {
|
|
48
44
|
const research = await s.transcript(describe);
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
await client.start();
|
|
52
|
-
|
|
53
|
-
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
54
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
55
|
-
await session.sendAndWait(
|
|
46
|
+
await s.session.sendAndWait(
|
|
56
47
|
{
|
|
57
48
|
prompt: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
58
49
|
},
|
|
59
50
|
SEND_TIMEOUT_MS,
|
|
60
51
|
);
|
|
61
52
|
|
|
62
|
-
s.save(await session.getMessages());
|
|
63
|
-
await session.disconnect();
|
|
64
|
-
await client.stop();
|
|
53
|
+
s.save(await s.session.getMessages());
|
|
65
54
|
},
|
|
66
55
|
),
|
|
67
|
-
ctx.
|
|
56
|
+
ctx.stage(
|
|
68
57
|
{ name: "summarize-b", description: "Summarize the description as a one-liner" },
|
|
58
|
+
{},
|
|
59
|
+
{},
|
|
69
60
|
async (s) => {
|
|
70
61
|
const research = await s.transcript(describe);
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
await client.start();
|
|
74
|
-
|
|
75
|
-
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
76
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
77
|
-
await session.sendAndWait(
|
|
63
|
+
await s.session.sendAndWait(
|
|
78
64
|
{
|
|
79
65
|
prompt: `Summarize the following in a single sentence:\n\n${research.content}`,
|
|
80
66
|
},
|
|
81
67
|
SEND_TIMEOUT_MS,
|
|
82
68
|
);
|
|
83
69
|
|
|
84
|
-
s.save(await session.getMessages());
|
|
85
|
-
await session.disconnect();
|
|
86
|
-
await client.stop();
|
|
70
|
+
s.save(await s.session.getMessages());
|
|
87
71
|
},
|
|
88
72
|
),
|
|
89
73
|
]);
|
|
90
74
|
|
|
91
75
|
// Sequential: merge
|
|
92
|
-
await ctx.
|
|
76
|
+
await ctx.stage(
|
|
93
77
|
{ name: "merge", description: "Merge both summaries into a final output" },
|
|
78
|
+
{},
|
|
79
|
+
{},
|
|
94
80
|
async (s) => {
|
|
95
81
|
const bullets = await s.transcript(summarizeA);
|
|
96
82
|
const oneliner = await s.transcript(summarizeB);
|
|
97
83
|
|
|
98
|
-
|
|
99
|
-
await client.start();
|
|
100
|
-
|
|
101
|
-
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
102
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
103
|
-
await session.sendAndWait(
|
|
84
|
+
await s.session.sendAndWait(
|
|
104
85
|
{
|
|
105
86
|
prompt: [
|
|
106
87
|
"Combine the following two summaries into one concise paragraph:",
|
|
@@ -115,9 +96,7 @@ export default defineWorkflow({
|
|
|
115
96
|
SEND_TIMEOUT_MS,
|
|
116
97
|
);
|
|
117
98
|
|
|
118
|
-
s.save(await session.getMessages());
|
|
119
|
-
await session.disconnect();
|
|
120
|
-
await client.stop();
|
|
99
|
+
s.save(await s.session.getMessages());
|
|
121
100
|
},
|
|
122
101
|
);
|
|
123
102
|
})
|
|
@@ -9,23 +9,19 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
|
-
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
13
12
|
|
|
14
|
-
export default defineWorkflow({
|
|
13
|
+
export default defineWorkflow<"opencode">({
|
|
15
14
|
name: "hello-parallel",
|
|
16
15
|
description: "Parallel OpenCode demo: describe → [summarize-a, summarize-b] → merge",
|
|
17
16
|
})
|
|
18
17
|
.run(async (ctx) => {
|
|
19
|
-
const describe = await ctx.
|
|
18
|
+
const describe = await ctx.stage(
|
|
20
19
|
{ name: "describe", description: "Ask the agent to describe the project" },
|
|
20
|
+
{},
|
|
21
|
+
{ title: "describe" },
|
|
21
22
|
async (s) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const session = await client.session.create({ title: "describe" });
|
|
25
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
26
|
-
|
|
27
|
-
const result = await client.session.prompt({
|
|
28
|
-
sessionID: session.data!.id,
|
|
23
|
+
const result = await s.client.session.prompt({
|
|
24
|
+
sessionID: s.session.id,
|
|
29
25
|
parts: [{ type: "text", text: s.userPrompt }],
|
|
30
26
|
});
|
|
31
27
|
|
|
@@ -34,17 +30,15 @@ export default defineWorkflow({
|
|
|
34
30
|
);
|
|
35
31
|
|
|
36
32
|
const [summarizeA, summarizeB] = await Promise.all([
|
|
37
|
-
ctx.
|
|
33
|
+
ctx.stage(
|
|
38
34
|
{ name: "summarize-a", description: "Summarize the description as bullet points" },
|
|
35
|
+
{},
|
|
36
|
+
{ title: "summarize-a" },
|
|
39
37
|
async (s) => {
|
|
40
38
|
const research = await s.transcript(describe);
|
|
41
|
-
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
42
|
-
|
|
43
|
-
const session = await client.session.create({ title: "summarize-a" });
|
|
44
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
45
39
|
|
|
46
|
-
const result = await client.session.prompt({
|
|
47
|
-
sessionID: session.
|
|
40
|
+
const result = await s.client.session.prompt({
|
|
41
|
+
sessionID: s.session.id,
|
|
48
42
|
parts: [
|
|
49
43
|
{
|
|
50
44
|
type: "text",
|
|
@@ -56,17 +50,15 @@ export default defineWorkflow({
|
|
|
56
50
|
s.save(result.data!);
|
|
57
51
|
},
|
|
58
52
|
),
|
|
59
|
-
ctx.
|
|
53
|
+
ctx.stage(
|
|
60
54
|
{ name: "summarize-b", description: "Summarize the description as a one-liner" },
|
|
55
|
+
{},
|
|
56
|
+
{ title: "summarize-b" },
|
|
61
57
|
async (s) => {
|
|
62
58
|
const research = await s.transcript(describe);
|
|
63
|
-
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
64
59
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const result = await client.session.prompt({
|
|
69
|
-
sessionID: session.data!.id,
|
|
60
|
+
const result = await s.client.session.prompt({
|
|
61
|
+
sessionID: s.session.id,
|
|
70
62
|
parts: [
|
|
71
63
|
{
|
|
72
64
|
type: "text",
|
|
@@ -80,18 +72,16 @@ export default defineWorkflow({
|
|
|
80
72
|
),
|
|
81
73
|
]);
|
|
82
74
|
|
|
83
|
-
await ctx.
|
|
75
|
+
await ctx.stage(
|
|
84
76
|
{ name: "merge", description: "Merge both summaries into a final output" },
|
|
77
|
+
{},
|
|
78
|
+
{ title: "merge" },
|
|
85
79
|
async (s) => {
|
|
86
80
|
const bullets = await s.transcript(summarizeA);
|
|
87
81
|
const oneliner = await s.transcript(summarizeB);
|
|
88
|
-
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
89
|
-
|
|
90
|
-
const session = await client.session.create({ title: "merge" });
|
|
91
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
92
82
|
|
|
93
|
-
const result = await client.session.prompt({
|
|
94
|
-
sessionID: session.
|
|
83
|
+
const result = await s.client.session.prompt({
|
|
84
|
+
sessionID: s.session.id,
|
|
95
85
|
parts: [
|
|
96
86
|
{
|
|
97
87
|
type: "text",
|
|
@@ -10,11 +10,7 @@
|
|
|
10
10
|
* Run: atomic workflow -n ralph -a claude "<your spec>"
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
defineWorkflow,
|
|
15
|
-
createClaudeSession,
|
|
16
|
-
claudeQuery,
|
|
17
|
-
} from "@bastani/atomic/workflows";
|
|
13
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
18
14
|
|
|
19
15
|
import {
|
|
20
16
|
buildPlannerPrompt,
|
|
@@ -35,7 +31,7 @@ function asAgentCall(agentName: string, prompt: string): string {
|
|
|
35
31
|
return `@"${agentName} (agent)" ${prompt}`;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
export default defineWorkflow({
|
|
34
|
+
export default defineWorkflow<"claude">({
|
|
39
35
|
name: "ralph",
|
|
40
36
|
description:
|
|
41
37
|
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
@@ -43,73 +39,66 @@ export default defineWorkflow({
|
|
|
43
39
|
.run(async (ctx) => {
|
|
44
40
|
let consecutiveClean = 0;
|
|
45
41
|
let debuggerReport = "";
|
|
46
|
-
// Track the most recent session so the next stage can declare it as a
|
|
47
|
-
// dependency — this chains planner → orchestrator → reviewer → [confirm]
|
|
48
|
-
// → [debugger] → next planner in the graph instead of showing every
|
|
49
|
-
// stage as an independent sibling under the root.
|
|
50
|
-
let prevStage: string | undefined;
|
|
51
|
-
const depsOn = (): string[] | undefined =>
|
|
52
|
-
prevStage ? [prevStage] : undefined;
|
|
53
42
|
|
|
54
43
|
for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
|
|
55
44
|
// ── Plan ────────────────────────────────────────────────────────────
|
|
56
45
|
const plannerName = `planner-${iteration}`;
|
|
57
|
-
await ctx.
|
|
58
|
-
{ name: plannerName
|
|
46
|
+
await ctx.stage(
|
|
47
|
+
{ name: plannerName },
|
|
48
|
+
{},
|
|
49
|
+
{},
|
|
59
50
|
async (s) => {
|
|
60
|
-
await
|
|
61
|
-
|
|
62
|
-
paneId: s.paneId,
|
|
63
|
-
prompt: asAgentCall(
|
|
51
|
+
await s.session.query(
|
|
52
|
+
asAgentCall(
|
|
64
53
|
"planner",
|
|
65
54
|
buildPlannerPrompt(s.userPrompt, {
|
|
66
55
|
iteration,
|
|
67
56
|
debuggerReport: debuggerReport || undefined,
|
|
68
57
|
}),
|
|
69
58
|
),
|
|
70
|
-
|
|
59
|
+
);
|
|
71
60
|
s.save(s.sessionId);
|
|
72
61
|
},
|
|
73
62
|
);
|
|
74
|
-
|
|
63
|
+
|
|
75
64
|
|
|
76
65
|
// ── Orchestrate ─────────────────────────────────────────────────────
|
|
77
66
|
const orchName = `orchestrator-${iteration}`;
|
|
78
|
-
await ctx.
|
|
79
|
-
{ name: orchName
|
|
67
|
+
await ctx.stage(
|
|
68
|
+
{ name: orchName },
|
|
69
|
+
{},
|
|
70
|
+
{},
|
|
80
71
|
async (s) => {
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
paneId: s.paneId,
|
|
84
|
-
prompt: asAgentCall(
|
|
72
|
+
await s.session.query(
|
|
73
|
+
asAgentCall(
|
|
85
74
|
"orchestrator",
|
|
86
75
|
buildOrchestratorPrompt(s.userPrompt),
|
|
87
76
|
),
|
|
88
|
-
|
|
77
|
+
);
|
|
89
78
|
s.save(s.sessionId);
|
|
90
79
|
},
|
|
91
80
|
);
|
|
92
|
-
|
|
81
|
+
|
|
93
82
|
|
|
94
83
|
// ── Review (first pass) ─────────────────────────────────────────────
|
|
95
84
|
let gitStatus = await safeGitStatusS();
|
|
96
85
|
const reviewerName = `reviewer-${iteration}`;
|
|
97
|
-
const review = await ctx.
|
|
98
|
-
{ name: reviewerName
|
|
86
|
+
const review = await ctx.stage(
|
|
87
|
+
{ name: reviewerName },
|
|
88
|
+
{},
|
|
89
|
+
{},
|
|
99
90
|
async (s) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
paneId: s.paneId,
|
|
103
|
-
prompt: asAgentCall(
|
|
91
|
+
const result = await s.session.query(
|
|
92
|
+
asAgentCall(
|
|
104
93
|
"reviewer",
|
|
105
94
|
buildReviewPrompt(s.userPrompt, { gitStatus, iteration }),
|
|
106
95
|
),
|
|
107
|
-
|
|
96
|
+
);
|
|
108
97
|
s.save(s.sessionId);
|
|
109
98
|
return result.output;
|
|
110
99
|
},
|
|
111
100
|
);
|
|
112
|
-
|
|
101
|
+
|
|
113
102
|
|
|
114
103
|
let reviewRaw = review.result;
|
|
115
104
|
let parsed = parseReviewResult(reviewRaw);
|
|
@@ -121,13 +110,13 @@ export default defineWorkflow({
|
|
|
121
110
|
// Confirmation pass — re-run reviewer only
|
|
122
111
|
gitStatus = await safeGitStatusS();
|
|
123
112
|
const confirmName = `reviewer-${iteration}-confirm`;
|
|
124
|
-
const confirm = await ctx.
|
|
125
|
-
{ name: confirmName
|
|
113
|
+
const confirm = await ctx.stage(
|
|
114
|
+
{ name: confirmName },
|
|
115
|
+
{},
|
|
116
|
+
{},
|
|
126
117
|
async (s) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
paneId: s.paneId,
|
|
130
|
-
prompt: asAgentCall(
|
|
118
|
+
const result = await s.session.query(
|
|
119
|
+
asAgentCall(
|
|
131
120
|
"reviewer",
|
|
132
121
|
buildReviewPrompt(s.userPrompt, {
|
|
133
122
|
gitStatus,
|
|
@@ -135,12 +124,12 @@ export default defineWorkflow({
|
|
|
135
124
|
isConfirmationPass: true,
|
|
136
125
|
}),
|
|
137
126
|
),
|
|
138
|
-
|
|
127
|
+
);
|
|
139
128
|
s.save(s.sessionId);
|
|
140
129
|
return result.output;
|
|
141
130
|
},
|
|
142
131
|
);
|
|
143
|
-
|
|
132
|
+
|
|
144
133
|
|
|
145
134
|
reviewRaw = confirm.result;
|
|
146
135
|
parsed = parseReviewResult(reviewRaw);
|
|
@@ -158,25 +147,25 @@ export default defineWorkflow({
|
|
|
158
147
|
// ── Debug (only if findings remain AND another iteration is allowed) ─
|
|
159
148
|
if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
|
|
160
149
|
const debuggerName = `debugger-${iteration}`;
|
|
161
|
-
const debugger_ = await ctx.
|
|
162
|
-
{ name: debuggerName
|
|
150
|
+
const debugger_ = await ctx.stage(
|
|
151
|
+
{ name: debuggerName },
|
|
152
|
+
{},
|
|
153
|
+
{},
|
|
163
154
|
async (s) => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
paneId: s.paneId,
|
|
167
|
-
prompt: asAgentCall(
|
|
155
|
+
const result = await s.session.query(
|
|
156
|
+
asAgentCall(
|
|
168
157
|
"debugger",
|
|
169
158
|
buildDebuggerReportPrompt(parsed, reviewRaw, {
|
|
170
159
|
iteration,
|
|
171
160
|
gitStatus,
|
|
172
161
|
}),
|
|
173
162
|
),
|
|
174
|
-
|
|
163
|
+
);
|
|
175
164
|
s.save(s.sessionId);
|
|
176
165
|
return result.output;
|
|
177
166
|
},
|
|
178
167
|
);
|
|
179
|
-
|
|
168
|
+
|
|
180
169
|
debuggerReport = extractMarkdownBlock(debugger_.result);
|
|
181
170
|
}
|
|
182
171
|
}
|