@nanobpm/nano-workforce 0.170.0 → 0.170.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.170.1](https://github.com/nanobpm/nano-workforce/compare/v0.170.0...v0.170.1) (2026-08-31)
2
+
3
+ ### Documentation
4
+
5
+ * **agent-guide:** make the operator guide tool-aware + tool↔curl crosswalk (N1) ([#669](https://github.com/nanobpm/nano-workforce/issues/669)) ([44c5937](https://github.com/nanobpm/nano-workforce/commit/44c5937fb3a1cdaa5a106d67ed16225c35d614f6)), closes [#664](https://github.com/nanobpm/nano-workforce/issues/664) [#666](https://github.com/nanobpm/nano-workforce/issues/666) [#667](https://github.com/nanobpm/nano-workforce/issues/667) [#665](https://github.com/nanobpm/nano-workforce/issues/665) [666/#667](https://github.com/666/nano-workforce/issues/667) [#666](https://github.com/nanobpm/nano-workforce/issues/666) [#667](https://github.com/nanobpm/nano-workforce/issues/667) [#665](https://github.com/nanobpm/nano-workforce/issues/665)
6
+
1
7
  ## [0.170.0](https://github.com/nanobpm/nano-workforce/compare/v0.169.0...v0.170.0) (2026-08-31)
2
8
 
3
9
  ### Features
package/app/agentGuide.ts CHANGED
@@ -106,6 +106,7 @@ export const GUIDE_SECTIONS: readonly GuideSectionMeta[] = [
106
106
  { id: "unstick", summary: "Unstick a wedged process — publish a correlating message, cancel, or otherwise recover a stalled instance." },
107
107
  { id: "raise-issue", summary: "Raise an issue or open a PR against the nano-workforce repository itself." },
108
108
  { id: "delivery-graphs", summary: "Author, preview, compile/stage and run an agent-authored delivery graph (ADR 0005): node/wait/connector vocabulary." },
109
+ { id: "tool-crosswalk", summary: "Tool↔curl crosswalk: map every guide action to its projected MCP tool (status, version, urban_debug_* engine reads, escalation answer, cancel) with the curl no-MCP fallback." },
109
110
  ] as const;
110
111
 
111
112
  /** A parsed section: its stable id + summary (from the registry), the derived heading `title` (the
@@ -25,18 +25,28 @@ in full so you can tell which are under the control-API base and which are not.
25
25
 
26
26
  ## 0. Orient yourself first
27
27
 
28
- Before acting, confirm what is running and what is in flight:
28
+ Before acting, confirm what is running and what is in flight. **Prefer the projected
29
+ tools** (`getVersion`, `listActivePrs`); curl is the no-MCP fallback only. Every step in
30
+ this guide that has a tool is mapped in the **tool↔curl crosswalk (§10)** — reach for the
31
+ tool first and drop to curl only when you are not driving this instance over MCP.
29
32
 
30
- ```bash
33
+ ```text
31
34
  # Which code is live (app version, urban version, git sha/branch, uptime):
32
- curl -sS __BASE__/version | jq
35
+ tool: getVersion # GET /version
33
36
 
34
37
  # Every PR currently in flight (not converged/abandoned), with its engine
35
38
  # process key, status, round, and any open escalation:
39
+ tool: listActivePrs # GET /status
40
+ ```
41
+
42
+ ```bash
43
+ # No-MCP fallback (agent without the projected tools):
44
+ curl -sS __BASE__/version | jq
36
45
  curl -sS __BASE__/status | jq
37
46
  ```
38
47
 
39
- `/status` is your primary situational-awareness endpoint. Each entry carries:
48
+ `listActivePrs` (the `/status` endpoint) is your primary situational-awareness surface.
49
+ Each entry carries:
40
50
  `prKey` (`owner/repo#123`), `status`, `round`, `processKey` (the **engine process
41
51
  instance key** — the bridge to the engine REST API, §5), `openEscalation`,
42
52
  `activeWorker`/`leaseUntil` (is an agent actually working the round, or is the job
@@ -152,24 +162,36 @@ A loop escalates only when an agent returns `needs_input`/`blocked`, or a safety
152
162
  fires (round cap, a review that never arrives, a merge conflict, an unfixable CI
153
163
  failure). The parked process waits for a human answer.
154
164
 
155
- Find the open escalations, then answer them:
165
+ Find the open escalations, then answer them. **Prefer the `listEscalations` tool**
166
+ (the projected read tool that lists every open escalation with the `userTaskKey` and form —
167
+ tracked by sibling #666; on a deployment that predates it, use the `/status` / task-inbox
168
+ curl fallback below —
169
+ you need to answer it); curl is the no-MCP fallback.
170
+
171
+ ```text
172
+ # Every open escalation waiting for a human, across all four kinds — each with its
173
+ # userTaskKey, prKey, kind (elementId), question/findings/task, and formVariables:
174
+ tool: listEscalations
175
+ ```
156
176
 
157
177
  ```bash
158
- # Which in-flight PRs have an open escalation waiting for a human?
178
+ # No-MCP fallback: read the open escalations off /status
159
179
  curl -sS __BASE__/status | jq '.prs[] | select(.openEscalation != null)
160
180
  | { prKey, status, round, openEscalation }'
161
181
  ```
162
182
 
163
183
  The four decision-required escalation kinds — **PR review-loop**, **implementation
164
184
  (feature) task**, **plan-review**, and **trial-merge** — are now native BPMN
165
- `userTask`s bearing a linked `.form`, all answered the same way through the **task
166
- inbox** surface. There is no bespoke per-kind webhook or answer page any more.
185
+ `userTask`s bearing a linked `.form`, all answered the same way through the
186
+ `completeUserTask` tool (a human uses the same **task inbox** surface underneath).
187
+ There is no bespoke per-kind webhook or answer page any more.
167
188
 
168
- **List the open escalation tasks.** Each task carries its context (e.g. `prKey` /
169
- `question` / `findings` / `task`) in its `variables`, and its kind in `elementId`:
189
+ **List the open escalation tasks.** `listEscalations` returns each open task with its
190
+ context (`prKey` / `question` / `findings` / `task`) and its kind in `elementId` /
191
+ `kind`, so you do **not** need to reach for the raw Tasks inbox. No-MCP fallback only:
170
192
 
171
193
  ```bash
172
- # Every parked escalation, across all kinds:
194
+ # No-MCP fallback the un-projected engine task inbox (prefer `listEscalations`):
173
195
  curl -sS __BASE__/../../tasks/api/tasks | jq '.[] | { userTaskKey, elementId, variables }'
174
196
 
175
197
  # Filter to one kind (e.g. plan-review decisions) by elementId:
@@ -187,34 +209,52 @@ answer (assignee/candidate-group and age surface on each task once assignment la
187
209
  > node then runs against the unmet dependency. See §9.2 before clearing one.
188
210
 
189
211
  **Answer a task** by completing it with the typed variables its form expects — the
190
- completion resumes the parked process:
212
+ completion resumes the parked process. **Prefer the `completeUserTask` tool** (or
213
+ `agentCompleteEscalation` when you answer as an agent assignee, ADR 0046); the
214
+ `curl … /actions/complete-user-task` app-owned door is the no-MCP fallback.
191
215
 
192
- ```bash
216
+ ```text
193
217
  # PR review-loop (elementId `wait-answer`, pr-escalation form):
194
- curl -sS -X POST __BASE__/../../tasks/api/complete -H 'content-type: application/json' \
195
- -d '{ "userTaskKey": "<key>", "variables": { "answer": "Cap retries at 5 and proceed." } }'
218
+ tool: completeUserTask { "userTaskKey": "<key>",
219
+ "variables": { "answer": "Cap retries at 5 and proceed." } }
196
220
 
197
221
  # Implementation (feature) task (elementId `feature-escalation`):
198
222
  # { "resolution": "answer", "answer": "…" } to resume, or { "resolution": "abandon" }
199
- curl -sS -X POST __BASE__/../../tasks/api/complete -H 'content-type: application/json' \
200
- -d '{ "userTaskKey": "<key>", "variables": { "resolution": "answer", "answer": "Use v2." } }'
223
+ tool: completeUserTask { "userTaskKey": "<key>",
224
+ "variables": { "resolution": "answer", "answer": "Use v2." } }
201
225
 
202
226
  # Plan-review (elementId `plan-review-decision`):
203
227
  # { "directive": "revise", "notes": "…" } (fresh review budget) or { "directive": "proceed" }
204
- curl -sS -X POST __BASE__/../../tasks/api/complete -H 'content-type: application/json' \
205
- -d '{ "userTaskKey": "<key>", "variables": { "directive": "revise", "notes": "Make issue-7 the seam." } }'
228
+ tool: completeUserTask { "userTaskKey": "<key>",
229
+ "variables": { "directive": "revise", "notes": "Make issue-7 the seam." } }
206
230
 
207
231
  # Trial-merge (elementId `trial-merge-decision`):
208
232
  # { "action": "proceed" | "rebase" | "abandon", "notes"?: "…" }
209
- curl -sS -X POST __BASE__/../../tasks/api/complete -H 'content-type: application/json' \
210
- -d '{ "userTaskKey": "<key>", "variables": { "action": "rebase", "notes": "Re-run after the fix." } }'
233
+ tool: completeUserTask { "userTaskKey": "<key>",
234
+ "variables": { "action": "rebase", "notes": "Re-run after the fix." } }
235
+ ```
236
+
237
+ ```bash
238
+ # No-MCP fallback — the app-owned complete-user-task door (prefer the tool).
239
+ # `variables` must carry the shape THIS task's escalation kind expects (see the per-kind
240
+ # tool forms above): `{ answer }` here is the PR review-loop form — a feature task wants
241
+ # `{ resolution, answer }`, plan-review `{ directive, notes }`, trial-merge `{ action, notes? }`.
242
+ curl -sS -X POST __BASE__/actions/complete-user-task -H 'content-type: application/json' \
243
+ -d '{ "userTaskKey": "<key>", "variables": { "answer": "Cap retries at 5 and proceed." } }'
211
244
  ```
212
245
 
213
246
  **Answer a PR escalation** (both the review-loop `wait-answer` and the merge-loop
214
247
  `wait-merge-answer` — both are now native user tasks answered the same way, #256).
215
- Use the PR key's parked user task and submit the `pr-escalation` form's `{ answer }`:
248
+ Use the PR key's parked user task and submit the `pr-escalation` form's `{ answer }`
249
+ through the `completeUserTask` tool; curl is the no-MCP fallback:
250
+
251
+ ```text
252
+ tool: completeUserTask { "userTaskKey": "<key>",
253
+ "variables": { "answer": "Yes — cap the retries at 5 and proceed." } }
254
+ ```
216
255
 
217
256
  ```bash
257
+ # No-MCP fallback:
218
258
  curl -sS -X POST __BASE__/actions/complete-user-task \
219
259
  -H 'content-type: application/json' \
220
260
  -d '{
@@ -223,9 +263,11 @@ curl -sS -X POST __BASE__/actions/complete-user-task \
223
263
  }'
224
264
  ```
225
265
 
226
- The `userTaskKey` comes from `GET /status` or the Tasks inbox. This is the ONE
227
- canonical answer door for every escalation kind; the merge loop no longer uses a
228
- durable `escalation-answered` message catch.
266
+ The `userTaskKey` comes from `listEscalations` (or `GET /status`/the Tasks inbox). This
267
+ is the ONE canonical answer door (`completeUserTask`) for every escalation kind with
268
+ `agentCompleteEscalation` the agent-assignee variant that records identity for
269
+ attribution (ADR 0046); the merge loop no longer uses a durable `escalation-answered`
270
+ message catch.
229
271
 
230
272
  If `NANO_PR_WEBHOOK_SECRET` is set on the deployment, add `-H "x-hook-secret: <secret>"`.
231
273
 
@@ -266,10 +308,35 @@ The app stores each PR/plan's engine **process instance key** in its `process_ke
266
308
  column and surfaces it as `processKey` in `/status`. That key is the join between the
267
309
  app's business view and the engine's execution view.
268
310
 
269
- **Find the instance for a PR:** take `processKey` from `/status`, then query the
270
- engine's Camunda-8 v2 REST API:
311
+ **Find the instance for a PR:** take `processKey` from `listActivePrs` (`/status`), then
312
+ query engine truth. **Prefer the framework `urban_debug_*` engine-debug tools** (owned by
313
+ the nano-ide urban runtime — see the crosswalk in §10); the `curl __ENGINE__/…` engine
314
+ REST calls are the no-MCP fallback:
315
+
316
+ ```text
317
+ PK = <processKey from listActivePrs>
318
+
319
+ # The instance itself (state, the BPMN process it is running, start time):
320
+ tool: urban_debug_search_process_instances { "filter": { "processInstanceKey": PK } }
321
+
322
+ # Where is it parked? — active jobs on the instance (a CREATED senior:pr-review job
323
+ # with a `worker` set means an agent has leased the round; none means it is queued).
324
+ # `urban_debug_search_jobs`/`_search_variables` are projected only where the framework
325
+ # offers them (sibling nano-ide epic); else drop to the curl door (see §10 crosswalk):
326
+ tool: urban_debug_search_jobs { "filter": { "processInstanceKey": PK, "state": "CREATED" } } # where projected
327
+
328
+ # Its variables (the aggregate/job payload the engine holds):
329
+ tool: urban_debug_search_variables { "filter": { "processInstanceKey": PK } } # where projected
330
+
331
+ # Is it dead-in-the-water on a technical fault? — active incidents:
332
+ tool: urban_debug_search_incidents { "filter": { "processInstanceKey": PK, "state": "ACTIVE" } }
333
+
334
+ # Which BPMN element/wait-state is it sitting on?
335
+ tool: urban_debug_search_element_instance_wait_states { "filter": { "processInstanceKey": PK } }
336
+ ```
271
337
 
272
338
  ```bash
339
+ # No-MCP fallback — the raw engine Camunda-8 v2 REST API:
273
340
  PK=<processKey-from-status>
274
341
 
275
342
  # The instance itself (state, the BPMN process it is running, start time):
@@ -277,8 +344,7 @@ curl -sS -X POST __ENGINE__/process-instances/search \
277
344
  -H 'content-type: application/json' \
278
345
  -d "{ \"filter\": { \"processInstanceKey\": \"$PK\" } }" | jq
279
346
 
280
- # Where is it parked? — active jobs on the instance (a CREATED senior:pr-review job
281
- # with a `worker` set means an agent has leased the round; none means it is queued):
347
+ # Where is it parked? — active jobs on the instance:
282
348
  curl -sS -X POST __ENGINE__/jobs/search \
283
349
  -H 'content-type: application/json' \
284
350
  -d "{ \"filter\": { \"processInstanceKey\": \"$PK\", \"state\": \"CREATED\" } }" | jq
@@ -294,15 +360,23 @@ curl -sS -X POST __ENGINE__/element-instances/search \
294
360
  -d "{ \"filter\": { \"processInstanceKey\": \"$PK\" } }" | jq
295
361
  ```
296
362
 
363
+ > The `urban_debug_search_jobs`, `urban_debug_search_variables` and
364
+ > `urban_debug_get_process_definition_xml` reads are owned by the sibling nano-ide urban
365
+ > epic; where a deployment's framework has not yet projected them, use the curl fallback
366
+ > for that row. The three always-present engine reads are
367
+ > `urban_debug_search_process_instances`, `urban_debug_search_element_instance_wait_states`
368
+ > and `urban_debug_search_incidents`.
369
+
297
370
  The app already mirrors an ACTIVE incident onto the PR row (`incident`/incident
298
371
  message), so a PR that shows an incident in the UI is parked on an engine fault —
299
- inspect it with `incidents/search` above. If the engine is not at the default, the
300
- deployment's engine base is `__ENGINE__` (set via `NANOBPMN_BASE_URL` or
301
- `CAMUNDA_REST_ADDRESS`).
372
+ inspect it with `urban_debug_search_incidents` (or `incidents/search`) above. If the
373
+ engine is not at the default, the deployment's engine base is `__ENGINE__` (set via
374
+ `NANOBPMN_BASE_URL` or `CAMUNDA_REST_ADDRESS`).
302
375
 
303
376
  **Relate an instance back to a PR:** if you have a `processKey` but not the PR, match
304
- it against `/status` (`.prs[] | select(.processKey == "<PK>")`). A terminal PR is no
305
- longer in `/status`; its instance has already completed or been cancelled.
377
+ it against `listActivePrs`/`/status` (`.prs[] | select(.processKey == "<PK>")`). A
378
+ terminal PR is no longer in `/status`; its instance has already completed or been
379
+ cancelled.
306
380
 
307
381
  ---
308
382
 
@@ -311,9 +385,15 @@ longer in `/status`; its instance has already completed or been cancelled.
311
385
  The behaviour is defined by durable BPMN processes and model-authored agent prompts —
312
386
  both live in the source repo, not in the job payload.
313
387
 
314
- - **Processes:** `resources/processes/*.bpmn` `convergence-loop.bpmn` (review),
315
- `merge-loop.bpmn` (merge/CI-fix/rebase), `plan-fanout.bpmn` (planning),
316
- `retro.bpmn`. These are the source of truth for routing. To understand *why* an
388
+ - **Processes:** to inspect the model an instance is **actually running**, prefer the
389
+ framework tool **`urban_debug_get_process_definition_xml`** (owned by the sibling
390
+ nano-ide urban epic see §10), which returns the deployed BPMN XML for a process
391
+ definition; that is engine truth and cannot drift from what is live. The repo copies
392
+ `resources/processes/*.bpmn` — `convergence-loop.bpmn` (review), `merge-loop.bpmn`
393
+ (merge/CI-fix/rebase), `plan-fanout.bpmn` (planning), `retro.bpmn` — are the
394
+ **source of truth in git** for routing, read them from the checkout when you want the
395
+ authored form (or have no MCP). They can lag a running deployment, so confirm against
396
+ the deployed XML when *why did this instance route here* matters. To understand *why* an
317
397
  instance went where it did, read the gateway conditions (FEEL expressions on the
318
398
  sequence flows) for the element it is parked on (§5).
319
399
  - **Prompts (agent base instructions):** `resources/prompts/*.md` — `review-round.md`,
@@ -369,11 +449,24 @@ Work through this order:
369
449
  `NANO_PR_REVIEW_WAIT_TIMEOUT` (default `PT20M`); the poller also re-nudges the
370
450
  reviewer periodically. If the reviewer bot is not provisioned on the repo, no
371
451
  review will ever land — that is a repo-config problem, not an app bug.
372
- 5. **Cancel + resubmit** as a last resort. Cancel the instance via the app (the UI's
373
- per-row Cancel, `POST /app/actions/cancel { "processInstanceKey": "<PK>" }`), which
374
- marks the PR `abandoned`, then re-submit the PR (§1) to start a fresh loop. Do not
375
- cancel a raw engine instance out from under the app — go through the app so its
376
- record state stays consistent.
452
+ 5. **Cancel + resubmit** as a last resort. Cancel through the **app-owned** door
453
+ **prefer the `cancelInstance` tool** (projected by sibling #667; the same
454
+ record-consistent path as the UI's per-row Cancel, `POST /app/actions/cancel
455
+ { "processInstanceKey": "<PK>" }`), which cancels
456
+ the engine instance **and** marks the PR `abandoned` so it drops out of
457
+ `listActivePrs`. Then re-submit the PR (§1) to start a fresh loop. **Do not** cancel a
458
+ raw engine instance out from under the app with the engine-level
459
+ `urban_debug_cancel_instance` tool (or a direct engine REST cancel) — that cancels the
460
+ token but leaves the PR row inconsistent. Always go through `cancelInstance` /
461
+ `POST /app/actions/cancel` so the app's record state stays consistent. Note the door
462
+ sits at `/app/actions/cancel`, **outside** the `__BASE__` control-API base (ADR 0059) —
463
+ until #667 projects `cancelInstance`, that curl door is the only cancel path. No-MCP
464
+ fallback:
465
+
466
+ ```bash
467
+ curl -sS -X POST __BASE__/../actions/cancel -H 'content-type: application/json' \
468
+ -d '{ "processInstanceKey": "<PK>" }'
469
+ ```
377
470
 
378
471
  ---
379
472
 
@@ -767,3 +860,66 @@ Semantics:
767
860
  `stage:"merged"` once its PR merges. So `wait[epic]` targeting a feature/epic **root issue**
768
861
  observes that thread's aggregate frontier and releases on `stage:"merged" && active:false` either
769
862
  way — see `getDeliveryGraphVocabulary` (the `epic` probe entry) for the structured contract.
863
+
864
+ ---
865
+
866
+ ## 10. Tool ↔ curl crosswalk (MCP-first, curl is the no-MCP fallback)
867
+
868
+ When you drive this instance over MCP, **call the projected tool** — every recipe above
869
+ leads with it. `curl __BASE__/…`/`curl __ENGINE__/…` is only the **no-MCP fallback** for an
870
+ agent that has no tools (see the runbook, `docs/mcp-runbook.md` §2/§3, and [§5
871
+ Fallback](mcp-runbook.md#5-fallback)). This table maps every guide action to its projected
872
+ tool name and its no-MCP fallback underneath (usually a curl door; a few rows fall back to reading a repo file instead).
873
+
874
+ App-owned operations (projected from this app's `openapi.yaml`; the `__BASE__` control API).
875
+ Rows tagged *(projected by sibling #NNN)* name a tool that lands with that sibling task —
876
+ until it does, only the curl door in the third column exists on the deployment:
877
+
878
+ | Guide action | Projected tool | No-MCP fallback |
879
+ |---|---|---|
880
+ | Which code is live (§0) | `getVersion` | `curl __BASE__/version` |
881
+ | Every PR in flight (§0/§5) | `listActivePrs` | `curl __BASE__/status` |
882
+ | List open escalations (§3) | `listEscalations` *(projected by sibling #666; curl door until then)* | `curl __BASE__/../../tasks/api/tasks` (the answerable list — carries each `userTaskKey`); `/status` filtered with the jq recipe just below this table is only a quick *indicator* (it surfaces `openEscalation` as a question string, **no `userTaskKey`**, so you can't answer from it) |
883
+ | Answer an escalation (§3) | `completeUserTask` (agent-assignee: `agentCompleteEscalation`) | `curl -X POST __BASE__/actions/complete-user-task` |
884
+ | Cancel an instance (record-consistent) (§7) | `cancelInstance` *(projected by sibling #667; curl door until then)* | `curl -X POST __BASE__/../actions/cancel` *(the `/app/actions/cancel` door, outside `__BASE__`)* |
885
+ | Publish a BPMN message (§7) | `postMessage` | `curl -X POST __BASE__/actions/message` |
886
+ | The operator guide itself | `getAgentInstructions` (full) / `getAgentGuide(section?)` (addressable) | `curl __BASE__/agent` / `curl __BASE__/agent/guide` |
887
+
888
+ The `listEscalations` no-MCP fallback that filters `/status` with `jq` lives here, not inside the
889
+ table cell above: a literal `|` can't be written in a GFM table cell without escaping it as `\|`,
890
+ and this guide is served **raw** (byte-for-byte, `__BASE__`/`__ENGINE__` aside) to no-MCP agents, so
891
+ the escaped form would be copied verbatim and the shell would treat `\|` as a literal argument
892
+ rather than a pipe. It's only a **quick indicator** — `/status` surfaces `openEscalation` as a
893
+ question string with **no `userTaskKey`**, so to actually *answer* one you still need the tasks
894
+ inbox (`curl __BASE__/../../tasks/api/tasks`) or the `listEscalations` tool for its `userTaskKey`
895
+ (§3). It selects the same `{ prKey, status, round, openEscalation }` fields as the §3 fallback for
896
+ consistency. Copy it from here:
897
+
898
+ ```sh
899
+ curl __BASE__/status | jq '.prs[] | select(.openEscalation != null)
900
+ | { prKey, status, round, openEscalation }'
901
+ ```
902
+
903
+
904
+ Framework engine-debug tools (owned by the nano-ide urban runtime, not this app; they front
905
+ the engine's Camunda-8 v2 REST API at `__ENGINE__`). The first three are always projected;
906
+ `urban_debug_search_jobs` / `urban_debug_search_variables` /
907
+ `urban_debug_get_process_definition_xml` are owned by the sibling nano-ide urban epic — use
908
+ them by name where the framework projects them, else drop to the curl door:
909
+
910
+ | Guide action | Projected tool | No-MCP fallback |
911
+ |---|---|---|
912
+ | Search process instances (§5) | `urban_debug_search_process_instances` | `curl -X POST __ENGINE__/process-instances/search` |
913
+ | Inspect element/wait states (§5) | `urban_debug_search_element_instance_wait_states` | `curl -X POST __ENGINE__/element-instances/search` |
914
+ | Active incidents (§5) | `urban_debug_search_incidents` | `curl -X POST __ENGINE__/incidents/search` |
915
+ | Jobs on an instance (§5) | `urban_debug_search_jobs` | `curl -X POST __ENGINE__/jobs/search` |
916
+ | Instance variables (§5) | `urban_debug_search_variables` | `curl -X POST __ENGINE__/variables/search` |
917
+ | Deployed BPMN model XML (§6) | `urban_debug_get_process_definition_xml` | read the source copy `resources/processes/*.bpmn` from the checkout |
918
+ | Engine-level cancel — **avoid** (§7) | `urban_debug_cancel_instance` | *(use the app-owned `cancelInstance` instead — engine cancel leaves the PR row inconsistent)* |
919
+
920
+ **Rule of thumb (agent-guide §5 fallback discipline):** if a row has a projected tool, use
921
+ it; reach for its curl door only when you are genuinely not on MCP (or the tool's sibling
922
+ task has not yet landed — see the *(projected by sibling #NNN)* tags above). The app-owned
923
+ cancel door (`cancelInstance`, #667) is always preferred over the engine-level
924
+ `urban_debug_cancel_instance`,
925
+ because only the app door transitions the PR/plan record to `abandoned`.
@@ -91,7 +91,14 @@ family). Ask:
91
91
 
92
92
  > *"Using workforce-local, show what's in flight and any open escalations."*
93
93
 
94
- The agent should call the status operation tool, not curl.
94
+ The agent should call the status operation tool (`listActivePrs`), not curl. The operator
95
+ guide's **tool↔curl crosswalk** (agent-guide §10, via `getAgentGuide(section="tool-crosswalk")`)
96
+ maps every guide action to its projected tool name and the curl door underneath — so a
97
+ tool-aware agent leads with the tool (`listActivePrs`, `getVersion`, `completeUserTask`,
98
+ the `urban_debug_*` engine reads — plus `listEscalations` and `cancelInstance` *where the
99
+ deployment projects them*; those two are sibling tasks #666/#667 and are not on a
100
+ deployment that predates them, so fall back to the curl door there) and treats curl as
101
+ the no-MCP fallback only.
95
102
 
96
103
  ## 3. Debug a wedged instance
97
104
 
@@ -99,10 +106,19 @@ The agent should call the status operation tool, not curl.
99
106
  > instance, compare engine truth against the app's projections, and tell me where
100
107
  > it's stuck."*
101
108
 
102
- The agent has: instance search, wait states, variables, incidents (engine truth) +
109
+ The agent has: instance search, wait states, incidents (engine truth) — plus
110
+ `variables` where the framework projects the `urban_debug_search_variables` read
111
+ (otherwise via the engine curl fallback) — plus
103
112
  the `urban_*` projection reads (app belief) + the operator guide (the
104
113
  `getAgentInstructions` tool) for the convergence-loop-specific meaning of each wedge
105
- shape. A wedge is frequently exactly a disagreement between the two planes.
114
+ shape. A wedge is frequently exactly a disagreement between the two planes. The guide's
115
+ **tool↔curl crosswalk** (agent-guide §10) names each engine-truth tool
116
+ (`urban_debug_search_process_instances` / `urban_debug_search_element_instance_wait_states`
117
+ / `urban_debug_search_incidents`, and where projected `urban_debug_search_jobs` /
118
+ `urban_debug_search_variables` / `urban_debug_get_process_definition_xml`) so the agent
119
+ reaches for the tool, not the raw `__ENGINE__` curl — and steers cancels to the app-owned
120
+ `cancelInstance` (projected by sibling #667; until it lands, the `/app/actions/cancel`
121
+ door the UI's Cancel uses), never the record-desyncing engine-level `urban_debug_cancel_instance`.
106
122
 
107
123
  ## 4. Guard posture
108
124
 
@@ -166,7 +182,8 @@ limit. Over MCP, prefer the **addressable** companion tool `getAgentGuide(sectio
166
182
 
167
183
  - **No argument** → a compact **table of contents**: every stable section id
168
184
  (`orient`, `submit-pr`, `submit-epic`, `escalations`, `lifecycle`, `debug`,
169
- `debug-models`, `unstick`, `raise-issue`, `delivery-graphs`) with a one-line summary.
185
+ `debug-models`, `unstick`, `raise-issue`, `delivery-graphs`, `tool-crosswalk`) with a
186
+ one-line summary.
170
187
  - **`section=<id>`** → **only** that section's markdown, small enough to fit a typical
171
188
  limit. An unknown id is rejected with `issues[{path,message}]` listing the valid ids.
172
189
 
package/openapi.yaml CHANGED
@@ -3628,7 +3628,7 @@ paths:
3628
3628
  example: delivery-graphs
3629
3629
  description: OPTIONAL stable section id (from the table of contents `getAgentGuide` returns
3630
3630
  with no argument), e.g. `orient`, `submit-pr`, `submit-epic`, `escalations`, `lifecycle`,
3631
- `debug`, `debug-models`, `unstick`, `raise-issue`, `delivery-graphs`. Omit it to get the
3631
+ `debug`, `debug-models`, `unstick`, `raise-issue`, `delivery-graphs`, `tool-crosswalk`. Omit it to get the
3632
3632
  table of contents. An unknown id yields a 400 listing the valid ids.
3633
3633
  responses:
3634
3634
  "200":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.170.0",
3
+ "version": "0.170.1",
4
4
  "description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
5
5
  "type": "module",
6
6
  "main": "main.ts",
@@ -82,9 +82,9 @@
82
82
  "variant": "ghost",
83
83
  "modal": {
84
84
  "title": "Point your agent at Nano Workforce",
85
- "description": "Copy this prompt and paste it into your coding agent (Copilot, Claude, etc.). It connects the agent to this instance's MCP server \u2014 the workforce's operations become native tools (including the live operator guide as `getAgentInstructions`), so it can drive and debug your workforce; agents with no MCP client fall back to fetching the operator skill. Note: `/app/mcp` is served on the same HTTP surface as the rest of the app \u2014 reachable on loopback by default, and from a remote instance (merlin, an ngrok tunnel) only when the app is bound wide (`network.bind`) or fronted by a reverse proxy, per the MCP runbook.",
85
+ "description": "Copy this prompt and paste it into your coding agent (Copilot, Claude, etc.). It connects the agent to this instance's MCP server \u2014 the workforce's operations become native tools (including the live operator guide as `getAgentInstructions`, or its addressable companion `getAgentGuide(section?)` \u2014 preferred over the ~43KB blob to avoid tool-result overrun), so it can drive and debug your workforce; agents with no MCP client fall back to fetching the operator skill. Note: `/app/mcp` is served on the same HTTP surface as the rest of the app \u2014 reachable on loopback by default, and from a remote instance (merlin, an ngrok tunnel) only when the app is bound wide (`network.bind`) or fronted by a reverse proxy, per the MCP runbook.",
86
86
  "copyLabel": "Copy prompt",
87
- "copyText": "Add this running Nano Workforce instance as an MCP server, then help me drive and debug my workforce \u2014 a durable orchestration app that drives pull requests to review convergence against an automated reviewer, merges them, and can take a whole issue and plan \u2192 implement \u2192 converge it across a fleet of coding agents.\n\nRegister ONE MCP server entry per instance (streamable-HTTP transport, URL {{appBase}}app/mcp). Naming the instance makes targeting the wrong one structurally impossible \u2014 its tools are namespaced under that name:\n\n copilot mcp add --transport http workforce-local {{appBase}}app/mcp\n\nGive each instance its own entry (e.g. `workforce-merlin` for a LAN/remote node). In config form (~/.copilot/mcp-config.json or repo-scoped .mcp.json):\n\n {\n \"mcpServers\": {\n \"workforce-local\": {\n \"type\": \"http\",\n \"url\": \"{{appBase}}app/mcp\",\n \"tools\": [\"*\"]\n }\n }\n }\n\nIf this instance is secured with a shared secret (NANO_PR_WEBHOOK_SECRET), pass it on the MCP connection \u2014 reads AND mutations both require it:\n\n copilot mcp add --transport http workforce-local {{appBase}}app/mcp \\\n --header \"x-hook-secret: $NANO_PR_WEBHOOK_SECRET\"\n\nIn config form, add a `headers` entry alongside `url` on that server (per the MCP runbook) \u2014 the config path has no header flag, so omitting this yields 401s:\n\n \"headers\": { \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\" }\n\nA Basic-Auth-fronted instance (behind a reverse proxy) additionally needs `Authorization: Basic \u2026` on the connection.\n\nMCP servers register at host startup \u2014 add the entry, THEN start a new session so its tools load. The `workforce-*` tools then appear (the full set of operations projected from this instance's OpenAPI contract, including the live operator guide itself as the `getAgentInstructions` read tool, plus the `urban_debug_*` family for inspecting a wedged instance's process instances, wait states, variables, and incidents).\n\nThe instance's operations are now native tools. Ask, naming the instance: \"Using workforce-local, show what's in flight and any open escalations.\" It should call the status tool, not curl. Operator-only doors (the delivery-graph stage/dispatch/dismiss lifecycle \u2014 the human click IS the approval) are deliberately not tools.\n\nNo MCP client? The curl path is unchanged \u2014 fetch and follow the live guide (the response is JSON with a `skill` markdown field), adding `-H \"x-hook-secret: <secret>\"` if this instance is secured:\n\n curl -sS {{appBase}}app/api/agent/skill\n\nThat skill bootstraps you to this instance's live operator guide at {{appBase}}app/api/agent (the same guide MCP exposes as `getAgentInstructions`). If you find a bug or a stuck process, the guide explains how to raise an issue or a PR against nanobpm/nano-workforce."
87
+ "copyText": "Add this running Nano Workforce instance as an MCP server, then help me drive and debug my workforce \u2014 a durable orchestration app that drives pull requests to review convergence against an automated reviewer, merges them, and can take a whole issue and plan \u2192 implement \u2192 converge it across a fleet of coding agents.\n\nRegister ONE MCP server entry per instance (streamable-HTTP transport, URL {{appBase}}app/mcp). Naming the instance makes targeting the wrong one structurally impossible \u2014 its tools are namespaced under that name:\n\n copilot mcp add --transport http workforce-local {{appBase}}app/mcp\n\nGive each instance its own entry (e.g. `workforce-merlin` for a LAN/remote node). In config form (~/.copilot/mcp-config.json or repo-scoped .mcp.json):\n\n {\n \"mcpServers\": {\n \"workforce-local\": {\n \"type\": \"http\",\n \"url\": \"{{appBase}}app/mcp\",\n \"tools\": [\"*\"]\n }\n }\n }\n\nIf this instance is secured with a shared secret (NANO_PR_WEBHOOK_SECRET), pass it on the MCP connection \u2014 reads AND mutations both require it:\n\n copilot mcp add --transport http workforce-local {{appBase}}app/mcp \\\n --header \"x-hook-secret: $NANO_PR_WEBHOOK_SECRET\"\n\nIn config form, add a `headers` entry alongside `url` on that server (per the MCP runbook) \u2014 the config path has no header flag, so omitting this yields 401s:\n\n \"headers\": { \"x-hook-secret\": \"$NANO_PR_WEBHOOK_SECRET\" }\n\nA Basic-Auth-fronted instance (behind a reverse proxy) additionally needs `Authorization: Basic \u2026` on the connection.\n\nMCP servers register at host startup \u2014 add the entry, THEN start a new session so its tools load. The `workforce-*` tools then appear (the full set of operations projected from this instance's OpenAPI contract, including the live operator guide itself as the `getAgentInstructions` read tool \u2014 or its addressable companion `getAgentGuide(section?)`, which the MCP runbook recommends over the ~43KB blob to avoid tool-result overrun \u2014 plus the `urban_debug_*` family for inspecting a wedged instance's process instances, wait states, and incidents).\n\nThe instance's operations are now native tools. Ask, naming the instance: \"Using workforce-local, show what's in flight and any open escalations.\" It should call the status tool, not curl. Operator-only doors (the delivery-graph stage/dispatch/dismiss lifecycle \u2014 the human click IS the approval) are deliberately not tools.\n\nNo MCP client? The curl path is unchanged \u2014 fetch and follow the live guide (the response is JSON with a `skill` markdown field), adding `-H \"x-hook-secret: <secret>\"` if this instance is secured:\n\n curl -sS {{appBase}}app/api/agent/skill\n\nThat skill bootstraps you to this instance's live operator guide at {{appBase}}app/api/agent (the same guide MCP exposes as `getAgentInstructions`, or section-addressably as `getAgentGuide`). If you find a bug or a stuck process, the guide explains how to raise an issue or a PR against nanobpm/nano-workforce."
88
88
  }
89
89
  }
90
90
  },