agentxchain 0.8.5 → 0.8.6

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
@@ -25,7 +25,7 @@ cd my-project/
25
25
  agentxchain kickoff
26
26
  ```
27
27
 
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.
28
+ Each agent runs in its own Cursor window for a single turn at a time. The referee loop (`watch` / `supervise --autonudge`) determines the next agent and wakes that specific session.
29
29
 
30
30
  Agents are now required to maintain `TALK.md` as the human-readable handoff log each turn.
31
31
 
@@ -48,6 +48,25 @@ Agents are now required to maintain `TALK.md` as the human-readable handoff log
48
48
  | `config` | View/edit config, add/remove agents, change rules |
49
49
  | `update` | Self-update CLI from npm |
50
50
 
51
+ ### Full command list
52
+
53
+ ```bash
54
+ agentxchain init
55
+ agentxchain status
56
+ agentxchain start
57
+ agentxchain kickoff
58
+ agentxchain stop
59
+ agentxchain config
60
+ agentxchain generate
61
+ agentxchain watch
62
+ agentxchain supervise
63
+ agentxchain claim
64
+ agentxchain release
65
+ agentxchain update
66
+ agentxchain doctor
67
+ agentxchain validate
68
+ ```
69
+
51
70
  ### IDE options
52
71
 
53
72
  ```bash
@@ -62,15 +81,21 @@ agentxchain start --ide claude-code # Claude Code — spawns CLI processes
62
81
  agentxchain kickoff # guided first-run PM-first workflow
63
82
  agentxchain kickoff --ide vscode # guided flow for VS Code mode
64
83
  agentxchain kickoff --send # with Cursor auto-nudge auto-send enabled
84
+ agentxchain kickoff --interval 2 # nudge poll interval override
85
+ agentxchain kickoff --no-autonudge # skip auto-nudge prompt
65
86
 
66
87
  agentxchain start --agent pm # launch only one specific agent
67
88
  agentxchain start --remaining # launch all agents except PM (PM-first flow)
68
89
  agentxchain start --dry-run # preview agents without launching
69
90
  agentxchain validate --mode kickoff # required before --remaining
70
91
  agentxchain validate --mode turn --agent pm
92
+ agentxchain validate --json # machine-readable validation output
71
93
  agentxchain watch --daemon # run watch in background
72
94
  agentxchain supervise --autonudge # run watch + AppleScript nudge loop
73
95
  agentxchain supervise --autonudge --send # auto-press Enter after paste
96
+ agentxchain supervise --interval 2 # set auto-nudge poll interval
97
+ agentxchain claim --agent pm # guarded claim as agent turn owner
98
+ agentxchain release --agent pm # guarded release as agent turn owner
74
99
  agentxchain release --force # force-release non-human holder lock
75
100
  ```
76
101
 
@@ -120,7 +145,7 @@ Notes:
120
145
  1. `agentxchain kickoff` launches PM first for human-product alignment
121
146
  2. Each window gets a unique prompt copied to clipboard
122
147
  3. Kickoff validates PM signoff and launches remaining agents
123
- 4. Agent prompts include a self-polling loop: read `lock.json` → check if it's my turn → claim → work → releasesleep 60s repeat
148
+ 4. Agent prompts are single-turn: claim → work → validatereleasestop
124
149
  5. Agents know their rotation order from `agentxchain.json` and only claim when the previous agent released
125
150
  6. Human can `claim` to pause and `release` to resume anytime
126
151
 
@@ -141,7 +166,7 @@ Agents follow a round-robin order defined in `agentxchain.json`:
141
166
  ## Key features
142
167
 
143
168
  - **One window per agent** — each agent has its own Cursor window and chat session
144
- - **Self-polling coordination** — agents check `lock.json` every 60s, no external process needed
169
+ - **Referee-driven coordination** — `watch`/`supervise` wakes the next correct agent each turn
145
170
  - **Works in Cursor, VS Code, Claude Code** — adapters for each IDE
146
171
  - **User-defined teams** — any number of agents, any roles
147
172
  - **No API keys or cloud required** — everything runs locally
@@ -94,12 +94,14 @@ program
94
94
  program
95
95
  .command('claim')
96
96
  .description('Claim the lock as a human (take control)')
97
+ .option('--agent <id>', 'Claim lock as a specific agent (guarded by turn order)')
97
98
  .option('--force', 'Force-claim even if an agent holds the lock')
98
99
  .action(claimCommand);
99
100
 
100
101
  program
101
102
  .command('release')
102
103
  .description('Release the lock (hand back to agents)')
104
+ .option('--agent <id>', 'Release lock as a specific agent')
103
105
  .option('--force', 'Force release even if a non-human holder has the lock')
104
106
  .action(releaseCommand);
105
107
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "CLI for AgentXchain — multi-agent coordination in your IDE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -107,7 +107,7 @@ export async function launchCursorLocal(config, root, opts) {
107
107
  console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# watcher / trigger writer')}`);
