oc-checkpoint-protocol — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited oc-checkpoint-protocol (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A cross-skill convention for session persistence. Any skill that runs multi-step workflows across conversations adopts this protocol to save, resume, and recover state without the user re-explaining context.
This is a protocol, not a skill. It defines a file format, naming convention, resume behavior, and integration contract that individual skills implement. Think of it like HTTP — the spec lives here, the implementations live in each skill.
"1.1". Itlives inside every checkpoint file and only changes when the file shape grows in a way the validator must know about. The validator (scripts/checkpoint.mjs) enforces it and accepts both `"1.0"` and `"1.1"`.
version in this file's YAML frontmatter. Ittracks the evolution of the docs and tooling (the pm_refs extension, the doctor/next commands, etc.) and moves independently, in lockstep with the rest of the catalog. oc-release-ops bumps it.
Additive fields, two ways to ship them. An optional additive field can ride under the same wire version — pm_refs (skill release 1.2) was added under schema "1.0" with no wire bump, because old checkpoints stayed valid and the shape only grew. OR a release can group additive fields into a marked minor wire bump when it wants a visible protocol revision. v1.6 ("the instrumented pipeline") took the second path: cost, eval_scores, and telemetry_handle landed together as wire `"1.1"`. It is still fully backward compatible — "1.0" checkpoints validate unchanged, those three fields stay optional — the 1.1 stamp just marks the revision. New writes stamp "1.1"; oc-migration-ops sweeps existing "1.0" files forward. A wire bump is never a breaking change here: it only ever adds optional fields, never removes or repurposes one.
Claude's context resets between conversations. Multi-step skills (oc-app-architect, oc-reverse-spec, oc-stack-forge, oc-code-auditor, oc-deploy-ops, oc-git-ops) lose all progress when a session ends unless they write it down. Before this protocol (v1.0), oc-reverse-spec kept a bespoke checkpoint.md, oc-app-architect had gates but no session persistence, and most skills had no continuity at all.
Each skill reinvented (or didn't) its own persistence, and the user paid the cost: re-explaining context, re-reading files, and hoping Claude picked up where it left off. This protocol gives every skill one shared format instead.
Every checkpoint-aware skill writes a single checkpoint.json file to its project directory. JSON (not markdown) because it's machine-parseable for cross-skill reads.
Location: {project-dir}/.checkpoints/{skill-name}.checkpoint.json
Example paths:
/home/claude/storefront/.checkpoints/oc-app-architect.checkpoint.json
/home/claude/storefront/.checkpoints/oc-code-auditor.checkpoint.json
/home/claude/storefront/.checkpoints/oc-reverse-spec.checkpoint.jsonMultiple skills can checkpoint the same project simultaneously without collision.
{
// === HEADER (required) ===
"protocol_version": "1.1", // On-disk schema version — stamp new writes "1.1"; validator also accepts "1.0"
"skill": "oc-app-architect", // Skill that owns this checkpoint
"project": "storefront", // Human-readable project name
"project_dir": "/home/claude/storefront", // Absolute path
"created_at": "2026-03-31T14:00:00Z",
"updated_at": "2026-03-31T15:30:00Z",
// === PROGRESS (required) ===
"phase": "build-loop", // Current phase name (skill-defined)
"step": "sprint-2-eval-round-1", // Current step within phase
"status": "in_progress", // in_progress | blocked | complete | failed
"progress_summary": "Sprint 1 passed (8.2/10). Sprint 2 generator built, evaluator running.",
// === PROGRESS TABLE (recommended; validator warns if missing while in_progress) ===
// Ordered list of all phases/steps with completion status
"progress_table": [
{ "id": "planning", "label": "Planner", "status": "complete" },
{ "id": "sprint-1", "label": "Sprint 1: Auth", "status": "complete" },
{ "id": "sprint-2", "label": "Sprint 2: CRUD", "status": "in_progress" },
{ "id": "sprint-3", "label": "Sprint 3: UI", "status": "not_started" }
],
// === CONTEXT PRIMER (recommended — this is what lets resume skip a full re-read) ===
// Dense, self-contained summary. A new session reads ONLY this + generated files
// to resume work. Must be complete enough that re-reading the full codebase or
// re-running prior phases is unnecessary.
"context_primer": {
"key_decisions": [
"Stack: Hono + D1 + Workers. Auth: WebAuthn passkeys.",
"Two users: Alex (admin), Sam (viewer). User IDs in D1.",
"Sprint 1 delivered auth + session middleware. All tests pass."
],
"generated_files": [
"spec.md",
"sprint-plan.md",
"sprints/sprint-1/contract.md",
"sprints/sprint-1/eval-round-1.md",
"sprints/sprint-1/eval-round-2.md",
"src/auth/passkey.ts",
"src/middleware/session.ts"
],
"user_preferences": [
"Prefers table-based layouts",
"Terminal/dark aesthetic",
"Direct, concise communication"
]
},
// === BLOCKERS & OPEN QUESTIONS (optional) ===
"blockers": [
{
"id": "b1",
"description": "Evaluator flagged missing rate limiting on auth endpoints",
"blocking": "sprint-2",
"needs": "user_decision", // user_decision | code_fix | external_dep
"proposed_resolution": "Add rate-limit middleware in sprint 2 scope"
}
],
// === NEXT ACTIONS (required while status is in_progress — resume reads [0] first) ===
// What the next session should do FIRST. Ordered by priority.
// Each item is a string, or { "text": "...", "done_when": "<shell cmd>" } so a
// session can self-verify completion. `checkpoint done <skill>` pops item [0].
"next_actions": [
"Read evaluator report: sprints/sprint-2/eval-round-1.md",
"Fix rate limiting gap flagged in blocker b1",
{ "text": "Re-run evaluator on sprint 2", "done_when": "npm test" }
],
// === SKILL-SPECIFIC STATE (optional) ===
// Freeform object for skill-internal state that doesn't fit the schema above.
// Private to the owning skill: siblings read only keys its owner documents for them.
"skill_state": {
"current_sprint": 2,
"iteration": 1,
"max_iterations": 3,
"pass_threshold": 6.0,
"scores": {
"sprint-1": { "final": 8.2, "rounds": 2 }
}
}
}| Status | Meaning | Resume behavior |
|---|---|---|
in_progress | Work is actively happening | Pick up from step |
blocked | Waiting on user decision or external dependency | Show blockers, ask for resolution |
complete | All phases done | Inform user, offer next steps |
failed | Unrecoverable error occurred | Show failure context, offer restart or manual fix |
Two enums, deliberately. The top-levelstatusis one of the four above. Individualprogress_table[].statusrows use a superset that also allowsnot_started— a single phase can be not-started even though the checkpoint as a whole isin_progress. The validator enforces each set in its own place.
When any checkpoint-aware skill is invoked and a checkpoint exists for the current project, follow this sequence:
Check: {project-dir}/.checkpoints/{skill-name}.checkpoint.json exists?
YES → read it, go to Step 2
NO → start fresh (normal skill flow)Read the checkpoint. Display a brief status to the user:
RESUMING: [skill] on [project]
Last session: [updated_at, relative time]
Status: [status] — [progress_summary]
Progress: [X/Y phases complete]
Next: [first item from next_actions]Resuming your own work shouldn't cost a question every time. Default behavior:
"Resuming from next_actions[0]: …" and proceed. The user can always redirect.
than 7 days while in_progress, 14 days while complete, or 3 days while blocked) → stop and ask: "Continue from here, restart, or show the full checkpoint?" These are the cases where silently continuing is wrong.
context_primer and next_actions, load referencedgenerated_files, proceed.
.checkpoints/history/<skill>.<timestamp>.checkpoint.json (what checkpoint.mjs reset <skill> does; tracked in git like the checkpoint itself), then start fresh.
Before doing any new work:
context_primer.key_decisions — this IS your session contextcontext_primer.generated_files — but only the ones relevant to thecurrent step (don't read all files from all sprints if you're on sprint 3)
blockers — if any are needs: user_decision, ask before proceedingnext_actions[0]If the checkpoint is >24h old, do a quick consistency check:
src/auth/passkey.ts but the file hasbeen modified since the last session. Want me to re-read it?"
Checkpoint writes are mandatory lifecycle work, not optional bookkeeping. Update the checkpoint after:
| Event | Action |
|---|---|
| Session resume | Restamp updated_at and revalidate or replace next_actions[0] before continuing. |
| Session pause / user says stop / context is getting long | Write a compact progress_summary, current step, blockers, and the exact next action to run first. |
| Phase or gate completion | Update progress_table, phase, step, summary, and next action before moving on. |
| Key decision / user decision made | Append to context_primer.key_decisions, clear any matching blocker, and restamp. |
| File generated | Append to context_primer.generated_files. |
| Blocker discovered | Add to blockers and set status to blocked if it stops progress. |
| Blocker resolved | Remove or mark the blocker resolved and set status back to in_progress. |
| Cross-skill handoff | Current skill writes the handoff reason, named artifacts, and receiving skill before the receiver acts. |
| Branch / PR opened | Record branch, PR number, scope, touched artifacts, and pending verification. |
| PR merged | Mark PR-wait actions complete and replace them with deploy/release/follow-up actions. |
| Deploy to staging/prod | Record environment, commit SHA, health result, user-visible route checked, and next rollout step. |
| Release cut/ship | Reconcile release-relevant skill checkpoints to the shipped release state. |
| Merge conflict or rebase resolution | Restamp if conflict resolution changed generated files, summaries, or next actions. |
checkpoint doctor drift found | Write a reconciliation checkpoint or add an explicit blocker; do not leave drift only in console output. |
If a future session would be misled without the write, the current skill must write before handing control back.
You do not need any special tooling to write a checkpoint — your own file tools are the writer. The scripts/checkpoint.mjs CLI described under Tooling is an optional convenience that exists only inside the opchain.dev repo; on a user's own project it is absent, and you write checkpoints directly instead. Do not treat a missing CLI or a missing npm run checkpoint:* script as a reason to skip the write. Do not tell the user that status is unreliable merely because generated CLI output is unavailable: read the checkpoint JSON directly, then corroborate its claims against the referenced specs, git state, tests, and release artifacts. Record CLI adoption as tooling debt only when the user actually wants that convenience added.
The operation is always read → merge → write, four concrete steps you perform with the tools you already have:
{project-dir}/.checkpoints/ if it isn'tthere yet — mkdir -p {project-dir}/.checkpoints with the Bash tool, or just rely on your Write tool creating parent directories. ({project-dir} is the root of the project you're working in — the directory that holds its .git, package.json, or src/ — not the skill's install directory.)
{project-dir}/.checkpoints/{skill-name}.checkpoint.jsonif it exists; treat a missing file as {}.
updated_at to the current time (ISO-8601). Never blindly overwrite — another skill may have written a sibling checkpoint, and this file may have been hand-edited.
required header + progress fields from the schema above (protocol_version, skill, project, project_dir, created_at, updated_at, phase, step, status, progress_summary, and — while in_progress — next_actions), and stamp protocol_version as "1.1".
# The shape of the operation — do it with your own Read/Write tools, no CLI required:
existing = read_json("{project-dir}/.checkpoints/{skill}.checkpoint.json") or {}
updated = merge(existing, new_state)
updated["updated_at"] = now() # ISO-8601
write_json("{project-dir}/.checkpoints/{skill}.checkpoint.json", updated)A hand-written checkpoint is a first-class checkpoint. If the CLI happens to be present (you're in the opchain repo), it automates the merge and validates the result — use it. Everywhere else, the four steps above ARE the protocol. The one unacceptable outcome is ending a session with state that a future session needed but that was never written to disk.
There is no hard byte cap — these are session-state docs, and an honest one beats an artificially short one. (An earlier draft of this protocol said "under 4KB"; real checkpoints run 5–16KB and that's fine. .checkpoints/README.md is the authority: keep them readable, keep them honest.)
What actually matters is keeping the resumable core scannable and stopping append-only telemetry from growing without bound:
progress_summary is read on every resume — keep it to a few sentences. Thevalidator warns above ~1200 characters. Push detail into progress_table.
context_primer.key_decisions exceeds ~20 items, consolidate older ones intoa summary paragraph and keep only the recent items individually.
skill_state (merged-PR lists, reconciliation logs) isthe usual source of bloat and of merge conflicts. Rotate closed sessions into .checkpoints/history/<skill>.<timestamp>.json instead of letting skill_state grow. The validator warns once a file passes ~32KB.
Anti-pattern (worked example): don't auto-stamp `merged_prs` per merge. opchain.dev once ran a.github/workflows/checkpoint-after-merge.ymlthat, on every merge tomain, appended the merged PR toskill_state.merged_prsand opened abot/checkpoint-stamp-<PR>PR. It was removed 2026-06-22 after two compounding failures: (1) under branch protection the bot'sgh pr merge --autocould never satisfy required review, so each merge left a permanent open PR (two dozen piled up); and (2) every stamp PR appended to the same array, so they mutually conflicted — merging one staled the rest, and GitHub's server-side resolution bypasses the merge driver (see Tooling).merged_prsis fully reconstructable fromgit log; it is not worth a CI-gated PR per merge. Restamp oc-git-ops at inflection points by hand (the assistant does this — e.g. a single reconciliation PR), and rotate the array into.checkpoints/history/.
Skills can read each other's checkpoints (read-only) for coordination. The table below is illustrative — the maintained upstream/downstream map lives in orchestrator.md § "Upstream/Downstream Map", which has a row for every skill in the catalog. Treat that as the single source of truth; this table is just the common cases.
| Reader | Reads | Why |
|---|---|---|
| oc-app-architect | oc-reverse-spec checkpoint | Know what analysis exists for the codebase |
| oc-deploy-ops | oc-code-auditor + oc-security-auditor checkpoints | Audit grade and posture before the deploy gate |
| oc-git-ops | oc-app-architect + oc-bug-check checkpoints | Sprint context for the commit, and the gate verdict |
| oc-git-ops | oc-docs-forge + oc-repo-ops checkpoints | Pre-PR gate: docs packet + readiness verdict before opening a PR |
| oc-code-auditor | oc-reverse-spec checkpoint | Know what analysis has been done |
Rules:
header, progress, progress_table, context_primer, and blockersskill_state unless its owner documents that key forsiblings (in the owner's Cross-Skill Reads / "Read by" table or its schema section). A documented key is a published contract its owner keeps stable; everything else is private. The commit gate is one such reader: it reads oc-bug-check's skill_state.last_run_verdict (or last_run.verdict) and skill_state.verified_tree per oc-bug-check's § Commit gate contract. (The opchain repo's checkpoint doctor also scans every skill_state for merged/shipped tokens when flagging stale next actions; it relies on no shape.)
"depends_on": "oc-app-architect checkpoint shows spec approved at 2026-03-31T12:00:00Z"
The oc-checkpoint-protocol skill includes scripts/checkpoint.mjs. Resolve that path relative to this skill's installed SKILL.md and run its absolute path from the consuming project:
node /absolute/path/to/oc-checkpoint-protocol/scripts/checkpoint.mjs status
node /absolute/path/to/oc-checkpoint-protocol/scripts/checkpoint.mjs validateThe launcher finds the consuming Git repository root, even from a subdirectory; outside Git it uses the current directory. An explicit OPCHAIN_ROOT overrides discovery. State stays in the consuming project's .checkpoints/, never in the skill installation or plugin cache. The same commands below work with this bundled path substituted for scripts/checkpoint.mjs; no npm setup is needed. Installing or updating the helper must not initialize or overwrite checkpoints.
This entire section is an optional fast-path, not a requirement. The commands below use the opchain.dev source repo'sscripts/checkpoint.mjsandcheckpoint:*npm aliases. In installed projects, use the bundled helper above. If that helper is unavailable, you still create, read, and update checkpoints directly with your file tools (see How to Write above). If any command in this section is not found, skip it and write the file yourself; a missingscripts/checkpoint.mjsis never a reason not to checkpoint.
When they are available, these commands automate the read → merge → write and validate the result. scripts/checkpoint.mjs is zero-deps pure Node.
Two companion guides ship in this skill's own directory (they are not bundled into the other skills): references/INTEGRATION.md, the per-skill integration guide, and references/WALKTHROUGH.md, an end-to-end resume walkthrough. The executable wire-1.1 lifecycle, typed-handoff, and atomic-store interfaces are specified in references/state-contract-v1.md; existing 1.0/1.1 checkpoints remain valid.
Read / resume:
node scripts/checkpoint.mjs status # "where did I leave off?" — full summary
node scripts/checkpoint.mjs status <skill> # one checkpoint; exits 1 when that skill has none
node scripts/checkpoint.mjs status --brief # just the top skill + its next action + blockers
node scripts/checkpoint.mjs status --since=2026-06-01T00:00:00Z # momentum digest
node scripts/checkpoint.mjs next # the SINGLE highest-priority non-stale actionstatus leads with a ⛔ N decisions waiting on you banner when any blocker needs: user_decision, and flags ⚠ stale (Nd) on checkpoints untouched for more than 7 days (in_progress), 14 days (complete) or 3 days (blocked). next encodes the priority hierarchy (blocked-on-decision > failed > in_progress-at-gate > in_progress > complete-with-queued-work > not-started) so you don't need the oc-orchestrator's registry to answer "what now?".
Reconcile / verify (catch drift before it bites):
node scripts/checkpoint.mjs doctor # cross-check vs git history + filesystem
node scripts/checkpoint.mjs doctor --online # also compare deployed /api/health vs the approved release baseline
node scripts/checkpoint.mjs doctor --fail-on-warningsdoctor flags: a project_dir that doesn't exist on this machine, stale in_progress checkpoints, generated_files that reference missing paths, and next_actions that point at PRs/tickets already merged (the "telling future-self to redo shipped work" failure that caused three manual reconciliations in this repo).
next uses the same drift evidence before recommending work. If a queued action references already-merged PR work or a completed ticket, next skips to the first fresh action or recommends checkpoint reconciliation instead of telling the next agent to redo shipped work.
Write:
npm run checkpoint:validate # schema gate — CI runs this (add --strict to fail on warnings)
node scripts/checkpoint.mjs list # list all checkpoint files
node scripts/checkpoint.mjs show [skill] # display full JSON for one/all checkpoints
node scripts/checkpoint.mjs reset <skill> # archive current file into .checkpoints/history/
node scripts/checkpoint.mjs update <skill> --field=value [...] # apply updates, auto-stamp updated_at
node scripts/checkpoint.mjs done <skill> # pop next_actions[0] → recently_done, restamp
node scripts/checkpoint.mjs init # scaffold .checkpoints/ on a fresh projectupdate supports three operators on dotted paths:
--key=value — replace a scalar--key+=value — append to an array (creates if missing)--key:json=... — parse the value as JSON for objects/arrays/numbers+ and :json combine in either order (--key:json+= or --key+:json=). An append adds its value as one element, so pass an object to add one entry. A key that still carries an operator after those are read is refused, rather than written as a literal key.
The validator runs after every update/done so you can't silently corrupt a file. Timestamps accept Z or a numeric offset (+00:00); the CLI writes Z.
Mutating CLI commands (update, done, and reset) serialize per skill with the same process-owned advisory-lock policy as local MCP, and checkpoint writes use same-directory temporary files plus atomic rename. Concurrent append updates therefore merge against the latest accepted record instead of overwriting one another; a killed writer leaves the prior accepted JSON intact. This remains a single-file, zero-package-dependency CLI when copied elsewhere. Atomic mutation supports macOS with /usr/bin/lockf and Linux with flock on PATH; unsupported hosts fail explicitly rather than falling back to unsafe stale-lock recovery.
The opchain repository's Claude Code Stop hook additionally derives its enforced skill inventory from skills/, normalizes namespaced Skill-tool invocation IDs, and requires the checkpoint's record_updated_at ?? updated_at to be at or after the transcript invocation event. Its stop_hook_active guard prevents a block loop. This current-run enforcement belongs to that repo-local hook; raw skill installs, MCP clients, and the distributed plugin do not gain it implicitly.
Merge-driver caveat (read this)..gitattributesregisters a custom merge driver (scripts/merge-checkpoint.mjs) that auto-resolves telemetry-only conflicts. It runs on local `git merge` only — GitHub's server-side "Update branch" / auto-merge buttons do not invoke it. A checkpoint that conflicts there can produce invalid JSON and fail CI (this happened, 2026-05-15). Mitigations: rotate volatile telemetry out of the CI-gated file (see Checkpoint Size), and prefer resolving checkpoint conflicts with a localgit mergeso the driver runs.node scripts/checkpoint.mjs validateafter any manual conflict resolution catches the broken-JSON case before you push.
This protocol is not invoked directly (it has no commands of its own). Scaffolding a fresh project is deliberately trivial and needs no tooling:
.checkpoints/ directory simply creates it (mkdir -p {project-dir}/.checkpoints, or lets the Write tool create it) and writes the file, per How to Write. That is the whole scaffold — a directory and a JSON file.
/oc-ops on a project with no .checkpoints/)does the same thing as step zero.
That is all a user's project ever needs. Everything below is opchain-repo-only convenience and is not required to use the protocol on your own project:
scripts/checkpoint.mjs — an optional validator/status/next/doctor/update CLI thatautomates the read→merge→write. If you want it on another repo you can copy it from the opchain.dev reference implementation, but nothing here depends on it.
.checkpoints/README.md — human-readable schema reference (nice to have; the schemain this document is the authority).
package.json scripts / CI validation / .gitattributes merge driver — repoergonomics for a team that tracks many checkpoints. Optional.
recommended a .github/workflows/checkpoint-after-merge.yml that opened a stamp PR on every merge to main. opchain.dev shipped it and removed it 2026-06-22 because it was net-negative under branch protection — see Anti-pattern: don't auto-stamp `merged_prs` per merge under Write Protocol → Checkpoint Size. Let the assistant restamp oc-git-ops at inflection points instead.
Do not add .checkpoints/ to .gitignore. Tracking the directory in git is what makes checkpoints survive across sessions and machines — including ephemeral runners like Claude Code on the web.
Any skill that adopts this protocol should recognize /checkpoint as a utility command. It is a prose convention, not a registered verb: no skill declares it in frontmatter and the plugin ships no command file for it, so a skill handles it when the user types it.
/checkpoint Show current checkpoint status for active project
/checkpoint next Show the single highest-priority next action (priority engine)
/checkpoint doctor Cross-check checkpoints against git + filesystem for drift
/checkpoint show Display full checkpoint contents
/checkpoint reset Archive current checkpoint and start fresh
/checkpoint list List all checkpoints in the project directoryThis is a cross-skill command — any checkpoint-aware skill handles it the same way. The behavior is identical regardless of which skill the user is currently in.
project-dir/
├── .checkpoints/
│ ├── oc-app-architect.checkpoint.json
│ ├── oc-code-auditor.checkpoint.json
│ ├── oc-reverse-spec.checkpoint.json
│ └── oc-deploy-ops.checkpoint.json
├── spec/ (oc-app-architect output)
├── sprints/ (oc-app-architect Phase 6 build loop output)
├── src/ (project source)
└── ...The .checkpoints/ directory is:
Claude Code on the web has an ephemeral worker — gitignored state doesn't survive sessions). Treat checkpoints as living "session state docs" the team can read in PRs alongside the code change.
npm run checkpoint:validate (see.checkpoints/README.md and scripts/checkpoint.mjs for the schema and tooling)
npm run checkpoint:status — the canonical "where did Ileave off?" command for new sessions
Local MCP also maintains .checkpoints/.gitignore entries for its private runtime artifacts: .mcp-sessions.json, *.lock, .*.tmp, and the reserved .local/ subtree. Skill *.checkpoint.json files remain trackable.
pm_refspm_refs is an optional, additive field (introduced in skill release 1.2) under on-disk schema 1.0 — checkpoints without it stay valid. It lets a skill's checkpoint carry the PM tickets it touched so downstream skills find context without re-asking.
Status: the field is fully specified and validated when present (see Validation below), but in practice skills today still record PM refs ad-hoc inskill_state(e.g.linear_active_parents). Preferpm_refsfor anything a sibling skill needs to read; keep skill-private PM bookkeeping inskill_state.
{
// ...existing fields
"pm_refs": [
{
"provider": "linear", // or "jira" | "github-issues"
"id": "PLAT-4471", // ticket id
"url": "https://linear.app/onramp/issue/PLAT-4471",
"role": "source", // "source" | "child" | "deploy" | "incident" | "linked"
"created_by_skill": "oc-app-architect", // which skill linked it
"first_seen_at": "2026-05-04T12:00:00Z"
}
]
}role semantics:
source — the originating ticket (one per checkpoint, typically).child — sprint / step / sub-ticket created by this skill.deploy — deploy-ops-created deploy ticket.incident — monitoring-ops-created incident ticket.linked — a related ticket the skill referenced but didn't author.When any skill starts, after reading its own checkpoint, it also reads `pm_refs` from sibling-skill checkpoints to find context. Example: oc-git-ops, on /oc-git-sync with no explicit ticket id, looks at oc-app-architect.checkpoint.json#pm_refs to find the source ticket from the most recent build phase.
Every skill that touches a PM ticket appends to its own pm_refs array at the same time it writes the ticket. The array is append-only within a session; entries are not deduplicated across sessions (the audit trail is per-session).
Skills that follow oc-integrations-engineer's PM-MCP protocol (references/pm-mcp-protocol.md in that skill) may also write pm_deferred_actions, pm_flush_log and pm_idempotent_skips beside pm_refs. They are optional, shaped by that protocol, and the validator does not check them.
npm run checkpoint:status does not render pm_refs. To follow a ticket across skills at resume, read each checkpoint's pm_refs directly (node scripts/checkpoint.mjs show <skill>, or open the JSON).
npm run checkpoint:validate treats pm_refs as optional, but when it is present it checks the shape: each entry needs a provider and id, and any role must be one of source|child|deploy|incident|linked. Checkpoints without the field validate unchanged. There is no batch migration — the field appears on a skill's next write.
The pm_refs array stores ticket ids + URLs. It does not store ticket bodies. Tickets in regulated environments may contain CUI / PHI; the body retrieval path runs through the broker + redactor on each access. The checkpoint is therefore safe to commit + share within the project.
cost, eval_scores, telemetry_handleThree optional, additive fields introduced with on-disk schema "1.1" (v1.6 "the instrumented pipeline"). Checkpoints without them stay valid; they are validated only when present. Together they make the pipeline instrumented: every phase can report cost, every skill can report an eval score, every checkpoint can carry a budget and an opt-in telemetry link.
cost — LLM spend attribution + budget gate (owner: oc-cost-ops){
"cost": {
"currency": "USD", // optional, default USD
"total_usd": 12.34, // optional, ≥ 0 — spend attributed to this checkpoint
"budget_usd": 50, // optional, ≥ 0 — the budget ceiling; gate trips when total > budget
"by_phase": { "spec": 3.10, "build": 8.40, "audit": 0.84 }, // optional, name → ≥ 0
"by_model": { "claude-opus-4-8": 9.2, "claude-haiku-4-5": 3.1 }, // optional, name → ≥ 0
"tokens": { "input": 1_200_000, "output": 240_000 }, // optional, free-form
"updated_at": "2026-06-25T12:00:00Z" // optional
}
}oc-cost-ops owns this field. The validator enforces: cost is an object; total_usd/budget_usd are non-negative numbers; by_phase/by_model map names to non-negative numbers. When total_usd > budget_usd > 0 the validator warns ("budget gate tripped") rather than erroring — overspend is a signal, not a corrupt file. The budget gate (block vs warn) is policy oc-cost-ops applies; the checkpoint just records the numbers.
eval_scores — scores against a stable rubric (owners: oc-bug-check, oc-code-auditor, oc-prompt-ops){
"eval_scores": [
{ "rubric": "oc-code-auditor", "score": 8.2, "max": 10, "at": "2026-06-25T12:00:00Z",
"ref": "sprints/sprint-2/eval-round-1.md",
"dimensions": { "functionality": 9, "completeness": 8, "quality": 8, "ux": 8 } },
{ "rubric": "oc-prompt-ops", "score": 0.93, "max": 1 } // 0..1 pass_rate uses max: 1
]
}Append-only list. The score is rubric-relative — pair it with max when the scale isn't 0..10 (a prompt-ops pass_rate is 0..1 with max: 1; a code-auditor grade is 0..10). The validator requires each entry to be an object with a string rubric and a numeric score; max (if present) must be positive and score must not exceed it; at (if present) must be ISO-8601; dimensions (if present) maps names to numbers. This is what lets oc-orchestrator and oc-cost-ops reason about quality trend, not just pass/fail.
telemetry_handle — opt-in local-metering link (owner: oc-telemetry-ops){
// a bare anonymous id …
"telemetry_handle": "anon-7f3a91c0"
// … or the richer opt-in object form:
// "telemetry_handle": { "enabled": true, "id": "anon-7f3a91c0",
// "sink": ".checkpoints/usage.sqlite",
// "since": "2026-06-25T12:00:00Z" }
}Links a checkpoint to its rows in the local usage.sqlite metering store without storing any PII or content. Default stance is OFF — the field's presence never grants consent, including a copied enabled: true value. Only local SQLite metadata written by an explicit telemetry enable operation grants permission on this machine. A string is an anonymous handle; the legacy object may carry historical status, sink path and start time. Neither form enables tracking in a clone. The validator accepts a non-empty string or an object whose enabled (when present) is boolean, id/sink are strings, and since is ISO-8601. oc-telemetry-ops owns the metering store and the consent gate; the checkpoint only carries the link.
npm run checkpoint:validate treats all three as optional and checks their shape only when present (exactly like pm_refs). Existing "1.0" checkpoints validate unchanged. The forward sweep ("1.0" → "1.1" stamp) is an oc-migration-ops job — additive, no data transform, reversible — the same way governance frontmatter rolled out in v1.4.
The checkpoint is the only truth that survives.
The cost of writing is negligible; the cost of losing progress is a full session.
never needs to re-read the entire codebase or all generated files. 20 lines of dense summary beats 200 lines of re-analysis.
parse it. The progress_summary and next_actions are human-readable so the user can understand what's happening.
skill_state is an opaque bag the protocol doesn'tdefine; other skills read only the keys its owner documents for them.
from an incompatible version, the skill starts fresh and tells the user why.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.