work-ai-protocol
v1.0.2
Published
AI Work Protocol stores resumable AI software work in repository-owned Markdown. The files under `.work-ai/` are the source of truth for Codex and OpenCode; chat history is not part of the recovery contract.
Readme
AI Work Protocol
AI Work Protocol stores resumable AI software work in repository-owned Markdown. The files under .work-ai/ are the source of truth for Codex and OpenCode; chat history is not part of the recovery contract.
All commands below run from the repository root.
功能与维护文档
新增、变更、弃用或删除功能时,必须在同一个变更中同步更新功能清单和 CHANGELOG.md 的 [Unreleased] 小节;命令、协议或治理行为受到影响时,还必须更新对应参考文档。发布前按功能清单中的维护检查表复核文档、测试和版本号。
安装并关联项目
全局安装一次 CLI,然后在每个需要持久化 AI 工作状态的 Git 项目根目录执行一次 init:
npm install --global work-ai-protocol
cd your-project
work-ai initwork-ai init 不创建业务任务,也不需要标题。它会:
- 创建
.work-ai/project.md和空任务索引,将当前 Git 仓库与 work-ai 关联。 - 安装项目级 Codex 和 OpenCode skills。
- 保留已有项目 ID,可安全重复执行;升级 skill 时如存在不同内容,需要显式使用
--force。
全局安装的是 CLI;项目关联、任务和检查点仍保存在各项目自己的 .work-ai/ 中。
在聊天中创建任务
项目初始化后,直接在 Codex 或 OpenCode 中描述要完成的工作:
修复登录后跳回首页的问题,并补充回归测试。Agent 会先检查是否存在匹配的未完成任务。匹配时执行 inspect 并恢复;没有匹配任务时,Agent 从聊天目标生成摘要和任务 ID,然后调用:
work-ai task create "修复登录后跳回首页的问题" \
--task fix-login-redirect \
--tool codex \
--source chat人不需要重复输入任务标题。Agent 会把聊天目标保存为 Original Goal,分析代码库并确认需求与计划,获得用户批准后再 claim、实现、验证和 checkpoint。
问答、代码解释、审查和状态报告等只读工作不会自动创建任务。只有项目存在 .work-ai/project.md 且用户请求修改代码、测试、配置或文档时,Agent 才进入 work-ai 生命周期。
旧版 work-ai init "任务目标" 暂时仍能创建任务,但输出会包含弃用提示;请迁移到 work-ai task create。
Prerequisites
- Node.js 22 or later and npm.
- Git, with the repository containing at least one commit.
- A POSIX-compatible shell for the command examples.
Set the values used throughout this runbook:
export WORK_AI_ROOT="$PWD"
export WORK_AI_TASK="demo-task"
export WORK_AI_BASELINE="$(git rev-parse HEAD)"Build
Install the locked dependencies and build the CLI plus protocol assets:
npm --prefix work-ai ci
npm --prefix work-ai run buildPublish To npm
版本号遵循语义化版本。先把本次变更写入 work-ai/CHANGELOG.md 的 [Unreleased] 小节,然后选择升级类型:
发布前还必须同步核对 docs/features.md 中的软件版本、协议版本、Schema、最近核对日期和功能描述;仅完成代码或版本号更新不视为完整发布。
# 修复:1.0.0 -> 1.0.1
npm --prefix work-ai run release:patch
# 兼容的新功能:1.0.0 -> 1.1.0
npm --prefix work-ai run release:minor
# 不兼容升级:1.0.0 -> 2.0.0
npm --prefix work-ai run release:major版本命令会运行测试和发布检查,更新 package.json、package-lock.json、CLI 版本与 CHANGELOG.md,然后创建中文版本提交和 v<version> Git 标签。确认版本提交后推送并发布:
npm --prefix work-ai run release:check
npm --prefix work-ai pack --dry-run
npm --prefix work-ai publish --access=public
git push origin master --follow-tags用户查看和升级版本:
work-ai --version
npm install --global work-ai-protocol@latestSchema Compatibility And Migration Audit
New task documents declare schema_version: 2. The readers remain compatible
with legacy documents that omit schema_version; an omitted value is treated as
schema 1 for migration reporting.
Run the read-only migration audit before changing existing task documents:
node work-ai/dist/src/cli.js migrate \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK"The command reports every required, session, review, and fact document that needs additive defaults. It never writes task state. Future schema versions fail closed instead of being silently downgraded.
The test suite invokes the local TypeScript compiler through the active Node
runtime rather than an implicit global npx. On hosts that cannot create
symbolic links or enforce POSIX permission bits, capability-specific security
tests are reported as skipped; the same tests remain required on capable CI
workers.
Inspect The Eligible Task Set
The scheduler computes a deterministic, read-only dispatch set from persisted plans and statuses:
node work-ai/dist/src/cli.js schedule --root "$WORK_AI_ROOT"Plans may declare task_dependencies; nodes may declare path_claims and
resource_claims. Legacy nodes use their artifact as the default path claim.
The scheduler rejects node/task cycles, unknown or unfinished dependencies,
unsafe path claims, active-session ownership, blockers, and overlapping path or
resource claims. Invalid tasks remain in the report instead of disappearing.
Bind An Implementation Worktree
Implementation claims can be bound to an isolated worktree at an exact full
commit SHA. The target must be outside the repository, and the current plan
node's path_claims (or its legacy artifact) become the session's write
scope:
node work-ai/dist/src/cli.js claim --root . --task demo-task --session codex-1 --tool codex --revision 4 --baseline <full-sha> --worktree D:\worktrees\demo-task --branch work-ai/demo-task/codex-1
node work-ai/dist/src/cli.js verify-worktree --root . --task demo-task --session codex-1The claim records the branch, base SHA, worktree path, allowed paths, and task
revision in both task state and .work-ai/active/<task>.json. Bound checkpoints
fail closed when the pointer owner, branch, ancestry, HEAD, or changed-file
scope drifts. Ending the session releases the active pointer; the worktree is
retained as auditable evidence until explicitly cleaned up.
Run An Independent Review
After implementation enters review_pending and releases its session, a fresh
review session must bind itself to the frozen candidate before writing a receipt:
node work-ai/dist/src/cli.js claim-review --root . --task demo-task --session review-1 --tool opencode --revision 12 --reviewer cursor-reviewer
node work-ai/dist/src/cli.js review --root . --task demo-task --session review-1 --spec-revision 4 --reviewed-head <full-sha> --verdict APPROVE --reviewer cursor-reviewerThe reviewer session must differ from the implementation session. Candidate Git
reachability, task revision, frozen spec, exact head, and candidate revision are
checked before the receipt is written. Approval routes directly to
ready_for_merge; CHANGES_REQUESTED routes to in_progress. Rework creates a
new candidate revision, so an older receipt cannot satisfy the merge gate even
when the commit SHA is unchanged.
For CHANGES_REQUESTED, pass --findings <absolute-json-path> containing a
non-empty array of { "severity", "code", "summary", "path"?, "line"? }
records. This keeps actionable review findings machine-readable.
Install Codex Skill
Install the Codex adapter and its canonical shared workflow into this repository:
node work-ai/scripts/install-skill.mjs --tool codex --target "$WORK_AI_ROOT"The installer does not replace a different existing skill unless --force is supplied. Inspect the existing files and obtain explicit user confirmation before forcing replacement.
Install OpenCode Skill
Install the OpenCode adapter and its canonical shared workflow into this repository:
node work-ai/scripts/install-skill.mjs --tool opencode --target "$WORK_AI_ROOT"The installer does not replace a different existing skill unless --force is supplied. Inspect the existing files and obtain explicit user confirmation before forcing replacement.
Create A Task
Create the task documents and derived index:
node work-ai/dist/src/cli.js init \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--title "Prove a cross-tool handoff"The new task starts at revision 0 in draft state.
Approve Requirements
Analyze the original goal and checkpoint the task through analyzing and awaiting_approval. Before moving it to ready, show the confirmed goal, scope, assumptions, acceptance criteria, and plan to the user. Explicit user confirmation is required because this establishes the execution baseline.
Claim the draft before writing checkpoints:
node work-ai/dist/src/cli.js claim \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--session "codex-1" \
--tool codex \
--revision 0 \
--baseline "$WORK_AI_BASELINE"Use revision-checked checkpoint requests for the draft -> analyzing -> awaiting_approval transitions:
export WORK_AI_REQUEST="$(mktemp)"
node -e 'const fs=require("node:fs"); const [root,out]=process.argv.slice(1); const payload={root,taskId:"demo-task",sessionId:"codex-1",expectedRevision:1,nextState:"analyzing",currentNode:null,nodeUpdates:{},blockers:[],summary:"Requirement analysis started",codeBaseline:process.env.WORK_AI_BASELINE,nextAction:"Request requirement approval",endSession:false,requirementUpdate:null,planUpdate:null}; fs.writeFileSync(out,JSON.stringify(payload,null,2)+"\n")' "$WORK_AI_ROOT" "$WORK_AI_REQUEST"
node work-ai/dist/src/cli.js checkpoint --input "$WORK_AI_REQUEST"
rm "$WORK_AI_REQUEST"
export WORK_AI_REQUEST="$(mktemp)"
node -e 'const fs=require("node:fs"); const [root,out]=process.argv.slice(1); const payload={root,taskId:"demo-task",sessionId:"codex-1",expectedRevision:2,nextState:"awaiting_approval",currentNode:null,nodeUpdates:{},blockers:[],summary:"Requirement is ready for approval",codeBaseline:process.env.WORK_AI_BASELINE,nextAction:"Wait for user approval",endSession:false,requirementUpdate:null,planUpdate:null}; fs.writeFileSync(out,JSON.stringify(payload,null,2)+"\n")' "$WORK_AI_ROOT" "$WORK_AI_REQUEST"
node work-ai/dist/src/cli.js checkpoint --input "$WORK_AI_REQUEST"
rm "$WORK_AI_REQUEST"The final approval request must contain a non-empty requirementUpdate and the user's approval timestamp.
Define Plan Nodes
The same awaiting_approval -> ready checkpoint supplies planUpdate.nodes. Every node needs an ID, dependencies, artifact, acceptance condition, and verification command. The following runnable command creates a combined approval request for revision 3; change the content before confirming it with the user:
export WORK_AI_REQUEST="$(mktemp)"
node -e 'const fs=require("node:fs"); const [root,out]=process.argv.slice(1); const payload={root,taskId:"demo-task",sessionId:"codex-1",expectedRevision:3,nextState:"ready",currentNode:"N1",nodeUpdates:{},blockers:[],summary:"User approved requirement and plan",codeBaseline:process.env.WORK_AI_BASELINE,nextAction:"Execute N1",endSession:false,requirementUpdate:{confirmedGoal:"Complete a two-node cross-tool task",acceptanceCriteria:["Both nodes have evidence","A fresh tool resumes at N2"],approvedAt:new Date().toISOString(),body:"# Requirements\n\nApproved cross-tool delivery baseline.\n"},planUpdate:{nodes:[{id:"N1",goal:"Create artifact",depends_on:[],artifact:"artifact/n1.txt",acceptance:"Artifact exists",verification:"test -f artifact/n1.txt"},{id:"N2",goal:"Verify artifact",depends_on:["N1"],artifact:"artifact/n2.txt",acceptance:"Verification exists",verification:"test -f artifact/n2.txt"}],body:"# Plan\n\nN1 creates; N2 verifies.\n"}}; fs.writeFileSync(out,JSON.stringify(payload,null,2)+"\n")' "$WORK_AI_ROOT" "$WORK_AI_REQUEST"
node work-ai/dist/src/cli.js checkpoint --input "$WORK_AI_REQUEST"
rm "$WORK_AI_REQUEST"Do not submit this transition until the user confirms both the requirement baseline and node plan.
The approval checkpoint returns revision 4 and still belongs to codex-1. End that planning session with a same-state checkpoint before another tool resumes:
export WORK_AI_REQUEST="$(mktemp)"
node -e 'const fs=require("node:fs"); const [root,out]=process.argv.slice(1); const payload={root,taskId:"demo-task",sessionId:"codex-1",expectedRevision:4,nextState:"ready",currentNode:"N1",nodeUpdates:{},blockers:[],summary:"Approved requirement and plan are ready for handoff",codeBaseline:process.env.WORK_AI_BASELINE,nextAction:"Resume N1 after user confirmation",endSession:true,requirementUpdate:null,planUpdate:null}; fs.writeFileSync(out,JSON.stringify(payload,null,2)+"\n")' "$WORK_AI_ROOT" "$WORK_AI_REQUEST"
node work-ai/dist/src/cli.js checkpoint --input "$WORK_AI_REQUEST"
rm "$WORK_AI_REQUEST"This returns revision 5 with no active session, so the resume flow below can claim it without --force.
Resume A Task
Start every new Codex or OpenCode session with a read-only inspection:
node work-ai/dist/src/cli.js inspect --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK"Read the requirement, plan, status, decisions, handoff, session records, and current Git state. Report progress, differences, risks, and next_node. User confirmation is required before claiming the task or modifying code after a resume, even when inspection reports no drift.
After confirmation, read and claim the exact reported revision with a fresh session ID and current baseline:
export WORK_AI_REVISION="$(node work-ai/dist/src/cli.js inspect --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK" | node -e 'let source=""; process.stdin.on("data",chunk=>source+=chunk); process.stdin.on("end",()=>process.stdout.write(String(JSON.parse(source).revision)))')"
node work-ai/dist/src/cli.js claim \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--session "opencode-1" \
--tool opencode \
--revision "$WORK_AI_REVISION" \
--baseline "$(git rev-parse HEAD)"Checkpoint Request
Write checkpoint JSON to a temporary absolute path, then submit it through the public command:
node work-ai/dist/src/cli.js checkpoint --input "$WORK_AI_REQUEST"The request must use the active session and current revision. Checkpoint every completed node and at session end, retain evidence for completed nodes, verify the returned revision, then remove the temporary request. Routine node checkpoints are automatic; user confirmation is required when a checkpoint changes approved requirements or plan direction, or adopts recovered state after an interruption.
The checked example at work-ai/examples/cross-tool-handoff/.work-ai/ shows revisions 0 through 9, both session files, and evidence-gated completion.
Force A Handoff
First inspect and report the current owner, revision, Git differences, and risks:
node work-ai/dist/src/cli.js inspect --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK"Explicit user confirmation is required before forcing a handoff. Confirm the prior writer is no longer active, then use the exact inspected revision:
export WORK_AI_REVISION="$(node work-ai/dist/src/cli.js inspect --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK" | node -e 'let source=""; process.stdin.on("data",chunk=>source+=chunk); process.stdin.on("end",()=>process.stdout.write(String(JSON.parse(source).revision)))')"
node work-ai/dist/src/cli.js claim \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--session "opencode-recovery-1" \
--tool opencode \
--revision "$WORK_AI_REVISION" \
--baseline "$(git rev-parse HEAD)" \
--forceThe old session cannot checkpoint afterward because its revision is stale.
Recover A Stale Lock
Never silently delete .work-ai/.locks/$WORK_AI_TASK.lock. A task lock is a directory containing exactly one *.owner token. Resolve the exact directory, reject symbolic links, select exactly one regular owner token, and inspect that exact path:
export WORK_AI_LOCK="$WORK_AI_ROOT/.work-ai/.locks/$WORK_AI_TASK.lock"
if test -L "$WORK_AI_LOCK" || ! test -d "$WORK_AI_LOCK"; then
echo "Expected a real task-lock directory; stop and inspect the path" >&2
exit 1
fi
set -- "$WORK_AI_LOCK"/*.owner
if test "$#" -ne 1 || ! test -f "$1" || test -L "$1"; then
echo "Expected exactly one regular, non-symlink owner token; stop and inspect the lock" >&2
exit 1
fi
export WORK_AI_OWNER="$1"
cat -- "$WORK_AI_OWNER" || exit 1Use the token's session ID and creation time plus operating-system or team evidence to verify no writer is still running. Explicit user confirmation is required after that investigation and before deletion. After confirmation, remove only the exact inspected owner token, then remove the now-empty lock directory non-recursively:
rm -- "$WORK_AI_OWNER" || exit 1
if ! rmdir -- "$WORK_AI_LOCK"; then
echo "Lock contents changed; stop and inspect the lock again" >&2
find "$WORK_AI_LOCK" -maxdepth 1 -print
exit 1
fi
node work-ai/dist/src/cli.js inspect --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK"If another token or unexpected file appeared, rmdir fails and preserves it for re-inspection.
Validate State
Validate all required documents, task IDs, dependencies, evidence, and completion guards:
node work-ai/dist/src/cli.js validate --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK"A valid result is JSON with "valid": true and an empty errors array.
Rebuild Index
The index is derived state. Rebuild it from task status files when it is missing, stale, or malformed:
node work-ai/dist/src/cli.js rebuild-index --root "$WORK_AI_ROOT"Review .work-ai/index.md; malformed tasks remain visible under Invalid Tasks instead of hiding healthy tasks.
Write A Review Receipt
After a task reaches verifying, an independent review session writes a formal review receipt. The receipt binds the reviewed Git head to the frozen spec revision:
node work-ai/dist/src/cli.js review \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--spec-revision 3 \
--reviewed-head "$(git rev-parse HEAD)" \
--verdict APPROVE \
--reviewer "cursor-review-1"Use APPROVE_WITH_NOTES for approved reviews with comments, or CHANGES_REQUESTED to send the task back to in_progress.
Record A Git Fact
After the provider confirms the candidate merge, record the Git fact before the
ready_for_merge -> merged checkpoint so that the transition can verify it:
node work-ai/dist/src/cli.js record-fact \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--source git \
--candidate-head "$WORK_AI_CANDIDATE_HEAD"The Git adapter records a five-minute, SHA-bound observation with its local repository identity and any collection errors. MR, CI, readiness, and production observations are imported from an absolute JSON file so credentials and provider-specific API clients remain outside the protocol core:
node work-ai/dist/src/cli.js record-fact \
--root "$WORK_AI_ROOT" \
--task "$WORK_AI_TASK" \
--source ci \
--input "/absolute/path/to/ci-fact.json"Every imported payload must contain provider, provider_instance,
collector, observed_at, observed_sha, expires_at, errors, evidence,
and details. The evidence kind must equal --source, and its source SHA
must equal observed_sha. Invalid or incomplete input fails before the existing
fact file is replaced. The source-specific evidence fields are defined by
FactEvidence in src/protocol/model.ts.
Readiness evidence also carries release_classification, an explicit
decision (merge_only or deploy_required), and manual_hold. Only
internal is merge-only; standard, hotfix, feature, and breaking must
reach deployed with a matching production SHA before closure. Every path
requires fresh passing CI and readiness facts.
Check Closure Readiness
Before transitioning to completed, verify that the closure lineage is consistent:
node work-ai/dist/src/cli.js close --root "$WORK_AI_ROOT" --task "$WORK_AI_TASK"A valid result has "can_close": true and an empty errors array.
Check Platform Governance
Validate repository process authority and optionally compare it with a read-only Codeup snapshot:
node work-ai/dist/src/cli.js platform-check \
--root "$WORK_AI_ROOT" \
--policy "$WORK_AI_ROOT/work-ai/governance/platform-policy.json" \
--snapshot "/absolute/path/to/codeup-governance-snapshot.json"Omit --snapshot for a local-only dry run. This command never changes remote
settings; a missing, stale, errored, or mismatched snapshot produces
can_activate: false with machine-readable diagnostics.
Before runtime cutover, run the combined read-only activation audit:
node dist/src/cli.js activate-check --root .. --task <task-id>The audit verifies the canonical runtime manifest, task migration compatibility, Codeup governance, independent exact-candidate review, and release fact lineage. Missing external evidence is returned as structured blockers with a next actor and resolution condition; it never implies that merge, deployment, or closure occurred.
Local Dashboard
Build and start the read-only dashboard from the repository root:
npm --prefix work-ai run build
node work-ai/dist/src/cli.js dashboard --root "$PWD"The command prints one JSON line containing dashboard_url and the selected
port. The server binds only to 127.0.0.1, exposes GET and HEAD, applies a
strict content-security policy, and never writes task state. The UI refreshes
every two seconds; the Refresh button requests an immediate update, and a
stale-data banner appears when polling fails. Malformed tasks remain visible as
invalid overview rows instead of preventing healthy tasks from loading. Stop
the server with Ctrl+C.
Run unit/integration and real desktop/mobile Chromium acceptance with:
npm --prefix work-ai test
npm --prefix work-ai run test:browser
npm --prefix work-ai run checkInstall the matching local browser binary once with
npm --prefix work-ai exec -- playwright install chromium.
Remove Skills
Skill removal deletes repository files and therefore requires explicit user confirmation. The installer owns only the adapter SKILL.md and shared workflow.md files. After confirmation, remove those exact files, then remove their directories non-recursively:
rm -- .agents/skills/work-ai-codex/SKILL.md
rm -- .agents/skills/shared/workflow.md
rmdir -- .agents/skills/work-ai-codex
rmdir -- .agents/skills/shared
rm -- .opencode/skills/work-ai-opencode/SKILL.md
rm -- .opencode/skills/shared/workflow.md
rmdir -- .opencode/skills/work-ai-opencode
rmdir -- .opencode/skills/sharedUnexpected or unrelated content makes rmdir fail instead of deleting it. Removing skills does not remove .work-ai/ task history.
MVP Acceptance Evidence
| Criterion | Automated or manual evidence |
| ---: | --- |
| 1 | tests/integration/cross-tool-handoff.test.ts starts an allowlisted clean child process, invokes public inspect, reads requirement/status/handoff/decisions, and asserts the goal, completed N1 evidence, decision, blockers, code-drift risk, and next node without the prior-chat marker. |
| 2 | The same test executes Codex-to-OpenCode and OpenCode-to-Codex handoffs, then asserts the requirement, plan, decision, both generated session histories, evidence, revision, and progress remain in repository state. |
| 3 | tests/workflows/rebuild-index.test.ts deletes and rebuilds the index; the checked example includes a rebuilt completed row. |
| 4 | tests/protocol/state-machine.test.ts rejects execution before approval; skills/shared/workflow.md and Resume A Task require user confirmation before resumed modification. |
| 5 | tests/workflows/checkpoint.test.ts rejects completed nodes without evidence and task completion with unfinished nodes or blockers; the example has evidence on N1 and N2. |
| 6 | tests/integration/cross-tool-handoff.test.ts verifies node checkpoints and both generated session histories; only approval and recovery choices are assigned to the user. |
| 7 | The cross-tool test creates real artifact drift and asserts public inspect returns dirty paths, code_changed_since_checkpoint: true, and a recovery risk; validation, inspection, and checkpoint tests also cover malformed state and concurrent revisions. |
