# Standalone workflow runtime

`dynworkflow` exposes the repository's deterministic workflow engine as a
host-neutral local service. Pi remains a first-class executor, but a workflow
can be launched and controlled from a shell, Claude Code, Codex, CI, or another
program without loading the Pi extension UI.

This is a Claude Code-inspired portable runtime, not an implementation of a
private Claude Code protocol. The durable journal, control protocol, and event
projection documented here belong to this project.

## Quick start

Install the package and start a runtime for a project:

```bash
npm install -g @fish59fish/dynamic-workflows
cd /path/to/project
dynworkflow serve ./audit.workflow.js --executor codex
```

This install can run Codex and Claude Code workflows without Pi. The Pi SDK and
TUI packages are optional peers; they are loaded only if a call selects the Pi
executor. `dynworkflow doctor` distinguishes the optional Pi SDK from the two
external CLI executables.

`serve` remains in the foreground so it can own child processes and cooperative
abort controllers. It prints a dashboard URL containing a random local bearer
token. Press Ctrl-C for a graceful shutdown: active runs are changed to
`paused`, their completed-call journals remain on disk, and a later process can
resume them.

To run without a long-lived server:

```bash
dynworkflow run ./audit.workflow.js --executor claude-code
```

This process waits for the workflow to settle. `--ui` also starts the local
dashboard and keeps it available until Ctrl-C.
Without a UI, `checkpoint()` uses its declared headless default/abort policy;
it never opens an unreachable prompt. Server and embedded interactive modes
publish pending checkpoints to the dashboard/API, including after resume.

## Commands

| Command | Behavior |
| --- | --- |
| `serve [file]` | Start the project runtime/dashboard and optionally submit one workflow |
| `run <file\|->` | Submit to an active runtime, or run locally when none exists |
| `list` | List durable project runs |
| `show <id>` | Print a run with phase, agents, prompts, outputs, history, and usage |
| `pause <id>` | Cooperatively abort live work and retain a resumable journal |
| `resume <id>` | Replay the unchanged completed prefix and continue live work |
| `stop <id>` | Abort a run and make it non-resumable |
| `delete <id>` | Delete the durable run record |
| `doctor` | Check the optional Pi SDK and the Codex/Claude Code executables |

Useful options:

```text
--cwd <dir>
--state-dir <dir>
--executor pi|codex|claude-code
--args <json|@file>
--concurrency <n>
--max-agents <n>
--agent-retries <n>
--agent-timeout-ms <n|none>
--token-budget <n>
--attach
--json
```

When an active runtime exists, `run` returns its run ID immediately. Pass
`--attach` to follow it until it completes, pauses, fails, or is stopped.
`resume --script revised.workflow.js` uses the existing edit-and-resume
contract: cached calls replay only through the longest unchanged positional
prefix.

`list --json` returns lightweight run summaries. Use `show <id> --json` for the
full script-facing state, including agent input/output/history and the result.

## Runtime ownership and recovery

There is one live owner per project:

1. The server writes `runtime.json` next to the project's durable workflow
   state. The descriptor contains its PID, loopback address, protocol version,
   and bearer token and is created with mode `0600`.
2. A later CLI process validates the descriptor through `/api/health`.
3. Start, pause, resume, stop, and delete requests go to the live owner, so the
   process holding the `AbortController` and run lease performs the operation.
4. If the owner disappears, a fresh `WorkflowManager` reconciles a persisted
   `running` record to `paused`. Completed calls remain in the journal.

The lightweight overview plus selected-run detail reconstruct UI state after an
SSE disconnect or process restart. SSE is a live acceleration channel; the
persisted run snapshot and journal remain authoritative.

## Persistence and resume

The default layout stays compatible with the Pi extension:

```text
~/.pi/workflows/
  projects/<project-key>/
    runtime.json
    runs/<run-id>.json
    runs/<run-id>.json.bak
    runs/<run-id>.lock
    saved/
```

Use `--state-dir` or `DYNAMIC_WORKFLOWS_HOME` for a host-neutral root:

```bash
DYNAMIC_WORKFLOWS_HOME=~/.dynamic-workflows dynworkflow serve
```

State directories are created/tightened to mode `0700`; run JSON, recovery
backups, locks, logs, and the runtime descriptor use mode `0600` on Unix. Run
JSON is written atomically with a recovery backup. A cross-process lease
prevents two owners from executing the same run. Resume replays a successful
call only when its positional call index and identity hash still match.
Unfinished calls, and all calls after the first changed one, run again.
On manual pause, the old execution keeps its lease until abort cleanup has
actually settled. An immediate resume waits up to five seconds for that fence
and otherwise returns a conflict instead of overlapping old and new agents.

The workflow journal is the correctness layer for every executor. Pi, Codex,
and Claude Code provider sessions may evolve independent native-resume
optimizations, but failure of a provider session must never invalidate the
workflow journal.

## Dashboard and checkpoints

The dependency-free dashboard uses:

