@mingchuno/agent-workflows 0.1.0 → 0.2.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 (70) hide show
  1. package/README.md +32 -6
  2. package/dist/src/adapters/agents.js +6 -3
  3. package/dist/src/adapters/hosting.js +16 -10
  4. package/dist/src/adapters/sdk-protocol.d.ts +3 -3
  5. package/dist/src/adapters/sdk-protocol.js +9 -7
  6. package/dist/src/cli.d.ts +1 -1
  7. package/dist/src/cli.js +40 -19
  8. package/dist/src/config.d.ts +22 -22
  9. package/dist/src/config.js +31 -26
  10. package/dist/src/defaults.d.ts +2 -0
  11. package/dist/src/defaults.js +2 -0
  12. package/dist/src/domain.d.ts +24 -4
  13. package/dist/src/domain.js +10 -3
  14. package/dist/src/evidence.d.ts +54 -0
  15. package/dist/src/evidence.js +214 -0
  16. package/dist/src/index.d.ts +1 -0
  17. package/dist/src/index.js +1 -0
  18. package/dist/src/invocation.d.ts +24 -0
  19. package/dist/src/invocation.js +163 -0
  20. package/dist/src/operations.d.ts +7 -2
  21. package/dist/src/operations.js +76 -134
  22. package/dist/src/prompts.d.ts +28 -0
  23. package/dist/src/prompts.js +63 -0
  24. package/dist/src/recovery.d.ts +19 -0
  25. package/dist/src/recovery.js +99 -0
  26. package/dist/src/runner.d.ts +5 -0
  27. package/dist/src/runner.js +145 -18
  28. package/dist/src/runtime/process.d.ts +2 -0
  29. package/dist/src/runtime/process.js +41 -12
  30. package/dist/src/store.d.ts +21 -2
  31. package/dist/src/store.js +122 -1
  32. package/dist/src/tui/actions.d.ts +16 -0
  33. package/dist/src/tui/actions.js +23 -0
  34. package/dist/src/tui/constants.d.ts +6 -0
  35. package/dist/src/tui/constants.js +3 -0
  36. package/dist/src/{tui-data.d.ts → tui/data.d.ts} +8 -6
  37. package/dist/src/tui/data.js +141 -0
  38. package/dist/src/tui/dialogs.d.ts +17 -0
  39. package/dist/src/tui/dialogs.js +149 -0
  40. package/dist/src/tui/format.d.ts +7 -0
  41. package/dist/src/tui/format.js +62 -0
  42. package/dist/src/tui/index.d.ts +2 -0
  43. package/dist/src/tui/index.js +1 -0
  44. package/dist/src/tui/layout.d.ts +25 -0
  45. package/dist/src/tui/layout.js +36 -0
  46. package/dist/src/tui/log-file.d.ts +26 -0
  47. package/dist/src/tui/log-file.js +156 -0
  48. package/dist/src/tui/log.d.ts +11 -0
  49. package/dist/src/tui/log.js +90 -0
  50. package/dist/src/tui/monitor.d.ts +8 -0
  51. package/dist/src/tui/monitor.js +222 -0
  52. package/dist/src/tui/text.d.ts +3 -0
  53. package/dist/src/tui/text.js +10 -0
  54. package/dist/src/tui/use-log-controller.d.ts +27 -0
  55. package/dist/src/tui/use-log-controller.js +192 -0
  56. package/dist/src/tui/views.d.ts +17 -0
  57. package/dist/src/tui/views.js +97 -0
  58. package/docs/api.md +119 -6
  59. package/docs/architecture.md +21 -4
  60. package/docs/configuration.md +137 -5
  61. package/docs/database.md +7 -0
  62. package/docs/operations.md +117 -2
  63. package/docs/providers.md +58 -2
  64. package/docs/releases.md +34 -79
  65. package/examples/config.ts +2 -2
  66. package/package.json +4 -2
  67. package/dist/src/tui-data.js +0 -89
  68. package/dist/src/tui.d.ts +0 -5
  69. package/dist/src/tui.js +0 -69
  70. package/docs/acceptance.md +0 -35
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { recoveryUnavailable } from "../recovery.js";
4
+ import { cells, colorFor, duration, elapsedRun, executionDuration, wrapLines, } from "./format.js";
5
+ function stepText(event) {
6
+ if (!event)
7
+ return "No steps recorded";
8
+ const payload = event.payload;
9
+ return `${payload.name ?? "step"} · ${payload.status ?? "recorded"} · attempt ${payload.attempt ?? "—"}`;
10
+ }
11
+ export function summaryLines(run, now, event) {
12
+ return [
13
+ `#${run.issue.number} ${run.issue.title}`,
14
+ `${run.outcome.toUpperCase()} · ${run.phase}`,
15
+ `Attempt ${run.attempt} · Execution ${executionDuration(run.executions?.at(-1), now)}`,
16
+ "",
17
+ "PROGRESS",
18
+ stepText(event),
19
+ "",
20
+ "VALIDATION",
21
+ ...(run.validation?.map((check) => `${check.command}: exit ${check.exitCode}`) ?? ["No checks recorded"]),
22
+ "",
23
+ "NEXT ACTION",
24
+ ...(run.error
25
+ ? [
26
+ `Error: ${run.error.split("\n")[0]}`,
27
+ "Enter details for the full error and recovery evidence",
28
+ ]
29
+ : [
30
+ run.outcome === "running"
31
+ ? "Workflow is running"
32
+ : run.outcome === "queued"
33
+ ? "Waiting for runner and project intake"
34
+ : ["failed", "blocked", "cancelled"].includes(run.outcome)
35
+ ? "Inspect details before retry or recovery"
36
+ : "Workflow finished; inspect the outcome",
37
+ ]),
38
+ ...(run.change ? [`Change request: ${run.change.url}`] : []),
39
+ ];
40
+ }
41
+ export function detailLines(run, sessions, now, recoveryReason = recoveryUnavailable(run)) {
42
+ return [
43
+ `#${run.issue.number} ${run.issue.title}`,
44
+ `${run.outcome} · ${run.phase} · attempt ${run.attempt}`,
45
+ `Run: ${run.id}`,
46
+ `Branch: ${run.branch}`,
47
+ `Issue: ${run.issue.url}`,
48
+ `Total elapsed: ${elapsedRun(run, now)}`,
49
+ ...(run.executions ?? []).flatMap((execution, index) => [
50
+ "",
51
+ `EXECUTION ${index + 1}: ${execution.id}`,
52
+ `${execution.outcome} · ${execution.phase} · duration ${executionDuration(execution, now)}`,
53
+ `Queue wait: ${duration(execution.createdAt, execution.startedAt ?? execution.finishedAt, now)}`,
54
+ `Started: ${execution.startedAt ?? "Not started"} · Finished: ${execution.finishedAt ?? "—"}`,
55
+ ...(execution.recoveryOf
56
+ ? [
57
+ `Recovered from: ${execution.recoveryOf}`,
58
+ `Reused: ${execution.reusedSteps?.join(", ") ?? "—"}`,
59
+ ]
60
+ : []),
61
+ ]),
62
+ "",
63
+ "RECOVERY",
64
+ recoveryReason ?? "Eligible for admission; runner checks still required",
65
+ "",
66
+ "ERROR",
67
+ run.error ?? "None",
68
+ "",
69
+ "VALIDATION",
70
+ ...(run.validation?.flatMap((check) => [
71
+ `${check.command} ${check.args.join(" ")} · exit ${check.exitCode} · ${duration(check.startedAt, check.finishedAt, now)}`,
72
+ `Log: ${check.log}`,
73
+ ]) ?? ["No checks recorded"]),
74
+ "",
75
+ "AGENT SESSIONS",
76
+ ...sessions.flatMap((session) => [
77
+ `${session.step} · invocation ${session.attempt} · ${session.outcome}`,
78
+ `Session: ${session.sessionId ?? session.sessionState}`,
79
+ `Duration: ${duration(session.startedAt, session.finishedAt, now)}`,
80
+ `Requested: ${JSON.stringify(session.requested)}`,
81
+ `Effective: ${JSON.stringify(session.effective)}`,
82
+ `Log: ${session.log}`,
83
+ "",
84
+ ]),
85
+ ];
86
+ }
87
+ export function Lines({ lines, width, height, offset = 0, }) {
88
+ const wrapped = wrapLines(lines, width);
89
+ const start = Math.min(offset, Math.max(0, wrapped.length - height));
90
+ return (_jsx(Box, { flexDirection: "column", width: width, height: height, overflow: "hidden", children: wrapped.slice(start, start + height).map((line, index) => (_jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", children: line || " " }) }, `${start + index}`))) }));
91
+ }
92
+ export function RunList({ runs, selected, width, height, now, }) {
93
+ const count = Math.max(1, Math.floor(height / 3));
94
+ const current = Math.max(0, runs.findIndex((run) => run.id === selected));
95
+ const start = Math.max(0, Math.min(current - Math.floor(count / 2), runs.length - count));
96
+ return (_jsx(Box, { flexDirection: "column", height: height, overflow: "hidden", children: runs.length ? (runs.slice(start, start + count).map((run) => (_jsxs(Box, { flexDirection: "column", height: 3, children: [_jsx(Text, { inverse: run.id === selected, bold: run.id === selected, wrap: "truncate", children: cells(`${run.id === selected ? ">" : " "} #${run.issue.number} ${run.issue.title}`, width) }), _jsx(Text, { color: colorFor(run.outcome), wrap: "truncate", children: cells(` ${run.outcome} · ${run.phase} · ${executionDuration(run.executions?.at(-1), now)}`, width) }), _jsx(Text, { dimColor: true, children: cells(` attempt ${run.attempt}`, width) })] }, run.id)))) : (_jsx(Text, { children: "No runs yet. Waiting for eligible issues." })) }));
97
+ }
package/docs/api.md CHANGED
@@ -4,9 +4,9 @@ Exports are in `src/index.ts`; the built package resolves to `dist/src/index.js`
4
4
 
5
5
  ## Runner and controls
6
6
 
7
- `new Runner({config,databaseUrl,hosting,agents,workspace?,workflow?,workflowVersion?})` injects hosting/agent adapters and optionally a workspace strategy or workflow. `hosting(project)` returns a host-qualified adapter. `agents` maps provider names to adapters. The default workspace uses the existing checkout. One DBOS runtime runs per Node process; one runner owns each configuration and checkout.
7
+ `new Runner({config,databaseUrl,hosting,agents,workspace?,workflow?,workflowVersion?,promptBaseDirectory?})` injects hosting/agent adapters and optionally a workspace strategy or workflow. `hosting(project)` returns a host-qualified adapter. `agents` maps provider names to adapters. The default workspace uses the existing checkout. One DBOS runtime runs per Node process; one runner owns each configuration and checkout.
8
8
 
9
- `start()` validates registration, acquires ownership, launches DBOS, registers concurrency-one project queues, and starts polling. `poll(projectId?)` performs an immediate scan. `pause(projectId)` stops new starts while active work continues. `resume(projectId)` refuses blocked checkouts. `stop(runId)` waits for the active invocation/process to end, or cancels queued work. `retry(runId)` requires a terminal failed/blocked/cancelled run and a clean checkout, then returns a new linked run ID. `shutdown()` stops intake, cancels and awaits active work, closes DBOS and releases ownership.
9
+ `start()` validates registration, acquires ownership, launches DBOS, registers concurrency-one project queues, and starts polling. `poll(projectId?)` performs an immediate scan. `pause(projectId)` stops new starts while active work continues. `resume(projectId)` refuses blocked checkouts. `stop(runId)` waits for the active invocation/process to end, or cancels queued work. `retry(runId)` requires a terminal failed/blocked/cancelled run and a clean checkout, then returns a new linked run ID. `recover(runId)` returns a new execution ID for publication recovery of the same run. `shutdown()` stops intake, cancels and awaits active work, closes DBOS and releases ownership.
10
10
 
11
11
  Use `try/finally` to call `shutdown()`, including failed startup. A custom `workflowVersion` must change when its durable step order changes; finish existing work before replacing an incompatible version.
12
12
 
@@ -36,17 +36,110 @@ commit together; failure preserves the blocked state.
36
36
  | `publishReview()` | Reject stale head; reconcile review marker; map valid added-line findings |
37
37
  | `complete(outcome?)` | Require clean checkout and persist terminal outcome |
38
38
  | `step(name, operation)` | Custom durable operation receiving current `RunRecord` |
39
- | `invoke(name, stage, prompt, readOnly?)` | Custom agentic step with profile resolution and session history |
39
+ | `invoke(name, stage, task)` | Custom agentic step with profile resolution and session history |
40
+
41
+ `task` separates `defaultPrompt` from an optional `context(run)` supplier.
42
+ Optional `readOnly`, Zod `outputContract`, and captured `evidence` control runtime
43
+ checks. Stage overrides replace only `defaultPrompt`. Custom stages have no
44
+ inferred built-in default; resolve file-based custom stages before invocation
45
+ with `resolveStagePrompt(stage, defaultPrompt, baseDirectory)` if they must be
46
+ frozen alongside startup configuration. The runner resolves built-in prompt files
47
+ at construction using `promptBaseDirectory`.
48
+
49
+ ```ts
50
+ await operations.invoke("report", stage, {
51
+ defaultPrompt: "Summarize the recorded validation.",
52
+ context: (run) => JSON.stringify(run.validation ?? []),
53
+ readOnly: true,
54
+ });
55
+ ```
56
+
57
+ An output contract enables strict response validation and one format correction
58
+ within the original deadline. Both attempts retain separate invocation/session
59
+ records under the same durable step. The returned string is validated JSON when
60
+ a contract is supplied. Codex output contracts must use its supported JSON-schema
61
+ subset: every property is required; use nullable defaults for optional locations.
62
+ Built-in review parsing accepts omitted locations and normalizes them to `null`. Interrupted calls are never automatically replayed.
40
63
 
41
64
  Put side effects inside `step`; custom effects must be idempotent or reconcile their own ambiguous results. A DBOS checkpoint does not snapshot a checkout. Returning from a custom workflow without calling a terminal operation is invalid. [Reporting workflow](../examples/custom-workflow.ts) inserts a validation report without editing provider code.
42
65
 
66
+ ## DBOS SDK direct usage
67
+
68
+ A custom `workflow(operations)` runs inside the runner's registered DBOS workflow.
69
+ It can mix predefined operations with direct SDK calls; no additional workflow
70
+ registration or DBOS runtime is needed. In a consuming application, declare
71
+ `@dbos-inc/dbos-sdk` as a direct dependency compatible with this package's SDK
72
+ version, and ensure both resolve to the same runtime instance.
73
+
74
+ This example adds a checkpointed health check against a local service before the
75
+ standard workflow. The service must expose `/health` on port 8080.
76
+
77
+ ```ts
78
+ import { DBOS } from "@dbos-inc/dbos-sdk";
79
+ import {
80
+ defaultWorkflow,
81
+ type Operations,
82
+ } from "@mingchuno/agent-workflows";
83
+
84
+ export async function customWorkflow(operations: Operations): Promise<void> {
85
+ await DBOS.runStep(
86
+ async () => {
87
+ const signal = AbortSignal.any([
88
+ operations.dependencies.signal,
89
+ AbortSignal.timeout(5_000),
90
+ ]);
91
+ signal.throwIfAborted();
92
+ const response = await fetch("http://127.0.0.1:8080/health", { signal });
93
+ await response.body?.cancel();
94
+ if (!response.ok) {
95
+ throw new Error(`Local service returned ${response.status}`);
96
+ }
97
+ },
98
+ { name: "check-local-service", retriesAllowed: false },
99
+ );
100
+
101
+ await defaultWorkflow(operations);
102
+ }
103
+ ```
104
+
105
+ In the [runner example](../examples/run.ts), import this function and replace its
106
+ `workflow` and `workflowVersion` options:
107
+
108
+ ```ts
109
+ workflow: customWorkflow,
110
+ workflowVersion: "local-health-v1",
111
+ ```
112
+
113
+ The successful check is checkpointed and skipped during recovery; it is not a
114
+ fresh health check on every restart. `defaultWorkflow` supplies the normal coding
115
+ operations and terminal outcome. See the [DBOS step API](https://docs.dbos.dev/typescript/reference/workflows-steps).
116
+
117
+ Caveats:
118
+
119
+ - `operations.step(name, callback)` adds cancellation and checkout checks, phase
120
+ updates, step events and error redaction around `DBOS.runStep`. Prefer it for
121
+ custom application operations. Raw steps retain DBOS history but bypass those
122
+ additions; pass the runner's abort signal to cancellable work, as above.
123
+ - Keep I/O, time reads and randomness inside durable steps. Call orchestration
124
+ APIs such as `DBOS.sleepms` at workflow level. Do not wrap predefined operations
125
+ or the whole workflow inside `runStep`; each operation owns its checkpoints.
126
+ - Disabling retries does not make external writes exactly-once. A crash after an
127
+ effect but before checkpointing can repeat it. Use stable idempotency keys or
128
+ reconcile ambiguous results. DBOS does not snapshot or restore checkout files.
129
+ - Finish custom paths with a terminal operation such as `operations.complete()`;
130
+ returning while a run remains queued/running is invalid.
131
+ - Change `workflowVersion` when durable step order changes, and finish or
132
+ explicitly resolve pending runs before deploying an incompatible workflow.
133
+ - Let `Runner` own `DBOS.setConfig`, `launch` and `shutdown`. Direct SDK use inside
134
+ a workflow does not grant another runtime or bypass checkout ownership rules.
135
+
43
136
  ## Extension contracts
44
137
 
45
138
  `Workspace` separates `check`, `prepare`, `inspect`, `verify`, `commit`, `push` and `release`. `Snapshot` contains branch/head, changed paths, diff and a content fingerprint. Never implement release by discarding files. A future isolated workspace implementation can replace this interface without changing workflow composition.
46
139
 
47
140
  `prepare`, `commit` and `push` receive an optional final `AbortSignal`. Custom workspaces must stop their subprocesses before settling a cancelled operation. The existing-checkout strategy journals Git processes under the Git directory; ownership acquisition rejects surviving process groups after a runner crash.
48
141
 
49
- `AgentAdapter.validate(profile)` returns observable effective settings. `invoke(input)` receives working directory, prompt/skills, read-only intent, abort signal, stage `timeoutMs` and session/event callbacks. The abort signal also covers cancellation and time spent validating the profile. Call `session(id)` immediately when available. Await event persistence; invocation must not settle until its work has stopped. SDK adapters enforce process-group lifecycle; custom adapters must uphold the same contract. `processFile` is available for controlled subprocess ownership.
142
+ `AgentAdapter.validate(profile)` returns observable effective settings. `invoke(input)` receives working directory, prompt, optional application-owned `outputSchema`, read-only intent, abort signal, stage `timeoutMs` and session/event callbacks. The abort signal also covers cancellation and time spent validating the profile. Call `session(id)` immediately when available. Await event persistence; invocation must not settle until its work has stopped. SDK adapters enforce process-group lifecycle; custom adapters must uphold the same contract. `processFile` is available for controlled subprocess ownership.
50
143
 
51
144
  `HostingAdapter` provides issue pagination/revalidation, instance-qualified `identity`, change-request lookup/create, remote head, and idempotent review publication. `preflight` is optional. Reconciliation keys must be stable across response loss; providers must never infer successful publication from agent prose.
52
145
 
@@ -59,6 +152,26 @@ and process safety check, which runs under the project lock for new admissions
59
152
  only. This callback must not mutate Store records. Operator tools should use
60
153
  `retry` commands or `Runner.retry`, preserving those safety checks.
61
154
 
62
- Invocation records include project/run IDs, stable DBOS step ID and name, invocation ID, attempt, timestamps, requested/effective profile, provider, prompt/skill snapshots, artifact path and session state (`pending`, `available`, `unavailable`). Repeated custom steps retain separate invocations. A retry has a separate run record linked to its predecessor.
155
+ Invocation records include project/run IDs, stable DBOS step ID and name, invocation ID, attempt, timestamps, requested/effective profile, provider, effective task prompt/source/hash, output-contract and evidence identities, artifact path and session state (`pending`, `available`, `unavailable`). Repeated custom steps retain separate invocations. A retry has a separate run record linked to its predecessor.
156
+
157
+ `request(kind,target)` queues the same `pause`, `resume`, `stop`, `retry`, or `recover` commands used by the CLI/TUI; `commands()` reports pending/success/failure. A runner must be active to execute them. `finishCommand` and record-writing methods support adapters and custom workflows; operator tools should prefer commands over direct mutation.
158
+
159
+ ## Publication recovery
160
+
161
+ `Runner.recover(runId, commandId?)` supports failed publication steps in the
162
+ default workflow. It validates the source execution, checkout, configuration,
163
+ artifacts and remote state. `Store.admitRecovery` commits intent and its event
164
+ under the same project lock as retry admission; its safety callback must not
165
+ mutate Store records. Operator tools should use the runner or queued commands.
166
+
167
+ `RunRecord.executions` contains the initial execution and recovery executions,
168
+ including DBOS IDs, source execution, restart step, reused steps, configuration
169
+ fingerprint and outcomes. Run IDs remain stable for invocations and publication
170
+ markers. `Store.recoveryPlan(runId)` reports persisted eligibility and its reason;
171
+ live safety checks happen at admission and execution. Runs without execution
172
+ metadata remain readable and retryable, but cannot be recovered.
63
173
 
64
- `request(kind,target)` queues the same `pause`, `resume`, `stop`, or `retry` commands used by the CLI/TUI; `commands()` reports pending/success/failure. A runner must be active to execute them. `finishCommand` and record-writing methods support adapters and custom workflows; operator tools should prefer commands over direct mutation.
174
+ Recovery uses DBOS forks, retaining the original workflow input and checkpoint
175
+ prefix. The accepted command ID is the fork ID: dispatch adopts an existing fork
176
+ after an uncertain response or crash. Copied start gates do not replace live
177
+ checks in the first non-replayed operation. Successful prefixes cannot rerun.
@@ -5,20 +5,37 @@ DBOS owns workflow execution, durable steps and concurrency-one project queues.
5
5
  - `config.ts` / `domain.ts`: validated configuration, vocabulary and adapter contracts.
6
6
  - `runner.ts`: local ownership, intake/deduplication, DBOS lifecycle and operator controls.
7
7
  - `operations.ts`: reusable durable coding operations and the default workflow.
8
+ - `prompts.ts` / `invocation.ts`: resolved task text, output contracts and bounded format correction.
9
+ - `evidence.ts`: indexed, hashed change artifacts and capture limits.
10
+ - `recovery.ts`: publication recovery eligibility, input fingerprints and live safety checks.
8
11
  - `workspace.ts`: existing-checkout Git operations and change verification.
9
12
  - `store.ts`: typed Drizzle queries for run, invocation, project, command and event records; `db/schema.ts` and `drizzle/` own the application schema and migrations.
10
13
  - `adapters/`: provider clients and isolated SDK workers.
11
14
  - `runtime/`: process groups, ownership journals and redacted logging.
12
- - `cli.ts` / `tui.tsx`: shared command/query interfaces.
15
+ - `cli.ts` / `tui/`: shared command/query interfaces.
13
16
 
14
- Application records live in `agent_workflows`; DBOS maintains its own execution schema in the same PostgreSQL database. Large streamed agent/validation logs live under the configured state directory, referenced by records. Agent calls are never automatically retried. Publication retries reconcile external state first. Clean terminal state and terminal workflow outcome are deliberately separate.
17
+ Application records live in `agent_workflows`; DBOS maintains its own execution schema in the same PostgreSQL database. Large streamed agent/validation logs live under the configured state directory, referenced by records. Interrupted or failed agent calls are never automatically retried. A returned response that fails its output contract may receive one fresh inspection-only format-correction attempt within the same stage deadline. Publication retries and explicit recovery reconcile external state first. Clean terminal state and terminal workflow outcome are deliberately separate.
15
18
 
16
19
  Node/PostgreSQL/Git are the only runtime infrastructure; providers require their normal local authentication. Zod, Commander, Ink/React, Drizzle/node-postgres, Pino, Octokit and Gitbeaker handle standard infrastructure. Drizzle ORM and Codex SDK are Apache-2.0; the other listed runtime libraries and Copilot SDK are MIT-licensed. Exact dependency versions are pinned by the lockfile. No custom HTTP client, CLI parser or terminal renderer is introduced.
17
20
 
18
21
  The test boundary is the public runner/workflow API using real PostgreSQL, real temporary Git repositories and controlled adapters. Separate adapter contracts exercise SDK argument/event mapping and HTTP behavior. Process-level recovery tests terminate a runner after external effects and restart it against the same state. Runtime/provider smoke calls are intentionally separate from deterministic acceptance tests.
19
22
 
20
- Phase 1 supports existing checkouts only. Higher per-project concurrency requires isolated workspaces and lifecycle design; changing the DBOS queue limit alone is unsafe.
23
+ The runner supports existing checkouts only. Higher per-project concurrency requires isolated workspaces and lifecycle design; changing the DBOS queue limit alone is unsafe.
21
24
 
22
25
  ## Package boundary
23
26
 
24
- Keep one package while the SDK, CLI and TUI share a runtime, schema and release cycle. `src/adapters`, `src/runtime` and `src/db` provide internal boundaries without workspace packages. Split into a monorepo when a separately deployed app or independently versioned package needs its own dependencies and build. `pnpm-workspace.yaml` currently configures installation policy only.
27
+ Keep one package while the SDK, CLI and TUI share a runtime, schema and release cycle. `src/adapters`, `src/runtime`, `src/db` and `src/tui` provide internal boundaries without workspace packages. Split into a monorepo when a separately deployed app or independently versioned package needs its own dependencies and build. `pnpm-workspace.yaml` currently configures installation policy only.
28
+
29
+ ## Run and execution identity
30
+
31
+ A run owns the branch, commit and publication markers. Its initial DBOS execution
32
+ uses the run ID; publication recovery forks the failed execution at its failed
33
+ step under a new execution ID, preserving completed checkpoints and the original
34
+ run input. Execution history stays in the run record. Fresh retry creates a new
35
+ run and branch.
36
+
37
+ Recovery admission and retry share a project lock. Admission persists the fork ID
38
+ before dispatch so a restarted runner can adopt an existing fork. Recovery gates
39
+ run inside the first operation that actually executes, avoiding copied pause and
40
+ safety decisions. Recovery is limited to the default workflow's publication
41
+ steps; interrupted agents still require manual inspection.
@@ -6,7 +6,7 @@ The CLI reads `agent-workflows.json`, or `--config PATH`. Unknown properties are
6
6
  | ---------------- | --------------------------------------------------------------------------------------------------------- |
7
7
  | `id` | Required stable letters/digits/underscore/hyphen identity; scopes records and queues |
8
8
  | `databaseUrlEnv` | `AGENT_WORKFLOWS_DATABASE_URL`; environment variable containing a PostgreSQL connection URL with username |
9
- | `stateDirectory` | `.agent-workflows`; persistent local artifact directory |
9
+ | `stateDirectory` | `.agent-workflows`; must resolve outside every managed checkout |
10
10
  | `projects` | Nonempty array; duplicate IDs or canonical checkout roots are rejected |
11
11
 
12
12
  | Project field | Default / meaning |
@@ -20,10 +20,64 @@ The CLI reads `agent-workflows.json`, or `--config PATH`. Unknown properties are
20
20
  | `gitIdentity` | Required `name` and `email`; used by application commits |
21
21
  | `validation` | Array of `{command,args,timeoutMs}`; no shell expansion; timeout defaults to 300000 ms |
22
22
  | `agent` | Required default profile |
23
- | `stages` | `implementation`, `writing`, `review`; each has optional `profile`, `prompt`, `skills`, `timeoutMs` |
23
+ | `stages` | `implementation`, `publication`, `review`; each has optional `profile`, `prompt`, `promptFile`, `timeoutMs` |
24
24
 
25
25
  Issues are selected in ascending issue-number order within each intake scan. Deduplication persists across restarts. An explicit retry is a new numbered attempt linked through `retryOf`.
26
26
 
27
+ ## CLI environment files
28
+
29
+ Select one file explicitly for any CLI command:
30
+
31
+ ```sh
32
+ agent-workflows --env-file ./runner.env run
33
+ agent-workflows --env-file ./runner.env status --json
34
+ agent-workflows --env-file /absolute/path/runner.env monitor
35
+ ```
36
+
37
+ Example `runner.env` (replace placeholders locally):
38
+
39
+ ```dotenv
40
+ AGENT_WORKFLOWS_DATABASE_URL="postgresql://USER:PASSWORD@localhost/agent_workflows"
41
+ GITHUB_TOKEN="YOUR_GITHUB_TOKEN"
42
+ APP_MODE=development # unquoted comment
43
+ APP_GREETING="hello # literal text"
44
+ APP_REFERENCE='${APP_MODE}'
45
+ ```
46
+
47
+ - Existing process values win, including empty strings. Empty required database
48
+ or hosting values still fail existing validation. Missing keys are filled from
49
+ the file; arbitrary application variable names are supported. `databaseUrlEnv`
50
+ and `hosting.tokenEnv` still select which names the runner uses.
51
+ - Relative paths resolve from the CLI launch directory, independently of
52
+ `--config` and project checkouts. Absolute paths work too. No `.env` discovery
53
+ or multiple-file layering is performed.
54
+ - Parsing uses Node's literal dotenv syntax: quotes and comments are supported;
55
+ `$NAME`, `${NAME}`, backticks and `$(command)` in values are not expanded or
56
+ executed. This is not shell sourcing.
57
+ - The file is read once before the command action, database access, hosting
58
+ adapters or runner creation. Missing or unreadable files stop the command with
59
+ a nonzero exit and a path/error code, without printing file contents. Restart
60
+ the runner to pick up edits. Help only displays usage and does not load files.
61
+ - The merged environment is shared across all projects in the runner. Validation,
62
+ Git and agent worker subprocesses inherit it. Provider runtimes may apply their
63
+ own environment policies to tools they launch; see [providers](providers.md).
64
+ No per-project environment isolation is added.
65
+
66
+ Keep local environment files out of version control. Add their actual names to
67
+ `.gitignore` (or `.git/info/exclude`), especially inside managed checkouts where
68
+ untracked files interfere with cleanliness checks. Do not copy credentials into
69
+ configuration, prompts or issue bodies. Existing database/hosting credential
70
+ redaction remains in effect; arbitrary variable support does not classify every
71
+ application value as a secret. A GitHub API token does not configure Git push
72
+ credentials.
73
+
74
+ The flag belongs to `agent-workflows`, not the separate `pnpm db:migrate` command.
75
+ SDK callers load their own process environment before creating a runner and
76
+ continue passing `databaseUrl` explicitly. Node startup-only settings, such as
77
+ `NODE_EXTRA_CA_CERTS`, must be set before launching Node to affect the CLI process.
78
+ When invoking the script directly through Node, separate Node arguments from
79
+ application arguments: `node -- dist/src/cli.js --env-file ./runner.env status`.
80
+
27
81
  ## Profiles
28
82
 
29
83
  A profile has `provider`, optional `model`, optional `reasoningEffort`, and optional `context`. Each stage merges its profile over project defaults. Switching provider discards the old provider's settings, so incompatible defaults cannot leak between providers. Each invocation starts a fresh session, including repeated custom steps.
@@ -34,8 +88,86 @@ Copilot queries its SDK model catalog for explicit settings. Supported context c
34
88
 
35
89
  See [checked examples](../examples/config.ts). Model IDs in `modelOverrides` are placeholders that must be replaced with available models. No provider silently clamps or substitutes explicit options.
36
90
 
37
- ## Prompts and skills
91
+ ## Stage prompts
92
+
93
+ Omit `prompt` and `promptFile` to use the installed defaults. `init` leaves both
94
+ out so package upgrades update default task instructions. An override replaces
95
+ only task instructions; issue context, captured change evidence, permission rules
96
+ and output contracts remain application-owned.
97
+
98
+ ### Default stage prompts
99
+
100
+ Implementation:
101
+
102
+ ```text
103
+ Implement the supplied issue in the current checkout. Follow repository
104
+ instructions and existing conventions. Keep changes focused on the issue's
105
+ requirements, and add or update tests where needed to verify the behavior.
106
+ ```
107
+
108
+ Publication:
109
+
110
+ ```text
111
+ Prepare a Git commit message and a pull request or merge request title and
112
+ description for the supplied changes. Follow repository conventions. Describe
113
+ what changed and why, summarize the recorded validation accurately, and state
114
+ material limitations. Do not claim checks passed unless the supplied evidence
115
+ shows they ran and passed.
116
+ ```
117
+
118
+ Review:
119
+
120
+ ```text
121
+ Independently review the supplied published changes against the issue's
122
+ requirements and repository conventions. Inspect the change artifacts and
123
+ relevant source for correctness, regressions, and missing validation. Report
124
+ actionable findings with supporting locations where possible. State any gaps
125
+ in inspection explicitly; do not present an incomplete review as a clean review.
126
+ ```
127
+
128
+ These exact defaults are checked against the runtime source.
129
+
130
+ Use either nonblank literal `prompt` text or a `promptFile` path, never both.
131
+ Files must contain nonblank UTF-8 text. Relative paths resolve from the CLI
132
+ configuration file's directory, independently of the launch directory; absolute
133
+ paths are allowed. Files load once when the runner is constructed. Restart to
134
+ apply edits. No templating, interpolation, or includes are supported. SDK callers
135
+ supply `promptBaseDirectory` for relative paths.
136
+
137
+ ```json
138
+ "stages": {
139
+ "implementation": {},
140
+ "publication": { "promptFile": "prompts/publication.md" },
141
+ "review": { "prompt": "Review correctness and missing regression tests." }
142
+ }
143
+ ```
144
+
145
+ `writing` is now `publication`; `skills` was removed. Both old keys are rejected
146
+ without aliases. Configure skills in the selected agent runtime and request them
147
+ in task text. Old invocation records and skill snapshots remain readable.
148
+
149
+ Stage timeout defaults to 30 minutes, including profile validation and at most
150
+ one format-correction attempt. Correction uses a fresh inspection-only session
151
+ and only the remaining deadline. Provider failures, cancellation, timeout and
152
+ workspace mutation never trigger correction. Custom text-only stages do not
153
+ receive format correction. Credentials belong in environment variables or
154
+ runtime authentication stores, not prompts.
155
+
156
+ ## Change evidence
157
+
158
+ Use a `stateDirectory` outside every managed checkout. `init` chooses a sibling
159
+ `<checkout-name>.agent-workflows` directory. Publication and review receive a
160
+ small overview and an absolute index path, then read ordered artifact chunks.
161
+ Publication captures staged, unstaged and untracked changes. Review captures the
162
+ exact base and published head. Hash checks reject missing or modified evidence.
38
163
 
39
- `prompt` is literal stage text. `skills` contains paths to `SKILL.md` files, relative to the checkout or absolute. The runner snapshots their content and SHA-256 revision into invocation records and explicitly tells the agent to apply them. Copilot additionally receives skill directories; Codex receives explicit skill text. Automatic provider discovery is not assumed equivalent.
164
+ Text chunks are at most 64 KiB; total text evidence, including indexes, is at most
165
+ 32 MiB per stage. Capture fails explicitly above the limit. Large indexes have
166
+ bounded pages; binary changes contain metadata instead of encoded content.
167
+ These are internal limits, not configurable model context limits.
40
168
 
41
- Stage timeout defaults to 30 minutes. Custom agent steps use the same `Stage` schema, profile resolution, snapshots, cancellation and session tracking as built-in stages. Credentials stay in environment variables/runtime authentication stores; do not place them in prompts or source files.
169
+ Review output includes `complete` and `limitations`. Incomplete reviews preserve
170
+ partial findings locally and block normal review publication. Prompt content,
171
+ output contract and evidence identities are retained with each response attempt.
172
+ Changed effective prompts, including upgraded defaults, block recovery with
173
+ fresh-retry guidance; file edits do not change an already running instance.
package/docs/database.md CHANGED
@@ -26,3 +26,10 @@ attempts. Runner's checkout/process safety check runs while that lock is held;
26
26
  command replay skips it and does not write new events.
27
27
 
28
28
  `src/db/locks.ts` contains the only application driver SQL: fixed, parameterized PostgreSQL session-lock calls, which have no Drizzle query-builder equivalent. Generated migration SQL and the frozen legacy-schema test fixture are intentional SQL artifacts. No interpolated SQL template strings are used for record access.
29
+
30
+ Execution history is stored in the existing run JSON record. Publication recovery
31
+ adds optional fields without changing SQL tables; no migration or backfill is
32
+ required. Legacy records remain readable, but lack the evidence needed for
33
+ recovery. Recovery admission atomically appends an execution, queues the same
34
+ run and writes an event under the project/task locks used by retry admission.
35
+ The persisted execution ID lets dispatch reconcile a DBOS fork across crashes.
@@ -13,12 +13,100 @@ All commands accept `--config PATH` before the subcommand.
13
13
  | `logs RUN [--invocation ID]` | Local agent and validation artifacts |
14
14
  | `pause PROJECT` / `resume PROJECT` | Queue an intake control command |
15
15
  | `stop RUN` | Queue cancellation; success means active local work has stopped |
16
+ | `recover RUN` | Continue a failed publication step using completed checkpoints |
16
17
  | `retry RUN` | Queue an explicit new attempt after checkout validation |
17
18
  | `monitor` | Attach an interactive terminal view |
18
19
 
19
20
  Control commands return a command ID and `pending`; inspect `status --json` or the monitor for success/failure. With no runner, commands stay pending. Run and monitor are separate processes. Closing the monitor never cancels work. Ctrl-C on the runner stops intake, cancels active work, waits for process termination and releases ownership. Queued issues remain durable for the next start.
20
21
 
21
- In the monitor, Left/Right selects a project, Up/Down a run, `[`/`]` a step/attempt event, Tab an agent invocation, `l` displays its log tail, `v` cycles validation logs, `p` pauses/resumes intake, `s` stops the selected run, `r` retries it, and `q` closes the view. Session IDs are displayed in full for terminal selection/copying. Outcomes, validation, profiles and session state use text as well as color. Noninteractive tools use `status --json` and `inspect`.
22
+ The monitor uses a full-screen view and restores the terminal when closed.
23
+ It refreshes persisted workflow state and open logs every 400 ms. Database
24
+ connectivity is shown separately from command acknowledgements; it does not
25
+ prove that a runner is alive. Requires an interactive terminal of at least
26
+ 80 columns by 24 rows. Wide terminals show run list, summary and sessions;
27
+ compact terminals show the focused pane. Titles and identifiers are available
28
+ in full in scrollable details. `NO_COLOR=1` disables semantic colors.
29
+
30
+ | Context | Keys |
31
+ | --- | --- |
32
+ | Dashboard | Left/Right project; Tab/Shift+Tab pane; Up/Down selection or scroll |
33
+ | Details | Enter opens; Up/Down or PgUp/PgDn scroll; Esc returns |
34
+ | Progress | `[`/`]` inspect step history; End follows latest event |
35
+ | Sessions | `a` focuses session list; Up/Down selects invocation; `l` opens log |
36
+ | Validation | `v` opens validation logs |
37
+ | Controls | `p` pauses/resumes intake; `s` stops; `r` retries; `c` recovers publication |
38
+ | Monitor | `?` opens the shortcut dialog; `q` or Ctrl-C closes only the monitor |
39
+ | Shortcut dialog | Tab/Shift+Tab or Left/Right changes category; Up/Down scrolls; Esc closes |
40
+ | Logs | Up/Down or `j`/`k` scroll; PgUp/PgDn page; Left/Right pan long lines |
41
+ | Logs | Home/End or `g`/`G` first/last page; `f` resumes live follow |
42
+ | Logs | Tab selects next log; `R` toggles readable/raw presentation |
43
+ | Search | `/` opens; Enter applies; `n`/`N` next/previous matching record |
44
+ | Back | Esc dismisses search/help first, then returns from logs |
45
+
46
+ Stop, retry and publication recovery require confirmation of the selected issue;
47
+ The centered dialog defaults to Cancel. Tab/Shift+Tab or Left/Right switches
48
+ between Cancel and Confirm; Enter activates the highlighted option and Esc
49
+ cancels. Unavailable actions are omitted from the footer.
50
+ A pending command stays pending until the runner acknowledges it, including
51
+ while switching projects. Logs and search cannot send workflow commands.
52
+
53
+ Search is literal and covers the entire selected file, not only the visible page.
54
+ Lowercase queries ignore case; any uppercase character makes the query
55
+ case-sensitive. Search wraps at file boundaries. Scrolling or searching pauses
56
+ follow so arriving output does not move the view. Known agent events render as
57
+ readable messages/tool activity; unknown events remain visible as JSON. Raw
58
+ presentation preserves the stored text except unsafe terminal control codes.
59
+
60
+ Execution duration includes eligibility, preparation and waits within one
61
+ execution. It excludes queue waiting and gaps before publication recovery.
62
+ Details show each execution, its queue wait and the run's total elapsed time.
63
+ Completed execution durations freeze at their first terminal outcome. Records
64
+ without timing evidence show an em dash. Noninteractive tools use `status --json`
65
+ and `inspect`. See [TUI design](tui-redesign.md) for implementation boundaries.
66
+
67
+ ## Observability Landscape
68
+
69
+ This section includes only options that run locally without a license key.
70
+ Cloud services and tools requiring a license key are excluded. These boundaries
71
+ apply to observability; agent and hosting providers retain their own requirements.
72
+
73
+ | Option | Available information | Integration path |
74
+ | ------ | --------------------- | ---------------- |
75
+ | Project CLI/TUI | Run outcomes, phases, invocations, sessions and local logs | Built in: `monitor`, `status --json`, `inspect RUN`, `logs RUN` |
76
+ | DBOS SDK CLI | Durable workflow status and step history | Connect directly to the runner's PostgreSQL database |
77
+ | Project `Store` API | Application records and ordered events | Build a local script or dashboard using `runs`, `run`, `invocations`, `events` and `subscribe` |
78
+ | `DBOSClient` | DBOS workflow and step records | Build a local inspector using `getWorkflow`, `listWorkflows` and `listWorkflowSteps`; close it with `destroy()` |
79
+
80
+ For DBOS CLI inspection from this repository:
81
+
82
+ ```sh
83
+ pnpm exec dbos workflow list --sys-db-url "$AGENT_WORKFLOWS_DATABASE_URL"
84
+ pnpm exec dbos workflow get "<run-id>" --sys-db-url "$AGENT_WORKFLOWS_DATABASE_URL"
85
+ pnpm exec dbos workflow steps "<run-id>" --sys-db-url "$AGENT_WORKFLOWS_DATABASE_URL"
86
+ ```
87
+
88
+ Use the database URL selected by `config.databaseUrlEnv` if it differs from the
89
+ default above. These commands use the installed SDK CLI and require no running
90
+ Conductor service or cloud login. See the [DBOS CLI reference](https://docs.dbos.dev/typescript/reference/cli).
91
+
92
+ The initial DBOS workflow ID equals the run ID. Publication recovery keeps the
93
+ run ID and adds a new DBOS execution ID; `inspect RUN` includes execution history
94
+ and a persisted recovery eligibility assessment. Admission performs live checks.
95
+ Inspect both layers: the
96
+ runner catches execution errors and persists application outcomes, so a DBOS
97
+ `SUCCESS` can accompany an application `failed` or `blocked` outcome. DBOS step
98
+ history does not replace the local agent/validation artifacts.
99
+
100
+ A browser dashboard is an extension path, not a bundled feature. A local server
101
+ could combine the [Store query/event API](api.md#query-and-event-interface) with
102
+ `DBOSClient.create({ systemDatabaseUrl: databaseUrl })`, joining records by run ID.
103
+ Neither inspector needs to launch another DBOS runtime. Keep database access on
104
+ the server and bind a local-only dashboard to loopback. See the
105
+ [inspection example](../examples/observe.ts) for Store lifecycle handling.
106
+
107
+ Route dashboard controls through `Store.request` or the runner's public controls.
108
+ Direct DBOS cancellation, resumption or forking bypasses application coordination
109
+ for process termination, checkout safety and retry admission.
22
110
 
23
111
  ## Ownership and recovery
24
112
 
@@ -30,7 +118,32 @@ Startup and phase boundaries check ownership assumptions, branch/head and actual
30
118
 
31
119
  Publication effects have independent DBOS checkpoints. A task commit carries `Agent-Workflows-Run`; commit recovery checks parent and marker, push recovery checks the remote ref, request creation checks the source branch, and review publication checks stable markers. Transient publication failures use bounded retries and reconciliation. Interrupted agent stages block rather than starting another writer.
32
120
 
33
- After a blocked/failed task:
121
+ ## Publication recovery
122
+
123
+ For the default workflow, `recover RUN` continues a run whose latest execution
124
+ failed at `push`, `change-request`, or `review-publication`. It reuses completed
125
+ checkpoints, keeps the branch, commit and publication markers, and gives the
126
+ failed step three new attempts. Later steps execute normally. Each execution
127
+ retains its outcome, error and source execution; a repeated command ID identifies
128
+ the same recovery.
129
+
130
+ Keep the original branch and commit checked out with a clean working tree.
131
+ Recovery verifies process ownership, complete checkpoint history, local artifacts,
132
+ remote revision, workflow version, configuration and skill contents. Environment
133
+ credential rotation is allowed. Changed code or execution inputs require a fresh
134
+ retry. Recovery honors project pause and never removes an existing project block.
135
+ Checks run again inside the first step that executes, including after a crash.
136
+
137
+ Use `inspect RUN` before recovery and check command outcomes afterward. Monitor
138
+ shows execution history and the recovery restriction, if any. Eligibility based
139
+ on persisted records is provisional until the runner finishes live checks.
140
+
141
+ Agent/validation failures, cancelled or blocked runs, custom workflows, and older
142
+ runs without recovery metadata use the existing inspection and fresh-retry path.
143
+ Recovery does not restore checkouts or accept arbitrary restart steps. A newer
144
+ fresh attempt supersedes recovery of the older run.
145
+
146
+ After a blocked/failed task that cannot be recovered:
34
147
 
35
148
  1. Read `inspect RUN`, logs, session IDs and the local Git diff.
36
149
  2. Establish that no worker/process group is still running. If startup reports an old PID or process journal, inspect that exact process and stop it before recovery. Never remove a live owner's lease.
@@ -41,6 +154,8 @@ A failed review after publication remains a failed automation attempt, even if i
41
154
 
42
155
  ## Evidence and limits
43
156
 
157
+ Git hooks are disabled for application-authored task commits; configure required checks as validation commands. Changed symlinks and submodules require manual handling. Checkout checks detect boundary changes but do not sandbox custom adapters or prevent unrelated local tools from writing.
158
+
44
159
  Validation records say exactly which command ran, when, its exit code and artifact path. No-change work skips publication. Generated commit/request text is validated and saved before Git/API writes. Logs, prompts and errors redact configured credentials and recognized secret environment values; this does not sanitize arbitrary repository content or secrets unknown to the runner.
45
160
 
46
161
  Back up both PostgreSQL and the state directory if history/artifacts matter. The checkout and runtime session stores are separate local state. Losing them cannot be repaired from DBOS checkpoints alone. Do not change a custom workflow's step order or rename projects while its runs are pending; use a new workflow version and finish or explicitly resolve existing runs first.