108
108
  console.log(` ${chalk.bold('agentxchain doctor')} ${chalk.dim('# check local setup + trigger health')}`);
109
109
  console.log('');
110
- console.log(chalk.dim(' Agents self-coordinate via lock.json polling (sleep 60s between checks).'));
110
+ console.log(chalk.dim(' Agents run single turns. Watch/supervise wakes the correct next agent.'));
111
111
  console.log(chalk.dim(' Re-paste a prompt: cat .agentxchain-prompts/<agent>.prompt.md | pbcopy'));
112
112
  console.log('');
113
113
  }
@@ -162,31 +162,34 @@ Project root (strict boundary): "${projectRoot}"
162
162
  Work only inside this project folder. Do NOT scan unrelated local directories.
163
163
 
164
164
  Actions:
165
- 1) Read:
165
+ 1) FIRST QUESTION TO HUMAN (mandatory before anything else):
166
+ "Please describe your product idea in one paragraph, in as much detail as possible."
167
+
168
+ 2) Read:
166
169
  - .planning/PROJECT.md
167
170
  - .planning/REQUIREMENTS.md
168
171
  - .planning/ROADMAP.md
169
172
  - TALK.md
170
173
  - state.md
171
174
  - lock.json
172
- 2) Ask the human focused product questions until scope is clear:
175
+ 3) After receiving that paragraph, summarize it in TALK.md, then ask focused follow-up questions:
173
176
  - target user
174
177
  - top pain point
175
178
  - core workflow
176
179
  - MVP boundary
177
180
  - success metric
178
- 3) Update planning docs with concrete acceptance criteria and Get Shit Done structure:
181
+ 4) Update planning docs with concrete acceptance criteria and Get Shit Done structure:
179
182
  - .planning/ROADMAP.md must define Waves and Phases.
180
183
  - Create .planning/phases/phase-1/PLAN.md and TESTS.md.
181
- 4) Update .planning/PM_SIGNOFF.md:
184
+ 5) Update .planning/PM_SIGNOFF.md:
182
185
  - Set "Approved: YES" only when human agrees kickoff is complete.
183
- 5) Append kickoff summary to TALK.md with:
186
+ 6) Append kickoff summary to TALK.md with:
184
187
  - Status
185
188
  - Decision
186
189
  - Action
187
190
  - Risks/Questions
188
191
  - Next owner
189
- 6) Do NOT start round-robin agent handoffs yet.
192
+ 7) Do NOT start round-robin agent handoffs yet.
190
193
 
191
194
  Context:
192
195
  - Project: ${config.project}
