specrails-core 5.2.3 → 5.3.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 (101) hide show
  1. package/README.md +3 -0
  2. package/bin/specrails-core.mjs +5 -0
  3. package/dist/agent-runtime/cli-executor.d.ts +34 -0
  4. package/dist/agent-runtime/cli-executor.js +335 -0
  5. package/dist/agent-runtime/cli-executor.js.map +1 -0
  6. package/dist/agent-runtime/cli-process.d.ts +32 -0
  7. package/dist/agent-runtime/cli-process.js +159 -0
  8. package/dist/agent-runtime/cli-process.js.map +1 -0
  9. package/dist/agent-runtime/cli.d.ts +38 -0
  10. package/dist/agent-runtime/cli.js +168 -0
  11. package/dist/agent-runtime/cli.js.map +1 -0
  12. package/dist/agent-runtime/config.d.ts +7 -0
  13. package/dist/agent-runtime/config.js +162 -0
  14. package/dist/agent-runtime/config.js.map +1 -0
  15. package/dist/agent-runtime/core-host.d.ts +33 -0
  16. package/dist/agent-runtime/core-host.js +97 -0
  17. package/dist/agent-runtime/core-host.js.map +1 -0
  18. package/dist/agent-runtime/durable-store.d.ts +22 -0
  19. package/dist/agent-runtime/durable-store.js +205 -0
  20. package/dist/agent-runtime/durable-store.js.map +1 -0
  21. package/dist/agent-runtime/executor-types.d.ts +92 -0
  22. package/dist/agent-runtime/executor-types.js +29 -0
  23. package/dist/agent-runtime/executor-types.js.map +1 -0
  24. package/dist/agent-runtime/executors.d.ts +22 -0
  25. package/dist/agent-runtime/executors.js +44 -0
  26. package/dist/agent-runtime/executors.js.map +1 -0
  27. package/dist/agent-runtime/gemini-policy.d.ts +8 -0
  28. package/dist/agent-runtime/gemini-policy.js +36 -0
  29. package/dist/agent-runtime/gemini-policy.js.map +1 -0
  30. package/dist/agent-runtime/graph/artifacts.d.ts +40 -0
  31. package/dist/agent-runtime/graph/artifacts.js +179 -0
  32. package/dist/agent-runtime/graph/artifacts.js.map +1 -0
  33. package/dist/agent-runtime/graph/nodes.d.ts +36 -0
  34. package/dist/agent-runtime/graph/nodes.js +274 -0
  35. package/dist/agent-runtime/graph/nodes.js.map +1 -0
  36. package/dist/agent-runtime/graph/review-policy.d.ts +18 -0
  37. package/dist/agent-runtime/graph/review-policy.js +32 -0
  38. package/dist/agent-runtime/graph/review-policy.js.map +1 -0
  39. package/dist/agent-runtime/graph/roles.d.ts +44 -0
  40. package/dist/agent-runtime/graph/roles.js +126 -0
  41. package/dist/agent-runtime/graph/roles.js.map +1 -0
  42. package/dist/agent-runtime/graph/state.d.ts +75 -0
  43. package/dist/agent-runtime/graph/state.js +23 -0
  44. package/dist/agent-runtime/graph/state.js.map +1 -0
  45. package/dist/agent-runtime/graph-checkpointer.d.ts +61 -0
  46. package/dist/agent-runtime/graph-checkpointer.js +137 -0
  47. package/dist/agent-runtime/graph-checkpointer.js.map +1 -0
  48. package/dist/agent-runtime/index.d.ts +14 -0
  49. package/dist/agent-runtime/index.js +15 -0
  50. package/dist/agent-runtime/index.js.map +1 -0
  51. package/dist/agent-runtime/kimi-acp.d.ts +7 -0
  52. package/dist/agent-runtime/kimi-acp.js +179 -0
  53. package/dist/agent-runtime/kimi-acp.js.map +1 -0
  54. package/dist/agent-runtime/openai-executor.d.ts +18 -0
  55. package/dist/agent-runtime/openai-executor.js +174 -0
  56. package/dist/agent-runtime/openai-executor.js.map +1 -0
  57. package/dist/agent-runtime/prompts.d.ts +40 -0
  58. package/dist/agent-runtime/prompts.js +291 -0
  59. package/dist/agent-runtime/prompts.js.map +1 -0
  60. package/dist/agent-runtime/workflow-types.d.ts +210 -0
  61. package/dist/agent-runtime/workflow-types.js +2 -0
  62. package/dist/agent-runtime/workflow-types.js.map +1 -0
  63. package/dist/agent-runtime/workflow.d.ts +17 -0
  64. package/dist/agent-runtime/workflow.js +609 -0
  65. package/dist/agent-runtime/workflow.js.map +1 -0
  66. package/dist/agent-runtime/workspace-tools.d.ts +25 -0
  67. package/dist/agent-runtime/workspace-tools.js +120 -0
  68. package/dist/agent-runtime/workspace-tools.js.map +1 -0
  69. package/dist/installer/cli.d.ts +35 -0
  70. package/dist/installer/cli.js +3 -0
  71. package/dist/installer/cli.js.map +1 -1
  72. package/dist/installer/commands/doctor.d.ts +27 -0
  73. package/dist/installer/commands/framework.d.ts +85 -0
  74. package/dist/installer/commands/init.d.ts +147 -0
  75. package/dist/installer/commands/update.d.ts +56 -0
  76. package/dist/installer/commands/v5-migration.d.ts +32 -0
  77. package/dist/installer/phases/framework-lifecycle.d.ts +53 -0
  78. package/dist/installer/phases/install-config.d.ts +64 -0
  79. package/dist/installer/phases/manifest.d.ts +45 -0
  80. package/dist/installer/phases/prereqs.d.ts +51 -0
  81. package/dist/installer/phases/provider-detect.d.ts +89 -0
  82. package/dist/installer/phases/scaffold.d.ts +211 -0
  83. package/dist/installer/runtime/kimi.d.ts +84 -0
  84. package/dist/installer/runtime/pipeline-state.d.ts +210 -0
  85. package/dist/installer/runtime/pipeline-state.js +46 -22
  86. package/dist/installer/runtime/pipeline-state.js.map +1 -1
  87. package/dist/installer/util/errors.d.ts +46 -0
  88. package/dist/installer/util/exec.d.ts +41 -0
  89. package/dist/installer/util/fs.d.ts +153 -0
  90. package/dist/installer/util/git.d.ts +44 -0
  91. package/dist/installer/util/install-transaction.d.ts +29 -0
  92. package/dist/installer/util/logger.d.ts +31 -0
  93. package/dist/installer/util/paths.d.ts +34 -0
  94. package/dist/installer/util/prompts.d.ts +23 -0
  95. package/dist/installer/util/registry.d.ts +174 -0
  96. package/dist/installer/util/template.d.ts +23 -0
  97. package/docs/README.md +1 -0
  98. package/docs/agent-runtime.md +268 -0
  99. package/integration-contract.json +79 -7
  100. package/package.json +16 -1
  101. package/schemas/agent-runtime.schema.json +55 -0
