Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 20 additions & 148 deletions assets/skills/spok-flow/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,171 +1,43 @@
---
name: spok-flow
description: end-to-end problem validation → research → design → plan → implement → review → commit workflow for a single chunk, with native or hybrid model routing and an optional post-commit self-learn gate. Driven by spok-apply.
description: end-to-end problem validation → research → design → plan → implement → review → commit workflow for a single chunk, executed by delegating the whole loop to the `spok run` CLI command, with native or hybrid model routing. Driven by spok-apply.
argument-hint: [hybrid] <task-dir> (absolute path to a pre-staged chunk directory containing ticket.md)
version: 0.9.0
version: 1.0.0
---
# Flow Instructions

## 0. Receive Pre-Staged Task Directory
## 1. Receive the task directory

`spok-apply` has already created the task directory and written `ticket.md` for the chunk to execute. The argument to this skill is the **absolute path** to that directory (e.g. `spok/changes/<change-slug>/.flow/<chunk-slug>/`). A leading `hybrid` token selects the built-in Claude + Codex execution profile; remove that token before resolving the task directory.
`spok-apply` has already created the task directory and written `ticket.md` for the chunk to execute. The argument to this skill is the **absolute path** to that directory (e.g. `spok/changes/<change-slug>/.flow/<chunk-slug>/`). A leading `hybrid` token selects the built-in Claude + Codex execution profile; remove that token before resolving the path.

> Verify the directory exists and contains `ticket.md` using the **Read** tool. Do NOT recreate the directory or overwrite `ticket.md`.
Verify the directory exists and contains `ticket.md` using the **Read** tool. If `ticket.md` is missing, halt and report back — `spok-apply` is responsible for staging it. Do NOT recreate the directory or overwrite `ticket.md`.

If `ticket.md` is missing, halt and report back — `spok-apply` is responsible for staging it.
## 2. Run the flow

## 1. Deterministic Control Loop

The `spok` CLI owns the inner flow sequence and resume state. Do not choose, skip, reorder, or rename steps yourself.

For a hybrid invocation, prefix every `spok flow status`, `spok flow next`, and
`spok flow complete` command with `SPOK_FLOW_PROFILE=hybrid`. For a default
invocation, run the commands without that environment variable. An existing
workflow state owns its persisted profile; if the requested profile conflicts,
surface the CLI blocker exactly.

Run:
Execute the flow in the foreground with the host's shell tool:

```bash
spok flow status "<task-dir>" --json
spok run "<task-dir>" --json
```

Hybrid equivalent:
When the `hybrid` token was present, append `--profile hybrid`:

```bash
SPOK_FLOW_PROFILE=hybrid spok flow status "<task-dir>" --json
spok run "<task-dir>" --json --profile hybrid
```

If it returns `state: "blocked"`, halt and report the `reason` exactly, applying the design-review clause in step 6 when it matches.

Then repeat this loop until the CLI returns `state: "complete"`:

1. Run:

```bash
spok flow next "<task-dir>" --json
```

For hybrid execution, use:

```bash
SPOK_FLOW_PROFILE=hybrid spok flow next "<task-dir>" --json
```

2. If `next` returns `state: "blocked"`, halt and report the `reason` exactly, applying the design-review clause in step 6 when it matches. If it returns `state: "complete"`, return success to `spok-apply`.

3. Read the returned `step` object:
- `id` is the workflow step id.
- `skill` is the exact skill to invoke.
- `runner` is the exact tool that must execute the step: `claude` or `codex`.
- `model` is the exact model to pass to that runner.
- `effort` is present when the step carries a reasoning-effort hint; relay it to the selected runner when present.
- `argument` is the exact argument to pass to that skill.
- `expectedOutput` is present for file-producing steps.
- `prompt` is the **complete subagent prompt**, composed by the CLI. It already
carries the skill invocation, the return contract, any step-specific clause,
and the repository rules from `spok/MEMORY.md`. Do not rewrite, summarize, or
add to it.

If the response carries `memoryWarning` or `workRootWarning`, surface it to the user once and continue.

4. Dispatch the step through `step.runner`.

Detect the active harness once: a non-empty `CODEX_HOME` means `codex`;
otherwise it is `claude`.