@@ -11,6 +11,10 @@ export async function claimCommand(opts) {
11
11
  const lock = loadLock(root);
12
12
  if (!lock) { console.log(chalk.red(' lock.json not found.')); process.exit(1); }
13
13
 
14
+ if (opts.agent) {
15
+ return claimAsAgent({ opts, root, config, lock });
16
+ }
17
+
14
18
  if (lock.holder === 'human') {
15
19
  console.log('');
16
20
  console.log(chalk.yellow(' You already hold the lock.'));
@@ -52,6 +56,10 @@ export async function releaseCommand(opts) {
52
56
  const lock = loadLock(root);
53
57
  if (!lock) { console.log(chalk.red(' lock.json not found.')); process.exit(1); }
54
58
 
59
+ if (opts.agent) {
60
+ return releaseAsAgent({ opts, root, config, lock });
61
+ }
62
+
55
63
  if (!lock.holder) {
56
64
  console.log(chalk.yellow(' Lock is already free.'));
57
65
  return;
@@ -85,6 +93,67 @@ export async function releaseCommand(opts) {
85
93
  console.log('');
86
94
  }
87
95
 
96
+ function claimAsAgent({ opts, root, config, lock }) {
97
+ const agentId = opts.agent;
98
+ if (!config.agents?.[agentId]) {
99
+ console.log(chalk.red(` Agent "${agentId}" is not defined in agentxchain.json.`));
100
+ process.exit(1);
101
+ }
102
+
103
+ if (lock.holder && !opts.force) {
104
+ console.log(chalk.red(` Lock is currently held by "${lock.holder}".`));
105
+ console.log(chalk.dim(' Use --force only for recovery scenarios.'));
106
+ process.exit(1);
107
+ }
108
+
109
+ const expected = pickNextAgent(lock, config);
110
+ if (!opts.force && expected && expected !== agentId) {
111
+ console.log(chalk.red(` Out-of-turn claim blocked. Expected: ${expected}, got: ${agentId}.`));
112
+ process.exit(1);
113
+ }
114
+
115
+ const lockPath = join(root, LOCK_FILE);
116
+ const next = {
117
+ holder: agentId,
118
+ last_released_by: lock.last_released_by,
119
+ turn_number: lock.turn_number,
120
+ claimed_at: new Date().toISOString()
121
+ };
122
+ writeFileSync(lockPath, JSON.stringify(next, null, 2) + '\n');
123
+ console.log(chalk.green(` ✓ Lock claimed by ${agentId} (turn ${next.turn_number})`));
124
+ }
125
+
126
+ function releaseAsAgent({ opts, root, config, lock }) {
127
+ const agentId = opts.agent;
128
+ if (!config.agents?.[agentId]) {
129
+ console.log(chalk.red(` Agent "${agentId}" is not defined in agentxchain.json.`));
130
+ process.exit(1);
131
+ }
132
+ if (lock.holder !== agentId && !opts.force) {
133
+ console.log(chalk.red(` Lock is held by "${lock.holder}", not "${agentId}".`));
134
+ process.exit(1);
135
+ }
136
+
137
+ const lockPath = join(root, LOCK_FILE);
138
+ const next = {
139
+ holder: null,
140
+ last_released_by: agentId,
141
+ turn_number: lock.turn_number + 1,
142
+ claimed_at: null
143
+ };
144
+ writeFileSync(lockPath, JSON.stringify(next, null, 2) + '\n');
145
+ console.log(chalk.green(` ✓ Lock released by ${agentId} (turn ${next.turn_number})`));
146
+ }
147
+
148
+ function pickNextAgent(lock, config) {
149
+ const agentIds = Object.keys(config.agents || {});
150
+ if (agentIds.length === 0) return null;
151
+ const lastAgent = lock.last_released_by;
152
+ if (!lastAgent || !agentIds.includes(lastAgent)) return agentIds[0];
153
+ const lastIndex = agentIds.indexOf(lastAgent);
154
+ return agentIds[(lastIndex + 1) % agentIds.length];
155
+ }
156
+
88
157
  function clearBlockedState(root) {
89
158
  const statePath = join(root, 'state.json');
90
159
  if (!existsSync(statePath)) return;
@@ -50,6 +50,17 @@ export async function watchCommand(opts) {
50
50
 
51
51
  const stateKey = `${lock.holder}:${lock.turn_number}`;
52
52
 
53
+ if (lock.holder && lock.holder !== 'human') {
54
+ const expected = pickNextAgent(lock, config);
55
+ if (!isValidClaimer(lock, config)) {
56
+ log('warn', `Illegal claim detected: holder=${lock.holder}, expected=${expected}. Handing lock to HUMAN.`);
57
+ blockOnIllegalClaim(root, lock, config, expected);
58
+ sendNotification(`Illegal claim detected (${lock.holder}). Human intervention required.`);
59
+ lastState = null;
60
+ return;
61
+ }
62
+ }
63
+
53
64
  if (lock.holder && lock.holder !== 'human' && lock.claimed_at) {
54
65
  const elapsed = Date.now() - new Date(lock.claimed_at).getTime();
55
66
  const ttlMs = ttlMinutes * 60 * 1000;
@@ -129,6 +140,13 @@ function pickNextAgent(lock, config) {
129
140
  return agentIds[(lastIndex + 1) % agentIds.length];
130
141
  }
131
142
 
143
+ function isValidClaimer(lock, config) {
144
+ if (!lock.holder || lock.holder === 'human') return true;
145
+ if (!config.agents?.[lock.holder]) return false;
146
+ const expected = pickNextAgent(lock, config);
147
+ return lock.holder === expected;
148
+ }
149
+
132
150
  function forceRelease(root, lock, staleAgent, config) {
133
151
  const lockPath = join(root, LOCK_FILE);
134
152
  const newLock = {
@@ -192,6 +210,39 @@ function blockOnValidation(root, lock, config, validation) {
192
210
  }
193
211
  }
194
212
 
213
+ function blockOnIllegalClaim(root, lock, config, expected) {
214
+ const lockPath = join(root, LOCK_FILE);
215
+ const newLock = {
216
+ holder: 'human',
217
+ last_released_by: lock.last_released_by,
218
+ turn_number: lock.turn_number,
219
+ claimed_at: new Date().toISOString()
220
+ };
221
+ writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n');
222
+
223
+ const statePath = join(root, 'state.json');
224
+ if (existsSync(statePath)) {
225
+ try {
226
+ const current = JSON.parse(readFileSync(statePath, 'utf8'));
227
+ const nextState = {
228
+ ...current,
229
+ blocked: true,
230
+ blocked_on: `illegal-claim: expected ${expected}, got ${lock.holder}`
231
+ };
232
+ writeFileSync(statePath, JSON.stringify(nextState, null, 2) + '\n');
233
+ } catch {}
234
+ }
235
+
236
+ const logFile = config.log || 'log.md';
237
+ const logPath = join(root, logFile);
238
+ if (existsSync(logPath)) {
239
+ appendFileSync(
240
+ logPath,
241
+ `\n---\n\n### [system] (Watch Guard) | Turn ${lock.turn_number}\n\n**Status:** Illegal out-of-turn lock claim detected.\n\n**Action:** Lock assigned to human for intervention.\n\n**Details:** expected \`${expected}\`, got \`${lock.holder}\`.\n\n`
242
+ );
243
+ }
244
+ }
245
+
195
246
  function log(type, msg) {
196
247
  const time = new Date().toLocaleTimeString();
197
248
  const tags = {
@@ -142,12 +142,13 @@ Create or update these files when your role requires it.
142
142
 
143
143
  The AgentXchain system coordinates turns. When prompted, do this:
144
144
 
145
- 1. **CLAIM**: Write \`lock.json\` with \`holder="${agentId}"\` and \`claimed_at\` = current time. Re-read to confirm.
145
+ 1. **CLAIM**: Run \`agentxchain claim --agent ${agentId}\`. If blocked, stop.
146
146
  2. **READ**: ${readInstructions}
147
147
  3. **THINK**: What did the previous agent do? What is most important for YOUR role? What is one risk?
148
148
  4. **WORK**: ${writeInstructions}${verifyInstructions}
149
- 5. **RELEASE**: Write \`lock.json\`: \`holder=null\`, \`last_released_by="${agentId}"\`, \`turn_number\` = previous + 1, \`claimed_at=null\`.
149
+ 5. **RELEASE**: Run \`agentxchain release --agent ${agentId}\`.
150
150
  This MUST be the last thing you write.
151
+ 6. **STOP**: End your turn. The referee will wake the next agent.
151
152
 
152
153
  ---
153
154
 
@@ -107,7 +107,7 @@ ${turnCondition}
107
107
 
108
108
  ---
109
109
 
110
- YOUR LOOP (run forever, never exit, never say "I'm done"):
110
+ TURN MODE (single turn only, referee wakes you again later):
111
111
 
112
112
  0. CHECK WORKING DIRECTORY:
113
113
  - Run: pwd
@@ -117,18 +117,14 @@ YOUR LOOP (run forever, never exit, never say "I'm done"):
117
117
  1. READ lock.json.
118
118
 
119
119
  2. CHECK — is it my turn?
120
- - If holder is NOT null → someone else is working. Run the shell command: sleep 60
121
- Then go back to step 1.
122
- - If holder IS null check last_released_by:
123
- ${isFirstAgent
124
- ? `- If last_released_by is null, "human", starts with "system", or "${agentIds[agentIds.length - 1]}" → IT IS YOUR TURN. Go to step 3.`
125
- : `- If last_released_by is "${prevAgent}" → IT IS YOUR TURN. Go to step 3.`}
126
- - Otherwise → it is another agent's turn. Run the shell command: sleep 60
127
- Then go back to step 1.
120
+ ${isFirstAgent
121
+ ? `- It is your turn only when lock holder is null and last_released_by is null/human/system/${agentIds[agentIds.length - 1]}.`
122
+ : `- It is your turn only when lock holder is null and last_released_by is "${prevAgent}".`}
123
+ - If NOT your turn: STOP. Do not claim lock and do not write files.
128
124
 
129
125
  3. CLAIM the lock:
130
- Write lock.json: {"holder":"${agentId}","last_released_by":<keep previous>,"turn_number":<keep previous>,"claimed_at":"<current ISO timestamp>"}
131
- Then RE-READ lock.json immediately. If holder is not "${agentId}", someone else won. Go to step 1.
126
+ Run: agentxchain claim --agent ${agentId}
127
+ If claim is blocked, STOP.
132
128
 
133
129
  4. DO YOUR WORK:
134
130
  ${readSection}
@@ -142,20 +138,16 @@ YOUR LOOP (run forever, never exit, never say "I'm done"):
142
138
  If validation fails, fix docs/artifacts first. Do NOT release.
143
139
 
144
140
  5. RELEASE the lock:
145
- Write lock.json: {"holder":null,"last_released_by":"${agentId}","turn_number":<previous + 1>,"claimed_at":null}
141
+ Run: agentxchain release --agent ${agentId}
146
142
  THIS MUST BE THE LAST FILE YOU WRITE.
147
143
 
148
- 6. Run the shell command: sleep 60
149
- Then go back to step 1.
150
-
151
144
  ---
152
145
 
153
146
  CRITICAL RULES:
154
- - ACTUALLY RUN "sleep 60" in the terminal between checks. Do NOT skip this. Do NOT just say "waiting."
155
147
  - Never write files or code without holding the lock. Reading is always allowed.
156
148
  - One git commit per turn: "Turn N - ${agentId} - description"
157
149
  - Max ${maxClaims} consecutive turns. If you have held the lock ${maxClaims} times in a row, do a short turn and release.
158
150
  - ALWAYS release the lock. A stuck lock blocks the entire team.
159
151
  - ALWAYS find at least one problem, risk, or question about the previous work. Blind agreement is forbidden.
160
- - NEVER exit or stop. After releasing, always sleep and poll again. You are a persistent agent.`;
152
+ - This session is SINGLE-TURN. After release, STOP and wait for the referee to wake you again.`;
161
153
  }