expxagents 0.24.0 → 0.24.2

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.
@@ -19,7 +19,14 @@ For each pipeline step:
19
19
 
20
20
  #### Agent Steps (type: agent or default)
21
21
 
22
- 1. **Set agent status** → `working` in state.json
22
+ 1. **Update state.json BEFORE executing:**
23
+ - Set `status` → `running`
24
+ - Set `step.current` → current step number (1-based)
25
+ - Set `step.label` → step label from squad.yaml
26
+ - Set agent `status` → `working`
27
+ - **CRITICAL: Set agent `stepIndex`** → current step number (1-based). This triggers the dashboard kanban to move the previous task to "done" and the current task to "executing". Without this update, kanban tasks won't transition.
28
+ - Set agent `stepLabel` → step label
29
+ - Write state.json immediately (before doing any work)
23
30
  2. **Build context:**
24
31
  - Agent prompt from `agents/<agent-id>.md`
25
32
  - Previous agent output (if `deliverFrom` is set)
@@ -30,12 +37,41 @@ For each pipeline step:
30
37
  - `inline` execution: run in current context
31
38
  - `subagent` execution: spawn background process
32
39
  4. **Save output** to `output/step-XX.md`
33
- 5. **Update state.json:**
40
+ 5. **Update state.json AFTER executing:**
34
41
  - Agent status → `delivering` (if handoff) or `done`
35
42
  - Handoff message
36
- - Step progress
43
+ - Write state.json immediately
37
44
  6. **Handoff delay** (2 seconds for dashboard animation)
38
45
 
46
+ #### Step Failure Handling (on_fail)
47
+
48
+ Each step can define `on_fail` in squad.yaml:
49
+
50
+ - `abort` (default) — stop the pipeline immediately
51
+ - `skip` — log the error, mark agent as done with skip message, continue to next step
52
+ - `retry` — retry the step up to `max_retries` times (default: 2), then abort
53
+
54
+ Example in squad.yaml:
55
+ ```yaml
56
+ pipeline:
57
+ steps:
58
+ - id: step-01
59
+ agent: transcriber
60
+ label: Extract transcript
61
+ on_fail: skip # continue pipeline if transcript fails
62
+ - id: step-02
63
+ agent: writer
64
+ label: Write article
65
+ on_fail: retry
66
+ max_retries: 3 # try up to 4 times total
67
+ ```
68
+
69
+ When a step is skipped:
70
+ 1. Set agent status → `done`
71
+ 2. Set agent message → "Skipped: [error reason]"
72
+ 3. Write state.json
73
+ 4. Continue to next step (the next agent may need to handle missing input)
74
+
39
75
  #### Checkpoint Steps (type: checkpoint)
40
76
 
41
77
  1. **Set status** → `checkpoint` in state.json
@@ -50,28 +86,50 @@ For each pipeline step:
50
86
 
51
87
  ### State Management (state.json)
52
88
 
89
+ **IMPORTANT:** The dashboard file watcher monitors state.json for changes. When agent `stepIndex` changes, it automatically updates the kanban board (previous step → done, current step → executing). You MUST update `stepIndex` on each agent before starting their step.
90
+
53
91
  ```json
54
92
  {
55
93
  "squad": "<squad-code>",
56
94
  "status": "running",
57
- "step": { "current": 1, "total": 5, "label": "step-01-research" },
95
+ "step": { "current": 2, "total": 5, "label": "step-02-writing" },
58
96
  "agents": [
59
97
  {
60
98
  "id": "researcher",
61
99
  "name": "Angela Researcher",
62
100
  "icon": "magnifying-glass",
63
- "status": "working",
101
+ "status": "done",
102
+ "stepIndex": 1,
103
+ "stepLabel": "step-01-research",
64
104
  "desk": { "col": 1, "row": 1 },
105
+ "deliverTo": "writer",
106
+ "message": "Research complete"
107
+ },
108
+ {
109
+ "id": "writer",
110
+ "name": "Bruno Writer",
111
+ "icon": "pen",
112
+ "status": "working",
113
+ "stepIndex": 2,
114
+ "stepLabel": "step-02-writing",
115
+ "desk": { "col": 2, "row": 1 },
65
116
  "deliverTo": null,
66
117
  "message": ""
67
118
  }
68
119
  ],
69
120
  "handoff": null,
70
121
  "startedAt": "2026-03-13T00:00:00Z",
71
- "updatedAt": "2026-03-13T00:00:00Z"
122
+ "updatedAt": "2026-03-13T00:00:05Z"
72
123
  }
73
124
  ```