- When `step.runner` matches the active harness, delegate in the foreground
through the current host's native subagent mechanism to the
host-owned `general-purpose` agent. Pass `model: <step.model>`, (when
present) `effort: <step.effort>`, and `<step.prompt>` **verbatim** as the
prompt.
- When `step.runner` is `codex` from another harness, first verify `codex` is
on `PATH`, then run `codex exec` sequentially in the foreground. Use
`--ephemeral`, `--dangerously-bypass-hook-trust`, `--cd <project-root>`,
`--model <step.model>`, `--sandbox workspace-write`, and, when
`step.effort` is present, `-c model_reasoning_effort="<step.effort>"`.
Pass `<step.prompt>` **verbatim** on stdin with `-`; do not interpolate it
into a shell command.
- When `step.runner` is `claude` from another harness, first verify `claude`
is on `PATH`, then run `claude -p` sequentially in the foreground. Use
`--no-session-persistence`, `--model <step.model>`,
`--permission-mode auto`, text output, and, when `step.effort` is present,
`--effort <step.effort>`. Pass `<step.prompt>` **verbatim** on stdin; do not
interpolate it into a shell command.

Resolve `<project-root>` with `git -C "<task-dir>" rev-parse --show-toplevel`.
Use `--dangerously-bypass-hook-trust` to run enabled hooks without an
interactive trust prompt; it does not enable disabled hooks or relax the
sandbox. Do not use `--dangerously-bypass-approvals-and-sandbox`. If the
executable is missing, authentication fails, or the child exits nonzero,
report the error and halt. Do not call `spok flow complete`; leaving the
current step ready makes the run safely resumable after the tool is fixed.

Run every path **sequentially in the foreground** because each step depends
on the previous step's validated artifact or recorded result. Do not invoke
the step skill inline: process isolation keeps each step's context bounded.

5. Record completion with the CLI.

A `--summary` is recorded permanently in `workflow-state.json`. Do not relay a verification claim that cannot be attributed to a command that ran during the step. If the subagent reports "lint clean" or "tests pass" without naming the command it ran, drop the claim from the summary rather than passing it through — the CLI checks only that the summary is non-empty, so you are the last check on it.
- File-producing steps (the CLI verifies `expectedOutput` exists and is non-empty):

```bash
spok flow complete "<task-dir>" --step "<id>" --json
```

Prefix this command with `SPOK_FLOW_PROFILE=hybrid` for a hybrid run, as
described above.

- `validate` additionally has its recorded verdict read from `validation.md` by the CLI: `PASS` completes the step. A `FAIL` with repair attempts remaining is a *successful* completion that routes to a `repair` step and then back to `validate` — the CLI may return `validate` more than once; complete every occurrence with the same bare `--step validate`. When repair attempts are exhausted and the verdict is still `FAIL`, `complete` (and subsequent `next`/`status`) return `state: "blocked"` with an exhausted-repair reason: report it exactly as returned — do not retry the step and do not edit `validation.md` to unblock it. An unreadable verdict blocks as before.

- `implement`, `simplify`, and `repair` (dispatched like any other step when the CLI returns it):

```bash
spok flow complete "<task-dir>" --step "<id>" --summary "<summary>" --json
```

The `implement` prompt requires the subagent to end its reply with a
`Work root: <absolute path>` line naming the repository it edited. Pass that
path through so the commit step is told where the changes live:

```bash
spok flow complete "<task-dir>" --step "implement" --summary "<summary>" --work-root "<absolute-path>" --json
```

Pass `--work-root` on `simplify` or `repair` too when that subagent reports a
different repository. If the subagent reported no work root, omit the flag —
the CLI degrades to unsteered commit discovery and warns. Never invent the
path. A path that is relative or does not exist blocks the completion.

- `commit`:

```bash
spok flow complete "<task-dir>" --step "commit" --commit "<commit-sha>" --summary "<summary>" --json
```

When a work root was recorded, the CLI resolves the SHA in that repository
and blocks when it is not a commit reachable from `HEAD` there. Report such
a block exactly; do not retry with a different SHA.

- `self-learn` is an optional file-producing advisory gate returned only when
project config enables `flow.self_learn: true`. Complete it like any other
file-producing step. Its findings do not fail or amend the commit.
Do not background the command and do not run anything in parallel with it. Harness progress streams on stderr; stdout carries one JSONL event per line, each with `schemaVersion: 1`.

6. If `complete` returns `state: "blocked"`, halt and report the `reason` exactly. When the `reason` starts with `Step design-review recorded a FAIL verdict`, read `<task-dir>/design-review.md` and present its `## Human Decisions Required` section verbatim alongside the reason, then halt — do not retry the step and do not edit `design-review.md` to unblock it. When the `reason` starts with `Step design-review has no readable verdict`, read `<task-dir>/design-review.md` if it exists and present its `## Human Decisions Required` section verbatim alongside the reason if that section is present, then halt and report as a generic blocked state.
## 3. Relay the outcome

