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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "CLI for AgentXchain — multi-agent coordination in your IDE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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) Do NOT start round-robin agent handoffs yet.
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}
@@ -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. Update \`state.json\` if phase or blocked status changed.`
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. Update \`state.json\` if phase or blocked status changed.`;
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. Update state.json if phase or blocked status changed.`
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. Update state.json if phase or blocked status changed.`;
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?
@@ -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;