@nanobpm/nano-workforce 0.150.3 → 0.151.0
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 +12 -0
- package/docs/agent-guide.md +52 -5
- package/package.json +1 -1
- package/pages/overview.page.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.151.0](https://github.com/nanobpm/nano-workforce/compare/v0.150.4...v0.151.0) (2026-08-28)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* make Agent Instructions modal MCP-first, skill-as-fallback ([#587](https://github.com/nanobpm/nano-workforce/issues/587)) ([cd2b613](https://github.com/nanobpm/nano-workforce/commit/cd2b613a35c0411ad5000dece36e54b584cffbc7)), closes [#575](https://github.com/nanobpm/nano-workforce/issues/575) [nanobpm/nano-workforce#586](https://github.com/nanobpm/nano-workforce/issues/586)
|
|
6
|
+
|
|
7
|
+
## [0.150.4](https://github.com/nanobpm/nano-workforce/compare/v0.150.3...v0.150.4) (2026-08-28)
|
|
8
|
+
|
|
9
|
+
### Documentation
|
|
10
|
+
|
|
11
|
+
* **agent guide:** document the PT30M wait-gate default and poll bounds in §9 ([#585](https://github.com/nanobpm/nano-workforce/issues/585)) ([3092ae9](https://github.com/nanobpm/nano-workforce/commit/3092ae9c1448c8b540aa62abf58b268e14f3f0ee)), closes [nanobpm/nano-workforce#584](https://github.com/nanobpm/nano-workforce/issues/584)
|
|
12
|
+
|
|
1
13
|
## [0.150.3](https://github.com/nanobpm/nano-workforce/compare/v0.150.2...v0.150.3) (2026-08-28)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
package/docs/agent-guide.md
CHANGED
|
@@ -180,6 +180,12 @@ curl -sS __BASE__/../../tasks/api/tasks \
|
|
|
180
180
|
The inbox UI is also served at `__BASE__/../../tasks` for a human to browse, filter, and
|
|
181
181
|
answer (assignee/candidate-group and age surface on each task once assignment lands).
|
|
182
182
|
|
|
183
|
+
> **`wait`-gate escalations are different — completing one does NOT re-arm the gate.** The
|
|
184
|
+
> escalation kinds above resume a loop with your answer. A **delivery-graph `wait` node**
|
|
185
|
+
> (§9) that elapses its bound also parks a task, but completing *that* one releases the token
|
|
186
|
+
> **as not-ready** and the graph proceeds **past the gate** — the downstream side-effecting
|
|
187
|
+
> node then runs against the unmet dependency. See §9.2 before clearing one.
|
|
188
|
+
|
|
183
189
|
**Answer a task** by completing it with the typed variables its form expects — the
|
|
184
190
|
completion resumes the parked process:
|
|
185
191
|
|
|
@@ -438,7 +444,20 @@ layer schedules, it does not re-implement execution):
|
|
|
438
444
|
| `connector` | `connector: { target, dedupeKey?, payload? }` | an automated, side-effecting outbound action. Carries a `dedupeKey` (at-least-once safe). Two **real targets** ship today — **`converge`** and **`converge-merge`** (§9.4); other targets are a forward-declared stub. | yes |
|
|
439
445
|
|
|
440
446
|
A **`wait` node's `wait` is a `ReadinessProbe` verbatim** (the same shape feature-run
|
|
441
|
-
intake uses): `{ kind, target, onTimeout?, match?, poll? }
|
|
447
|
+
intake uses): `{ kind, target, onTimeout?, match?, poll? }`, where `poll` is
|
|
448
|
+
`{ everyMs?, timeoutMs?, backoff? }` — `everyMs` is the re-probe cadence, `timeoutMs` the
|
|
449
|
+
total bounded budget, and `backoff ∈ fixed|exponential`.
|
|
450
|
+
|
|
451
|
+
> **The bound is invisible unless you set it.** When `poll` (or `poll.timeoutMs`) is
|
|
452
|
+
> **omitted**, the gate inherits the built-in default budget of **`PT30M` (30 minutes),
|
|
453
|
+
> re-probing every 15s** (`DEFAULT_READINESS_TIMEOUT` / `DEFAULT_EVERY_MS`,
|
|
454
|
+
> `app/readiness.ts`). That default is right for *"is the package published yet"* and badly
|
|
455
|
+
> wrong for `wait[pr, merged]` / `wait[epic]`, which routinely wait **hours or days** — an
|
|
456
|
+
> unpopulated `poll` on such a gate escalates after 30 minutes for no visible reason. Neither
|
|
457
|
+
> `compile` nor `preview` surfaces the effective bound, so **set `poll.timeoutMs` explicitly**
|
|
458
|
+
> on any gate that waits on a merge or an epic (see §9.4 / §9.5).
|
|
459
|
+
|
|
460
|
+
The **`pr` kind** watches an
|
|
442
461
|
in-flight PR — `target: "owner/repo#123"`, `match.prState ∈ ready|merged|mergeable|checks-green`
|
|
443
462
|
(default `merged`) — and on a merged match binds `mergedSha` as an output fact. The **`epic`
|
|
444
463
|
kind** (issue #568) gates on an **nwf plan-fanout epic reaching "fully merged"** — `target:
|
|
@@ -526,6 +545,18 @@ Graphs** grid (e.g. *"parked on human node: manual OTP publish"*). A `human` nod
|
|
|
526
545
|
**Tasks** inbox and is answered exactly as an escalation is (§3) — its completion emits any
|
|
527
546
|
declared facts, which downstream edges bind.
|
|
528
547
|
|
|
548
|
+
> **Completing a `wait` escalation proceeds *as not-ready* — it does NOT re-arm the gate.**
|
|
549
|
+
> A `wait` node with `onTimeout: "escalate"` (the default) that elapses its bound parks a
|
|
550
|
+
> human-completable escalation task on the Tasks inbox. **Completing that task does not retry
|
|
551
|
+
> the probe or wait for readiness** — it releases the token **as not-ready** and the graph
|
|
552
|
+
> proceeds **past the gate**, so the downstream side-effecting node then runs *against the
|
|
553
|
+
> unmet dependency* (`waitBodyLines`, `app/deliveryGraphCompiler.ts`). An operator clearing
|
|
554
|
+
> what looks like a stuck task therefore *launches the very work the gate was holding back*.
|
|
555
|
+
> If the dependency genuinely is not ready, do **not** complete the escalation to "unstick"
|
|
556
|
+
> it — extend the gate's `poll.timeoutMs` and re-dispatch, or abandon the run. (Same for
|
|
557
|
+
> `onTimeout: "continue"`, which proceeds past the gate as not-ready with **no** human stop
|
|
558
|
+
> at all.)
|
|
559
|
+
|
|
529
560
|
> **Why the split?** Making the compile door the end of the agent surface closes a
|
|
530
561
|
> self-approval hole: the old flow handed the same caller a content-addressed approval token
|
|
531
562
|
> to re-submit with, so any holder of the API credential approved its own graph. Removing the
|
|
@@ -544,7 +575,8 @@ publish and records the version → open+merge PR #303 (repo 3) consuming that v
|
|
|
544
575
|
"name": "cross-repo release: merge #101 → un-draft+merge #202 → manual OTP publish → consume in #303",
|
|
545
576
|
"nodes": [
|
|
546
577
|
{ "id": "merge-a", "kind": "wait",
|
|
547
|
-
"wait": { "kind": "pr", "target": "acme/repo-1#101", "match": { "prState": "merged" },
|
|
578
|
+
"wait": { "kind": "pr", "target": "acme/repo-1#101", "match": { "prState": "merged" },
|
|
579
|
+
"poll": { "everyMs": 300000, "timeoutMs": 259200000 }, "onTimeout": "escalate" } },
|
|
548
580
|
{ "id": "undraft-merge-b", "kind": "agent",
|
|
549
581
|
"agent": { "jobType": "senior:merge", "prompt": "Take draft PR acme/repo-2#202 out of draft and merge it once its required checks are green." } },
|
|
550
582
|
{ "id": "manual-publish", "kind": "human",
|
|
@@ -553,7 +585,8 @@ publish and records the version → open+merge PR #303 (repo 3) consuming that v
|
|
|
553
585
|
{ "id": "open-pr-c", "kind": "agent",
|
|
554
586
|
"agent": { "jobType": "senior:feature", "prompt": "Bump @acme/widget to the published version in acme/repo-3 and open PR #303." } },
|
|
555
587
|
{ "id": "merge-c", "kind": "wait",
|
|
556
|
-
"wait": { "kind": "pr", "target": "acme/repo-3#303", "match": { "prState": "merged" },
|
|
588
|
+
"wait": { "kind": "pr", "target": "acme/repo-3#303", "match": { "prState": "merged" },
|
|
589
|
+
"poll": { "everyMs": 300000, "timeoutMs": 259200000 }, "onTimeout": "escalate" } }
|
|
557
590
|
],
|
|
558
591
|
"edges": [
|
|
559
592
|
{ "from": "merge-a", "to": "undraft-merge-b" },
|
|
@@ -622,7 +655,8 @@ author never knows the PR number at compose time, so reference it by fact:
|
|
|
622
655
|
{ "id": "land", "kind": "connector",
|
|
623
656
|
"connector": { "target": "converge-merge", "payload": { "pr": "open.pr" } } },
|
|
624
657
|
{ "id": "merged", "kind": "wait",
|
|
625
|
-
"wait": { "kind": "pr", "target": "open.pr", "match": { "prState": "merged" },
|
|
658
|
+
"wait": { "kind": "pr", "target": "open.pr", "match": { "prState": "merged" },
|
|
659
|
+
"poll": { "everyMs": 300000, "timeoutMs": 259200000 }, "onTimeout": "escalate" } }
|
|
626
660
|
],
|
|
627
661
|
"edges": [
|
|
628
662
|
{ "from": "open.pr", "to": "land" },
|
|
@@ -631,6 +665,11 @@ author never knows the PR number at compose time, so reference it by fact:
|
|
|
631
665
|
}
|
|
632
666
|
```
|
|
633
667
|
|
|
668
|
+
The `merged` gate carries an explicit **`poll`** (re-probe every 5 minutes, budget 3 days:
|
|
669
|
+
`timeoutMs: 259200000`) because a `wait[pr, merged]` waits on a human-paced merge — **omitting
|
|
670
|
+
`poll` inherits the 30-minute default** (§9.1) and escalates mid-review. Set `poll.timeoutMs`
|
|
671
|
+
to a realistic budget on any merge/epic gate.
|
|
672
|
+
|
|
634
673
|
The `pr` fact is threaded along the **fact-qualified edges** (`open.pr → land`, `open.pr → merged`) —
|
|
635
674
|
those edges are what carry the observed PR into each consumer, so they are **required** when you
|
|
636
675
|
reference `open.pr` (the validator rejects a reference with no threading edge, `unbound-pr`). Three
|
|
@@ -661,7 +700,9 @@ babysitting a `confirm` gate.
|
|
|
661
700
|
"nodes": [
|
|
662
701
|
{ "id": "gate-epic", "kind": "wait",
|
|
663
702
|
"wait": { "kind": "epic", "target": "nanobpm/nano-ide#488",
|
|
664
|
-
"match": { "epicState": "merged" },
|
|
703
|
+
"match": { "epicState": "merged" },
|
|
704
|
+
"poll": { "everyMs": 300000, "timeoutMs": 259200000 },
|
|
705
|
+
"onTimeout": "escalate" },
|
|
665
706
|
"emits": [ { "name": "prCount", "type": "number" } ] },
|
|
666
707
|
{ "id": "start-b", "kind": "agent",
|
|
667
708
|
"agent": { "jobType": "senior:feature", "prompt": "Implement nanobpm/nano-workforce#567 and open a PR." } }
|
|
@@ -680,5 +721,11 @@ Semantics:
|
|
|
680
721
|
- **A failed/abandoned/mixed epic never reports merged**, so it never falsely releases the
|
|
681
722
|
gate; the **bounded** wait elapses and routes via **`onTimeout`** (`escalate`/`continue`) —
|
|
682
723
|
it does **not** hang.
|
|
724
|
+
- **Set `poll.timeoutMs` to a realistic budget.** An epic reaching "fully merged" is a
|
|
725
|
+
multi-day, human-paced event, so the example gives it `poll: { everyMs: 300000, timeoutMs:
|
|
726
|
+
259200000 }` (re-probe every 5 minutes, budget 3 days). **Omitting `poll` inherits the
|
|
727
|
+
30-minute default** (§9.1) — the gate would escalate long before the epic lands, and (per
|
|
728
|
+
§9.2) completing that escalation would release `start-b` **as not-ready**, launching feature
|
|
729
|
+
B before its dependency merged. Size `timeoutMs` to how long the epic realistically takes.
|
|
683
730
|
- On a fully-merged match it binds **`prCount`** (how many slice PRs the epic landed) as an
|
|
684
731
|
output fact, so a downstream node can consume it (parity with the `pr` kind's `mergedSha`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.151.0",
|
|
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",
|
package/pages/overview.page.json
CHANGED
|
@@ -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
|
|
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.",
|
|
86
86
|
"copyLabel": "Copy prompt",
|
|
87
|
-
"copyText": "
|
|
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."
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
},
|