Do not restate or assume the step order — `spok flow next` is the only source of truth.
Do not derive or override runner or model routing inside this skill — `spok flow next --json` is the source of truth, including `step.runner`, `step.model`, and `step.effort`.
In plain terms: spok flow next --json is the source of truth for model routing.
Step-specific instructions — including the `implement` no-commit rule — are composed
into `step.prompt` by the CLI. Do not restate them.
Report the run's result from its exit code and final JSONL event. Relay what the CLI reported; never invent or soften an outcome.

<guidance>
## Important guidelines
- **Exit 0** (`complete` event): report success to `spok-apply`, including the recorded commit when the event carries a `commit`.
- **Exit 2** (`blocked` event): report the event's `reason` verbatim. When the event carries `humanDecisions`, present that `## Human Decisions Required` content verbatim alongside the reason. Do not retry, and do not edit artifacts to unblock the flow.
- **Exit 3** (`step_failed` event): report the failure. Re-invoking the same command resumes at the same step.
- **Exit 1 or a signal code (130/143)**: report the error or cancellation verbatim.
- **A `warning` event**, at any point in the stream: surface its `message` to the user once and continue; a warning does not stop the run.

- Raise questions or concerns about objectives, design, or plan to the user at any time using the **AskUserQuestion** tool.
- Run step subagents **sequentially in the foreground** because each step depends on the previous step's validated artifact or recorded result.
- Let `spok flow next` choose the next step. Let `spok flow complete` validate step completion.
- Use a **TaskList** to track the steps and their status.
## Guardrails

</guidance>
`spok run` owns all execution semantics: step order, routing, models, prompt composition, harness dispatch, and completion recording. Do not choose, skip, reorder, or re-route steps; do not drive the flow state machine directly; do not construct harness invocations; do not restate routing, models, or step prompts.
50 changes: 49 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ For slash commands, see [Commands](commands.md). For workflow patterns, see [Wor

| Surface | Verbs | Purpose |
|---------|-------|---------|
| **User-facing** | `version`, `init`, `doctor`, `update`, `skills install`, `archive`, `list` | Inspect, setup, diagnose, refresh, finalize, browse |
| **User-facing** | `version`, `init`, `doctor`, `update`, `skills install`, `archive`, `list`, `run` | Inspect, setup, diagnose, refresh, finalize, browse, drive |
| **Agent discovery** | `capabilities` | Machine-readable CLI manifest for agents and scripts |
| **Internal plumbing** | `new`, `status`, `instructions` | Called by skills; safe to inspect, not meant for daily human use |

Expand Down Expand Up @@ -274,6 +274,43 @@ spok archive update-ci-config --skip-specs

---

### `spok run`

Drive the deterministic flow of a staged task to a terminal outcome, dispatching each step to its routed harness (`claude` or `codex`) as a foreground subprocess. Progress is reported as human-readable lines, or as a machine-readable JSONL event stream with `--json` for orchestrators and CI.

```
spok run <task-dir> [options]
```

**Arguments:**

| Argument | Required | Description |
|----------|----------|-------------|
| `task-dir` | Yes | Flow task directory (e.g. `spok/changes/<name>/.flow/<chunk>`) |

**Options:**

| Option | Description |
|--------|-------------|
| `--profile <profile>` | Flow profile: `claude`, `codex`, or `hybrid` |
| `--json` | Emit JSONL supervision events on stdout |

**Examples:**

```bash
# Drive a flow to completion with human-readable progress
spok run spok/changes/add-dark-mode/.flow/chunk-one

# Supervise from an orchestrator via JSONL events
spok run spok/changes/add-dark-mode/.flow/chunk-one --json
```

**JSONL protocol (`--json`):**

With `--json`, stdout is pure protocol — one JSON object per line, each carrying `schemaVersion: 1` — while harness output stays on stderr. Events: `run_started` (taskDir, profile), `step_started` (step, runner, model, effort?, attempt?), `step_completed` (step), `warning` (message — a relayed memory or work-root warning, emitted at most once per distinct message per run), `blocked` (code, reason, and `humanDecisions` when a design-review FAIL records a `## Human Decisions Required` section), `step_failed` (step, reason), and terminal `complete` (commit? from the recorded commit step).

---

## Agent Discovery

### `spok capabilities`
Expand Down Expand Up @@ -525,6 +562,17 @@ spok instructions design --change add-dark-mode --json
| `0` | Success |
| `1` | Error (validation failure, missing files, etc.) |

`spok run` reports the flow's terminal outcome through its exit code:

| Code | Meaning |
|------|---------|
| `0` | Flow complete |
| `1` | Usage error (e.g. invalid `--profile` value) |
| `2` | Blocked (the state machine reported a blocker) |
| `3` | Execution failure (no dispatchable prompt, missing runner, harness failure, or completion-contract violation) |
| `130` | Interrupted (SIGINT) |
| `143` | Terminated (SIGTERM) |

---

## Environment Variables
Expand Down
31 changes: 31 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ import {
flowCompleteCommand,
flowNextCommand,
flowStatusCommand,
isFlowProfile,
runCommand,
DEFAULT_SCHEMA,
type StatusOptions,
type InstructionsOptions,
type NewChangeOptions,
type FlowCommandOptions,
type FlowCompleteCommandOptions,
type RunCommandOptions,
} from '../commands/workflow/index.js';
import { maybeShowTelemetryNotice, trackCommand, trackTelemetryEvent, shutdown } from '../telemetry/index.js';
import { collectCliSignals } from './signals.js';
Expand Down Expand Up @@ -97,6 +100,9 @@ const COMMAND_VISIBILITY: Record<string, CommandVisibility> = {
instructions: 'skill',
new: 'skill',
'new change': 'skill',
// Documented under User-Facing Commands in docs/cli.md; humans and external
// orchestrators invoke it directly, not only skills.
run: 'user',
flow: 'internal',
'flow status': 'internal',
'flow next': 'internal',
Expand Down Expand Up @@ -592,6 +598,31 @@ flowCmd
}
});

