Skip to content
Back to skills

Graph

ASecurity

Declarative DAG pipeline contract — deterministic graph semantics and journal-based crash recovery, executed model-driven in omd until the phase-2 runtime lands

  • 8 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 13, 2026
ai-agentsrustgoshellnoderailsgitsecurity

Works with

  • terminal
  • cli
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 13, 2026

npx -y skills add 1225Sakura/oh-my-deepseekharness --skill graph --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Graph?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Graph
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/1225sakura-graph/badge)](https://www.skillsdirectory.com/skills/1225sakura-graph)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: graph
description: Declarative DAG pipeline contract — deterministic graph semantics and journal-based crash recovery, executed model-driven in omd until the phase-2 runtime lands
when-to-use: Repeatable multi-step pipelines with explicit dependencies (DAG), work that must survive interruption, auditable runs. Not for exploratory one-off work (use conversation or team) or anything needing adaptive re-planning mid-run (graphs are deterministic).
---

# Graph

Run a deterministic orchestration graph from a declarative JSON descriptor.

> **Port status (read first).** The OMC original executes through an independent OS process (`omc graph run`) backed by the sealed-descriptor and pure-scheduler contracts in `src/graph/*`, with an OCC journal for real crash recovery. **The omd phase-1 runtime has landed (U4)**: the deterministic core lives in `lib/graph/` (schema/descriptor/journal/scheduler/fence/runner), shared by the MCP six-tool surface and a background script entry (see "Runtime tool surface" below). Model-driven execution remains a legitimate fallback — the journal contract is unchanged; journals written by the model today are tomorrow's resume input.

## Usage

```
graph <descriptor.json>
graph "build then test then ask me before deploy"   (author the descriptor first)
```

## When To Use

- Repeatable multi-step pipelines with explicit dependencies (DAG)
- Work that must survive interruption: rerun resumes from the journal
- Auditable runs: journal + snapshots under `.omd/graph-runs/<run_id>/`

When NOT to use: exploratory one-off work (use conversation or team); anything needing adaptive re-planning mid-run (graphs are deterministic).

## Workflow

1. **Descriptor given** → go to step 3.
2. **Pipeline described** → author the descriptor JSON (schema below), write it next to the project (suggest `.omd/graphs/<name>.json`) and show it to the user before running. `run_id` must be unique per logical pipeline; rerunning with the same `run_id` RESUMES, not restarts.
3. **Approval nodes**: if the descriptor contains any `"kind": "human-approval"` node, resolve it through the `ask_user_question` tool at that point in the run. (OMC's original ran these on an interactive terminal with live stdin; dsh's `ask_user_question` is the equivalent gate.)
4. **Execute model-driven**, in topological order, honoring `concurrency_limit`:
   - `"kind": "command"` → run with `pwsh`
   - `"kind": "agent"` → spawn a `subagent` with the node's `instructions`; keep it read-only (research/review role cards) unless the descriptor explicitly demands mutation
   - `"kind": "human-approval"` → `ask_user_question`; EOF/absence of an answer fails closed to denied
   - After each node commits, **append to the journal** `.omd/graph-runs/<run_id>/journal.jsonl` (one JSON line per committed transition: node id, status, timestamp, output digest) and refresh the snapshot `.omd/graph-runs/<run_id>/descriptor.json` + node results.
5. **Resume**: rerunning the same descriptor after an interruption replays the journal and continues. Completed nodes never re-execute — read the journal first and skip committed nodes.
6. Relay progress as you go: `[run]`, `[node]`, `[ok]`, `[fail]`, `[join]`, `[done]`.

### Runtime exit codes (live in the omd phase-1 runtime)

Normative for the future runtime, recorded here so descriptors stay forward-compatible: `0` succeeded | `1` terminal failed | `19` another writer owns this run (busy) | `20` corrupt/tampered journal (fail-closed) | `21` descriptor drift on resume | `70` runtime crash. Model-driven execution reports the same states in prose instead of exit codes.

## Descriptor Schema (minimal)

```json
{
  "descriptor_version": 1,
  "run_id": "unique-pipeline-id",
  "revision_id": "rev-1",
  "goal": "one line",
  "nodes": [
    { "id": "n1", "kind": "command", "title": "...", "timeout_ms": 60000,
      "max_attempts": 2, "effect_policy": { "policy": "side_effect_free" },
      "command": "npm test" },
    { "id": "a1", "kind": "agent", "title": "...", "timeout_ms": 300000,
      "max_attempts": 1, "effect_policy": { "policy": "side_effect_free" },
      "instructions": "..." },
    { "id": "gate", "kind": "human-approval", "title": "...",
      "prompt": "Proceed?" }
  ],
  "edges": [ { "id": "e1", "kind": "fixed", "from": "n1", "to": "a1" } ],
  "entry_node_ids": ["n1"],
  "concurrency_limit": 2,
  "terminal_verification_node_id": "a1"
}
```

Edge kinds: `fixed` and `fan_out`/`join` pairs run today; `conditional` and `back_edge` are rejected at the entry with `UNSUPPORTED_EDGE` fail-fast (unsupported — say so, don't guess routes). The authoritative Zod schema is ported to `lib/graph/schema.js` — `graph_validate` performs strict validation and malformed descriptors fail fast.

## Capability Boundary & Semantics (read before authoring)

- **Edge support**: `fixed` edges and `fan_out`/`join` pairs run today. `conditional` and `back_edge` routes are **unsupported in the phase-1 runtime**: entries (graph_validate/graph_run) fail fast with `UNSUPPORTED_EDGE` (exit 1, listing the offending edges) — say so instead of guessing routes.
- **Crash-recovery guarantee is at-least-once** for command nodes: a crash between an external side effect and its journal append re-executes that node on resume. For `idempotent` commands, expose the resolved key to the command as the `GRAPH_IDEMPOTENCY_KEY` environment value and record it for downstream dedupe. Exactly-once for external side effects is out of scope.
- **Command trust boundary**: command nodes are arbitrary shell lines run via `pwsh` with real process authority in the current working directory. Only run descriptors you wrote or trust. Commands are not filesystem/process sandboxed.
- **Agent authority boundary**: built-in agent nodes are read-only by convention — in omd this is prompt-level discipline (dsh `subagent` has no tool-restriction parameter), so state it in the node's `instructions` and prefer read-only role cards. Treat `.omd/graph-runs/<run_id>/descriptor.json` as executable content.

## Journal format contract (the phase-2 runtime's anchor — model-driven execution writes it today)

`.omd/graph-runs/<run_id>/journal.jsonl` is append-only JSONL — one event per line, **append only, never rewrite, never reorder**. Resume semantics derive entirely from it (read journal, skip committed nodes, continue), so model-driven execution must write it in exactly this format:

```jsonl
{"seq":1,"ts":"<ISO-8601>","type":"run_started","run_id":"...","graph_id":"...","descriptor_hash":"<sha256>"}
{"seq":2,"ts":"...","type":"node_scheduled","node_id":"a1","attempt":1}
{"seq":3,"ts":"...","type":"node_committed","node_id":"a1","attempt":1,"exit_code":0,"output_ref":"outputs/a1.md"}
{"seq":4,"ts":"...","type":"node_failed","node_id":"a2","attempt":1,"error":"...","retryable":true}
{"seq":5,"ts":"...","type":"gate_waiting","node_id":"gate","prompt":"Proceed?"}
{"seq":6,"ts":"...","type":"gate_resolved","node_id":"gate","decision":"approved"}
{"seq":7,"ts":"...","type":"run_finished","terminal_status":"completed|failed|cancelled"}
```

Rules:
- `seq` is strictly increasing (resume continues from the max seq); `node_committed` is the ONLY completion witness — a node with `node_scheduled` but no `node_committed` is re-run on resume (at-least-once).
- Large outputs never land in the journal: `output_ref` points at files under `.omd/graph-runs/<run_id>/outputs/`.
- The `descriptor.json` snapshot lives next to the journal; a `descriptor_hash` mismatch refuses resume (guards against mid-run descriptor edits).
- The phase-2 deterministic runtime will consume this same format — journals written by the model today are tomorrow's resume input.

## Runtime Tool Surface (U4 phase-1, landed)

The deterministic runtime core lives in `lib/graph/` (schema validation / descriptor sealing / flat OCC journal / pure-function scheduler / owner fence / runner loop), **shared by two entries**: the MCP six tools (`omd-state` server) and the background script `node bin/graph-runner.mjs --descriptor <draft.json> --runs-root <dir> [--takeover] [--stale-grace-ms <ms>] [--cwd <dir>]` (process exit code = exit_code; last stdout line `GRAPH_RUN_RESULT <json>`).

- **graph_validate** `{cwd, descriptor}`: self-check the descriptor before running — strict schema + structural validation + `UNSUPPORTED_EDGE` fail-fast; returns the `descriptor_hash` that would be sealed.
- **graph_run** `{cwd, descriptor, takeover?, stale_grace_ms?}`: seals and advances synchronously to the first waiting point (gate_waiting / agent_pending / terminal) before returning; always returns `{exit_code, terminal_status, status, waiting, projection, ...}`. Re-entering the same `run_id`: live fence → `19`; dead owner past the stale grace → `takeover:true` explicit takeover.
- **graph_status** `{cwd, run_id}`: `projectGraph` projection (ready/waiting/blocked/done/failed/max_seq) + fence observation (`stalled`).
- **graph_resume** `{cwd, run_id, takeover?, stale_grace_ms?}`: the only resume-driver entry — rehydrates from the run directory's `descriptor.json` snapshot and advances; `takeover:true` is the explicit takeover for stalled runs (writes `fence_acquired` on success).
- **graph_node_result** `{cwd, run_id, node_id, attempt, exit_code, output_ref?, output?}`: the only commit port for agent nodes; must match the latest `agent_pending` (same attempt, no committed yet) — idempotent for identical payloads, rejects conflicting ones.
- **graph_gate_resolve** `{cwd, run_id, node_id, decision: approved|denied, output_summary?}`: the only adjudication port for human-approval nodes; must match that gate's latest `gate_waiting` (misaligned / already-resolved / terminal runs are rejected); `denied` is a first-class outcome (the run ends as gate_denied).

### Journal vocabulary registration (additions beyond the 7 frozen types above — binding for the runtime and hand-written journals alike)

```jsonl
{"seq":N,"ts":"<ISO-8601>","type":"agent_pending","node_id":"a1","attempt":1}
{"seq":N,"ts":"...","type":"fence_acquired","epoch":2,"owner_pid":12345}
```

- `agent_pending`: an agent node waiting for model-driven completion (`{node_id, attempt}`; the watchdog marks `node_failed{retryable}` past `timeout_ms`); commit via `graph_node_result` → `node_committed`.
- `fence_acquired`: audit line for fence acquisition on resume/takeover (`{epoch, owner_pid}`; not written on a fresh run's first start). Fence metadata itself never enters the journal.

### Fence sidecar layout (inside the run directory, none of it in the journal)

- `owner.lock`: created O_CREAT|O_EXCL with a held fd; payload `{pid, epoch, started_at}` — occupancy mutex and holder identity.
- `owner.epoch`: monotonically increasing integer sidecar (takeover only raises; numbering never rewinds after release).
- `owner.json`: `{pid, started_at, lease_ts}` heartbeat. **stalled = lease older than 30s (default) AND owner pid not alive** — both conditions required; takeover is only allowed when stalled.

### Agent guardrails (runtime-generated, not model discretion)

For agent nodes with `effect_policy=side_effect_free`, the runtime force-prepends a read-only/leaf-guard contract prefix to `instructions` before sealing (idempotent: already-prefixed text is not double-prefixed; MCP and bin share the prefix → same `descriptor_hash`, so cross-entry resume never drifts on the prefix). Exception: sealed inputs (descriptors carrying `descriptor_hash`) are adopted as-sealed and never re-computed — the runtime prefix does not apply to them.

### Authority-face honest declaration (prompt-level convention, not a security boundary)

`graph_gate_resolve` and `graph_node_result` **trust the MCP caller** with no proof of user presence — the gate is a workflow construct, not a security boundary (the model already holds pwsh; bypassing it costs nothing). Mitigation: decisions/commits must match the latest pending record (misaligned ones fail closed). A single graph_run/resume call runs unbounded until the first waiting point; client timeouts/disconnects do not affect run progress — poll with graph_status.

## State Contract (状态契约)

Graph **holds no mode state**: the journal and snapshots under `.omd/graph-runs/<run_id>/` ARE the state, written with plain file tools. No `state_write`/`state_clear` cycle. Resume means: read the journal, skip committed nodes, continue. A graph that needs interactive approvals must run in a session where `ask_user_question` is available — otherwise it fails closed at the gate.

Files in this skill

  • SKILL.md12.3 KB
  • SKILL.zh.md11.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.