74
125
 
126
+ **Key fields for kanban integration:**
127
+ - `step.current` — global step number (1-based)
128
+ - `agent.stepIndex` — the step number this agent is currently on (1-based). Changes to this field trigger kanban task transitions
129
+ - `agent.stepLabel` — label of the current step
130
+ - `agent.status` — `working` | `delivering` | `done` | `idle` | `checkpoint`
131
+ ```
132
+
75
133
  ### Output Versioning
76
134
 
77
135
  - First run: `output/v1/`
@@ -178,6 +178,7 @@ export async function runCommand(name) {
178
178
  console.log(`Pipeline: ${config.squad.pipeline.steps.length} steps\n`);
179
179
  }
180
180
  const steps = config.squad.pipeline.steps;
181
+ const stepAttempts = new Map();
181
182
  for (let i = 0; i < steps.length; i++) {
182
183
  const step = steps[i];
183
184
  const stepNumber = i + 1;
@@ -210,7 +211,32 @@ export async function runCommand(name) {
210
211
  output = await runWithProvider(prompt, agent, squadDir, spawnClaudeCode);
211
212
  }
212
213
  catch (err) {
213
- console.error(`\nStep ${stepNumber} failed: ${err.message}`);
214
+ const failAction = step.onFail ?? 'abort';
215
+ console.error(`\nStep ${stepNumber} failed: ${err.message} [on_fail: ${failAction}]`);
216
+ if (failAction === 'retry') {
217
+ const maxRetries = step.maxRetries ?? 2;
218
+ const attempt = stepAttempts.get(step.id) ?? 0;
219
+ if (attempt < maxRetries) {
220
+ stepAttempts.set(step.id, attempt + 1);
221
+ console.log(`\nRetrying step ${stepNumber} (attempt ${attempt + 2}/${maxRetries + 1})...\n`);
222
+ state = updateAgentStatus(state, step.agent, 'idle');
223
+ writeState(squadDir, state);
224
+ i--;
225
+ continue;
226
+ }
227
+ console.error(`\nStep ${stepNumber} failed after ${maxRetries + 1} attempts. Aborting.`);
228
+ }
229
+ if (failAction === 'skip') {
230
+ console.log(`\nSkipping step ${stepNumber} (on_fail: skip). Continuing pipeline.\n`);
231
+ state = updateAgentStatus(state, step.agent, 'done');
232
+ state = {
233
+ ...state,
234
+ agents: state.agents.map((a) => a.id === step.agent ? { ...a, message: `Skipped: ${err.message}` } : a),
235
+ };
236
+ writeState(squadDir, state);
237
+ continue;
238
+ }
239
+ // Default: abort
214
240
  state = updateAgentStatus(state, step.agent, 'idle');
215
241
  state = setSquadStatus(state, 'idle');
216
242
  writeState(squadDir, state);
@@ -12,6 +12,8 @@ export interface PipelineStep {
12
12
  agent: string;
13
13
  label: string;
14
14
  deliverFrom?: string;
15
+ onFail?: 'skip' | 'retry' | 'abort';
16
+ maxRetries?: number;
15
17
  }
16
18
  export interface ScheduleConfig {
17
19
  enabled: boolean;
@@ -122,6 +122,14 @@ export function loadSquad(squadDir) {
122
122
  };
123
123
  }
124
124
  const mcps = Array.isArray(squad.mcps) ? squad.mcps : [];
125
+ const mappedSteps = steps.map((s) => ({
126
+ id: s.id,
127
+ agent: s.agent,
128
+ label: s.label,
129
+ deliverFrom: s.deliverFrom ?? s.deliver_from,
130
+ onFail: ['skip', 'retry', 'abort'].includes(s.on_fail) ? s.on_fail : undefined,
131
+ maxRetries: typeof s.max_retries === 'number' ? s.max_retries : undefined,
132
+ }));
125
133
  return {
126
134
  squad: {
127
135
  code: squad.code,
@@ -134,7 +142,7 @@ export function loadSquad(squadDir) {
134
142
  icon: squad.icon,
135
143
  agents,
136
144
  skills,
137
- pipeline: { steps },
145
+ pipeline: { steps: mappedSteps },
138
146
  schedule,
139
147
  chain,
140
148
  webhooks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expxagents",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Multi-agent orchestration platform for AI squads",
5
5
  "author": "ExpxAgents",
6
6
  "license": "MIT",