program
.command('run <task-dir>')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When flow.self_learn is invalid, spok run silently disables the setting because CONFIG_WARNING_COMMANDS omits the new command. Add run to that set so the human-facing driver reports the same configuration diagnostics as the other flow commands.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/index.ts, line 602:

<comment>When `flow.self_learn` is invalid, `spok run` silently disables the setting because `CONFIG_WARNING_COMMANDS` omits the new command. Add `run` to that set so the human-facing driver reports the same configuration diagnostics as the other flow commands.</comment>

<file context>
@@ -592,6 +598,31 @@ flowCmd
   });
 
+program
+  .command('run <task-dir>')
+  .description('Drive the deterministic flow to completion by dispatching each step to its harness')
+  .option('--profile <profile>', 'Flow profile: claude, codex, or hybrid')
</file context>

.description('Drive the deterministic flow to completion by dispatching each step to its harness')
.option('--profile <profile>', 'Flow profile: claude, codex, or hybrid')
.option('--json', 'Output as JSON')
.action(async (taskDir: string, options: RunCommandOptions) => {
// An unrecognized profile is a usage error, caught before the state machine
// could report it as a blocker and exit 2.
if (options.profile && !isFlowProfile(options.profile)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When callers pass an explicitly empty --profile, this guard skips validation and the run silently uses another profile. Check for undefined instead of truthiness so every supplied value is validated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/index.ts, line 609:

<comment>When callers pass an explicitly empty `--profile`, this guard skips validation and the run silently uses another profile. Check for `undefined` instead of truthiness so every supplied value is validated.</comment>

<file context>
@@ -592,6 +598,31 @@ flowCmd
+  .action(async (taskDir: string, options: RunCommandOptions) => {
+    // An unrecognized profile is a usage error, caught before the state machine
+    // could report it as a blocker and exit 2.
+    if (options.profile && !isFlowProfile(options.profile)) {
+      if (!options.json) console.log();
+      ora().fail(
</file context>
Suggested change
if (options.profile && !isFlowProfile(options.profile)) {
if (options.profile !== undefined && !isFlowProfile(options.profile)) {

if (!options.json) console.log();
ora().fail(
`Error: Invalid --profile value: ${options.profile}. Expected claude, codex, or hybrid.`
);
process.exit(1);
}

try {
process.exitCode = await runCommand(taskDir, options);
} catch (error) {
if (!options.json) console.log();
ora().fail(`Error: ${(error as Error).message}`);
process.exit(1);
}
});

const newCmd = program.command('new').description('Create new items');

newCmd
Expand Down
Loading
Loading