@obversa/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/dist/api.d.ts +72 -0
  4. package/dist/api.js +9775 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/artifacts/conformance.d.ts +1 -0
  7. package/dist/artifacts/file-store.d.ts +8 -0
  8. package/dist/artifacts/store.d.ts +1 -0
  9. package/dist/callback/approval.d.ts +8 -0
  10. package/dist/callback/client.d.ts +38 -0
  11. package/dist/callback/gate.d.ts +1 -0
  12. package/dist/callback/stored-client.d.ts +5 -0
  13. package/dist/chunk-3L6YNPN6.js +3 -0
  14. package/dist/chunk-3L6YNPN6.js.map +1 -0
  15. package/dist/chunk-5GLEABOU.js +3 -0
  16. package/dist/chunk-5GLEABOU.js.map +1 -0
  17. package/dist/chunk-DEW5R23M.js +338 -0
  18. package/dist/chunk-DEW5R23M.js.map +1 -0
  19. package/dist/chunk-DV5P4QLI.js +3056 -0
  20. package/dist/chunk-DV5P4QLI.js.map +1 -0
  21. package/dist/chunk-NIBHM5I5.js +34 -0
  22. package/dist/chunk-NIBHM5I5.js.map +1 -0
  23. package/dist/chunk-RZLMX3IA.js +368 -0
  24. package/dist/chunk-RZLMX3IA.js.map +1 -0
  25. package/dist/core/agent-md.d.ts +36 -0
  26. package/dist/core/agent.d.ts +86 -0
  27. package/dist/core/approval-job.d.ts +43 -0
  28. package/dist/core/assert-graph.d.ts +34 -0
  29. package/dist/core/budget.d.ts +49 -0
  30. package/dist/core/concurrency.d.ts +2 -0
  31. package/dist/core/condition.d.ts +209 -0
  32. package/dist/core/context.d.ts +36 -0
  33. package/dist/core/cost.d.ts +59 -0
  34. package/dist/core/dag.d.ts +20 -0
  35. package/dist/core/decision.d.ts +29 -0
  36. package/dist/core/describe.d.ts +56 -0
  37. package/dist/core/engine-meta.d.ts +5 -0
  38. package/dist/core/env-overlay.d.ts +35 -0
  39. package/dist/core/errors.d.ts +46 -0
  40. package/dist/core/feedback.d.ts +68 -0
  41. package/dist/core/git.d.ts +152 -0
  42. package/dist/core/guards.d.ts +70 -0
  43. package/dist/core/isolated.d.ts +40 -0
  44. package/dist/core/job.d.ts +134 -0
  45. package/dist/core/limits.d.ts +22 -0
  46. package/dist/core/loop.d.ts +24 -0
  47. package/dist/core/merge.d.ts +30 -0
  48. package/dist/core/pipeline.d.ts +35 -0
  49. package/dist/core/process.d.ts +11 -0
  50. package/dist/core/progress.d.ts +82 -0
  51. package/dist/core/redact.d.ts +1 -0
  52. package/dist/core/stats.d.ts +63 -0
  53. package/dist/core/team.d.ts +34 -0
  54. package/dist/core/text.d.ts +8 -0
  55. package/dist/core/tournament.d.ts +25 -0
  56. package/dist/core/types.d.ts +650 -0
  57. package/dist/engines/command-runner.d.ts +1 -0
  58. package/dist/engines/conformance.d.ts +1 -0
  59. package/dist/engines/engine.d.ts +4 -0
  60. package/dist/engines/failure.d.ts +1 -0
  61. package/dist/engines/fallback.d.ts +35 -0
  62. package/dist/engines/message-map.d.ts +1 -0
  63. package/dist/engines/mock.d.ts +1 -0
  64. package/dist/engines/preflight.d.ts +36 -0
  65. package/dist/env/command.d.ts +50 -0
  66. package/dist/env/command.js +65 -0
  67. package/dist/env/command.js.map +1 -0
  68. package/dist/env/environment.d.ts +4 -0
  69. package/dist/env/mock.d.ts +24 -0
  70. package/dist/events/conformance.d.ts +1 -0
  71. package/dist/events/envelope.d.ts +1 -0
  72. package/dist/events/jsonl-store.d.ts +8 -0
  73. package/dist/events/store.d.ts +1 -0
  74. package/dist/graph/commands.d.ts +5 -0
  75. package/dist/graph/conformance.d.ts +32 -0
  76. package/dist/graph/kernel.d.ts +5 -0
  77. package/dist/graph/plan.d.ts +1 -0
  78. package/dist/graph/type.d.ts +7 -0
  79. package/dist/graph/value.d.ts +1 -0
  80. package/dist/graph-types/dag.d.ts +137 -0
  81. package/dist/graph-types/loop.d.ts +194 -0
  82. package/dist/graph-types/team.d.ts +68 -0
  83. package/dist/memory.d.ts +82 -0
  84. package/dist/memory.js +397 -0
  85. package/dist/memory.js.map +1 -0
  86. package/dist/proof/acceptance.d.ts +5 -0
  87. package/dist/proof/artifact.d.ts +6 -0
  88. package/dist/proof/cache.d.ts +4 -0
  89. package/dist/runtime/attempt.d.ts +19 -0
  90. package/dist/runtime/budget.d.ts +4 -0
  91. package/dist/runtime/engine-availability.d.ts +18 -0
  92. package/dist/runtime/graph-executor.d.ts +7 -0
  93. package/dist/runtime/monitor.d.ts +80 -0
  94. package/dist/runtime/node-lifecycle.d.ts +84 -0
  95. package/dist/runtime/paths.d.ts +2 -0
  96. package/dist/runtime/persist.d.ts +31 -0
  97. package/dist/runtime/preflight-record.d.ts +149 -0
  98. package/dist/runtime/process-tree.d.ts +1 -0
  99. package/dist/runtime/result-contract.d.ts +4 -0
  100. package/dist/runtime/result-parts.d.ts +1 -0
  101. package/dist/runtime/run-definition.d.ts +24 -0
  102. package/dist/runtime/run-event.d.ts +9 -0
  103. package/dist/runtime/runner.d.ts +139 -0
  104. package/dist/runtime/supervisor.d.ts +111 -0
  105. package/dist/runtime/team-rooms.d.ts +13 -0
  106. package/dist/runtime/workspace-policy.d.ts +25 -0
  107. package/dist/storage/error.d.ts +1 -0
  108. package/dist/storage/id.d.ts +1 -0
  109. package/dist/storage/local.d.ts +12 -0
  110. package/dist/storage/local.js +1492 -0
  111. package/dist/storage/local.js.map +1 -0
  112. package/dist/testing.d.ts +15 -0
  113. package/dist/testing.js +274 -0
  114. package/dist/testing.js.map +1 -0
  115. package/dist/workflow-agent-response.d.ts +3 -0
  116. package/dist/workflow-support.d.ts +36 -0
  117. package/dist/workflow-support.js +5 -0
  118. package/dist/workflow-support.js.map +1 -0
  119. package/dist/workflow.d.ts +73 -0
  120. package/dist/workspace/conformance.d.ts +1 -0
  121. package/dist/workspace/git-provider.d.ts +26 -0
  122. package/dist/workspace/provider.d.ts +2 -0
  123. package/package.json +91 -0
