@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.
@@ -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, deferred until the broker read API is reachable from the app's EngineClient). 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.",
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 = {
@@ -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 = 84_500;
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