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 +28 -3
- package/bin/agentxchain.js +2 -0
- package/package.json +1 -1
- package/src/adapters/cursor-local.js +10 -7
- package/src/commands/claim.js +69 -0
- package/src/commands/watch.js +51 -0
- package/src/lib/generate-vscode.js +3 -2
- package/src/lib/seed-prompt-polling.js +9 -17
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
|
|
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
|
|
148
|
+
4. Agent prompts are single-turn: claim → work → validate → release → stop
|
|
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
|
-
- **
|
|
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
|
package/bin/agentxchain.js
CHANGED
|
@@ -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
|
@@ -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
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
+
5) Update .planning/PM_SIGNOFF.md:
|
|
182
185
|
- Set "Approved: YES" only when human agrees kickoff is complete.
|
|
183
|
-
|
|
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
|
-
|
|
192
|
+
7) Do NOT start round-robin agent handoffs yet.
|
|
190
193
|
|
|
191
194
|
Context:
|
|
192
195
|
- Project: ${config.project}
|
package/src/commands/claim.js
CHANGED
|
@@ -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;
|
package/src/commands/watch.js
CHANGED
|
@@ -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**:
|
|
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**:
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
152
|
+
- This session is SINGLE-TURN. After release, STOP and wait for the referee to wake you again.`;
|
|
161
153
|
}
|