agentxchain 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/adapters/cursor-local.js +16 -6
- package/src/commands/init.js +3 -1
- package/src/lib/generate-vscode.js +18 -4
- package/src/lib/seed-prompt-polling.js +24 -3
- package/src/lib/validation.js +26 -0
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ agentxchain kickoff
|
|
|
27
27
|
|
|
28
28
|
Each agent runs in its own Cursor window with a self-polling loop. Agents check `lock.json` every 60 seconds, claim when it's their turn, do their work, release, and go back to waiting. `supervise --autonudge` handles watch + nudging automatically.
|
|
29
29
|
|
|
30
|
+
Agents are now required to maintain `TALK.md` as the human-readable handoff log each turn.
|
|
31
|
+
|
|
30
32
|
## Commands
|
|
31
33
|
|
|
32
34
|
| Command | What it does |
|
package/package.json
CHANGED
|
@@ -24,8 +24,8 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
24
24
|
// Save all prompts first
|
|
25
25
|
for (const [id, agent] of agentEntries) {
|
|
26
26
|
const prompt = isPmKickoff
|
|
27
|
-
? generateKickoffPrompt(id, agent, config)
|
|
28
|
-
: generatePollingPrompt(id, agent, config);
|
|
27
|
+
? generateKickoffPrompt(id, agent, config, root)
|
|
28
|
+
: generatePollingPrompt(id, agent, config, root);
|
|
29
29
|
writeFileSync(join(promptDir, `${id}.prompt.md`), prompt);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -36,8 +36,8 @@ export async function launchCursorLocal(config, root, opts) {
|
|
|
36
36
|
for (let i = 0; i < agentEntries.length; i++) {
|
|
37
37
|
const [id, agent] = agentEntries[i];
|
|
38
38
|
const prompt = isPmKickoff
|
|
39
|
-
? generateKickoffPrompt(id, agent, config)
|
|
40
|
-
: generatePollingPrompt(id, agent, config);
|
|
39
|
+
? generateKickoffPrompt(id, agent, config, root)
|
|
40
|
+
: generatePollingPrompt(id, agent, config, root);
|
|
41
41
|
|
|
42
42
|
// Create symlink: .agentxchain-workspaces/<id> -> project root
|
|
43
43
|
const agentWorkspace = join(workspacesDir, id);
|
|
@@ -153,16 +153,20 @@ function isPmLike(agentId, agentDef) {
|
|
|
153
153
|
return name.includes('product manager');
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
function generateKickoffPrompt(agentId, agentDef, config) {
|
|
156
|
+
function generateKickoffPrompt(agentId, agentDef, config, projectRoot) {
|
|
157
157
|
return `You are "${agentId}" — ${agentDef.name}.
|
|
158
158
|
|
|
159
159
|
This is PM kickoff mode. Your job now is to collaborate with the human and finalize scope before autonomous turns begin.
|
|
160
160
|
|
|
161
|
+
Project root (strict boundary): "${projectRoot}"
|
|
162
|
+
Work only inside this project folder. Do NOT scan unrelated local directories.
|
|
163
|
+
|
|
161
164
|
Actions:
|
|
162
165
|
1) Read:
|
|
163
166
|
- .planning/PROJECT.md
|
|
164
167
|
- .planning/REQUIREMENTS.md
|
|
165
168
|
- .planning/ROADMAP.md
|
|
169
|
+
- TALK.md
|
|
166
170
|
- state.md
|
|
167
171
|
- lock.json
|
|
168
172
|
2) Ask the human focused product questions until scope is clear:
|
|
@@ -176,7 +180,13 @@ Actions:
|
|
|
176
180
|
- Create .planning/phases/phase-1/PLAN.md and TESTS.md.
|
|
177
181
|
4) Update .planning/PM_SIGNOFF.md:
|
|
178
182
|
- Set "Approved: YES" only when human agrees kickoff is complete.
|
|
179
|
-
5)
|
|
183
|
+
5) Append kickoff summary to TALK.md with:
|
|
184
|
+
- Status
|
|
185
|
+
- Decision
|
|
186
|
+
- Action
|
|
187
|
+
- Risks/Questions
|
|
188
|
+
- Next owner
|
|
189
|
+
6) Do NOT start round-robin agent handoffs yet.
|
|
180
190
|
|
|
181
191
|
Context:
|
|
182
192
|
- Project: ${config.project}
|
package/src/commands/init.js
CHANGED
|
@@ -183,6 +183,7 @@ export async function initCommand(opts) {
|
|
|
183
183
|
project,
|
|
184
184
|
agents,
|
|
185
185
|
log: 'log.md',
|
|
186
|
+
talk_file: 'TALK.md',
|
|
186
187
|
state_file: 'state.md',
|
|
187
188
|
history_file: 'history.jsonl',
|
|
188
189
|
rules: {
|
|
@@ -205,6 +206,7 @@ export async function initCommand(opts) {
|
|
|
205
206
|
writeFileSync(join(dir, 'state.md'), `# ${project} — Current State\n\n## Architecture\n\n(Agents update this each turn with current decisions.)\n\n## Active Work\n\n(What's in progress right now.)\n\n## Open Issues\n\n(Bugs, blockers, risks.)\n\n## Next Steps\n\n(What should happen next.)\n`);
|
|
206
207
|
writeFileSync(join(dir, 'history.jsonl'), '');
|
|
207
208
|
writeFileSync(join(dir, 'log.md'), `# ${project} — Agent Log\n\n## COMPRESSED CONTEXT\n\n(No compressed context yet.)\n\n## MESSAGE LOG\n\n(Agents append messages below this line.)\n`);
|
|
209
|
+
writeFileSync(join(dir, 'TALK.md'), `# ${project} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n## How to write entries\n\nUse this exact structure:\n\n## Turn N — <agent_id> (<role>)\n- Status:\n- Decision:\n- Action:\n- Risks/Questions:\n- Next owner:\n\n---\n\n`);
|
|
208
210
|
writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
|
|
209
211
|
const gitignorePath = join(dir, '.gitignore');
|
|
210
212
|
const requiredIgnores = ['.env', '.agentxchain-trigger.json', '.agentxchain-prompts/', '.agentxchain-workspaces/'];
|
|
@@ -253,7 +255,7 @@ export async function initCommand(opts) {
|
|
|
253
255
|
console.log(` ${chalk.dim('├──')} agentxchain.json ${chalk.dim(`(${agentCount} agents)`)}`);
|
|
254
256
|
console.log(` ${chalk.dim('├──')} lock.json`);
|
|
255
257
|
console.log(` ${chalk.dim('├──')} state.json / state.md / history.jsonl`);
|
|
256
|
-
console.log(` ${chalk.dim('├──')} log.md / HUMAN_TASKS.md`);
|
|
258
|
+
console.log(` ${chalk.dim('├──')} TALK.md / log.md / HUMAN_TASKS.md`);
|
|
257
259
|
console.log(` ${chalk.dim('├──')} .planning/`);
|
|
258
260
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PROJECT.md / REQUIREMENTS.md / ROADMAP.md / PM_SIGNOFF.md`);
|
|
259
261
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} research/ / phases/`);
|
|
@@ -15,7 +15,7 @@ export function generateVSCodeFiles(dir, config) {
|
|
|
15
15
|
|
|
16
16
|
for (const id of agentIds) {
|
|
17
17
|
const agent = config.agents[id];
|
|
18
|
-
const md = buildAgentMd(id, agent, config, agentIds);
|
|
18
|
+
const md = buildAgentMd(id, agent, config, agentIds, dir);
|
|
19
19
|
writeFileSync(join(agentsDir, `${id}.agent.md`), md);
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -33,13 +33,14 @@ export function generateVSCodeFiles(dir, config) {
|
|
|
33
33
|
return { agentsDir, hooksDir, scriptsDir, agentCount: agentIds.length };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function buildAgentMd(agentId, agentDef, config, allAgentIds) {
|
|
36
|
+
function buildAgentMd(agentId, agentDef, config, allAgentIds, projectRoot) {
|
|
37
37
|
const otherAgents = allAgentIds.filter(id => id !== agentId);
|
|
38
38
|
const verifyCmd = config.rules?.verify_command || null;
|
|
39
39
|
const maxClaims = config.rules?.max_consecutive_claims || 2;
|
|
40
40
|
const stateFile = config.state_file || 'state.md';
|
|
41
41
|
const historyFile = config.history_file || 'history.jsonl';
|
|
42
42
|
const logFile = config.log || 'log.md';
|
|
43
|
+
const talkFile = config.talk_file || 'TALK.md';
|
|
43
44
|
const useSplit = config.state_file || config.history_file;
|
|
44
45
|
|
|
45
46
|
const handoffs = otherAgents.map(otherId => {
|
|
@@ -77,10 +78,12 @@ hooks:
|
|
|
77
78
|
? `Read these files at the start of your turn:
|
|
78
79
|
- \`${stateFile}\` — living project state (primary context)
|
|
79
80
|
- \`${historyFile}\` — last 3 lines for recent turns
|
|
81
|
+
- \`${talkFile}\` — team handoff updates (read latest entries)
|
|
80
82
|
- \`lock.json\` — current lock holder
|
|
81
83
|
- \`state.json\` — phase and blocked status`
|
|
82
84
|
: `Read these files at the start of your turn:
|
|
83
85
|
- \`${logFile}\` — message log (read last few messages)
|
|
86
|
+
- \`${talkFile}\` — team handoff updates (read latest entries)
|
|
84
87
|
- \`lock.json\` — current lock holder
|
|
85
88
|
- \`state.json\` — phase and blocked status`;
|
|
86
89
|
|
|
@@ -90,13 +93,15 @@ hooks:
|
|
|
90
93
|
2. Overwrite \`${stateFile}\` with current project state.
|
|
91
94
|
3. Append one line to \`${historyFile}\`:
|
|
92
95
|
\`{"turn": N, "agent": "${agentId}", "summary": "...", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}\`
|
|
93
|
-
4.
|
|
96
|
+
4. Append one handoff entry to \`${talkFile}\` with: Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
97
|
+
5. Update \`state.json\` if phase or blocked status changed.`
|
|
94
98
|
: `When you finish your work, write in this order:
|
|
95
99
|
1. Your actual work: code, files, commands, decisions.
|
|
96
100
|
2. Append one message to \`${logFile}\`:
|
|
97
101
|
\`### [${agentId}] (${agentDef.name}) | Turn N\`
|
|
98
102
|
with Status, Decision, Action, Next sections.
|
|
99
|
-
3.
|
|
103
|
+
3. Append one handoff entry to \`${talkFile}\` with: Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
104
|
+
4. Update \`state.json\` if phase or blocked status changed.`;
|
|
100
105
|
|
|
101
106
|
const verifyInstructions = verifyCmd
|
|
102
107
|
? `\n## Verify before release\nBefore releasing the lock, run: \`${verifyCmd}\`\nIf it fails, fix the problem and run again. Do NOT release with a failing verification.`
|
|
@@ -110,6 +115,15 @@ ${agentDef.mandate}
|
|
|
110
115
|
|
|
111
116
|
---
|
|
112
117
|
|
|
118
|
+
## Project boundary
|
|
119
|
+
|
|
120
|
+
- Project root: \`${projectRoot}\`
|
|
121
|
+
- Work only inside this project folder.
|
|
122
|
+
- Never scan unrelated local directories.
|
|
123
|
+
- Start your turn by checking \`pwd\`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
113
127
|
## Project documentation
|
|
114
128
|
|
|
115
129
|
Read the files relevant to your role in the \`.planning/\` folder:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export function generatePollingPrompt(agentId, agentDef, config) {
|
|
1
|
+
export function generatePollingPrompt(agentId, agentDef, config, projectRoot = '.') {
|
|
2
2
|
const logFile = config.log || 'log.md';
|
|
3
|
+
const talkFile = config.talk_file || 'TALK.md';
|
|
3
4
|
const maxClaims = config.rules?.max_consecutive_claims || 2;
|
|
4
5
|
const verifyCmd = config.rules?.verify_command || null;
|
|
5
6
|
const stateFile = config.state_file || 'state.md';
|
|
@@ -19,10 +20,12 @@ export function generatePollingPrompt(agentId, agentDef, config) {
|
|
|
19
20
|
? `READ THESE FILES:
|
|
20
21
|
- "${stateFile}" — the living project state. Read fully. Primary context.
|
|
21
22
|
- "${historyFile}" — turn history. Read last 3 lines for recent context.
|
|
23
|
+
- "${talkFile}" — team handoff updates. Read the latest 5 entries.
|
|
22
24
|
- lock.json — who holds the lock.
|
|
23
25
|
- state.json — phase and blocked status.`
|
|
24
26
|
: `READ THESE FILES:
|
|
25
27
|
- "${logFile}" — the message log. Read last few messages.
|
|
28
|
+
- "${talkFile}" — team handoff updates. Read the latest 5 entries.
|
|
26
29
|
- lock.json — who holds the lock.
|
|
27
30
|
- state.json — phase and blocked status.`;
|
|
28
31
|
|
|
@@ -32,7 +35,9 @@ a. Do your actual work: write code, create files, run commands, make decisions.
|
|
|
32
35
|
b. Update "${stateFile}" — OVERWRITE with current project state.
|
|
33
36
|
c. Append ONE line to "${historyFile}":
|
|
34
37
|
{"turn": N, "agent": "${agentId}", "summary": "what you did", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}
|
|
35
|
-
d.
|
|
38
|
+
d. Append ONE handoff entry to "${talkFile}" with:
|
|
39
|
+
Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
40
|
+
e. Update state.json if phase or blocked status changed.`
|
|
36
41
|
: `WRITE (in this order):
|
|
37
42
|
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
38
43
|
b. Append ONE message to ${logFile}:
|
|
@@ -42,7 +47,9 @@ b. Append ONE message to ${logFile}:
|
|
|
42
47
|
**Decision:** What you decided and why.
|
|
43
48
|
**Action:** What you did. Commands, files, results.
|
|
44
49
|
**Next:** What the next agent should focus on.
|
|
45
|
-
c.
|
|
50
|
+
c. Append ONE handoff entry to "${talkFile}" with:
|
|
51
|
+
Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
52
|
+
d. Update state.json if phase or blocked status changed.`;
|
|
46
53
|
|
|
47
54
|
const verifySection = verifyCmd
|
|
48
55
|
? `
|
|
@@ -58,6 +65,15 @@ ${agentDef.mandate}
|
|
|
58
65
|
|
|
59
66
|
---
|
|
60
67
|
|
|
68
|
+
PROJECT ROOT (strict boundary):
|
|
69
|
+
- Absolute project root: "${projectRoot}"
|
|
70
|
+
- You MUST work only inside this project root.
|
|
71
|
+
- Do NOT scan your home directory or unrelated folders.
|
|
72
|
+
- If unsure, run: pwd
|
|
73
|
+
- If not in project root, run: cd "${projectRoot}"
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
61
77
|
PROJECT DOCUMENTATION (.planning/ folder):
|
|
62
78
|
|
|
63
79
|
These files give you project context. Read the ones relevant to your role.
|
|
@@ -93,6 +109,11 @@ ${turnCondition}
|
|
|
93
109
|
|
|
94
110
|
YOUR LOOP (run forever, never exit, never say "I'm done"):
|
|
95
111
|
|
|
112
|
+
0. CHECK WORKING DIRECTORY:
|
|
113
|
+
- Run: pwd
|
|
114
|
+
- If not inside "${projectRoot}", run: cd "${projectRoot}"
|
|
115
|
+
- Never run broad searches outside this project root.
|
|
116
|
+
|
|
96
117
|
1. READ lock.json.
|
|
97
118
|
|
|
98
119
|
2. CHECK — is it my turn?
|
package/src/lib/validation.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
export function validateProject(root, config, opts = {}) {
|
|
5
5
|
const mode = opts.mode || 'full';
|
|
6
6
|
const expectedAgent = opts.expectedAgent || null;
|
|
7
|
+
const talkFile = config.talk_file || 'TALK.md';
|
|
7
8
|
|
|
8
9
|
const errors = [];
|
|
9
10
|
const warnings = [];
|
|
@@ -18,6 +19,7 @@ export function validateProject(root, config, opts = {}) {
|
|
|
18
19
|
'.planning/qa/UX-AUDIT.md',
|
|
19
20
|
'.planning/qa/ACCEPTANCE-MATRIX.md',
|
|
20
21
|
'.planning/qa/REGRESSION-LOG.md',
|
|
22
|
+
talkFile,
|
|
21
23
|
'state.md',
|
|
22
24
|
'history.jsonl',
|
|
23
25
|
'lock.json',
|
|
@@ -56,6 +58,10 @@ export function validateProject(root, config, opts = {}) {
|
|
|
56
58
|
errors.push(...history.errors);
|
|
57
59
|
warnings.push(...history.warnings);
|
|
58
60
|
|
|
61
|
+
const talk = validateTalkFile(root, talkFile, { requireEntry: mode !== 'kickoff' });
|
|
62
|
+
errors.push(...talk.errors);
|
|
63
|
+
warnings.push(...talk.warnings);
|
|
64
|
+
|
|
59
65
|
const qaSignals = validateQaArtifacts(root);
|
|
60
66
|
warnings.push(...qaSignals.warnings);
|
|
61
67
|
|
|
@@ -160,6 +166,26 @@ function validateQaArtifacts(root) {
|
|
|
160
166
|
return result;
|
|
161
167
|
}
|
|
162
168
|
|
|
169
|
+
function validateTalkFile(root, talkFile, opts) {
|
|
170
|
+
const result = { errors: [], warnings: [] };
|
|
171
|
+
const text = readText(root, talkFile);
|
|
172
|
+
if (!text) {
|
|
173
|
+
result.errors.push(`${talkFile} is missing or unreadable.`);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const hasTurnEntry = /##\s*Turn\s+\d+/i.test(text);
|
|
178
|
+
if (!hasTurnEntry) {
|
|
179
|
+
if (opts.requireEntry) {
|
|
180
|
+
result.errors.push(`${talkFile} has no turn entries.`);
|
|
181
|
+
} else {
|
|
182
|
+
result.warnings.push(`${talkFile} has no turn entries yet.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
163
189
|
function readText(root, rel) {
|
|
164
190
|
const path = join(root, rel);
|
|
165
191
|
if (!existsSync(path)) return null;
|