@nanobpm/nano-workforce 0.200.7 → 0.200.8
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 +6 -0
- package/SPEC.md +41 -0
- package/app/agentSla.test.ts +48 -0
- package/app/agentSla.ts +36 -11
- package/app/agentic/vocab/agent-sla-boundary.test.ts +170 -0
- package/app/agentic/vocab/job-types.ts +60 -0
- package/app/conformance.test.ts +114 -0
- package/app/conformance.ts +72 -0
- package/app/contracts.ts +2 -2
- package/app/deliveryRunner.ts +5 -0
- package/app/feature.ts +5 -0
- package/app/plan.ts +12 -0
- package/app/retro.test.ts +5 -0
- package/app/retro.ts +4 -0
- package/app/service.ts +27 -8
- package/e2e/agent-sla-boundary.e2e.ts +598 -0
- package/e2e/support/pin-agent-sla.ts +47 -0
- package/package.json +1 -1
- package/resources/processes/feature.bpmn +1 -0
- package/resources/processes/implement-cell.bpmn +96 -46
- package/resources/processes/merge-cell.bpmn +60 -12
- package/resources/processes/plan-fanout.bpmn +1 -0
- package/resources/processes/retro.bpmn +100 -3
- package/test/derivation-parity/derivation-parity.test.ts +35 -11
- package/test/derivation-parity/flows.ts +14 -1
- package/workers/conformance-record/worker.test.ts +87 -1
- package/workers/conformance-record/worker.ts +34 -7
package/app/deliveryRunner.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
18
18
|
import type { EngineClient } from "@nanobpm/urban";
|
|
19
19
|
import type { DeliveryFact, DeliveryGraph, DeliveryNode } from "../nano-generated/api-io.d.ts";
|
|
20
20
|
import { TRANSCRIPT_URL_BASE_VAR, transcriptUrlBaseFor } from "./agentic/transcript-url.ts";
|
|
21
|
+
import { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
21
22
|
import { isPlausibleBranchName } from "./baseBranch.ts";
|
|
22
23
|
import { AGENT_REPO_SPEC_HEADER, AGENT_TERMINAL_SUCCESS_STATUSES, assertNever, compileDeliveryGraph, DELIVERY_GRAPH_PROCESS_ID, redactFreeText } from "./deliveryGraphCompiler.ts";
|
|
23
24
|
import { DEFAULT_EVERY_MS, msToIsoDuration, parseProbe, readinessPollEvery, readinessTimeout } from "./readiness.ts";
|
|
@@ -239,6 +240,10 @@ export async function runDeliveryGraph(
|
|
|
239
240
|
// node ioMapping in deliveryGraphCompiler). Seeded once at the run root — the same value for
|
|
240
241
|
// every node — and read down into each agent job via `=transcriptUrlBase`.
|
|
241
242
|
[TRANSCRIPT_URL_BASE_VAR]: transcriptUrlBaseFor(),
|
|
243
|
+
// Agent-task liveness SLA (issue #849): the compiled graph's per-node `=nodeTimeout` boundary
|
|
244
|
+
// bounds a node's whole retry budget; this seeds the shared `agentSlaTimeout` knob so any
|
|
245
|
+
// agent cell modelled with the fleet-wide SLA boundary resolves it too.
|
|
246
|
+
agentSlaTimeout: AGENT_SLA_TIMEOUT,
|
|
242
247
|
},
|
|
243
248
|
});
|
|
244
249
|
// The engine can yield a numeric key; `DeliveryRunHandle.processInstanceKey` is typed `string` and
|
package/app/feature.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// SQL — matching app/plan.ts and app/service.ts.
|
|
17
17
|
import type { DataLayer, EngineClient } from "@nanobpm/urban";
|
|
18
18
|
import { TRANSCRIPT_URL_BASE_VAR, transcriptUrlBaseFor } from "./agentic/transcript-url.ts";
|
|
19
|
+
import { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
19
20
|
import { coalesceTitle, fetchIssueTitle } from "./github.ts";
|
|
20
21
|
import { derivedTrackingTable } from "./instanceTracking.ts";
|
|
21
22
|
import { ESCALATION_SLA_TIMEOUT, normalizeBaseBranch, type ParsedIssue, renderBaseBranchBrief } from "./plan.ts";
|
|
@@ -531,6 +532,10 @@ export async function startFeature(
|
|
|
531
532
|
// `feature-escalation` user task's interrupting timer boundary and assignment definition.
|
|
532
533
|
escalationSlaTimeout: ESCALATION_SLA_TIMEOUT,
|
|
533
534
|
escalationAssignee: null,
|
|
535
|
+
// Agent-task liveness SLA (issue #849): bounds the implement-cell's external agent task
|
|
536
|
+
// (`implement-task`) — its interrupting timer boundary evaluates `=agentSlaTimeout` at timer
|
|
537
|
+
// creation, so a hung or looping implementation agent escalates instead of parking forever.
|
|
538
|
+
agentSlaTimeout: AGENT_SLA_TIMEOUT,
|
|
534
539
|
// Base branch (ADR 0003): the branch the agent branches off and opens its PR against. The
|
|
535
540
|
// brief rides `appendPrompt` in the implement task, exactly like the epic implementer.
|
|
536
541
|
baseBranch: base,
|
package/app/plan.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// the process. Data access goes through the record gateway (`data.table`), never
|
|
11
11
|
// hand-written SQL — matching app/service.ts.
|
|
12
12
|
import type { DataLayer, EngineClient } from "@nanobpm/urban";
|
|
13
|
+
import { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
13
14
|
import {
|
|
14
15
|
InvalidBaseBranchError,
|
|
15
16
|
isPlausibleBranchName,
|
|
@@ -1021,6 +1022,17 @@ export async function startPlan(
|
|
|
1021
1022
|
// `operators` candidate group); an operator/agent can claim/reassign via the task inbox.
|
|
1022
1023
|
escalationSlaTimeout: ESCALATION_SLA_TIMEOUT,
|
|
1023
1024
|
escalationAssignee: null,
|
|
1025
|
+
// Agent-task liveness SLA (issue #849): PRE-SEED the canonical bound for the fan-out's
|
|
1026
|
+
// external agent tasks. Each slice's implementation agent (via the implement-cell
|
|
1027
|
+
// callActivity's explicit `agentSlaTimeout` input mapping) is bounded today; the wave's own
|
|
1028
|
+
// inline `trial-merge` agent (`plan-fanout.bpmn:524-542`) is NOT — it is a separate external
|
|
1029
|
+
// task from the standalone `merge-cell` process (which has no callActivity caller), sits on a
|
|
1030
|
+
// rerun back-edge, and its SLA boundary is deferred to #868 alongside the planner (`plan`) and
|
|
1031
|
+
// plan reviewer (`review-plan`), whose back-edge loop the layouter cannot yet route (layouter
|
|
1032
|
+
// bug #867). For those three this seed is preparation, not yet an armed bound. Each bounded
|
|
1033
|
+
// task's interrupting timer boundary evaluates `=agentSlaTimeout` at timer creation, so a hung
|
|
1034
|
+
// or looping agent escalates to a human instead of parking forever.
|
|
1035
|
+
agentSlaTimeout: AGENT_SLA_TIMEOUT,
|
|
1024
1036
|
// Capability-barrier bound (#289): the validated ISO-8601 duration read by the
|
|
1025
1037
|
// `wait-caps-timeout` timer arm of the `wait-caps-resolved` event-based gateway. A task whose
|
|
1026
1038
|
// declared cross-repo capabilities never resolve (most acutely an unresolvable capabilityRef the
|
package/app/retro.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { assert, assertEquals, assertStringIncludes } from "#test-assert";
|
|
|
4
4
|
import type { DataLayer, EngineClient } from "@nanobpm/urban";
|
|
5
5
|
import { memBlackboardSource } from "../test/blackboardDb.ts";
|
|
6
6
|
import { withTrackingViews } from "../test/trackingViews.ts";
|
|
7
|
+
import { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
7
8
|
import { appendEntry } from "./blackboard.ts";
|
|
8
9
|
import { recordTaskDelta } from "./taskDelta.ts";
|
|
9
10
|
import {
|
|
@@ -308,6 +309,10 @@ test("maybeStartRetro: starts the retro exactly once when the last PR lands with
|
|
|
308
309
|
assertEquals(started.length, 1);
|
|
309
310
|
assertEquals(started[0].processDefinitionId, "retro");
|
|
310
311
|
assertEquals(started[0].variables.planKey, PLAN);
|
|
312
|
+
// The retro seed must carry the canonical agent-SLA bound through its PRODUCTION start path
|
|
313
|
+
// (not just the e2e, which supplies agentSlaTimeout manually) — else the two external agent
|
|
314
|
+
// tasks (`conformance` / `synthesize`) would run unbounded (Previously-missed advisory, PR #864).
|
|
315
|
+
assertEquals(started[0].variables.agentSlaTimeout, AGENT_SLA_TIMEOUT);
|
|
311
316
|
assert(stores["plans"][0].retro_started_at, "retro_started_at must be stamped");
|
|
312
317
|
assertEquals(stores["plan_retro_starts"].length, 1);
|
|
313
318
|
|
package/app/retro.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// Data access goes through the record gateway (`data.table`), never hand-written SQL — matching
|
|
15
15
|
// app/plan.ts, app/blackboard.ts, and app/taskDelta.ts.
|
|
16
16
|
import type { DataLayer, EngineClient, Logger } from "@nanobpm/urban";
|
|
17
|
+
import { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
17
18
|
import { type BlackboardEntry, isUniqueViolation, readBlackboard } from "./blackboard.ts";
|
|
18
19
|
import { hasDeliveredImplementationForPlan } from "./conformance.ts";
|
|
19
20
|
import { TERMINAL_STATUSES } from "./delivery.ts";
|
|
@@ -355,6 +356,9 @@ export async function maybeStartRetro(
|
|
|
355
356
|
planKey,
|
|
356
357
|
repo: digest.repo,
|
|
357
358
|
issueUrl: digest.issueUrl,
|
|
359
|
+
// Agent-task liveness SLA (issue #849): bounds the retro's two external agent tasks
|
|
360
|
+
// (`conformance`, `synthesize`) — a hung agent escalates to a human instead of parking.
|
|
361
|
+
agentSlaTimeout: AGENT_SLA_TIMEOUT,
|
|
358
362
|
},
|
|
359
363
|
}));
|
|
360
364
|
} catch (err) {
|
package/app/service.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { DataLayer, EngineClient } from "@nanobpm/urban";
|
|
|
11
11
|
import { ABANDONED_STATUS, abandonUrl, mintAbandonToken, renderAbandonBrief } from "./abandon.ts";
|
|
12
12
|
import { matchAdjudication, prAdjudications, resetAdjudications } from "./adjudications.ts";
|
|
13
13
|
import { completeEscalationAutoApplied, escalationFormId } from "./agentCompletion.ts";
|
|
14
|
-
import {
|
|
14
|
+
import { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
15
15
|
import {
|
|
16
16
|
CAPS_RESOLVED_MESSAGE,
|
|
17
17
|
type CapabilityNeed,
|
|
@@ -214,13 +214,25 @@ export const MAX_ADVERSARIAL_PASSES = clampCiFixBudget(
|
|
|
214
214
|
* (allows 0 = escalate immediately, ceiling-capped). */
|
|
215
215
|
export const MAX_MERGE_STALL_ROUNDS = clampCiFixBudget(process.env.NANO_PR_MAX_MERGE_STALL_ROUNDS, 3);
|
|
216
216
|
|
|
217
|
-
/** How long
|
|
218
|
-
*
|
|
219
|
-
* `agentSlaTimeout` process variable at
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
|
|
217
|
+
/** How long an external AGENT service task may sit without completing before its interrupting
|
|
218
|
+
* timer boundary fires and the process escalates for human attention. Seeded as the
|
|
219
|
+
* `agentSlaTimeout` process variable at every process start that hosts an external agent task
|
|
220
|
+
* (issue #849), and evaluated by the BOUNDED subset's boundary timers
|
|
221
|
+
* (`<bpmn:timeDuration>=agentSlaTimeout`, FEEL at timer creation): the implement-cell, the
|
|
222
|
+
* merge-cell's trial-merge, the merge-loop's rebase / fix-ci, and retro's conformance / synthesize.
|
|
223
|
+
* The convergence-loop (review-round / adversarial-review / classify-scope) and plan-fanout (plan /
|
|
224
|
+
* review-plan) tasks are only PRE-SEEDED here — their boundaries are intentionally deferred to #868
|
|
225
|
+
* (a bpmn-auto-layout back-edge routing limitation, #867). Plan-fanout's inline wave `trial-merge`
|
|
226
|
+
* (`plan-fanout.bpmn:524-542`) is likewise deferred: it is a separate external agent task from the
|
|
227
|
+
* standalone `merge-cell` (which has no callActivity caller) on a rerun back-edge with no boundary
|
|
228
|
+
* yet. For all of these this seed is preparation, not yet an armed bound. Unlike a human-decision
|
|
229
|
+
* escalation (PT24H), an agent task has no human in
|
|
230
|
+
* the loop — if its capability is unstaffed or the agent hangs/crashes without failing the job, the
|
|
231
|
+
* token would otherwise park forever. Override with `NANO_PR_AGENT_SLA_TIMEOUT` (ISO-8601 duration).
|
|
232
|
+
* The canonical validated constant lives in the leaf module `app/agentSla.ts` (re-exported here) so
|
|
233
|
+
* `app/feature.ts`, `app/plan.ts`, `app/retro.ts`, and `app/deliveryRunner.ts` seed it without an
|
|
234
|
+
* import cycle. */
|
|
235
|
+
export { AGENT_SLA_TIMEOUT } from "./agentSla.ts";
|
|
224
236
|
|
|
225
237
|
/** How long the convergence loop waits for a fresh review before escalating to a human. Seeded as
|
|
226
238
|
* the `reviewWaitTimeout` process variable at submit and evaluated by the process's
|
|
@@ -761,6 +773,13 @@ async function submitPrCritical(
|
|
|
761
773
|
round: 1,
|
|
762
774
|
maxRounds: clampRounds(maxRounds, MAX_ROUNDS),
|
|
763
775
|
reviewWaitTimeout: REVIEW_WAIT_TIMEOUT,
|
|
776
|
+
// Agent-task liveness SLA (issue #849): PRE-SEED the bound for the loop's external agent
|
|
777
|
+
// tasks (review-round / adversarial-review / classify-scope). Those tasks sit on a
|
|
778
|
+
// back-edge loop whose boundary the layouter cannot yet route, so their SLA boundaries are
|
|
779
|
+
// intentionally deferred to #868 (layouter bug #867) — this seed is preparation for that
|
|
780
|
+
// follow-up, not yet an armed bound on this loop. (The merge-loop's rebase / fix-ci, seeded
|
|
781
|
+
// from the same constant, ARE bounded today.)
|
|
782
|
+
agentSlaTimeout: AGENT_SLA_TIMEOUT,
|
|
764
783
|
// Bounded agent auto-ack (issue #796): the convergence loop re-dispatches the review-round
|
|
765
784
|
// agent up to `ackRetryMax` times to ack suppressed advisories when the converge-gate blocks
|
|
766
785
|
// solely on unacked ones, before escalating to a human. `ackRetryRound` counts those passes.
|