@nanobpm/nano-workforce 0.183.2 → 0.184.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 +12 -0
- package/app/agentic/agent-history.test.ts +165 -0
- package/app/agentic/agent-history.ts +211 -0
- package/app/agentic/cockpit/agent-history-render.test.ts +85 -0
- package/app/agentic/cockpit/agent-history-render.ts +168 -0
- package/app/agentic/cockpit/agent-history-view.test.ts +104 -0
- package/app/agentic/cockpit/agent-history-view.ts +186 -0
- package/app/agentic/cockpit/index.ts +21 -0
- package/app/agentic/cockpit/mount.test.ts +133 -1
- package/app/agentic/cockpit/supply-boot-agent-history.test.ts +144 -0
- package/app/agentic/cockpit/supply-boot.ts +134 -1
- package/app/contracts.ts +9 -1
- package/app/mcpToolSurface.ts +8 -1
- package/db/migrations/101_agentic_history_read_expand.sql +34 -0
- package/openapi.yaml +360 -0
- package/operations/agentHistoryEndpoints.test.ts +106 -0
- package/operations/getAgentInstanceHistory.ts +37 -0
- package/operations/listAgentInstances.ts +42 -0
- package/package.json +3 -3
- package/pages/cockpit/cockpit.css +104 -0
- package/pages/cockpit/mount.js +286 -3
- package/pages/delivery-graphs/staged.mount.js +33 -47
- package/test/delivery-graphs-staged-confirm.test.ts +129 -8
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
TerminalSession,
|
|
29
29
|
type TerminalSink,
|
|
30
30
|
} from "@nanobpm/agentic/cockpit";
|
|
31
|
+
import { renderAgentHistory, renderAgentSessions } from "./agent-history-render.ts";
|
|
32
|
+
import {
|
|
33
|
+
type AgentHistoryReport,
|
|
34
|
+
type AgentInstanceListReport,
|
|
35
|
+
agentHistoryView,
|
|
36
|
+
agentSessionsView,
|
|
37
|
+
} from "./agent-history-view.ts";
|
|
31
38
|
import type { CockpitRoute } from "./cockpit-route.ts";
|
|
32
39
|
import { renderSupply } from "./supply-render.ts";
|
|
33
40
|
import type { SupplyReport, SupplyView } from "./supply-view.ts";
|
|
@@ -69,6 +76,23 @@ export interface SupplyCockpitEnv {
|
|
|
69
76
|
* Required for the "past sessions" replay to work; must be provided together with {@link fetchTranscripts}.
|
|
70
77
|
*/
|
|
71
78
|
readonly fetchTranscript?: (stream: string, from?: number) => Promise<TranscriptDataReport>;
|
|
79
|
+
/**
|
|
80
|
+
* Fetches the engine-native AgentInstance list (`GET /agentic/agent-instances`, served from
|
|
81
|
+
* `@nanobpm/urban`'s EngineClient `searchAgentInstances`) for the SETTLED "agent history" panel
|
|
82
|
+
* (issue #745/#747). Optional: when omitted the agent-history panel is not rendered. Must be
|
|
83
|
+
* provided together with {@link fetchAgentHistory}. This is the CONSUMER read path — settled history
|
|
84
|
+
* derives from engine truth keyed by agent-instance / process keys, NOT the slash-bearing relay
|
|
85
|
+
* stream id (so the #744 gateway-proxy bug class is moot); the relay past-sessions panel above stays
|
|
86
|
+
* the LIVE overlay only.
|
|
87
|
+
*/
|
|
88
|
+
readonly fetchAgentInstances?: (processInstanceKey?: string) => Promise<AgentInstanceListReport>;
|
|
89
|
+
/**
|
|
90
|
+
* Fetches one AgentInstance's durable conversation history (turns + per-turn metrics) from engine
|
|
91
|
+
* `searchAgentInstanceHistory` (`GET /agentic/agent-instances/{agentInstanceKey}/history`), for the
|
|
92
|
+
* historical transcript view. Required for the agent-history panel's drill-in to work; must be
|
|
93
|
+
* provided together with {@link fetchAgentInstances}.
|
|
94
|
+
*/
|
|
95
|
+
readonly fetchAgentHistory?: (agentInstanceKey: string) => Promise<AgentHistoryReport>;
|
|
72
96
|
/** Opens a socket to the app relay channel (one per drill-in connection). */
|
|
73
97
|
readonly connectRelay: SocketFactory;
|
|
74
98
|
/** Mounts the terminal widget (xterm.js in the browser) and returns its write sink. */
|
|
@@ -117,6 +141,8 @@ export interface SupplyCockpitHandle {
|
|
|
117
141
|
drill(stream: string): void;
|
|
118
142
|
/** Replay a captured past session's stored transcript statically into the terminal (no live worker). */
|
|
119
143
|
replay(stream: string): Promise<void>;
|
|
144
|
+
/** View one engine-native agent instance's settled conversation history in the agent-history panel. */
|
|
145
|
+
viewAgentHistory(agentInstanceKey: string): Promise<void>;
|
|
120
146
|
/** Open a worker's dedicated detail page. */
|
|
121
147
|
openWorker(instance: string): void;
|
|
122
148
|
/** Return to the main worker list. */
|
|
@@ -125,6 +151,8 @@ export interface SupplyCockpitHandle {
|
|
|
125
151
|
readonly currentRoute: CockpitRoute;
|
|
126
152
|
/** The stream currently drilled into or replayed, if any. */
|
|
127
153
|
readonly currentStream: string | undefined;
|
|
154
|
+
/** The agent instance whose settled history is currently shown in the agent-history panel, if any. */
|
|
155
|
+
readonly currentAgentInstanceKey: string | undefined;
|
|
128
156
|
/** Whether the terminal is showing a LIVE stream or a REPLAYED transcript (undefined when idle). */
|
|
129
157
|
readonly currentMode: TerminalMode | undefined;
|
|
130
158
|
/** Stop everything and release the terminal connection. */
|
|
@@ -147,6 +175,11 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
147
175
|
readonly #env: SupplyCockpitEnv;
|
|
148
176
|
readonly #listRegion: ElementLike;
|
|
149
177
|
readonly #pastRegion: ElementLike | undefined;
|
|
178
|
+
// The engine-native SETTLED agent-history panel (issue #745): a list region (the AgentInstance runs)
|
|
179
|
+
// and a detail region (a selected instance's ordered conversation turns + metrics). Present only when
|
|
180
|
+
// the engine agent-history read endpoints are wired. Distinct from #pastRegion (the relay live overlay).
|
|
181
|
+
readonly #agentRegion: ElementLike | undefined;
|
|
182
|
+
readonly #agentDetailRegion: ElementLike | undefined;
|
|
150
183
|
// A dedicated volatile region the STRUCTURED derived view (messages, rich tool/diff cards, permission
|
|
151
184
|
// prompts) is mounted into on a replay — beside, and additive to, the byte-level terminal replay
|
|
152
185
|
// (which is left untouched). Present only when the transcript read endpoints are wired.
|
|
@@ -182,6 +215,12 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
182
215
|
// hung transcripts endpoint can't accumulate pending calls.
|
|
183
216
|
#pastRefreshing = false;
|
|
184
217
|
#pastRefreshPending = false;
|
|
218
|
+
// Single-flight latch for the agent-history list refresh (mirrors #pastRefreshing), so the poll can
|
|
219
|
+
// never stack engine agent-instance fetches against a slow/unresponsive read endpoint.
|
|
220
|
+
#agentRefreshing = false;
|
|
221
|
+
#agentRefreshPending = false;
|
|
222
|
+
// The agent instance whose settled history is currently rendered in the detail region, if any.
|
|
223
|
+
#shownAgentInstanceKey: string | undefined;
|
|
185
224
|
#route: CockpitRoute = { kind: "main" };
|
|
186
225
|
#view: SupplyView | undefined;
|
|
187
226
|
// Bumped by every start()/stop() so an in-flight #tick() from a previous start cycle can't
|
|
@@ -217,6 +256,12 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
217
256
|
if ((env.fetchTranscripts === undefined) !== (env.fetchTranscript === undefined)) {
|
|
218
257
|
throw new Error("SupplyCockpitEnv.fetchTranscripts and fetchTranscript must be provided together (or neither)");
|
|
219
258
|
}
|
|
259
|
+
// The engine agent-history LIST source and the per-instance HISTORY source are likewise a matched
|
|
260
|
+
// pair: the panel renders whenever the list source is present, but its rows route through
|
|
261
|
+
// viewAgentHistory(), which no-ops without the history source. Require both together (or neither).
|
|
262
|
+
if ((env.fetchAgentInstances === undefined) !== (env.fetchAgentHistory === undefined)) {
|
|
263
|
+
throw new Error("SupplyCockpitEnv.fetchAgentInstances and fetchAgentHistory must be provided together (or neither)");
|
|
264
|
+
}
|
|
220
265
|
this.#setTimer =
|
|
221
266
|
env.setTimer ??
|
|
222
267
|
((run, ms) => {
|
|
@@ -255,6 +300,14 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
255
300
|
this.#pastRegion = env.doc.createElement("div");
|
|
256
301
|
this.#pastRegion.className = "cockpit-past-region";
|
|
257
302
|
}
|
|
303
|
+
// The engine-native agent-history panel: a list region + a detail region, present only when the
|
|
304
|
+
// engine agent-history read endpoints are wired.
|
|
305
|
+
if (env.fetchAgentInstances !== undefined) {
|
|
306
|
+
this.#agentRegion = env.doc.createElement("div");
|
|
307
|
+
this.#agentRegion.className = "cockpit-agent-region";
|
|
308
|
+
this.#agentDetailRegion = env.doc.createElement("div");
|
|
309
|
+
this.#agentDetailRegion.className = "cockpit-agent-detail-region";
|
|
310
|
+
}
|
|
258
311
|
this.#terminalPanel = env.doc.createElement("section");
|
|
259
312
|
this.#terminalPanel.className = "cockpit-terminal";
|
|
260
313
|
this.#terminalPanel.setAttribute("data-terminal-mode", "idle");
|
|
@@ -282,9 +335,14 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
282
335
|
this.#terminalNote.className = "cockpit-terminal-note";
|
|
283
336
|
this.#terminalNote.setAttribute("data-terminal-note", "none");
|
|
284
337
|
this.#terminalPanel.appendChild(this.#terminalNote);
|
|
338
|
+
// Order MUST match the browser twin (pages/cockpit/mount.js) and its tests: the terminal sits
|
|
339
|
+
// directly beneath the supply list, since cockpit.css keys layout off DOM order (no grid areas).
|
|
340
|
+
// supply list → terminal → past sessions → agent list → agent detail.
|
|
285
341
|
shell.appendChild(this.#listRegion);
|
|
286
|
-
if (this.#pastRegion !== undefined) shell.appendChild(this.#pastRegion);
|
|
287
342
|
shell.appendChild(this.#terminalPanel);
|
|
343
|
+
if (this.#pastRegion !== undefined) shell.appendChild(this.#pastRegion);
|
|
344
|
+
if (this.#agentRegion !== undefined) shell.appendChild(this.#agentRegion);
|
|
345
|
+
if (this.#agentDetailRegion !== undefined) shell.appendChild(this.#agentDetailRegion);
|
|
288
346
|
env.host.appendChild(shell);
|
|
289
347
|
}
|
|
290
348
|
|
|
@@ -292,6 +350,10 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
292
350
|
return this.#shownStream;
|
|
293
351
|
}
|
|
294
352
|
|
|
353
|
+
get currentAgentInstanceKey(): string | undefined {
|
|
354
|
+
return this.#shownAgentInstanceKey;
|
|
355
|
+
}
|
|
356
|
+
|
|
295
357
|
get currentMode(): TerminalMode | undefined {
|
|
296
358
|
return this.#mode;
|
|
297
359
|
}
|
|
@@ -350,6 +412,10 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
350
412
|
// transcripts endpoint that hangs (not just rejects) would otherwise stall #refresh() forever and
|
|
351
413
|
// wedge the live worker list. #refreshPast is single-flight, so a slow fetch can't pile up either.
|
|
352
414
|
void this.#refreshPast(this.#route.kind === "worker" ? this.#route.instance : undefined);
|
|
415
|
+
// Same fire-and-forget discipline for the engine agent-history list: a slow/hung read endpoint must
|
|
416
|
+
// never gate the supply poll's next tick. #refreshAgentHistory is single-flight + bounded. The list
|
|
417
|
+
// is engine-global (not route-filtered), so it takes no route instance — call it with no argument.
|
|
418
|
+
void this.#refreshAgentHistory();
|
|
353
419
|
}
|
|
354
420
|
|
|
355
421
|
#renderRoute(): void {
|
|
@@ -413,6 +479,73 @@ class SupplyCockpit implements SupplyCockpitHandle {
|
|
|
413
479
|
}
|
|
414
480
|
}
|
|
415
481
|
|
|
482
|
+
/** Fetch + render the engine-native SETTLED agent-history list, when the read endpoints are wired.
|
|
483
|
+
* Single-flight + bounded (mirrors {@link #refreshPast}): an engine read fault/hang never blocks the
|
|
484
|
+
* live worker list. The list is engine-global (settled AgentInstances), so it is not route-filtered. */
|
|
485
|
+
async #refreshAgentHistory(): Promise<void> {
|
|
486
|
+
const fetchAgentInstances = this.#env.fetchAgentInstances;
|
|
487
|
+
if (fetchAgentInstances === undefined || this.#agentRegion === undefined) return;
|
|
488
|
+
if (this.#agentRefreshing) {
|
|
489
|
+
this.#agentRefreshPending = true;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
this.#agentRefreshing = true;
|
|
493
|
+
try {
|
|
494
|
+
let report: AgentInstanceListReport;
|
|
495
|
+
try {
|
|
496
|
+
report = await this.#bounded(() => fetchAgentInstances(), "agent-instances");
|
|
497
|
+
} catch (err) {
|
|
498
|
+
if (this.#disposed) return;
|
|
499
|
+
this.#env.onError?.(err);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (this.#disposed || this.#agentRegion === undefined) return;
|
|
503
|
+
try {
|
|
504
|
+
renderAgentSessions(this.#agentRegion, this.#env.doc, agentSessionsView(report), {
|
|
505
|
+
onSelect: (agentInstanceKey) => void this.viewAgentHistory(agentInstanceKey),
|
|
506
|
+
...(this.#shownAgentInstanceKey !== undefined ? { activeInstanceKey: this.#shownAgentInstanceKey } : {}),
|
|
507
|
+
});
|
|
508
|
+
} catch (err) {
|
|
509
|
+
this.#env.onError?.(err);
|
|
510
|
+
}
|
|
511
|
+
} finally {
|
|
512
|
+
this.#agentRefreshing = false;
|
|
513
|
+
if (this.#agentRefreshPending && !this.#disposed) {
|
|
514
|
+
this.#agentRefreshPending = false;
|
|
515
|
+
void this.#refreshAgentHistory();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Fetch + render one engine-native agent instance's SETTLED conversation history (turns + per-turn /
|
|
522
|
+
* instance metrics) into the detail region, keyed by `agentInstanceKey` — the CONSUMER read path
|
|
523
|
+
* (issue #745/#747). Bounded so a hung engine read can't wedge the panel. Read-as-absence: an unknown
|
|
524
|
+
* key renders an explicit empty history, never an error.
|
|
525
|
+
*/
|
|
526
|
+
async viewAgentHistory(agentInstanceKey: string): Promise<void> {
|
|
527
|
+
if (this.#disposed) return;
|
|
528
|
+
const fetchAgentHistory = this.#env.fetchAgentHistory;
|
|
529
|
+
if (fetchAgentHistory === undefined || this.#agentDetailRegion === undefined) return;
|
|
530
|
+
let report: AgentHistoryReport;
|
|
531
|
+
try {
|
|
532
|
+
report = await this.#bounded(() => fetchAgentHistory(agentInstanceKey), "agent-history");
|
|
533
|
+
} catch (err) {
|
|
534
|
+
if (this.#disposed) return;
|
|
535
|
+
this.#env.onError?.(err);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (this.#disposed || this.#agentDetailRegion === undefined) return;
|
|
539
|
+
try {
|
|
540
|
+
this.#shownAgentInstanceKey = agentInstanceKey;
|
|
541
|
+
renderAgentHistory(this.#agentDetailRegion, this.#env.doc, agentHistoryView(report));
|
|
542
|
+
// Re-render the list so the just-selected run shows as active (best-effort).
|
|
543
|
+
void this.#refreshAgentHistory();
|
|
544
|
+
} catch (err) {
|
|
545
|
+
this.#env.onError?.(err);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
416
549
|
/**
|
|
417
550
|
* Race an injected fetch against a timeout so the returned promise ALWAYS settles, even if the fetch
|
|
418
551
|
* HANGS (never settles, not merely rejects). Both single-flight callers below — the past-sessions list
|
package/app/contracts.ts
CHANGED
|
@@ -484,9 +484,17 @@ export const WIRE_CONTRACTS = {
|
|
|
484
484
|
name: "agentTask.agentDefinition",
|
|
485
485
|
owner: "resources/processes/*.bpmn",
|
|
486
486
|
semantics:
|
|
487
|
-
"The engine-native AgentTask marker (issue #745, umbrella #746 — Camunda 8.10 parity). Every `senior:*` agent service task carries `<zeebe:agentDefinition agentType=\"external\" />` INSIDE its `<bpmn:extensionElements>`, COEXISTING with the existing `<zeebe:taskDefinition type=\"senior:*\"/>` dispatch verb (the verb stays, per #464). The marker makes the element eligible for engine-native AgentInstance minting by the worker harness (jwulf/c8ctl-plugin-nano#194): the harness mints Create/Update/Complete AgentInstance/AgentHistory records against the pinned engine (`@nanobpm/engine-wasm` 0.8.6, broker REST, SDK) while the element still emits its NORMAL `senior:*` job. `agentType=\"external\"` means the agent runs OUTSIDE the engine (a remote fleet worker), not an engine-embedded model call. This is the PRODUCER half; the durable AgentInstance/AgentHistory it mints is read back by the Cockpit historical view via `searchAgentInstanceHistory` (the CONSUMER half
|
|
487
|
+
"The engine-native AgentTask marker (issue #745, umbrella #746 — Camunda 8.10 parity). Every `senior:*` agent service task carries `<zeebe:agentDefinition agentType=\"external\" />` INSIDE its `<bpmn:extensionElements>`, COEXISTING with the existing `<zeebe:taskDefinition type=\"senior:*\"/>` dispatch verb (the verb stays, per #464). The marker makes the element eligible for engine-native AgentInstance minting by the worker harness (jwulf/c8ctl-plugin-nano#194): the harness mints Create/Update/Complete AgentInstance/AgentHistory records against the pinned engine (`@nanobpm/engine-wasm` 0.8.6, broker REST, SDK) while the element still emits its NORMAL `senior:*` job. `agentType=\"external\"` means the agent runs OUTSIDE the engine (a remote fleet worker), not an engine-embedded model call. This is the PRODUCER half; the durable AgentInstance/AgentHistory it mints is read back by the Cockpit historical view via `searchAgentInstanceHistory` (the CONSUMER half — see the `agentTask.historyRead` contract; the read path landed on `@nanobpm/urban`'s EngineClient in urban 0.93 / nanobpm/nano-ide#563). It is authored in the hand-written BPMN semantic model, NOT the generated `<bpmndi:…>` DI, and survives `npm run layout` untouched. Add the marker to a NEW `senior:*` agent task — never a second/synonym marker element.",
|
|
488
488
|
shape: '<zeebe:agentDefinition agentType="external" /> (sibling of <zeebe:taskDefinition> in a senior:* service task\'s extensionElements)',
|
|
489
489
|
},
|
|
490
|
+
"agentTask.historyRead": {
|
|
491
|
+
category: "wire",
|
|
492
|
+
name: "agentTask.historyRead",
|
|
493
|
+
owner: "app/agentic/agent-history.ts",
|
|
494
|
+
semantics:
|
|
495
|
+
"The engine-native AgentInstance/AgentHistory READ path (issue #745/#747, umbrella #746 — the CONSUMER half of the PRODUCER `agentTask.agentDefinition` marker). The Cockpit HISTORICAL transcript + per-turn/instance metrics are sourced from the engine read model through the SINGLE engine-read seam — `@nanobpm/urban`'s `EngineClient.searchAgentInstances`/`searchAgentInstanceHistory`/`getAgentInstance` (added in urban 0.93 / nanobpm/nano-ide#563; the escalation's option (a) — NO `orchestration-cluster-api-js` fork / no second broker-REST client, so the read path is exercised by the testkit WASM double, which records read-as-absence — an empty list / null — while a live engine validates the behavioural parity). ONE narrow reader (`AgentHistoryReader`) + ONE projection (`listAgentInstances`/`readAgentHistory`) in app/agentic/agent-history.ts, served by `GET /agentic/agent-instances` + `GET /agentic/agent-instances/{agentInstanceKey}/history`. Correlation keys are the AGENT-INSTANCE / PROCESS-INSTANCE / ELEMENT-INSTANCE keys (the same #544 per-occupancy element-instance handle the relay correlation uses) — NEVER the slash-bearing `job:<jobKey>` relay stream id, so the #744 gateway-proxy bug class is moot for settled history. The token-granular relay stays the LIVE overlay only (settled history = engine, live tail = relay). Advisory read-only (ADR 0056): it observes the engine read model, never activates/completes a job or gates a sequence flow. The wire shapes (`AgentInstance`/`AgentInstanceList`/`AgentHistoryRecord`/`AgentHistory` + the metrics shapes) are declared in openapi.yaml and reuse the `@nanobpm/agentic/transcript` parity conversation grammar the transcript store already models. Consume this ONE read path — do not add a second agent-history client or a synonym endpoint.",
|
|
496
|
+
shape: "GET /agentic/agent-instances[?processInstanceKey&rootProcessInstanceKey&elementId&status] → AgentInstanceList; GET /agentic/agent-instances/{agentInstanceKey}/history[?role&loopIteration&elementInstanceKey] → AgentHistory",
|
|
497
|
+
},
|
|
490
498
|
} as const satisfies Record<string, WireContract>;
|
|
491
499
|
|
|
492
500
|
export const TYPE_CONTRACTS = {
|
package/app/mcpToolSurface.ts
CHANGED
|
@@ -98,8 +98,15 @@ export const MCP_TOOL_COUNT_BUDGET = 60;
|
|
|
98
98
|
* each of the two transcript tools, keeping the `?stream=` form discoverable from the tool surface
|
|
99
99
|
* alone (measured +219 bytes serialized, 83,824 → 84,043 — over the old ceiling's 176-byte
|
|
100
100
|
* headroom). Deliberate, documented growth — not schema fat.
|
|
101
|
+
*
|
|
102
|
+
* RAISE PROVENANCE — 84_500 → 86_500 (#745/#747, umbrella #746): the engine-native agent-history
|
|
103
|
+
* read surface adds two GET tools — `listAgentInstances` and `getAgentInstanceHistory` — that read
|
|
104
|
+
* the durable AgentInstance / AgentHistory channels for wedge triage (kept on the surface per the
|
|
105
|
+
* `x-mcp` convention that read/orient doors stay exposed; only operator-only control doors are
|
|
106
|
+
* excluded, see app/mcpExclusions.test.ts). Measured +1,846 bytes serialized (84,043 → 85,889 —
|
|
107
|
+
* over the old ceiling's 457-byte headroom). Deliberate, documented growth — not schema fat.
|
|
101
108
|
*/
|
|
102
|
-
export const MCP_SURFACE_BYTES_BUDGET =
|
|
109
|
+
export const MCP_SURFACE_BYTES_BUDGET = 86_500;
|
|
103
110
|
|
|
104
111
|
/**
|
|
105
112
|
* The eagerly-loaded curated subset MUST stay materially smaller than the full surface — otherwise it
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- Engine-native AgentInstance/AgentHistory read path — EXPAND phase (issue #745/#747, umbrella #746).
|
|
2
|
+
--
|
|
3
|
+
-- The CONSUMER half of the durable-agent-transcript work. The Cockpit HISTORICAL transcript + per-turn
|
|
4
|
+
-- and instance metrics are now sourced from the engine read model (`searchAgentInstances` /
|
|
5
|
+
-- `searchAgentInstanceHistory` / `getAgentInstance` on `@nanobpm/urban`'s EngineClient — urban 0.93 /
|
|
6
|
+
-- nanobpm/nano-ide#563), served by `GET /agentic/agent-instances` and
|
|
7
|
+
-- `GET /agentic/agent-instances/{agentInstanceKey}/history` (app/agentic/agent-history.ts). Settled
|
|
8
|
+
-- history now derives from engine truth, keyed by agent-instance / process-instance / element-instance
|
|
9
|
+
-- keys — never the slash-bearing `job:<jobKey>` relay stream id.
|
|
10
|
+
--
|
|
11
|
+
-- EXPAND / CONTRACT DISCIPLINE (AGENTS.md — forward-only, additive expand; destructive contract later):
|
|
12
|
+
--
|
|
13
|
+
-- • EXPAND (this phase, no schema change): begin sourcing HISTORICAL reads from engine AgentHistory.
|
|
14
|
+
-- The engine read path holds no app-side table (it reads the engine read model over the SDK/broker),
|
|
15
|
+
-- so there is NO new DDL and NOTHING is dropped here. This migration records the transition in the
|
|
16
|
+
-- schema history so the expand phase is an explicit, ordered ledger entry, mirroring the DDL-less
|
|
17
|
+
-- documented-meaning precedent in 013_merge_train_waiting_lane.sql.
|
|
18
|
+
--
|
|
19
|
+
-- • RETAIN (unchanged): the relay transcript tables `agentic_transcript_stream` /
|
|
20
|
+
-- `agentic_transcript_chunk` (024_agentic_transcript.sql) and the `transcript.readUrl` scheme STAY.
|
|
21
|
+
-- They remain the LIVE OVERLAY — the token-granular tail of a still-running agent — which the engine
|
|
22
|
+
-- settled-history read deliberately does NOT replace. Do not drop them here.
|
|
23
|
+
--
|
|
24
|
+
-- • CONTRACT (a LATER, separate phase — tracked, NOT executed here): once nothing reads the relay
|
|
25
|
+
-- tables / the transcript-URL scheme for SETTLED history (i.e. the live overlay is the only remaining
|
|
26
|
+
-- consumer, or it too has moved), a future forward-only migration DROPs `agentic_transcript_*` and
|
|
27
|
+
-- retires the URL scheme. That destructive drop is intentionally deferred behind a release that has
|
|
28
|
+
-- stopped reading the old shape (expand-and-contract), and is tracked as its own issue so the
|
|
29
|
+
-- remainder is a filed, claimable work item rather than invisible prose.
|
|
30
|
+
-- Deferred-to: nanobpm/nano-workforce#755
|
|
31
|
+
--
|
|
32
|
+
-- Additive and idempotent: no column/table is created, altered, or dropped, so it is safe to apply
|
|
33
|
+
-- forward over any earlier schema and re-runs are no-ops.
|
|
34
|
+
SELECT 1;
|
package/openapi.yaml
CHANGED
|
@@ -1002,6 +1002,250 @@ components:
|
|
|
1002
1002
|
description: The retained chunks with `offset >= from`, in offset order.
|
|
1003
1003
|
items:
|
|
1004
1004
|
$ref: "#/components/schemas/AgenticTranscriptChunk"
|
|
1005
|
+
AgentInstanceMetrics:
|
|
1006
|
+
type: object
|
|
1007
|
+
description: >-
|
|
1008
|
+
Aggregated metrics the engine rolls up on an AgentInstance across all of its model
|
|
1009
|
+
calls (issue #745/#747 — the CONSUMER half). Total tokens consumed, and the count of model /
|
|
1010
|
+
tool calls made. Distinct from the per-turn metrics carried on an AgentHistoryRecord.
|
|
1011
|
+
required:
|
|
1012
|
+
- inputTokens
|
|
1013
|
+
- outputTokens
|
|
1014
|
+
- modelCalls
|
|
1015
|
+
- toolCalls
|
|
1016
|
+
properties:
|
|
1017
|
+
inputTokens:
|
|
1018
|
+
type: integer
|
|
1019
|
+
description: Total input (prompt) tokens consumed across the instance's model calls.
|
|
1020
|
+
outputTokens:
|
|
1021
|
+
type: integer
|
|
1022
|
+
description: Total output (completion) tokens produced across the instance's model calls.
|
|
1023
|
+
modelCalls:
|
|
1024
|
+
type: integer
|
|
1025
|
+
description: The number of LLM model calls the agent made.
|
|
1026
|
+
toolCalls:
|
|
1027
|
+
type: integer
|
|
1028
|
+
description: The number of tool calls the agent dispatched.
|
|
1029
|
+
AgentInstance:
|
|
1030
|
+
type: object
|
|
1031
|
+
description: >-
|
|
1032
|
+
One engine-native AgentInstance as `searchAgentInstances` / `getAgentInstance` report
|
|
1033
|
+
it (issue #745/#747, umbrella #746 — Camunda 8.10 parity). The durable projection of an agent
|
|
1034
|
+
task's run, minted by the worker harness against the `<zeebe:agentDefinition agentType="external"/>`
|
|
1035
|
+
marker (#748). Keyed by `agentInstanceKey` (passed to the history read) and correlated by
|
|
1036
|
+
process / element-instance keys — NEVER the slash-bearing `job:<jobKey>` relay stream id.
|
|
1037
|
+
required:
|
|
1038
|
+
- agentInstanceKey
|
|
1039
|
+
- status
|
|
1040
|
+
- processInstanceKey
|
|
1041
|
+
properties:
|
|
1042
|
+
agentInstanceKey:
|
|
1043
|
+
type: string
|
|
1044
|
+
description: The engine-unique agent-instance key — the identity a caller passes to the history read.
|
|
1045
|
+
status:
|
|
1046
|
+
type: string
|
|
1047
|
+
description: The lifecycle status (the engine's broad `AgentInstanceStatusEnum` — e.g.
|
|
1048
|
+
INITIALIZING / THINKING / TOOL_CALLING / IDLE / COMPLETED), a bare string.
|
|
1049
|
+
processInstanceKey:
|
|
1050
|
+
type: string
|
|
1051
|
+
description: The owning process-instance key.
|
|
1052
|
+
elementId:
|
|
1053
|
+
type: string
|
|
1054
|
+
description: The BPMN element id (the AI-agent task) that owns the instance, when reported.
|
|
1055
|
+
elementInstanceKeys:
|
|
1056
|
+
type: array
|
|
1057
|
+
description: The engine element-instance keys the instance's token(s) occupied (#544) — per-occupancy
|
|
1058
|
+
handles, unambiguous across a looping / retried activity. Omitted when the engine reports none.
|
|
1059
|
+
items:
|
|
1060
|
+
type: string
|
|
1061
|
+
rootProcessInstanceKey:
|
|
1062
|
+
type: string
|
|
1063
|
+
description: The root process-instance key of the owning hierarchy, when reported.
|
|
1064
|
+
processDefinitionKey:
|
|
1065
|
+
type: string
|
|
1066
|
+
description: The owning process-definition key, when reported.
|
|
1067
|
+
processDefinitionId:
|
|
1068
|
+
type: string
|
|
1069
|
+
description: The owning process-definition id, when reported.
|
|
1070
|
+
metrics:
|
|
1071
|
+
$ref: "#/components/schemas/AgentInstanceMetrics"
|
|
1072
|
+
creationDate:
|
|
1073
|
+
type: string
|
|
1074
|
+
description: When the instance was created, ISO-8601, when reported.
|
|
1075
|
+
lastUpdatedDate:
|
|
1076
|
+
type: string
|
|
1077
|
+
description: When the instance was last updated, ISO-8601, when reported.
|
|
1078
|
+
completionDate:
|
|
1079
|
+
type: string
|
|
1080
|
+
description: When the instance completed, ISO-8601 (absent while still running).
|
|
1081
|
+
AgentInstanceList:
|
|
1082
|
+
type: object
|
|
1083
|
+
description: >-
|
|
1084
|
+
The list of engine-native agent instances (issue #745/#747), newest-created first —
|
|
1085
|
+
the cockpit "historical sessions" feed sourced from engine history (not the relay store).
|
|
1086
|
+
required:
|
|
1087
|
+
- count
|
|
1088
|
+
- instances
|
|
1089
|
+
properties:
|
|
1090
|
+
count:
|
|
1091
|
+
type: integer
|
|
1092
|
+
description: The number of instances returned (after any filters).
|
|
1093
|
+
generatedAt:
|
|
1094
|
+
type: string
|
|
1095
|
+
description: When this snapshot was taken, ISO-8601.
|
|
1096
|
+
instances:
|
|
1097
|
+
type: array
|
|
1098
|
+
items:
|
|
1099
|
+
$ref: "#/components/schemas/AgentInstance"
|
|
1100
|
+
AgentHistoryContentBlock:
|
|
1101
|
+
type: object
|
|
1102
|
+
description: One typed content block in a turn's message (Camunda `AgentHistoryMessageContentValue`
|
|
1103
|
+
parity). Exactly one payload is populated per `contentType`.
|
|
1104
|
+
required:
|
|
1105
|
+
- contentType
|
|
1106
|
+
properties:
|
|
1107
|
+
contentType:
|
|
1108
|
+
type: string
|
|
1109
|
+
enum: [TEXT, DOCUMENT, OBJECT, UNSPECIFIED]
|
|
1110
|
+
description: The content type; selects which payload field is populated.
|
|
1111
|
+
text:
|
|
1112
|
+
type: string
|
|
1113
|
+
description: Text payload; populated when `contentType` is TEXT.
|
|
1114
|
+
documentReference:
|
|
1115
|
+
type: string
|
|
1116
|
+
description: Document reference; populated when `contentType` is DOCUMENT.
|
|
1117
|
+
object:
|
|
1118
|
+
description: JSON value payload (any JSON type); populated when `contentType` is OBJECT.
|
|
1119
|
+
AgentHistoryToolCall:
|
|
1120
|
+
type: object
|
|
1121
|
+
description: A tool call embedded in a turn (Camunda `AgentHistoryEmbeddedToolCallValue` parity).
|
|
1122
|
+
required:
|
|
1123
|
+
- toolCallId
|
|
1124
|
+
- toolName
|
|
1125
|
+
- arguments
|
|
1126
|
+
properties:
|
|
1127
|
+
toolCallId:
|
|
1128
|
+
type: string
|
|
1129
|
+
description: The stable tool-call id (pairs a call to its result).
|
|
1130
|
+
toolName:
|
|
1131
|
+
type: string
|
|
1132
|
+
description: The tool that was called.
|
|
1133
|
+
elementId:
|
|
1134
|
+
type: string
|
|
1135
|
+
description: The tool task's BPMN element id, when reported.
|
|
1136
|
+
arguments:
|
|
1137
|
+
type: object
|
|
1138
|
+
description: The arguments passed to the tool (an arbitrary JSON object).
|
|
1139
|
+
additionalProperties: true
|
|
1140
|
+
AgentHistoryTurnMetrics:
|
|
1141
|
+
type: object
|
|
1142
|
+
description: Per-turn metrics (Camunda `AgentHistoryMetricsValue` parity) — the token counts a
|
|
1143
|
+
single turn's LLM call consumed/produced and its wall-clock duration. Distinct from the
|
|
1144
|
+
instance-level `AgentInstanceMetrics`.
|
|
1145
|
+
required:
|
|
1146
|
+
- inputTokens
|
|
1147
|
+
- outputTokens
|
|
1148
|
+
- reasoningTokenCount
|
|
1149
|
+
- cacheCreationTokenCount
|
|
1150
|
+
- cacheReadTokenCount
|
|
1151
|
+
- durationMs
|
|
1152
|
+
properties:
|
|
1153
|
+
inputTokens:
|
|
1154
|
+
type: integer
|
|
1155
|
+
outputTokens:
|
|
1156
|
+
type: integer
|
|
1157
|
+
reasoningTokenCount:
|
|
1158
|
+
type: integer
|
|
1159
|
+
cacheCreationTokenCount:
|
|
1160
|
+
type: integer
|
|
1161
|
+
cacheReadTokenCount:
|
|
1162
|
+
type: integer
|
|
1163
|
+
durationMs:
|
|
1164
|
+
type: integer
|
|
1165
|
+
AgentHistoryRecord:
|
|
1166
|
+
type: object
|
|
1167
|
+
description: >-
|
|
1168
|
+
One agent-instance history item (a turn) as `searchAgentInstanceHistory` reports it
|
|
1169
|
+
(issue #745/#747) — the projection of the engine's `AgentInstanceHistoryItemResult`, i.e. one
|
|
1170
|
+
Camunda `AgentHistoryRecordValue`. Its conversation grammar reuses the transcript parity types,
|
|
1171
|
+
so the engine-read seam and the relay transcript store project the SAME shape (No Drift Surfaces).
|
|
1172
|
+
required:
|
|
1173
|
+
- historyItemKey
|
|
1174
|
+
- agentInstanceKey
|
|
1175
|
+
- loopIteration
|
|
1176
|
+
- role
|
|
1177
|
+
- content
|
|
1178
|
+
- toolCalls
|
|
1179
|
+
- commitStatus
|
|
1180
|
+
properties:
|
|
1181
|
+
historyItemKey:
|
|
1182
|
+
type: string
|
|
1183
|
+
description: The stable, creation-ordered identity of the history item.
|
|
1184
|
+
agentInstanceKey:
|
|
1185
|
+
type: string
|
|
1186
|
+
description: The owning agent-instance key.
|
|
1187
|
+
loopIteration:
|
|
1188
|
+
type: integer
|
|
1189
|
+
description: The agent-loop counter (one LLM call + its tool dispatches + results share an iteration).
|
|
1190
|
+
role:
|
|
1191
|
+
type: string
|
|
1192
|
+
enum: [USER, ASSISTANT, TOOL_RESULT, CONFIGURATION, UNSPECIFIED]
|
|
1193
|
+
description: The conversation role of the turn.
|
|
1194
|
+
content:
|
|
1195
|
+
type: array
|
|
1196
|
+
description: The turn's typed content blocks.
|
|
1197
|
+
items:
|
|
1198
|
+
$ref: "#/components/schemas/AgentHistoryContentBlock"
|
|
1199
|
+
toolCalls:
|
|
1200
|
+
type: array
|
|
1201
|
+
description: The tool calls embedded in the turn.
|
|
1202
|
+
items:
|
|
1203
|
+
$ref: "#/components/schemas/AgentHistoryToolCall"
|
|
1204
|
+
metrics:
|
|
1205
|
+
$ref: "#/components/schemas/AgentHistoryTurnMetrics"
|
|
1206
|
+
commitStatus:
|
|
1207
|
+
type: string
|
|
1208
|
+
description: The engine's COMMITTED / PENDING / DISCARDED commit flag (a bare string).
|
|
1209
|
+
elementInstanceKey:
|
|
1210
|
+
type: string
|
|
1211
|
+
description: The element instance the item was produced under, when reported (best-effort).
|
|
1212
|
+
jobKey:
|
|
1213
|
+
type: string
|
|
1214
|
+
description: The job key the item was produced under, when reported (best-effort).
|
|
1215
|
+
producedAt:
|
|
1216
|
+
type: string
|
|
1217
|
+
description: When the item was produced, ISO-8601, when reported (best-effort).
|
|
1218
|
+
AgentHistory:
|
|
1219
|
+
type: object
|
|
1220
|
+
description: >-
|
|
1221
|
+
One agent instance's durable conversation history (turns + per-turn metrics) sourced
|
|
1222
|
+
from engine `searchAgentInstanceHistory` (issue #745/#747), in conversational order. The cockpit
|
|
1223
|
+
renders the HISTORICAL transcript + metrics from this; the token-granular relay stays the LIVE
|
|
1224
|
+
overlay only. Keyed by `agentInstanceKey` — never the slash-bearing relay stream id (#744 moot).
|
|
1225
|
+
required:
|
|
1226
|
+
- agentInstanceKey
|
|
1227
|
+
- count
|
|
1228
|
+
- records
|
|
1229
|
+
properties:
|
|
1230
|
+
agentInstanceKey:
|
|
1231
|
+
type: string
|
|
1232
|
+
description: The agent-instance key this history belongs to.
|
|
1233
|
+
count:
|
|
1234
|
+
type: integer
|
|
1235
|
+
description: The number of history records (turns) returned.
|
|
1236
|
+
generatedAt:
|
|
1237
|
+
type: string
|
|
1238
|
+
description: When this snapshot was taken, ISO-8601.
|
|
1239
|
+
instance:
|
|
1240
|
+
allOf:
|
|
1241
|
+
- $ref: "#/components/schemas/AgentInstance"
|
|
1242
|
+
description: The owning instance summary (rolled-up metrics + lifecycle), when the engine still
|
|
1243
|
+
reports it. Absent when the instance is unknown/aged out.
|
|
1244
|
+
records:
|
|
1245
|
+
type: array
|
|
1246
|
+
description: The history records (turns), ordered by loopIteration then creation-ordered key.
|
|
1247
|
+
items:
|
|
1248
|
+
$ref: "#/components/schemas/AgentHistoryRecord"
|
|
1005
1249
|
VersionInfo:
|
|
1006
1250
|
type: object
|
|
1007
1251
|
description: The running app's identity (which code is actually live).
|
|
@@ -3945,6 +4189,122 @@ paths:
|
|
|
3945
4189
|
application/json:
|
|
3946
4190
|
schema:
|
|
3947
4191
|
$ref: "#/components/schemas/ErrorBody"
|
|
4192
|
+
/agentic/agent-instances:
|
|
4193
|
+
get:
|
|
4194
|
+
operationId: listAgentInstances
|
|
4195
|
+
summary: >-
|
|
4196
|
+
List engine-native AgentInstances (issue #745/#747, umbrella #746) — the durable agent-task
|
|
4197
|
+
runs the worker harness minted against the `<zeebe:agentDefinition agentType="external"/>` marker,
|
|
4198
|
+
read back from the engine read model (`searchAgentInstances`), newest-created first. Keyed/filtered
|
|
4199
|
+
by process / element / status — NOT the slash-bearing relay stream id. Advisory read-only; never
|
|
4200
|
+
gates control flow. Feeds the cockpit "historical sessions" view (settled history = engine; live
|
|
4201
|
+
tail = relay overlay). Read-as-absence — an engine with no AgentInstance channel returns an empty list.
|
|
4202
|
+
security:
|
|
4203
|
+
- hookSecret: []
|
|
4204
|
+
- {}
|
|
4205
|
+
parameters:
|
|
4206
|
+
- name: processInstanceKey
|
|
4207
|
+
in: query
|
|
4208
|
+
required: false
|
|
4209
|
+
schema:
|
|
4210
|
+
type: string
|
|
4211
|
+
description: Return only agent instances owned by this process instance.
|
|
4212
|
+
- name: rootProcessInstanceKey
|
|
4213
|
+
in: query
|
|
4214
|
+
required: false
|
|
4215
|
+
schema:
|
|
4216
|
+
type: string
|
|
4217
|
+
description: Return only agent instances in this root process-instance hierarchy.
|
|
4218
|
+
- name: elementId
|
|
4219
|
+
in: query
|
|
4220
|
+
required: false
|
|
4221
|
+
schema:
|
|
4222
|
+
type: string
|
|
4223
|
+
description: Return only agent instances owned by this BPMN element (the AI-agent task).
|
|
4224
|
+
- name: status
|
|
4225
|
+
in: query
|
|
4226
|
+
required: false
|
|
4227
|
+
schema:
|
|
4228
|
+
type: string
|
|
4229
|
+
description: Return only agent instances in this lifecycle status (the engine's `AgentInstanceStatusEnum`).
|
|
4230
|
+
responses:
|
|
4231
|
+
"200":
|
|
4232
|
+
description: The engine-native agent instances matching the filters.
|
|
4233
|
+
content:
|
|
4234
|
+
application/json:
|
|
4235
|
+
schema:
|
|
4236
|
+
$ref: "#/components/schemas/AgentInstanceList"
|
|
4237
|
+
"401":
|
|
4238
|
+
description: Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).
|
|
4239
|
+
content:
|
|
4240
|
+
application/json:
|
|
4241
|
+
schema:
|
|
4242
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
4243
|
+
"503":
|
|
4244
|
+
description: No engine read path available (no engine client configured).
|
|
4245
|
+
content:
|
|
4246
|
+
application/json:
|
|
4247
|
+
schema:
|
|
4248
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
4249
|
+
/agentic/agent-instances/{agentInstanceKey}/history:
|
|
4250
|
+
get:
|
|
4251
|
+
operationId: getAgentInstanceHistory
|
|
4252
|
+
summary: >-
|
|
4253
|
+
Fetch one AgentInstance's durable conversation history (turns + per-turn metrics) from
|
|
4254
|
+
engine `searchAgentInstanceHistory` (issue #745/#747), in conversational order, with the owning
|
|
4255
|
+
instance's rolled-up metrics when still reported. The cockpit renders the HISTORICAL transcript +
|
|
4256
|
+
metrics from this — keyed by `agentInstanceKey`, never the slash-bearing relay stream id (#744
|
|
4257
|
+
moot). Advisory read-only; never gates control flow. Read-as-absence — an unknown key / an engine
|
|
4258
|
+
with no AgentHistory channel returns an empty history.
|
|
4259
|
+
security:
|
|
4260
|
+
- hookSecret: []
|
|
4261
|
+
- {}
|
|
4262
|
+
parameters:
|
|
4263
|
+
- name: agentInstanceKey
|
|
4264
|
+
in: path
|
|
4265
|
+
required: true
|
|
4266
|
+
schema:
|
|
4267
|
+
type: string
|
|
4268
|
+
description: The engine agent-instance key whose history to read (from `listAgentInstances`).
|
|
4269
|
+
- name: role
|
|
4270
|
+
in: query
|
|
4271
|
+
required: false
|
|
4272
|
+
schema:
|
|
4273
|
+
type: string
|
|
4274
|
+
enum: [USER, ASSISTANT, TOOL_RESULT, CONFIGURATION, UNSPECIFIED]
|
|
4275
|
+
description: Only turns with this conversation role. Any value from the enum is
|
|
4276
|
+
forwarded verbatim to the engine as the filter.
|
|
4277
|
+
- name: loopIteration
|
|
4278
|
+
in: query
|
|
4279
|
+
required: false
|
|
4280
|
+
schema:
|
|
4281
|
+
type: integer
|
|
4282
|
+
description: Only turns produced in this agent-loop iteration.
|
|
4283
|
+
- name: elementInstanceKey
|
|
4284
|
+
in: query
|
|
4285
|
+
required: false
|
|
4286
|
+
schema:
|
|
4287
|
+
type: string
|
|
4288
|
+
description: Only turns produced under this element instance.
|
|
4289
|
+
responses:
|
|
4290
|
+
"200":
|
|
4291
|
+
description: The agent instance's history (turns + metrics), possibly empty.
|
|
4292
|
+
content:
|
|
4293
|
+
application/json:
|
|
4294
|
+
schema:
|
|
4295
|
+
$ref: "#/components/schemas/AgentHistory"
|
|
4296
|
+
"401":
|
|
4297
|
+
description: Missing/invalid shared secret (only when NANO_PR_WEBHOOK_SECRET is set).
|
|
4298
|
+
content:
|
|
4299
|
+
application/json:
|
|
4300
|
+
schema:
|
|
4301
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
4302
|
+
"503":
|
|
4303
|
+
description: No engine read path available (no engine client configured).
|
|
4304
|
+
content:
|
|
4305
|
+
application/json:
|
|
4306
|
+
schema:
|
|
4307
|
+
$ref: "#/components/schemas/ErrorBody"
|
|
3948
4308
|
/reconcile:
|
|
3949
4309
|
post:
|
|
3950
4310
|
operationId: reconcileEngineState
|