@@ -0,0 +1,268 @@
1
+ # Programmatic agent runtime
2
+
3
+ Core can execute implementation as a local TypeScript workflow:
4
+
5
+ ```text
6
+ architect → developer → verify → reviewer → archive
7
+ │ ↑ │ │
8
+ │ └──────────┴─────────┘ bounded corrections
9
+ └─ investigate once, then ask (or proceed on stated assumptions)
10
+ ```
11
+
12
+ The workflow is a [LangGraph](https://docs.langchain.com/oss/javascript/langgraph/overview) state graph. LangGraph owns the traversal, the typed state and its reducers, the checkpoint history, dynamic interrupts (approvals and questions) and time travel; Core owns the role instructions, the structured artifacts, the verification subprocesses, the acceptance evidence, the receipts, budgets, leases and interrupted-write recovery. Provider adapters execute one role; they do not invoke a platform's implementation prompt or skill.
13
+
14
+ The developer role has the same autonomy the legacy Implement step had: it edits files and runs commands inside its CLI's own sandbox (Claude `--tools default --dangerously-skip-permissions` with nested agents and skills disallowed, Codex `workspace-write`, Gemini `--yolo`, Kimi print mode), so it can run the project's tests before handing off. Architect and reviewer roles stay read-only. Claude roles load only project settings (`--setting-sources project,local`), so `CLAUDE.md` and `.claude/rules` apply while the user's global memory and plugins do not.
15
+
16
+ This page describes the current source implementation, **runtime API 1**, workflow version 3, checkpoint envelope format 2. An older published Core package can have the same major version and lack this export. Build the paired checkout when developing this feature; do not assume `@latest` contains unreleased changes.
17
+
18
+ ## Requirements and ownership
19
+
20
+ - Node.js **20.19.0+** and Git on macOS or Windows. Individual provider CLIs may require a newer Node release.
21
+ - Installed and authenticated Claude, Codex, Gemini or Kimi CLIs for whichever roles use them; alternatively, a reachable OpenAI-compatible endpoint with tool support.
22
+ - A frozen execution context and a new change name. Verification commands are optional: configured commands run as given, the architect proposes the project's own checks for repositories that have none, and a repository with no automated check is admitted and recorded as unverified in the receipt.
23
+ - `ownership.git: "host"`. The runtime implements and archives; its caller owns worktrees, commits, pushes, pull requests and backlog delivery. It rejects Core-owned Git delivery rather than reporting success while shipping remains pending.
24
+
25
+ Specrails, LangGraph and the runtime infrastructure are open source and require no paid orchestration service, tracing backend, Python daemon or database server. Model inference, hardware and existing provider CLIs retain their own costs and license terms. No model download or provider subscription is included.
26
+
27
+ ## Configure providers and roles
28
+
29
+ Save this as `.specrails/agent-runtime.json`, adapting the verification command to your repository:
30
+
31
+ ```json
32
+ {
33
+ "schemaVersion": 1,
34
+ "enabled": true,
35
+ "providers": [
36
+ { "id": "claude", "kind": "cli", "cli": "claude" },
37
+ { "id": "codex", "kind": "cli", "cli": "codex" },
38
+ { "id": "gemini", "kind": "cli", "cli": "gemini" },
39
+ { "id": "kimi", "kind": "cli", "cli": "kimi" },
40
+ {
41
+ "id": "local",
42
+ "kind": "openai-compatible",
43
+ "baseUrl": "http://127.0.0.1:11434/v1"
44
+ }
45
+ ],
46
+ "agents": {
47
+ "architect": { "provider": "claude", "maxTurns": 24 },
48
+ "developer": { "provider": "codex", "maxTurns": 24 },
49
+ "reviewer": { "provider": "gemini", "maxTurns": 24 }
50
+ },
51
+ "limits": { "maxAttempts": 3, "timeoutMs": 900000 },
52
+ "verification": [
53
+ { "repositoryId": "app", "command": "node", "args": ["--test"] }
54
+ ],
55
+ "review": { "minScore": 80, "aspects": { "security": 85 } },
56
+ "architect": { "onLowConfidence": "ask" },
57
+ "approvalBeforeArchive": false
58
+ }
59
+ ```
60
+
61
+ `verification` may be an empty array. At run time Core builds the effective plan from the configured commands plus the architect's proposals for uncovered repositories; the plan is frozen in the graph state and reused by every verification and resume. When the plan covers no repository at all, the run still passes through task completion and review, and the receipt lists those repositories under `unverifiedRepositories`.
62
+
63
+ `review` tightens the review gate. Core's own gate is the floor: overall score at least 70, `security` at least 75, every other aspect at least 60. A lower value is rejected by validation, because the pipeline journal enforces the floor regardless of configuration. The reviewer is told the effective thresholds.
64
+
65
+ `architect.onLowConfidence` decides what happens when the architect still reports low confidence after its investigation pass (see [Questions and approvals](#questions-and-approvals)): `ask` (default) pauses the run with the architect's question; `proceed` continues on the assumptions the architect stated, recording `design-confidence.json` as `medium` with `assumed: true` and the original `reportedConfidence`.
66
+
67
+ Set any role's `provider` to `kimi` to use Kimi. A CLI role's optional `model` is passed to that provider; omitting it uses the CLI's default. Provider IDs are aliases, so you can configure several endpoints or replace an executor without changing workflow code.
68
+
69
+ To use the local endpoint, set the desired role to `{"provider":"local","model":"your-installed-model-id","maxTurns":24}`. The endpoint must support `POST <baseUrl>/chat/completions`, OpenAI-style function tool calls and a final assistant result. Its model must actually be able to use these tools; a chat-only endpoint is insufficient for the developer role. The example port is a placeholder for your own running server.
70
+
71
+ For an authenticated endpoint, add `"apiKeyEnv":"MY_MODEL_API_KEY"` to its provider configuration and set that variable in the process launching Core. Omit `apiKeyEnv` when no key is required. URLs cannot contain credentials, query parameters or fragments. Verification commands inherit process credentials; do not put secrets into their persisted `env` overrides.
72
+
73
+ The configuration schema is [agent-runtime.schema.json](../schemas/agent-runtime.schema.json). `validateRuntimeConfig()` also checks relationships such as role-to-provider references and the review floors. Runtime configuration is separate from the existing [profile v1 schema](../schemas/profile.v1.json); it does not translate legacy profile routing into programmatic phases.
74
+
75
+ ## Run from the CLI
76
+
77
+ Use an installed Core package that exposes runtime API 1. For a source checkout, run `npm ci` and `npm run build` in Core, then replace `specrails-core` below with `node /path/to/specrails-core/bin/specrails-core.mjs`.
78
+
79
+ Desktop constructs the execution context automatically. For standalone use, create a context JSON with absolute paths:
80
+
81
+ ```json
82
+ {
83
+ "schemaVersion": 1,
84
+ "runId": "feature-navigation-01",
85
+ "backlogRoot": "/absolute/path/to/app",
86
+ "artifactRoot": "/absolute/path/to/app",
87
+ "artifactRepositoryId": "app",
88
+ "repositories": [
89
+ { "id": "app", "name": "App", "path": "/absolute/path/to/app" }
90
+ ],
91
+ "ownership": { "git": "host", "backlog": "host", "worktrees": "host" },
92
+ "specs": [
93
+ {
94
+ "id": "navigation",
95
+ "title": "Keyboard navigation",
96
+ "description": "Implement the agreed keyboard navigation behavior.",
97
+ "repositoryIds": ["app"],
98
+ "acceptanceCriteria": ["Every setting can be reached using the keyboard."]
99
+ }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ On Windows use absolute paths such as `C:/work/app` or JSON-escaped `C:\\work\\app`. Repository paths must identify existing Git repositories. Multi-repository contexts list every selected repository; `artifactRoot` must be the path of `artifactRepositoryId`. Choose a fresh, portable run ID and a new kebab-case change name. Every acceptance criterion (or the description when a spec has none) becomes a frozen requirement the reviewer must certify.
105
+
106
+ ```sh
107
+ specrails-core runtime api
108
+ specrails-core runtime validate --config .specrails/agent-runtime.json
109
+ specrails-core runtime run --context .specrails/context.json --config .specrails/agent-runtime.json --change keyboard-navigation
110
+ specrails-core runtime status --context .specrails/context.json
111
+ specrails-core runtime resume --context .specrails/context.json --answer "Keep the existing shortcut map"
112
+ specrails-core runtime resume --context .specrails/context.json --approve archive
113
+ ```
114
+
115
+ The commands work in macOS shells and PowerShell; quote paths and answers containing spaces. Verification uses `command` plus an `args` array, not a shell command string. Prefer portable Node/npm commands over Bash scripts when the project supports Windows.
116
+
117
+ Run and resume emit JSON lines: `workflow-event` (the durable ledger events), `agent-event` (role narration and tool activity), `verification-output`, `span` (one per finished role attempt, with `traceId`, `spanId`, timing, status and usage, ready for an OpenTelemetry bridge) and a final `runtime-result`. The direct runtime entry point is `dist/agent-runtime/cli.js`; it also emits JSON errors. The main package CLI can report command-validation errors on stderr. Exit codes are `0` for success, `2` for a pause (approval or question pending), and `1` for failure, blocking or cancellation. A successful Core result means implementation, verification, review, acceptance evidence and archive completed; host delivery remains separate.
118
+
119
+ `runtime api` returns `{type:"runtime-api",apiVersion:1,coreVersion:"..."}` without invoking providers. Hosts can send a JSON configuration through stdin to `runtime validate --stdin` (maximum 2 MiB), avoiding temporary files and platform-specific shell quoting. It is mutually exclusive with `--config`. Use `runtime status --context <file> --compact` for process/UI integration: it retains the run and trace identities, phase status and visits, `pendingApproval`, `pendingQuestion`, usage, the completion verdict and the acceptance summary while omitting accumulated outputs, history and frozen context. Omit `--compact` for full inspection.
120
+
121
+ ## Questions and approvals
122
+
123
+ The graph pauses through LangGraph interrupts; the host resumes it with the matching answer.
124
+
125
+ - **Low design confidence.** When the architect reports `low`, Core first asks the same architect session to investigate the code once more and decide from evidence. If confidence is still low, the run pauses with the architect's single blocking question (`pendingQuestion`), the draft proposal is left on disk for inspection, and `resume --answer <text>` re-runs the architect with the answer as authoritative input. With `architect.onLowConfidence: "proceed"` the run continues on the stated assumptions instead of pausing.
126
+ - **Archive approval.** With `approvalBeforeArchive: true` the run pauses before archive (`pendingApproval`); `resume --approve archive` grants it. The default is no approval, so a fully autonomous run implements, verifies, reviews and archives without a human in the loop.
127
+
128
+ A resumed node collects its answer before doing any work, so the pass that asked the question is never repeated. Approvals and answers are persisted; a granted approval survives interruption, but invalidated candidate evidence clears it.
129
+
130
+ ## Recovery and durable state
131
+
132
+ State lives below `<backlogRoot>/.specrails/pipeline/<runId>/`:
133
+
134
+ | File | Purpose |
135
+ | --- | --- |
136
+ | `state.json` and `receipts/` | Authoritative Core gates, verification and acceptance evidence |
137
+ | `agent-runtime-request.json` | Frozen CLI change name and runtime configuration |
138
+ | `agent-workflow/<runId>/checkpoint.json` | One atomic envelope: the host ledger (attempts, receipts, usage, ordered events, pending interrupts) and the complete LangGraph checkpoint history |
139
+ | `agent-workflow/<runId>/.lease/` | Exclusive runtime process ownership |
140
+
141
+ Resume uses the saved configuration and change. It rejects a different frozen input, Core/instruction identity or workflow definition. Valid completed phases are retained; stale evidence invalidates the affected phase and everything declared after it, and the graph travels back in time to the checkpoint taken right before that phase last ran, so its predecessors' state is exactly what it saw then. The ledger is authoritative for which node runs next: if LangGraph's own position disagrees after a crash, traversal follows the ledger. Once archived, changed evidence requires a new run.
142
+
143
+ Correction loops stay cheap: when verification or review sends work back, the developer's previous provider session is resumed with a short correction prompt (Claude `--resume`, Codex `exec resume`, Gemini `--resume`, Kimi `--session`), so the code it wrote and the reasons behind it are already in context. If the session is gone the developer starts a fresh full turn with the same feedback. Unchecked tasks in `tasks.md` are returned to the developer as feedback, not treated as a workflow failure. A run blocked at `limits.maxAttempts` can be resumed explicitly: the resume grants a fresh attempt budget and transition ceiling; visits and history keep the complete record.
144
+
145
+ Architect and reviewer replies are validated against a JSON Schema (Claude `--json-schema`, Codex `--output-schema`; other providers are parsed leniently, accepting fenced or prefixed objects). The developer finishes with a structured summary (files, tests, verification run, incomplete tasks) validated the same way; a provider that returns prose instead is recorded as such rather than repaired. An unusable architect or reviewer reply gets one repair turn inside the same session before the phase fails.
146
+
147
+ ```sh
148
+ # Retry a reported failure with the same frozen configuration.
149
+ specrails-core runtime resume --context .specrails/context.json
150
+
151
+ # After inspecting partial edits, explicitly allow an interrupted write to run again.
152
+ specrails-core runtime resume --context .specrails/context.json --recover developer
153
+
154
+ # Recheck evidence explicitly; this also invalidates downstream review/archive.
155
+ specrails-core runtime resume --context .specrails/context.json --invalidate verify
156
+ ```
157
+
158
+ Inspect the saved phase and worktree before using `--recover`; it authorizes repeating an effect whose completion was not durably recorded. Dead local process leases can be reclaimed. Live, remote or unverifiable leases are never silently stolen. Do not delete a lease while its process may still be running.
159
+
160
+ Cancellation propagates to owned provider and verification processes. Programmatic callbacks must cooperate with `AbortSignal` and finish subprocess cleanup before returning; the engine retains its lease until they settle. A write step that settles after cancellation is recorded as interrupted and requires explicit recovery.
161
+
162
+ ## Acceptance evidence
163
+
164
+ Core 5.2 requires acceptance evidence before a change can be archived. The runtime produces it without a separate role:
165
+
166
+ - The reviewer receives every frozen acceptance criterion with stable coordinates (`specId`, `criterionIndex`) and certifies each one as `met`, `exception`, `blocked` or `pending` with concrete evidence. A reviewer may only accept a non-material exception itself; material scope changes stay `blocked`.
167
+ - Core records the verification commands it actually ran as required checks (passed or failed by exit code, with the receipt id as evidence), repositories admitted without a check as unavailable checks, and the reviewer's own inspections as supplementary, never required, checks.
168
+ - The report is validated against the frozen scope inside the reviewer turn (a malformed report gets the repair turn), then bound to the exact candidate before the reviewer verdict is recorded. Unresolved requirements or a failed required check block review and archive; the CLI status exposes the reasons.
169
+
170
+ ## Budgets and provider capability differences
171
+
172
+ | Setting | Behavior |
173
+ | --- | --- |
174
+ | `agents.<role>.maxTurns` | Bounded role/tool interaction, default 24; provider transports enforce their available turn/tool events |
175
+ | `limits.maxAttempts` | Maximum development visits per invocation, including correction cycles, default 3; an explicit resume starts a fresh budget |
176
+ | `limits.timeoutMs` | Workflow duration limit and provider timeout; default provider timeout is 15 minutes when omitted |
177
+ | `limits.maxTokens` | Rejects missing required usage or an observed overrun; CLI accounting may arrive only after a call |
178
+ | `limits.maxCostUsd` | Accepted by the built-in Claude executor through its native dollar limit; rejected by built-in Codex, Gemini, Kimi and OpenAI-compatible executors |
179
+
180
+ Do not configure `maxCostUsd` for a mixed-provider run unless every selected custom executor can enforce the requested cap. An observed token limit cannot guarantee that an opaque CLI stops before spending those tokens. Provider spend is accounted the moment each call returns, so a pause or failure after a call never loses it. Unknown usage stays `null`; known spend is tracked as a lower bound, never fabricated as zero. Local endpoints can report zero cost, but absent billing data remains unknown.
181
+
182
+ Kimi read-only roles use an enforced custom agent when the CLI exposes `--agent-file`; the Kimi 0.27 fallback uses ACP plan mode with scoped reads and denied writes/terminal operations. Unsupported ACP modes or multi-repository capabilities fail explicitly. Kimi lacks authoritative token accounting, so its built-in executor rejects token and dollar caps. Configure another provider for a role when its installed Kimi version cannot expose the required repository scope.
183
+
184
+ Gemini architect/reviewer roles require a CLI that advertises `--admin-policy` (verified with Gemini 0.49). Core supplies a temporary admin policy allowing only file reads, listing, glob and grep; all other tools are denied, including shell, writes and mode changes. This policy applies even if user settings disable plan mode or previously approve write tools. Gemini ignores per-run admin policies when system policy files exist, so Core rejects those managed environments before invoking a read-only role; select another provider for these roles or arrange a compatible environment with the system administrator. Older Gemini versions without this policy capability also fail explicitly. Gemini developer roles retain their existing `auto_edit` execution.
185
+
186
+ ## Embed and extend
187
+
188
+ Import the public ESM entry point; TypeScript declarations are included:
189
+
190
+ ```ts
191
+ import { readFileSync } from 'node:fs'
192
+ import { runCoreWorkflow, validateRuntimeConfig } from 'specrails-core/agent-runtime'
193
+
194
+ const context = JSON.parse(readFileSync('.specrails/context.json', 'utf8'))
195
+ const config = validateRuntimeConfig(JSON.parse(readFileSync('.specrails/agent-runtime.json', 'utf8')))
196
+ const controller = new AbortController()
197
+ const state = await runCoreWorkflow({
198
+ context, config, change: 'keyboard-navigation', signal: controller.signal,
199
+ onEvent: event => process.stdout.write(JSON.stringify(event) + '\n'),
200
+ onSpan: span => process.stdout.write(JSON.stringify(span) + '\n'),
201
+ })
202
+ console.log(state.status, state.nextStep, state.pendingQuestion?.question)
203
+ ```
204
+
205
+ Programmatic hosts retain their input/config and supply the same values with `resume: true`, plus `approve`, `answer`, `recoverInterrupted` or `invalidate` as needed. The CLI additionally creates the frozen request file for you.
206
+
207
+ Register an executor directly to add a provider in code. This example registers a private local endpoint with no CLI installation:
208
+
209
+ ```ts
210
+ import {
211
+ ExecutorRegistry, OpenAICompatibleExecutor, runCoreWorkflow,
212
+ type RuntimeConfig,
213
+ } from 'specrails-core/agent-runtime'
214
+
215
+ const registry = new ExecutorRegistry().register('on-prem',
216
+ new OpenAICompatibleExecutor({
217
+ id: 'on-prem', kind: 'openai-compatible', baseUrl: 'http://127.0.0.1:8080/v1',
218
+ }),
219
+ )
220
+ const config: RuntimeConfig = {
221
+ schemaVersion: 1, enabled: true, providers: [],
222
+ agents: {
223
+ architect: { provider: 'on-prem', model: 'your-installed-model-id' },
224
+ developer: { provider: 'on-prem', model: 'your-installed-model-id' },
225
+ reviewer: { provider: 'on-prem', model: 'your-installed-model-id' },
226
+ },
227
+ verification: [{ repositoryId: 'app', command: 'node', args: ['--test'] }],
228
+ }
229
+ // context is the frozen JSON object from the preceding example.
230
+ await runCoreWorkflow({ context, config, registry, change: 'local-navigation' })
231
+ ```
232
+
233
+ A custom executor implements `AgentExecutor.execute(request): Promise<AgentResult>`. The request contains role, central instructions, allowed roots, model, limits and signal. Return final text and honest `{inputTokens, outputTokens, costUsd}` usage; use `null` for unavailable values. The executor must enforce role permissions and supported limits, own its tool/process cleanup, and throw on incomplete results. `createExecutorRegistry(config, {executors: {alias: executor}})` can replace or supplement configured providers.
234
+
235
+ An optional, side-effect-free `validateLimits({maxTokens, maxCostUsd})` method rejects unsupported limits before any role runs. Built-in executors provide this preflight; custom registrations own their capabilities. Core also validates the complete verification command plan against the frozen repository scope before creating a run or invoking providers.
236
+
237
+ For a different host workflow, use `runWorkflow()` with a LangGraph state schema and typed nodes:
238
+
239
+ ```ts
240
+ import { Annotation } from '@langchain/langgraph'
241
+ import { runWorkflow } from 'specrails-core/agent-runtime'
242
+
243
+ const State = Annotation.Root({ notes: Annotation<string[]>({ reducer: (a, b) => [...a, ...b], default: () => [] }) })
244
+ await runWorkflow<typeof State.State>({
245
+ directory, runId, input: { change }, workflow: {
246
+ id: 'my-host', version: '1', schema: State, entry: 'plan',
247
+ nodes: {
248
+ plan: { ends: ['build'], run: async () => ({ status: 'succeeded', update: { notes: ['planned'] } }) },
249
+ build: { effect: 'write', ends: ['plan'], run: async (state, context) => {
250
+ context.reportUsage({ costUsd: 0.1, inputTokens: 10, outputTokens: 5 })
251
+ if (state.notes.length < 2) return { status: 'succeeded', next: 'plan' }
252
+ context.interrupt({ kind: 'approval', reason: 'Ship it?' })
253
+ return { status: 'succeeded', next: null }
254
+ } },
255
+ },
256
+ },
257
+ })
258
+ ```
259
+
260
+ Each node declares its `effect`, optional retry bounds, its possible successors (`ends`) and `run(state, context)`. A result supplies `status`, an optional graph `update` (merged through the schema's reducers), an optional ledger `output` and an optional `next` for conditional routing; `null` completes the workflow. Node names must not collide with state channel names. Nodes pause with `context.interrupt()` and report provider spend with `context.reportUsage()` as it happens. Write retries require `retrySafe: true`; interrupted writes still require explicit recovery. `readWorkflowState(directory, runId)` is read-only. Observer exceptions cannot replay a committed step. The Core nodes themselves are exported as `coreNodes(deps)` for hosts that want to reuse a phase inside another graph.
261
+
262
+ ## Tools, specifications and compatibility
263
+
264
+ The OpenAI-compatible tool executor exposes scoped file listing/reading and developer-only writing. It rejects traversal, symlink escapes and protected runtime metadata. It has no model-accessible shell tool; only the host's configured verification commands run as subprocesses. This is a tool policy, not an operating-system sandbox for arbitrary custom executors or external CLIs.
265
+
266
+ Architect and reviewer responses are structured JSON. The architect's `specs[].content` is the **complete intended main specification**, including unchanged requirements. Archive replaces `openspec/specs/<name>/spec.md` with that reviewed document. It does not interpret a partial OpenSpec delta as a merge instruction. The developer may update task checkboxes, but changing approved design, task descriptions or specification content invalidates the gates.
267
+
268
+ Legacy provider workflows and profile v1 remain available. Missing/disabled runtime configuration keeps Desktop's legacy path; an admitted programmatic run remains bound to its frozen runtime request even if the project's settings are later disabled. See the [integration contract](../integration-contract.json) for runtime API and artifact paths, and Desktop's programmatic runtime guide for release pairing and continuation/delivery behavior.
@@ -147,14 +147,14 @@
147
147
  "file": ".specrails/install-config.yaml",
148
148
  "version": 1,
149
149
  "fields": {
150
- "version": "number \u2014 schema version, currently 1",
151
- "provider": "string \u2014 claude | codex | gemini | kimi",
150
+ "version": "number schema version, currently 1",
151
+ "provider": "string claude | codex | gemini | kimi",
152
152
  "tier": "Deprecated legacy string, tolerated and ignored; all installs use deterministic placement.",
153
- "agents.selected": "string[] \u2014 unique lowercase kebab-case agent ids to install (1-64 characters)",
154
- "agents.excluded": "string[] \u2014 unique lowercase kebab-case agent ids to skip; must not overlap agents.selected",
155
- "models.preset": "string \u2014 balanced | budget | max; resolved within the selected provider catalog",
156
- "models.defaults.model": "string \u2014 exact provider model id or configured alias (overrides preset; Kimi: 1-128 characters matching [A-Za-z0-9][A-Za-z0-9._/:-]*; default: k3)",
157
- "models.overrides": "Record<safe-agent-id, string> \u2014 exact per-agent provider model ids or configured aliases with the same provider-specific validation (highest priority)"
153
+ "agents.selected": "string[] unique lowercase kebab-case agent ids to install (1-64 characters)",
154
+ "agents.excluded": "string[] unique lowercase kebab-case agent ids to skip; must not overlap agents.selected",
155
+ "models.preset": "string balanced | budget | max; resolved within the selected provider catalog",
156
+ "models.defaults.model": "string exact provider model id or configured alias (overrides preset; Kimi: 1-128 characters matching [A-Za-z0-9][A-Za-z0-9._/:-]*; default: k3)",
157
+ "models.overrides": "Record<safe-agent-id, string> exact per-agent provider model ids or configured aliases with the same provider-specific validation (highest priority)"
158
158
  }
159
159
  },
160
160
  "checkpoints": {
@@ -249,6 +249,78 @@
249
249
  "--lite"
250
250
  ]
251
251
  },
252
+ "agentRuntime": {
253
+ "apiVersion": 1,
254
+ "configSchemaVersion": 1,
255
+ "workflowStateSchemaVersion": 2,
256
+ "checkpointEnvelopeFormat": 2,
257
+ "checkpointEnvelope": "checkpoint.json holds {format: 2, checksum, state, graph}: the host ledger (state) and the LangGraph checkpoint history (graph) written atomically together",
258
+ "moduleExport": "specrails-core/agent-runtime",
259
+ "modulePath": "dist/agent-runtime/index.js",
260
+ "typesPath": "dist/agent-runtime/index.d.ts",
261
+ "cliPath": "dist/agent-runtime/cli.js",
262
+ "cliCommand": "runtime",
263
+ "cliOperations": [
264
+ "api",
265
+ "validate",
266
+ "run",
267
+ "status",
268
+ "resume"
269
+ ],
270
+ "apiOperation": "runtime api emits {type: runtime-api, apiVersion, coreVersion}; does not invoke providers",
271
+ "validationTransport": "runtime validate --stdin accepts UTF-8 configuration JSON up to 2 MiB, mutually exclusive with --config",
272
+ "compactStatus": "runtime status --compact omits workflow history, events and outputs, frozen pipeline context and verification command output; retains identities (runId, traceId), phase statuses and visits, pendingApproval, pendingQuestion, usage, completion, acceptance summary and verification metadata",
273
+ "interrupts": "A paused run (exit 2) carries pendingApproval (resume --approve <step>) or pendingQuestion (resume --answer <text>, nonempty, at most 20000 characters); the architect asks only after one autonomous investigation pass unless architect.onLowConfidence is proceed",
274
+ "eventStream": "run/resume emit JSON lines typed workflow-event, agent-event, verification-output, span ({traceId, spanId, name, stepId, attempt, visit, startedAt, endedAt, status, usage?, error?}) and a final runtime-result",
275
+ "reviewGate": "review.minScore and review.aspects may only tighten Core's own gate (minScore 70, security 75, other aspects 60); the pipeline journal enforces the floor",
276
+ "acceptance": "The reviewer certifies every frozen acceptance criterion by specId and criterionIndex; Core records the verification commands it ran as required checks and binds the report to the exact candidate before the reviewer verdict",
277
+ "configSchemaPath": "schemas/agent-runtime.schema.json",
278
+ "configSchemaExport": "specrails-core/schemas/agent-runtime.schema.json",
279
+ "projectConfigPath": ".specrails/agent-runtime.json",
280
+ "minimumNode": "20.19.0",
281
+ "platforms": [
282
+ "darwin",
283
+ "win32"
284
+ ],
285
+ "cliProviders": [
286
+ "claude",
287
+ "codex",
288
+ "gemini",
289
+ "kimi"
290
+ ],
291
+ "providerKinds": [
292
+ "cli",
293
+ "openai-compatible"
294
+ ],
295
+ "programmaticRegistration": "ExecutorRegistry.register(id, executor); custom executors implement AgentExecutor.execute(request)",
296
+ "phases": [
297
+ "architect",
298
+ "developer",
299
+ "verify",
300
+ "reviewer",
301
+ "archive"
302
+ ],
303
+ "contextSchema": "execution.context; ownership.git must be host",
304
+ "statePath": "<backlogRoot>/.specrails/pipeline/<runId>/agent-workflow/<runId>/checkpoint.json",
305
+ "requestPath": "<backlogRoot>/.specrails/pipeline/<runId>/agent-runtime-request.json",
306
+ "exitCodes": {
307
+ "succeeded": 0,
308
+ "paused": 2,
309
+ "failedBlockedOrCancelled": 1
310
+ },
311
+ "recovery": "Explicit resume uses frozen configuration and revalidates Core evidence; interrupted writes require named recovery; grants persist until evidence invalidation.",
312
+ "delivery": "Core implements through archive; the host owns worktrees, commits, pushes, PRs and backlog delivery. A standalone continuation does not restart Desktop delivery.",
313
+ "specificationFormat": "Architect specs contain complete main-spec replacements, including unchanged requirements; archive does not merge partial delta snippets.",
314
+ "compatibility": "Additive to integration schema 4 and execution context schema 1. Legacy workflows and profile v1 remain supported. Hosts must check RUNTIME_API_VERSION, not infer runtime support from Core major version.",
315
+ "documentation": "docs/agent-runtime.md",
316
+ "instructionsVersion": "3",
317
+ "workflowVersion": "3",
318
+ "developerTools": "The developer role runs with each CLI's autonomous edit+shell mode (claude --tools default --dangerously-skip-permissions with Agent/Task/Skill disallowed; codex workspace-write; gemini --yolo; kimi print mode). Architect and reviewer stay read-only. Claude roles load project settings only (--setting-sources project,local).",
319
+ "verificationPolicy": "Configured commands are optional. The architect proposes commands for repositories without one; repositories with no automated check are admitted and recorded in the receipt as unverifiedRepositories. Unchecked tasks return to the developer as feedback instead of failing the run.",
320
+ "sessionReuse": "Developer correction passes resume the previous provider session (claude --resume, codex exec resume, gemini --resume, kimi --session) with a short correction prompt; an unavailable session falls back to a fresh full turn. Structured roles get one repair turn in-session when their JSON is unusable.",
321
+ "structuredOutput": "Claude receives --json-schema and Codex --output-schema for architect/reviewer replies; other providers are parsed leniently.",
322
+ "resumeBudget": "An explicit resume resets the transition ceiling and grants the developer a fresh maxAttempts budget."
323
+ },
252
324
  "execution": {
253
325
  "schemaVersion": 1,
254
326
  "contextEnv": "SPECRAILS_EXECUTION_CONTEXT",
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "specrails-core",
3
- "version": "5.2.3",
3
+ "version": "5.3.0",
4
4
  "description": "Provider-independent AI agent workflow system for Claude Code, Codex, Gemini CLI, and Kimi Code",
5
5
  "type": "module",
6
+ "exports": {
7
+ "./agent-runtime": {
8
+ "types": "./dist/agent-runtime/index.d.ts",
9
+ "import": "./dist/agent-runtime/index.js",
10
+ "default": "./dist/agent-runtime/index.js"
11
+ },
12
+ "./package.json": "./package.json",
13
+ "./integration-contract.json": "./integration-contract.json",
14
+ "./schemas/agent-runtime.schema.json": "./schemas/agent-runtime.schema.json"
15
+ },
6
16
  "bin": {
7
17
  "specrails-core": "bin/specrails-core.mjs"
8
18
  },
@@ -68,11 +78,16 @@
68
78
  },
69
79
  "dependencies": {
70
80
  "@inquirer/prompts": "^7.0.0",
81
+ "@langchain/core": "1.2.10",
82
+ "@langchain/langgraph": "1.4.14",
83
+ "@langchain/langgraph-checkpoint": "1.1.5",
71
84
  "ajv": "^8.18.0",
85
+ "cross-spawn": "7.0.6",
72
86
  "js-yaml": "^4.1.0",
73
87
  "picocolors": "^1.1.1"
74
88
  },
75
89
  "devDependencies": {
90
+ "@types/cross-spawn": "6.0.6",
76
91
  "@types/js-yaml": "^4.0.9",
77
92
  "@types/node": "^22.10.2",
78
93
  "@vitest/coverage-v8": "^4.1.5",
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://specrails.dev/schemas/agent-runtime.schema.json",
4
+ "title": "Specrails programmatic agent runtime",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "enabled", "providers", "agents", "verification"],
8
+ "properties": {
9
+ "schemaVersion": { "const": 1 },
10
+ "enabled": { "type": "boolean" },
11
+ "providers": { "type": "array", "items": { "oneOf": [
12
+ { "type": "object", "additionalProperties": false, "required": ["id", "kind", "cli"], "properties": {
13
+ "id": { "$ref": "#/definitions/id" }, "kind": { "const": "cli" }, "cli": { "enum": ["claude", "codex", "gemini", "kimi"] }
14
+ } },
15
+ { "type": "object", "additionalProperties": false, "required": ["id", "kind", "baseUrl"], "properties": {
16
+ "id": { "$ref": "#/definitions/id" }, "kind": { "const": "openai-compatible" },
17
+ "baseUrl": { "type": "string", "pattern": "^https?://[^/?#@]+(?:/[^?#]*)?$" },
18
+ "apiKeyEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }
19
+ } }
20
+ ] } },
21
+ "agents": { "type": "object", "additionalProperties": false, "required": ["architect", "developer", "reviewer"], "properties": {
22
+ "architect": { "$ref": "#/definitions/agent" }, "developer": { "$ref": "#/definitions/agent" }, "reviewer": { "$ref": "#/definitions/agent" }
23
+ } },
24
+ "limits": { "type": "object", "additionalProperties": false, "properties": {
25
+ "maxAttempts": { "$ref": "#/definitions/positiveInteger" }, "maxTokens": { "$ref": "#/definitions/positiveInteger" },
26
+ "maxCostUsd": { "type": "number", "exclusiveMinimum": 0 }, "timeoutMs": { "$ref": "#/definitions/positiveInteger" }
27
+ } },
28
+ "verification": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["repositoryId", "command", "args"], "properties": {
29
+ "repositoryId": { "$ref": "#/definitions/id" }, "command": { "type": "string", "minLength": 1 },
30
+ "args": { "type": "array", "items": { "type": "string" } }, "cwd": { "type": "string", "minLength": 1 },
31
+ "env": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "additionalProperties": { "type": "string" } },
32
+ "timeoutMs": { "$ref": "#/definitions/positiveInteger" }
33
+ } } },
34
+ "approvalBeforeArchive": { "type": "boolean" },
35
+ "review": { "type": "object", "additionalProperties": false, "description": "Review gate thresholds; unset fields keep Core's defaults (score 70, security 75, other aspects 60).", "properties": {
36
+ "minScore": { "$ref": "#/definitions/score" },
37
+ "aspects": { "type": "object", "additionalProperties": false, "properties": {
38
+ "type_correctness": { "$ref": "#/definitions/score" }, "pattern_adherence": { "$ref": "#/definitions/score" }, "test_coverage": { "$ref": "#/definitions/score" },
39
+ "security": { "$ref": "#/definitions/score" }, "architectural_alignment": { "$ref": "#/definitions/score" }
40
+ } }
41
+ } },
42
+ "architect": { "type": "object", "additionalProperties": false, "properties": {
43
+ "onLowConfidence": { "enum": ["ask", "proceed"], "description": "After one autonomous investigation pass, a still-low design either pauses with its question (ask, default) or proceeds on stated assumptions (proceed)." }
44
+ } }
45
+ },
46
+ "definitions": {
47
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" },
48
+ "positiveInteger": { "type": "integer", "minimum": 1 },
49
+ "score": { "type": "number", "minimum": 0, "maximum": 100 },
50
+ "agent": { "type": "object", "additionalProperties": false, "required": ["provider"], "properties": {
51
+ "provider": { "$ref": "#/definitions/id" }, "model": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[^-\\r\\n\\u0000][^\\r\\n\\u0000]*$" },
52
+ "maxTurns": { "$ref": "#/definitions/positiveInteger" }
53
+ } }
54
+ }
55
+ }