@@ -0,0 +1,650 @@
1
+ /**
2
+ * The core contract: one universal runnable unit and two supporting types.
3
+ *
4
+ * - a `Job` — a unit of work that runs once and returns an `Outcome`.
5
+ * Any size: a single agent turn, or a whole nested loop.
6
+ * - a `Condition` — a question answered against the current context (a `when`).
7
+ * - a `Loop` — produced by `loop()`, and is itself a `Job`.
8
+ *
9
+ * Because a `Loop` is a `Job`, a loop's `body`/`review`/any stage can be
10
+ * another `loop(...)`, so loop jobs nest.
11
+ *
12
+ * Jenkins mapping: Job≈job/pipeline, Engine≈agent/node (where it runs),
13
+ * `start`≈trigger, `Condition`≈`when`, `review`+`onComplete`≈`post`,
14
+ * `retry`≈`retry`/`catchError`. The stage/DAG machinery is not imported here:
15
+ * the primitive is the loop, not a pipeline.
16
+ */
17
+ import type { Engine, EngineRef, UsageReceipt } from '../engines/engine.js';
18
+ import type { Memory } from '@obversa/api';
19
+ import type { LoopError } from './errors.js';
20
+ import type { EnvHandle, Environment } from '../env/environment.js';
21
+ import type { JsonValue, RunBrief } from '../graph/value.js';
22
+ import type { CallbackEvent, ClaimResult, ReleaseResult, SubmitResult } from '../callback/client.js';
23
+ import type { CallbackRequest } from '../callback/gate.js';
24
+ /**
25
+ * The client a run's questions go through: the in-memory `CallbackClient`,
26
+ * or the stored client (`createStoredCallbackClient`) whose questions survive
27
+ * a process exit. Each method may answer at once or as a promise; a step
28
+ * that asks awaits both shapes alike. Written as the awaited shape here, so
29
+ * the core types import no storage module.
30
+ */
31
+ export interface RunCallbacks {
32
+ post(request: CallbackRequest): void | Promise<void>;
33
+ listPending(): readonly CallbackRequest[] | Promise<readonly CallbackRequest[]>;
34
+ claim(requestId: string, routerId: string): ClaimResult | Promise<ClaimResult>;
35
+ submit(requestId: string, claimToken: string, routerId: string, requestDigest: string, response: JsonValue): SubmitResult | Promise<SubmitResult>;
36
+ release(requestId: string, claimToken: string): ReleaseResult | Promise<ReleaseResult>;
37
+ supersede(requestId: string, supersededBy: string): void | Promise<void>;
38
+ history(requestId?: string): readonly CallbackEvent[] | Promise<readonly CallbackEvent[]>;
39
+ }
40
+ /** Terminal disposition of a `Job`. */
41
+ export type OutcomeStatus = 'pass' | 'fail' | 'aborted' | 'exhausted' | 'paused';
42
+ /**
43
+ * How the run reacts to a provider rate limit, account/usage allowance, or its
44
+ * own token budget. `auto` (the default) waits when the reset is known and
45
+ * within `maxWaitMs`, else pauses the run.
46
+ */
47
+ export type LimitPolicy = 'auto' | 'wait' | 'exit' | 'fail';
48
+ export interface Outcome {
49
+ status: OutcomeStatus;
50
+ /** 0..1 confidence, when the outcome was decided by an agent validator. */
51
+ confidence?: number;
52
+ /**
53
+ * True when an engine finished after its soft timeout but before the hard
54
+ * timeout/grace boundary. The result is usable, but supervisors can still
55
+ * surface that it landed late.
56
+ */
57
+ late?: boolean;
58
+ /** One-line human summary for logs and host status views. */
59
+ summary?: string;
60
+ /** Arbitrary payload threaded to the next step / surfaced to the caller. */
61
+ data?: unknown;
62
+ /** Present when `status` is driven by a failure. */
63
+ error?: LoopError;
64
+ /**
65
+ * Present when a loop ended `exhausted` because its `noProgress` detector
66
+ * tripped: the evidence that the last `window` iterations reached no state
67
+ * the run had not already seen. Lets a supervisor distinguish a stall from a
68
+ * hit iteration cap without parsing the summary.
69
+ */
70
+ stall?: StallReport;
71
+ /**
72
+ * Structured feedback asking an earlier unit of work for another pass, and the
73
+ * single channel for it. When `revision.target` is set, the enclosing `dag`
74
+ * re-runs that node and its transitive dependents with `revision.reason`
75
+ * threaded in as `lastReview`, bounded by `DagConfig.maxKickbacks` (default
76
+ * 0 — ignored). The re-run happens in execution only; the graph stays acyclic
77
+ * and the re-run budget guarantees termination. Produce one with
78
+ * `revisionRequest({ target, findings })` or `kickback(to, reason)`.
79
+ */
80
+ revision?: RevisionRequest;
81
+ }
82
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
83
+ /**
84
+ * Where a job's code lives: a working directory and (when it is a git repo) the
85
+ * branch checked out there. Sequential jobs share one `Workspace`; concurrent
86
+ * writers can fork into isolated worktrees. Default: the process working directory.
87
+ */
88
+ export interface Workspace {
89
+ /** Absolute path to the working tree this job operates in. */
90
+ readonly dir: string;
91
+ /** The branch checked out in `dir`, when known (undefined on detached HEAD). */
92
+ readonly branch?: string;
93
+ }
94
+ export type FeedbackActionSeverity = 'block' | 'should-fix' | 'nice-to-have' | 'approve';
95
+ export type FeedbackSeverity = FeedbackActionSeverity;
96
+ export type FeedbackDecision = 'accepted' | 'rejected' | 'deferred' | 'escalated';
97
+ export interface FeedbackFinding {
98
+ reviewer?: string;
99
+ severity?: FeedbackSeverity;
100
+ decision?: FeedbackDecision;
101
+ /**
102
+ * The ownership surface this finding belongs to. A review/fix loop may be
103
+ * scoped to a smaller surface and escalate findings outside it instead of
104
+ * counting them against convergence.
105
+ */
106
+ scope?: string;
107
+ evidence: string;
108
+ recommendation?: string;
109
+ }
110
+ export type RevisionRerun = 'target-and-dependents';
111
+ export interface RevisionRequest {
112
+ target?: string;
113
+ reason: string;
114
+ findings?: FeedbackFinding[];
115
+ rerun?: RevisionRerun;
116
+ source?: string;
117
+ decision?: FeedbackDecision;
118
+ }
119
+ export interface GraphPosition {
120
+ dag: string;
121
+ node: string;
122
+ /** Which run of this node the DAG is executing, starting at 1. */
123
+ attempt?: number;
124
+ path: readonly string[];
125
+ needs: readonly string[];
126
+ dependents: readonly string[];
127
+ /** One sentence describing what this node does, when declared. */
128
+ desc?: string;
129
+ /** The written acceptance criterion for this node, when declared. */
130
+ gate?: string;
131
+ }
132
+ /**
133
+ * Threaded into every `Job`. Carries the engine, the abort signal, the event
134
+ * sink, a mutable scratchpad shared across the run, the workspace the work
135
+ * happens in, and the position in the loop tree (used by hosts and stats).
136
+ */
137
+ export interface JobContext {
138
+ /** Default engine for this run, when the host supplied one. */
139
+ readonly engine?: Engine;
140
+ /**
141
+ * Resolve an engine for a step. A name comes only from the host-supplied
142
+ * map; a ready-made `Engine` passes through; no argument uses the run default.
143
+ */
144
+ resolveEngine(ref?: EngineRef): Engine;
145
+ readonly signal: AbortSignal;
146
+ /** Stable id for this run when one was assigned by the runner. */
147
+ readonly runId?: string;
148
+ emit(event: LoopEvent): void;
149
+ /** Immutable run brief shared by every job in this run. */
150
+ readonly params: RunBrief;
151
+ /** Shared mutable state for the whole run (e.g. accumulating notes). */
152
+ readonly state: Record<string, unknown>;
153
+ /** Memory available to jobs in this run, when the caller supplied it. */
154
+ readonly memory?: Memory;
155
+ /**
156
+ * The run's callbacks client: where a step posts a question for a person or
157
+ * an outside router, and where the answer is found again on a resume. Every
158
+ * run has one, a fresh in-memory client by default; pass `callbacks` to
159
+ * `run` to keep questions across runs.
160
+ */
161
+ readonly callbacks?: RunCallbacks;
162
+ /** Wait for an outside answer, or return paused (the default). */
163
+ readonly onCallback?: 'wait' | 'exit';
164
+ /** Where this job's code lives — the working dir and branch (the substrate). */
165
+ readonly workspace: Workspace;
166
+ /** The running environment for this workspace, when one is up (gate target). */
167
+ readonly environment?: EnvHandle;
168
+ /**
169
+ * Env vars pinned for this scope and everything beneath it — gate commands,
170
+ * judge calls, and the subprocesses agent leaves spawn. Layered over
171
+ * `ctx.environment?.env`; set via `withEnv()`.
172
+ */
173
+ readonly envOverlay?: Record<string, string>;
174
+ /** 1-based iteration index within the enclosing loop; 0 outside a loop. */
175
+ readonly iteration: number;
176
+ /** Nesting depth (root steps are 0). */
177
+ readonly depth: number;
178
+ /** Loop/step names from the root down to here. */
179
+ readonly path: readonly string[];
180
+ /** The current DAG node position, when this job is running inside a dag node. */
181
+ readonly graph?: GraphPosition;
182
+ /**
183
+ * Timeout inherited by jobs in this scope. A node can set it once and agent
184
+ * leaves beneath it receive the same cap unless they override it directly.
185
+ */
186
+ readonly timeoutMs?: number;
187
+ /** Extra hard-timeout window after `timeoutMs` for accepting a completed turn. */
188
+ readonly timeoutGraceMs?: number;
189
+ /**
190
+ * Inside a `dag` node: the outcomes of the nodes this node `needs`, by the
191
+ * same names its config uses (`needs: ['test']` gives `ctx.needs.test`). A
192
+ * branch's `when` reads the deciding node's outcome here, so a command can
193
+ * choose the path the graph takes next with no agent deciding. Undefined
194
+ * outside a dag node.
195
+ */
196
+ readonly needs?: Readonly<Record<string, Outcome>>;
197
+ /** The previous body outcome in the enclosing loop (used by `review`/gates). */
198
+ readonly lastOutcome?: Outcome;
199
+ /** The most recent failed-review outcome, so a restart can act on it. */
200
+ readonly lastReview?: Outcome;
201
+ /**
202
+ * The previous iteration's explicit `until`-gate evaluation (met or not),
203
+ * including its diagnostic `output`. Undefined when the loop has no explicit
204
+ * `until`, on the first iteration, and outside loop jobs.
205
+ */
206
+ readonly lastGate?: ConditionResult;
207
+ /** How a loop reacts to a rate/quota/budget limit. Default `auto`. */
208
+ readonly onLimit: LimitPolicy;
209
+ /** Cap on an interruptible limit-wait under `auto`/`wait`. */
210
+ readonly maxWaitMs: number;
211
+ log(message: string, level?: LogLevel): void;
212
+ }
213
+ export interface NoProgressConfig {
214
+ /** Consecutive no-progress iterations before the loop stalls out. Default 3. */
215
+ window?: number;
216
+ /** Confidence improvement required to count as progress. Default 0.02. */
217
+ minConfidenceDelta?: number;
218
+ /** Optional progress state outside the workspace. */
219
+ signal?: (ctx: JobContext, last: Outcome | undefined) => string | number | undefined | Promise<string | number | undefined>;
220
+ /** Read the workspace fingerprint each iteration. Default true. */
221
+ workspace?: boolean;
222
+ /** Fingerprint a failing deterministic gate's output. Default false. */
223
+ gate?: boolean;
224
+ }
225
+ export type NoProgressInput = number | NoProgressConfig;
226
+ export interface StallReport {
227
+ readonly window: number;
228
+ readonly iterations: number[];
229
+ readonly reason: string;
230
+ readonly evidence: string[];
231
+ }
232
+ export type Job = (ctx: JobContext) => Promise<Outcome>;
233
+ /**
234
+ * The introspectable shape of a `Job`, attached by the builders (`loop`, `dag`,
235
+ * `agentJob`, ...) and read back by validation and description tools or any
236
+ * agent that wants to inspect a loop without running it. Held in a side table
237
+ * (see `core/describe.ts`), so the `Job` type stays a plain function. `kind`
238
+ * names the builder; the rest is builder-specific (a loop carries its gate and
239
+ * body, a dag carries its nodes).
240
+ */
241
+ export interface JobMeta {
242
+ kind: 'loop' | 'dag' | 'agent' | 'fn' | (string & {});
243
+ name?: string;
244
+ [key: string]: unknown;
245
+ }
246
+ export interface ConditionResult {
247
+ met: boolean;
248
+ /** 0..1 when an agent decided this; undefined for deterministic checks. */
249
+ confidence?: number;
250
+ reason: string;
251
+ /**
252
+ * Verbatim diagnostic output backing the verdict — the evidence, not the
253
+ * one-line `reason` (a failing command's stdout/stderr, a judge's full
254
+ * findings). Producers truncate and secret-scrub it. Flows into
255
+ * `loop:condition` events and to the next loop body via `ctx.lastGate`.
256
+ */
257
+ output?: string;
258
+ }
259
+ /**
260
+ * The single condition primitive. A question answered against the context and
261
+ * the most recent body outcome. Both deterministic checks and agent validators
262
+ * are this same type — `agentCheck(...)` simply returns one.
263
+ */
264
+ export type Condition = (ctx: JobContext, last: Outcome | undefined) => Promise<ConditionResult>;
265
+ /** A bare deterministic predicate — accepted anywhere a `Condition` is. */
266
+ export type RawPredicate = (ctx: JobContext, last: Outcome | undefined) => boolean | Promise<boolean>;
267
+ /**
268
+ * What a gate (`start`/`until`/`stopOn`) accepts: one item or many, freely
269
+ * mixing deterministic predicates and agent conditions. Arrays are reduced to
270
+ * the single `Condition` primitive by `toCondition` (default: all must hold;
271
+ * wrap in `any(...)` for or-semantics).
272
+ */
273
+ export type ConditionInput = Condition | RawPredicate | ConditionInput[];
274
+ export interface RetryPolicy {
275
+ /** On a thrown error in the body: keep looping, or end the loop as failed. */
276
+ onError: 'continue' | 'fail';
277
+ /** Cap on consecutive errored iterations before forcing 'fail'. */
278
+ maxConsecutive?: number;
279
+ backoffMs?: number;
280
+ }
281
+ export interface LoopConfig {
282
+ name: string;
283
+ /** The work done each iteration. Pass another `loop(...)` to nest. */
284
+ body: Job;
285
+ /** Gate before iterating; one or many checks. Unmet => loop is `aborted`. */
286
+ start?: ConditionInput;
287
+ /** After each body run; one or many checks. Met => stop (then `review`). */
288
+ until?: ConditionInput;
289
+ /**
290
+ * Evaluate the explicit `until` gate once at iteration 0, before the body.
291
+ * A met gate completes immediately (after `review`, when configured); an
292
+ * unmet verdict is exposed to the first body iteration as `ctx.lastGate`.
293
+ * Requires `until`.
294
+ */
295
+ checkFirst?: boolean;
296
+ /** Hard early-exit per iteration; one or many checks. Met => `aborted`. */
297
+ stopOn?: ConditionInput;
298
+ /** Iteration cap. Reached without passing => `exhausted`. */
299
+ max?: number;
300
+ /**
301
+ * The third hard stop, alongside `max` and `budget`: end the loop `exhausted`
302
+ * when this many consecutive iterations make no observable progress — no
303
+ * workspace state the run has not already visited, no custom `signal` value
304
+ * not already seen, no gate confidence beating its previous best. A bare
305
+ * number is the window (`3` ⇒ three flat iterations); pass a `NoProgressConfig`
306
+ * for the full knobs. Off by default: a polling loop legitimately makes no
307
+ * progress until the outside world changes, so this is opt-in like `commit`.
308
+ * The stalled outcome carries the evidence as `Outcome.stall`.
309
+ */
310
+ noProgress?: NoProgressInput;
311
+ /**
312
+ * Runs when `until` is met. If it returns `pass`, the loop completes.
313
+ * Any other status re-enters the loop — this is the "review fails, run the
314
+ * main loop again" behaviour, and `review` may itself be a `loop(...)`. The
315
+ * failed review outcome is exposed to the next iteration as `ctx.lastReview`.
316
+ */
317
+ review?: Job;
318
+ /**
319
+ * Cap on consecutive failed reviews before giving up with `exhausted`.
320
+ * Bounds the review-restart cycle independently of `max`; strongly advised
321
+ * when `review` is set with no `max` (otherwise a worker/reviewer standoff
322
+ * never terminates). Default: unbounded (relies on `max`).
323
+ */
324
+ maxReviewRestarts?: number;
325
+ /** Delay between iterations (polling intervals). Interruptible by abort. */
326
+ delayMs?: number;
327
+ retry?: RetryPolicy;
328
+ /** Side-effect hook after each iteration (logging, custom stats). */
329
+ onIteration?: (outcome: Outcome, ctx: JobContext) => void | Promise<void>;
330
+ /**
331
+ * Post-action run exactly once when the loop ends, whatever the status
332
+ * (Jenkins `post { always }`). For cleanup, notifications, final logging.
333
+ */
334
+ onComplete?: (outcome: Outcome, ctx: JobContext) => void | Promise<void>;
335
+ }
336
+ export interface DagNode {
337
+ job: Job;
338
+ /**
339
+ * Names of nodes that must finish before this one runs; a required producer
340
+ * must pass, a failed optional producer does not block.
341
+ */
342
+ needs?: string | string[];
343
+ /** One sentence describing what this node does. */
344
+ desc?: string;
345
+ /** The written acceptance criterion for this node. */
346
+ gate?: string;
347
+ /** Gate (one or many) — when unmet the node is skipped, not failed. */
348
+ when?: ConditionInput;
349
+ /** A failure here does not fail the DAG, and does not block dependents. */
350
+ optional?: boolean;
351
+ /**
352
+ * Run this node in its own git worktree on a fork branch (branches-as-teams).
353
+ * Concurrent writers then never collide on files or the index, and the node's
354
+ * committed work lands back into the parent branch on pass. Defaults to the
355
+ * DAG's `isolation`. Opt-in: forking a worktree has a real setup cost, and a
356
+ * read-only node never needs it.
357
+ */
358
+ isolate?: boolean;
359
+ /**
360
+ * Timeout inherited by this node's subtree. Agent leaves and agent judges use
361
+ * it unless they set their own timeout.
362
+ */
363
+ timeoutMs?: number;
364
+ /** Extra hard-timeout window after `timeoutMs` for completed-but-late leaves. */
365
+ timeoutGraceMs?: number;
366
+ /**
367
+ * Restrict which upstream nodes this node may kick work back to. When set, a
368
+ * `kickback` whose `to` is not in this list is rejected (logged, not run); when
369
+ * unset, any ancestor is a valid target. A kickback to a non-ancestor is always
370
+ * rejected. Only consulted when the dag's `maxKickbacks` is set.
371
+ */
372
+ acceptsKickbackTo?: string[];
373
+ }
374
+ export type KickbackBudget = number | Readonly<Record<string, number>>;
375
+ export interface DagConfig {
376
+ name: string;
377
+ /** Node name → a `DagNode`, or a bare `Job` (shorthand for no deps/gates). */
378
+ nodes: Record<string, DagNode | Job>;
379
+ /** Max nodes running at once. Default: 4. */
380
+ concurrency?: number;
381
+ /** When a required node fails, abort the rest. Default: true. */
382
+ stopOnError?: boolean;
383
+ /**
384
+ * Default isolation for nodes that do not set `isolate`. `'worktree'` runs each
385
+ * such node in its own worktree + fork branch, landed back on pass. Off by
386
+ * default — the shared workspace.
387
+ */
388
+ isolation?: 'worktree';
389
+ /**
390
+ * Give each ISOLATED node its own environment, brought up when its worktree
391
+ * forks and torn down when it joins — so every branch-team gets its own stage,
392
+ * named by the provider from the workspace branch. Requires isolation; a
393
+ * non-isolated node shares the workspace and gets no per-team env.
394
+ */
395
+ environment?: Environment;
396
+ /**
397
+ * What to do when an isolated node's land-back conflicts. `'fail'` (default)
398
+ * fails the node. `'synthesize'` runs `mergeSynthesis`: an agent resolves the
399
+ * conflict and writes a synthesised merge body.
400
+ */
401
+ onConflict?: 'fail' | 'synthesize';
402
+ /**
403
+ * Re-run budget for cross-stage feedback. A number keeps the graph-wide
404
+ * counter. A map gives each target node its own counter. Default 0 means
405
+ * kickbacks are ignored and behaviour is unchanged.
406
+ */
407
+ maxKickbacks?: KickbackBudget;
408
+ }
409
+ /** Per-node disposition within a DAG run. */
410
+ export type NodePhase = 'start' | 'skip' | 'done';
411
+ export type ProofKind = 'html' | 'image' | 'markdown' | 'table' | 'json';
412
+ export interface ProofArtifact {
413
+ kind: ProofKind;
414
+ title?: string;
415
+ description?: string;
416
+ mediaType?: string;
417
+ meta?: Record<string, string | number | boolean | null>;
418
+ path?: string;
419
+ data?: JsonValue;
420
+ }
421
+ export interface ProofRecord {
422
+ name: string;
423
+ path: string[];
424
+ artifact: ProofArtifact;
425
+ }
426
+ /** The token totals a run reports. */
427
+ export interface UsageTotals {
428
+ readonly inputTokens: number;
429
+ readonly outputTokens: number;
430
+ readonly cacheCreationInputTokens?: number;
431
+ readonly cacheReadInputTokens?: number;
432
+ /** Calls that reported no usage, so a total can say what it is missing. */
433
+ readonly unmeasuredCalls?: number;
434
+ }
435
+ export type ConditionKind = 'start' | 'until' | 'stopOn';
436
+ export type LoopEvent = {
437
+ kind: 'run:start';
438
+ ts: number;
439
+ path: [];
440
+ runId?: string;
441
+ recordPath?: string;
442
+ } | {
443
+ kind: 'run:end';
444
+ ts: number;
445
+ path: [];
446
+ outcome: Outcome;
447
+ usage: UsageTotals;
448
+ runId?: string;
449
+ recordPath?: string;
450
+ } | {
451
+ kind: 'workflow:start';
452
+ ts: number;
453
+ path: string[];
454
+ identity: string;
455
+ workspace: string;
456
+ recordId: string;
457
+ } | {
458
+ kind: 'loop:start';
459
+ ts: number;
460
+ path: string[];
461
+ depth: number;
462
+ max?: number;
463
+ } | {
464
+ kind: 'loop:iteration';
465
+ ts: number;
466
+ path: string[];
467
+ iteration: number;
468
+ } | {
469
+ kind: 'loop:condition';
470
+ ts: number;
471
+ path: string[];
472
+ which: ConditionKind;
473
+ /** The enclosing loop iteration; 0 for start and check-first gates. */
474
+ iteration?: number;
475
+ result: ConditionResult;
476
+ } | {
477
+ /** A Condition executed outside loop start/stop/until control flow. */
478
+ kind: 'condition:result';
479
+ ts: number;
480
+ path: string[];
481
+ label: string;
482
+ iteration: number;
483
+ result: ConditionResult;
484
+ } | {
485
+ kind: 'loop:review';
486
+ ts: number;
487
+ path: string[];
488
+ outcome: Outcome;
489
+ /**
490
+ * Whether the loop will re-enter to act on a failing review (the review's
491
+ * revision was accepted), vs give up because it exhausted its iterations or
492
+ * `maxReviewRestarts`. Mirrors `dag:kickback`'s `accepted`. Only meaningful
493
+ * for a non-pass review; a downstream consumer that omits it (e.g. a test
494
+ * fixture) is treated as accepted.
495
+ */
496
+ accepted?: boolean;
497
+ } | {
498
+ kind: 'loop:end';
499
+ ts: number;
500
+ path: string[];
501
+ outcome: Outcome;
502
+ iterations: number;
503
+ } | {
504
+ kind: 'loop:stall';
505
+ ts: number;
506
+ path: string[];
507
+ iteration: number;
508
+ report: StallReport;
509
+ } | {
510
+ kind: 'limit:wait';
511
+ ts: number;
512
+ path: string[];
513
+ code: string;
514
+ waitMs: number;
515
+ /** Wall-clock epoch ms the wait ends at (ts + waitMs). */
516
+ resumeAt: number;
517
+ } | {
518
+ kind: 'limit:pause';
519
+ ts: number;
520
+ path: string[];
521
+ code: string;
522
+ reason: string;
523
+ } | {
524
+ kind: 'dag:start';
525
+ ts: number;
526
+ path: string[];
527
+ depth: number;
528
+ nodes: string[];
529
+ } | {
530
+ kind: 'dag:node';
531
+ ts: number;
532
+ path: string[];
533
+ node: string;
534
+ phase: NodePhase;
535
+ /** The node dependencies, normalised to an array when present. */
536
+ needs?: string[];
537
+ /** The node's declared purpose, when present. */
538
+ desc?: string;
539
+ /** The node's declared acceptance criterion, when present. */
540
+ gate?: string;
541
+ /**
542
+ * What the node produced, on a done or a skip. One attempt can emit this
543
+ * twice: a gate waiting for a person records its pause before it waits, so
544
+ * a live consumer sees a paused outcome and then, if an answer arrives, the
545
+ * answered one. A record keeps the latest outcome emitted for the node, so
546
+ * when a waiting run is killed the stored outcome is the pause itself,
547
+ * which is what lets a resumed run return that same still-pending question
548
+ * rather than treating the stage as interrupted and asking a person to
549
+ * reconcile it.
550
+ */
551
+ outcome?: Outcome;
552
+ /** Soft timeout in force for this node, when one is configured. */
553
+ timeoutMs?: number;
554
+ /**
555
+ * Which run of this node this is: 1 on the first pass, incremented each time
556
+ * a kickback re-runs it. Lets a records consumer tell a re-run's completion
557
+ * from the original and correlate it with the revision that caused it.
558
+ */
559
+ attempt?: number;
560
+ } | {
561
+ kind: 'dag:end';
562
+ ts: number;
563
+ path: string[];
564
+ outcome: Outcome;
565
+ }
566
+ /** The run's monitor page is up at `url`; emitted once, before the job starts. */
567
+ | {
568
+ kind: 'monitor';
569
+ ts: number;
570
+ path: string[];
571
+ url: string;
572
+ } | {
573
+ kind: 'dag:kickback';
574
+ ts: number;
575
+ path: string[];
576
+ from: string;
577
+ to: string;
578
+ reason: string;
579
+ accepted: boolean;
580
+ /** The one-based request count for this target in this DAG run. */
581
+ count: number;
582
+ /** The configured limit for this target, including the numeric form. */
583
+ limit: number;
584
+ note?: string;
585
+ } | {
586
+ kind: 'job:start';
587
+ ts: number;
588
+ path: string[];
589
+ label: string;
590
+ /** Soft timeout in force for this job, when one is configured. */
591
+ timeoutMs?: number;
592
+ } | {
593
+ kind: 'advisor:consult';
594
+ ts: number;
595
+ path: string[];
596
+ label: string;
597
+ call: number;
598
+ question: string;
599
+ reply: string;
600
+ model?: string;
601
+ } | {
602
+ kind: 'proof';
603
+ ts: number;
604
+ path: string[];
605
+ name: string;
606
+ artifact: ProofArtifact;
607
+ } | {
608
+ kind: 'job:end';
609
+ ts: number;
610
+ path: string[];
611
+ label: string;
612
+ outcome: Outcome;
613
+ } | {
614
+ kind: 'engine:text';
615
+ ts: number;
616
+ path: string[];
617
+ delta: string;
618
+ } | {
619
+ kind: 'engine:thinking';
620
+ ts: number;
621
+ path: string[];
622
+ delta: string;
623
+ } | {
624
+ kind: 'engine:tool';
625
+ ts: number;
626
+ path: string[];
627
+ name: string;
628
+ phase: 'use' | 'result';
629
+ } | {
630
+ kind: 'engine:usage';
631
+ ts: number;
632
+ path: string[];
633
+ model: string;
634
+ usage: UsageReceipt;
635
+ role?: 'writer' | 'reviewer';
636
+ stage?: string;
637
+ } | {
638
+ kind: 'log';
639
+ ts: number;
640
+ path: string[];
641
+ level: LogLevel;
642
+ message: string;
643
+ } | {
644
+ kind: 'error';
645
+ ts: number;
646
+ path: string[];
647
+ message: string;
648
+ code: string;
649
+ };
650
+ export type LoopEventKind = LoopEvent['kind'];
@@ -0,0 +1 @@
1
+ export { DEFAULT_OWNED_COMMAND_LIMITS, OwnedCommandError, attemptEnvironment, ownedCommandIdentity, resolveCommandExecutable, runOwnedCommand, type OwnedCommandErrorCode, type OwnedCommandObserver, type OwnedCommandRequest, type OwnedCommandResult, } from '@obversa/core/command';
@@ -0,0 +1 @@
1
+ export { assertEngineConformance, runEngineConformance, type EngineConformanceFailure, type EngineConformanceFixture, type EngineConformanceReport, type EngineConformanceScenario, } from '@obversa/api/testing';
@@ -0,0 +1,4 @@
1
+ /** Thin compatibility surface while the runtime package keeps its old name. */
2
+ export { CLAUDE_SUBAGENT_TOOLS, EngineError, EngineIncompleteResultError, SUBAGENT_TOOLS, isEngine, type AgentRequest, type AgentResult, type AgentResultPart, type AttemptMetadata, type Engine, type EngineEventSink, type EngineFailureKind, type EngineIncompleteResultEvidence, type EngineRef, type EngineSelectionRecord, type EngineStreamEvent, type EngineTransportFailure, type Usage, type UsageReceipt, } from '@obversa/api';
3
+ export { attemptEnvironment, attemptEnvironment as requestEnv } from '@obversa/core/command';
4
+ export type { EngineName } from '@obversa/api';
@@ -0,0 +1 @@
1
+ export { EngineError, EngineIncompleteResultError, LANE_DEAD_FAILURES, classifyEngineFailure, type EngineErrorInit, type EngineFailureKind, } from '@obversa/api';