- `GET /api/overview` for lightweight run summaries and checkpoints;
- `GET /api/runs/<id>` for only the selected run's full detail;
- `GET /api/events` for authenticated SSE refresh notifications;
- run action endpoints for pause/resume/stop/delete; and
- `POST /api/checkpoints/<id>/respond` for confirm, text, and select gates.

Agent data is rendered with DOM `textContent`, not interpolated HTML. It shows
the full retained prompt and result, compact history, provider/model identity,
errors, and normalized usage. Pi can stream compact history during a run.
Current Codex and Claude Code CLI adapters expose their parsed history after the
external process settles; their final input/output remains visible and durable.

## HTTP API

All endpoints require the descriptor token as `Authorization: Bearer <token>`,
`X-Workflow-Token`, or a `token` query parameter.

```text
GET    /api/health
GET    /api/overview
GET    /api/state
GET    /api/events
POST   /api/runs
GET    /api/runs/<id>
DELETE /api/runs/<id>
POST   /api/runs/<id>/pause
POST   /api/runs/<id>/resume
POST   /api/runs/<id>/stop
POST   /api/checkpoints/<id>/respond
```

Start request:

```json
{
  "script": "export const meta = { ... }",
  "args": { "scope": "src" },
  "options": {
    "defaultExecutor": "codex",
    "concurrency": 8,
    "agentRetries": 1
  }
}
```

SSE events have a monotonic sequence for the current owner:

```json
{
  "sequence": 42,
  "timestamp": "2026-07-27T10:00:00.000Z",
  "type": "agentEnd",
  "runId": "audit-...",
  "payload": {}
}
```

Dashboard-style clients should rebuild from `/api/overview` and the selected
`/api/runs/<id>` after reconnecting instead of treating the live event ring as
the durable database. `/api/state` remains the explicit all-runs full-detail
projection for trusted programmatic consumers and can be large.

## Library embedding

The standalone surface is exported separately:

```ts
import {
  StandaloneWorkflowRuntime,
  StandaloneWorkflowServer,
} from "@fish59fish/dynamic-workflows/standalone";

const runtime = new StandaloneWorkflowRuntime({
  cwd: process.cwd(),
  defaultExecutor: "codex",
  checkpointMode: "interactive",
});
const server = new StandaloneWorkflowServer(runtime);
const listening = await server.listen();

const { runId, promise } = runtime.start({
  script,
  args: { scope: "src" },
  options: { concurrency: 8 },
});
```

The runtime facade emits a normalized `event` envelope, exposes durable state,
and accepts an injected portable agent runner, executor registry,
saved-workflow storage, and host-neutral runtime options. Import this
`/standalone` entry when Pi is not installed; the package root remains the Pi
extension/library surface and intentionally retains Pi peer types.

## Security boundary

- The server is loopback-only. Direct non-loopback binds are rejected; use SSH
  port forwarding for access from another machine.
- The descriptor and every API request use a random bearer token.
- Durable state directories and files are private to the current Unix user by
  default; they contain prompts, outputs, scripts, args, and compact history.
- Browser responses disable framing, referrers, external assets, and
  cross-origin control.
- Request bodies are bounded.
- Workflow scripts are trusted code. The Node VM removes accidental sources of
  nondeterminism such as `Date.now()` and `Math.random()` so replay is stable;
  it is not a containment boundary.
- Worktrees isolate concurrent edits, not hostile code. Use an OS/container
  sandbox when workflows or repositories are untrusted.

## Executor notes

- **Pi** uses the in-process SDK, native tools, compact live history, optional
  session persistence, model tiers, and shared-store tools. Its SDK is an
  optional peer and is dynamically loaded only for Pi calls; a missing SDK
  produces an `EXECUTOR_UNAVAILABLE` diagnostic instead of preventing the CLI
  from starting.
- **Codex** currently uses `codex exec --json --ephemeral`; the workflow
  journal resumes completed calls, while an interrupted in-flight call starts
  a fresh process.
- **Claude Code** currently uses print mode with `stream-json`,
  `--no-session-persistence`, safe mode, and a bounded coding-tool set. The same
  journal rule applies to interrupted calls.

Codex App Server and Claude Agent SDK sessions are the intended richer adapter
surfaces for native thread events and optional provider-session resume. They
are optimizations below the stable workflow contract, not replacements for it.

## Design references

The public contracts used as design references are:

- [Claude Code dynamic workflows](https://code.claude.com/docs/en/workflows)
  for JavaScript orchestration, phases, and lifecycle controls;
- [Claude Agent SDK sessions](https://code.claude.com/docs/en/agent-sdk/sessions)
  for a future native Claude session adapter;
- [Codex non-interactive mode](https://developers.openai.com/codex/noninteractive)
  for JSONL executor events and CLI resume semantics;
- [Codex App Server](https://developers.openai.com/codex/app-server) for a
  future long-lived Codex adapter; and
- [Pi SDK](https://pi.dev/docs/latest/sdk) and
  [Pi RPC](https://pi.dev/docs/latest/rpc) for the existing Pi executor.
