OACP
Run this version gate first. If oacp is missing or older than 0.4.2, stop
and tell the user to install or upgrade oacp-cli[crypto] before continuing.
if ! command -v oacp >/dev/null 2>&1; then
echo "oacp CLI not found; install 'oacp-cli[crypto]>=0.4.2'" >&2
exit 1
fi
OACP_VERSION="$(oacp --version)"
printf '%s\n' "${OACP_VERSION}"
python3 - "${OACP_VERSION}" <<'PY'
import re
import sys
version = sys.argv[1].strip()
match = re.search(r"(\d+)\.(\d+)\.(\d+)", version)
if not match:
raise SystemExit(f"could not parse oacp version: {version}")
parts = tuple(int(part) for part in match.groups())
if parts < (0, 4, 2):
raise SystemExit(f"oacp {version} is older than required 0.4.2")
PY
Install or upgrade with pip install --upgrade 'oacp-cli[crypto]>=0.4.2'
(or uv tool install --upgrade 'oacp-cli[crypto]'). The crypto extra enables
message signing, verification, and trust checks used by the coordination
skills.
OACP is a filesystem coordination protocol. Use the installed oacp command
as the primary interface, and use the public
OACP specification when
you need protocol details that are not summarized here.
Start
Resolve the project before reading or writing messages. Prefer an explicit
--project from the user; otherwise detect .oacp from the repo root.
PROJECT="$(python3 - <<'PY'
import json
import os
project = ""
if os.path.islink(".oacp"):
target = os.path.realpath(".oacp")
marker = os.path.join(target, "workspace.json") if os.path.isdir(target) else target
try:
with open(marker, "r", encoding="utf-8") as f:
project = json.load(f).get("project_name", "") or ""
except Exception:
project = os.path.basename(target) if os.path.isdir(target) else os.path.basename(os.path.dirname(target))
elif os.path.isfile(".oacp"):
try:
with open(".oacp", "r", encoding="utf-8") as f:
project = json.load(f).get("project_name", "") or ""
except Exception:
project = ""
print(project)
PY
)"
OACP_ROOT="${OACP_HOME:-$HOME/oacp}"
test -n "${PROJECT}"
Run health checks before substantial coordination work:
oacp doctor --project "${PROJECT}" --oacp-dir "${OACP_ROOT}" --json
If the project uses memory sync, run oacp memory pull before relying on
shared memory and oacp memory push after intentional memory updates.
Read
Use CLI JSON for inbox discovery. Do not recurse through inbox directories or parse processed archives unless the user asks.
oacp inbox "${PROJECT}" --agent codex --oacp-dir "${OACP_ROOT}" --json
Treat the inbox listing as discovery, not as permission to custom-parse a live
path. Route each candidate through /check-inbox: it captures one immutable
snapshot, verifies before parsing, retains the accepted SHA-256, and enforces
the receiver's off, warn, or enforce posture. Under enforce, anything
other than signed-verified is held or quarantined in dead_letter/ without
surfacing attacker-controlled fields.
Keep these routing defaults:
task_request,brainstorm_request, andreview_request: summarize and get user confirmation before live execution.question: answer with a replynotificationusing--in-reply-to.notification: summarize; acknowledge only when the sender requested it.review_lgtm: report the quality gate result, then proceed according to the repo's merge policy.- Unknown, malformed, or expired messages: report and leave them in the inbox.
Archive a processed inbox file only after its handling path succeeds and its live digest still matches the accepted snapshot.
Send
Use oacp send for all message writes. Prefer --dry-run --json first when
composing a new message type or a complex body.
oacp send "${PROJECT}" \
--from codex \
--to "<agent>" \
--type notification \
--subject "Re: <subject>" \
--body-file "<body-file>" \
--in-reply-to "<message-id>" \
--priority P2 \
--oacp-dir "${OACP_ROOT}" \
--dry-run \
--json
Use --body-file for multi-line YAML bodies. Use --in-reply-to for replies
so OACP can inherit conversation context from the parent message; use
--parent-message-id only when you need to set the parent field directly. Use
--related-pr on review-loop messages. After the dry run is correct and the
user has approved the live write, rerun the same command without --dry-run.
When a live send succeeds, verify the reported inbox_path exists before
treating the message as delivered.
Validate hand-written or edited message files before relying on them:
oacp validate "<message-file>"
Poll
For recurring checks, use the user's runtime loop support or one shell loop. Do not spend repeated LLM turns polling the same wait state.
oacp watch --project "${PROJECT}" --agent codex \
--state-id "codex-<stable-subscriber-id>" --since epoch \
--oacp-dir "${OACP_ROOT}" --json
--state-id (v0.4.0+) gives each subscriber an independent cursor. Use
--since epoch when creating a cursor so existing backlog wakes the first pass;
it has no effect after that cursor exists. Duplicate delivery across concurrent
subscribers is expected, so confirm the file still exists before processing.
Use --show-archived only when archive events are part of the task; it is noisy
when watching your own inbox.
Signing and trust (v0.4.2+)
OACP messages may carry a detached-JWS Ed25519 auth trailer. Signing is sender-controlled; verification posture is receiver-controlled:
oacp key gen --agent codex --oacp-dir "${OACP_ROOT}"
oacp key list --agent codex --oacp-dir "${OACP_ROOT}"
oacp trust import <kid>.pub.json --project "${PROJECT}" --agent codex \
--oacp-dir "${OACP_ROOT}"
oacp trust list --project "${PROJECT}" --oacp-dir "${OACP_ROOT}"
oacp trust sign-policy --project "${PROJECT}" --agent codex \
--oacp-dir "${OACP_ROOT}"
oacp verify <message.yaml> --project "${PROJECT}" --receiver codex \
--oacp-dir "${OACP_ROOT}"
signing.verify_mode: warnannotates verification but grants no authority.signing.verify_mode: enforceaccepts only signed-verified intake; other outcomes fail closed and processing evidence is quarantined todead_letter/.- Attach authentication to an existing autonomy audit with
oacp verify <snapshot> ... --attach-audit <audit.yaml>; never hand-writemessage_auth.
Review Loops
Author flow:
- Open or update the PR and run the repo's required checks.
- Send a
notificationwith subject prefixWIP:if a dispatcher needs PR progress. - Send
review_requestdirectly to the reviewer withrelated_pr,pr,branch, anddiff_summaryin the body. - If the reviewer sends
review_feedback, address findings, then sendreview_addressed. - Send a fresh
review_requestfor re-review.review_addressedis context; it is not the trigger for a stateless reviewer invocation. - Merge only after
review_lgtm, required checks are green, and the repo's merge policy is satisfied.
Continuation grants (v0.4.3+) may authorize later reviewer invocations in one same-thread PR loop. They authorize running the round, not its verdict, GitHub effects, or merge. The reviewer still enforces exact-head, quality, budget, and permitted-side-effect guards.
Reviewer flow:
- On
review_request, inspect the PR diff and relevant project rules. - Send exactly one terminal response:
review_feedbackorreview_lgtm. - Exit after the terminal response. Do not wait in-session for
review_addressed.
Review feedback should point to a findings packet when the project uses one.
LGTM bodies should include quality_gate_result: pass and
merge_ready: true.
Safety
- Read the nearest repo instructions before running repo-scoped Git or GitHub commands.
- Do not print tokens, cookies, private headers, or full secrets in messages, logs, PR comments, or final replies.
- Do not put absolute local paths in shared OACP artifacts; prefer
$OACP_HOME,$HOME, repo-relative paths, or public URLs. - Ask before live writes when the message type or repo protocol requires user approval.
- Keep PR comments concise and data-minimized; detailed machine state belongs in inbox messages or packets.
Examples
Check health and the Codex inbox:
PROJECT="my-project"
OACP_ROOT="${OACP_HOME:-$HOME/oacp}"
oacp --version
oacp doctor --project "${PROJECT}" --oacp-dir "${OACP_ROOT}" --json
oacp inbox "${PROJECT}" --agent codex --oacp-dir "${OACP_ROOT}" --json
Draft a task_request before sending it:
BODY_FILE="$(mktemp)"
cat > "${BODY_FILE}" <<'EOF'
Implement the requested change.
Acceptance criteria:
- Keep the patch scoped.
- Run the relevant validation.
- Reply with results and follow-ups.
EOF
oacp send "${PROJECT}" \
--from codex \
--to "<agent>" \
--type task_request \
--subject "Implement scoped change" \
--body-file "${BODY_FILE}" \
--priority P2 \
--oacp-dir "${OACP_ROOT}" \
--dry-run \
--json
Request review for a PR:
PR_NUMBER="123"
BRANCH="$(git branch --show-current)"
DIFF_SUMMARY="$(git diff --stat "origin/main...${BRANCH}" | sed 's/^/ /')"
CONVERSATION_SEQUENCE="$(python3 - <<'PY'
import time
print(time.time_ns() % 1_000_000)
PY
)"
CONVERSATION_ID="conv-$(date -u +%Y%m%d)-codex-${CONVERSATION_SEQUENCE}"
BODY_FILE="$(mktemp)"
cat > "${BODY_FILE}" <<EOF
pr: ${PR_NUMBER}
branch: ${BRANCH}
diff_summary: |
${DIFF_SUMMARY}
max_turns_reviewer: 8
max_runtime_s_reviewer: 600
repo: <owner/repo>
round: 1
declared_head: <full-head-sha>
side_effects:
- writes_findings_packet
- sends_oacp_reply
- submits_github_review
EOF
oacp send "${PROJECT}" \
--from codex \
--to "<reviewer-agent>" \
--type review_request \
--subject "Review: PR #${PR_NUMBER}" \
--body-file "${BODY_FILE}" \
--conversation-id "${CONVERSATION_ID}" \
--related-pr "${PR_NUMBER}" \
--priority P1 \
--oacp-dir "${OACP_ROOT}" \
--dry-run \
--json
Generate the numeric sequence once (at most six digits), keep the resulting
conversation ID unchanged across every round and reply in this review thread,
and create a fresh ID only for a genuinely new thread. Declare
submits_github_review only when a
formal GitHub approval is intended and authorized; declare
comments_on_github separately when a status comment is also intended.
Recipients archive terminally processed inbox messages under the original
filename in inbox/archive/ with a digest-checked, no-clobber move. Pending,
expired, malformed, held, approval-gated, drifted, or collision cases stay in
inbox/; plain deletion and legacy processed/ moves lose receiver-side
evidence and are not protocol-compliant.