scripts/evaluate_state.py accepts only the exact top-level fields below. Hash the
complete object as UTF-8 JSON with sorted object keys, compact separators, and no
ASCII escaping. Store the lowercase SHA-256 beside the object. An omitted,
additional, reordered-list, or changed value invalidates the batch.
These are write-plan schemas. report-only supplies null remediation and
finalization manifests, null plan hashes, and a null execution boundary; it
classifies fresh GitHub response/thread/gate state without constructing a
hypothetical write batch. Its recurring remote_name is also null because
that mode never inspects a local checkout or Git remote. Its recurring
owned_automation_id is null and automations is empty because it also never
reads the Automation registry.
Before the first side effect, including fetch-base, construct an
initial_snapshot with exactly these fields. Hash it with the canonical JSON
rules above and use that digest as initial_manifest.snapshot_sha256:
{
"authenticated_user_id": 1234,
"base_branch": "<base-branch>",
"base_sha": "<40-hex-base-sha>",
"commands": [
{
"argv": ["<absolute-executable>", "<literal-argument>"],
"definition_sha256": "<64-hex>"
}
],
"commit_required": true,
"create_branch_required": true,
"current_head_sha": "<40-hex-head-sha>",
"diff_sha256": "<64-hex>",
"execution_boundary": {
"filesystem_read_scope_sha256": "<64-hex>",
"filesystem_write_scope_sha256": "<64-hex>",
"git": {
"active_clean_filters": [],
"attributes_snapshot_sha256": "<64-hex>",
"config_snapshot_sha256": "<64-hex>",
"credential_strategy": "gh-cli-https",
"external_diff_disabled": true,
"fsmonitor_disabled": true,
"git_metadata_snapshot_sha256": "<64-hex>",
"hooks_disabled": true,
"invocation_sha256": "<64-hex>",
"profile": "sanitized-gh-https-v1",
"protect_hfs_enabled": true,
"protect_ntfs_enabled": true,
"protocol_restricted": true,
"signing_disabled": true,
"textconv_disabled": true,
"transport_url_sha256": "<64-hex>"
},
"isolation": "no-network-minimal-env-checkout-only",
"risk_acceptance_sha256": null
},
"existing_pr_base_branch": null,
"existing_pr_head_branch": null,
"existing_pr_head_sha": null,
"existing_pr_number": null,
"existing_pr_same_repository": null,
"head_branch": "<final-head-branch>",
"heartbeat_rrule": "FREQ=MINUTELY;INTERVAL=2",
"host": "github.com",
"open_prs_sha256": "<64-hex>",
"paths": ["<sorted/repository-relative/path>"],
"path_records": [
{
"inside_worktree": true,
"lstat_chain_sha256": "<64-hex>",
"outside_git_metadata": true,
"path": "<matching-repository-relative-path>",
"resolved_target_sha256": "<64-hex>"
}
],
"policy_schema_version": 1,
"push_required": true,
"remote_name": "<validated-remote-name>",
"remote_refs_sha256": "<64-hex>",
"repository": "<lowercase-owner/lowercase-repo>",
"worktree_status_sha256": "<64-hex>"
}existing_pr_number, existing_pr_base_branch, existing_pr_head_branch,
existing_pr_head_sha, and existing_pr_same_repository must either all be
null or all describe the one reused pull request. In the latter case
existing_pr_same_repository must be true, the branch values must exactly
equal base_branch and head_branch, and the PR head SHA must equal
current_head_sha. heartbeat_rrule must exactly equal policy. paths are sorted, unique canonical paths;
path_records are sorted by path, have exactly the same paths, and require
both containment booleans to be true. Each lowercase digest binds the complete
fresh lstat chain and the resolved target/candidate location. commands use
the command schema below and preserve execution order. The four
state digests cover complete normalized observations, not selected entries.
The execution boundary uses the exact schema and containment rules below.
The corresponding initial_manifest has exactly these fields:
{
"automation_prompt_template": "<exact-template-string>",
"automation_prompt_template_sha256": "<64-hex>",
"kind": "initial",
"mode": "<autonomous-or-approval-gated>",
"operations": [
"fetch-base",
"create-branch",
"run-validation",
"stage-files",
"commit",
"push",
"create-pr",
"request-codex-review",
"bootstrap-automation"
],
"pr_body_sha256": "<64-hex>",
"pr_title_sha256": "<64-hex>",
"review_request_body_template": "@codex please review this pull request.\n<!-- github-codex-pr-loop-request run=<same-run-id> round=0 sha={{FINAL_HEAD_SHA}} -->",
"review_request_body_template_sha256": "<64-hex>",
"run_id": "<32-lowercase-hex>",
"snapshot_sha256": "<64-hex>",
"version": 1
}The operation order is fixed. Always start with fetch-base. Include
create-branch only when create_branch_required is true. Include
run-validation only when commands is non-empty. Include stage-files and
then commit together only when commit_required is true. Include push only
when push_required is true. Include exactly one of reuse-pr or create-pr
according to existing_pr_number, then always include
request-codex-review and bootstrap-automation. The example above is the
maximal new-PR form.
All five digest fields are non-null lowercase SHA-256 values. When reusing a
pull request, hash its current unchanged title and body; do not use null.
review_request_body_template must contain @codex, exactly one
{{FINAL_HEAD_SHA}}, no other placeholder, and the exact canonical marker for
the manifest run ID and round zero. Its SHA-256 binds the template before the
post-commit head is knowable. Render it only by replacing that one placeholder
with the verified final 40-character head SHA.
automation_prompt_template_sha256 binds the exact canonical prompt template
stored in automation_prompt_template. It must byte-for-byte equal the
evaluator-owned v1 template:
Continue $github-codex-pr-loop from the exact checkpoint below. Re-fetch all external state and follow only the installed evaluator's emitted action.
Loop key: {{AUTOMATION_KEY}}
Bootstrap checkpoint JSON: {{BOOTSTRAP_CHECKPOINT_JSON}}
Each supported placeholder appears exactly once and no other placeholder is
allowed. After creating the PAUSED inert stub and obtaining the PR/request
state, construct this exact prompt document and run
scripts/evaluate_state.py --prompt:
{
"initial_document": {
"initial_manifest": {"<all-fields-above>": "<unchanged>"},
"initial_plan_hash": "<64-hex>",
"initial_snapshot": {"<all-fields-above>": "<fresh-values>"}
},
"bootstrap_snapshot": {
"initial_plan_hash": "<same-64-hex-in-initial-document>",
"post_write_receipt": {
"base_sha": "<approved-40-hex-base-sha>",
"final_head_sha": "<verified-post-write-40-hex-sha>",
"head_descends_from_initial": true,
"initial_head_sha": "<approved-initial-40-hex-head-sha>",
"paths": ["<exact-approved-paths>"],
"pr_head_sha": "<same-final-head-sha>",
"pushed_diff_sha256": "<same-approved-diff-digest>",
"remote_head_sha": "<same-final-head-sha>",
"review_request_body": "<exact-rendered-round-0-template>",
"review_request_body_sha256": "<64-hex-of-rendered-body>",
"worktree_clean": true
},
"pr_url": "https://github.com/<owner>/<repo>/pull/42",
"request_deadline": "<policy-deadline>",
"state": {"<every-recurring-evaluator-field>": "<fresh-bootstrap-value>"}
},
"bootstrap_snapshot_sha256": "<64-hex>",
"rendered_prompt": "<exact-deterministic-rendering>",
"rendered_prompt_sha256": "<64-hex>"
}bootstrap_snapshot has exactly initial_plan_hash, post_write_receipt,
pr_url, request_deadline, and state. The plan hash must equal its value in
initial_document; the deadline is exactly the policy timeout after
state.request_created_at.
The post-write receipt has exactly the eleven fields shown. base_sha,
pushed_diff_sha256, and paths equal the approved initial snapshot. The
initial head also equals the approved snapshot. When commit_required is true,
the final head must differ from and descend from the initial head; otherwise it
must equal the initial head. remote_head_sha, pr_head_sha,
state.request_head_sha, and state.current_head_sha all equal that final
head. The worktree is clean. The review body is the approved template with only
{{FINAL_HEAD_SHA}} replaced by the final head, and its digest matches the
rendered UTF-8 body.
state is the complete
recurring evaluator state: one exact-key, recorded-ID, exact-schedule PAUSED
Automation; an open same-repository PR with freshly fetched matching base/head
branches and tips; the same authenticated user/run/mode; one canonical pending
request with the policy deadline; zero fix rounds and no completed keys; fresh
response/thread/gate observations; and null remediation/finalization manifests,
plan hashes, approvals, execution boundary, claim, and post-finalization fields.
The prompt validator revalidates the entire initial document, requires a
canonical same-repository GitHub PR URL, checks the bootstrap snapshot digest,
cross-binds every initial identity and policy value, including exact remote
name equality, and runs the recurring evaluator. It accepts only the expected
paused-bootstrap state-inconsistency,
then replaces {{AUTOMATION_KEY}} with the canonical key and
{{BOOTSTRAP_CHECKPOINT_JSON}} with canonical UTF-8 JSON of the complete
bootstrap_snapshot
using sorted keys, compact separators, and no ASCII escaping. Only a
prompt-ready result permits updating the stub. Then view and verify the exact
prompt and schedule before activation; the first wakeup re-fetches all external
response, thread, and gate state.
mode cannot be report-only because that mode has no initial write plan.
The outer initial document has exactly initial_snapshot, initial_manifest,
and initial_plan_hash. The plan hash is the lowercase SHA-256 of the complete
canonical initial_manifest, not the snapshot alone.
Run scripts/evaluate_state.py --initial on the complete snapshot, manifest,
and initial_plan_hash before presenting an approval request. In
approval-gated, the user must name that exact hash. Immediately before
fetch-base, rebuild all three values and run --initial again; its hash must
equal the approved hash byte-for-byte. autonomous mode performs both
validations as well and requires its second hash to equal its first validated
hash. Any schema error, drift, or hash change permits no initial side effect.
Normalize every qualifying response into this complete object. Use a decimal
string for the positive REST/database id, a numeric GitHub database ID for bot_id, a numeric request ID
or null for trigger_request_comment_id, lowercase hex digests, and ISO-8601
timestamps with timezones:
{
"body_sha256": "<64-hex>",
"bot_id": 199175422,
"bot_type": "Bot",
"commit_sha": "<40-hex-or-null>",
"content_version_at": "<timestamp>",
"created_at": "<timestamp>",
"id": "<positive-decimal-string>",
"source": "<issue-comment-or-review-or-review-comment>",
"trigger_request_comment_id": 1234,
"zero_findings_signal": false
}Sort response objects by source, then normalized id. The manifest response
list must exactly equal all qualifying trusted responses in the evaluator
snapshot, not just the response that appears decisive.
{
"actionable_threads": [
{
"body_sha256": "<64-hex>",
"content_version_at": "<timestamp>",
"id": "<thread-node-id>",
"position_sha256": "<64-hex-of-canonical-path-and-position>",
"root_comment_id": "<root-comment-node-id>"
}
],
"commands": [
{
"argv": ["<executable>", "<literal-argument>"],
"definition_sha256": "<64-hex-of-command-definition-or-hook>"
}
],
"base_branch": "<base-branch>",
"base_sha": "<requested-40-hex-base-sha>",
"current_head_sha": "<requested-40-hex-sha>",
"execution": {
"filesystem_read_scope_sha256": "<64-hex>",
"filesystem_write_scope_sha256": "<64-hex>",
"git": {
"active_clean_filters": [],
"attributes_snapshot_sha256": "<64-hex>",
"config_snapshot_sha256": "<64-hex>",
"credential_strategy": "gh-cli-https",
"external_diff_disabled": true,
"fsmonitor_disabled": true,
"git_metadata_snapshot_sha256": "<64-hex>",
"hooks_disabled": true,
"invocation_sha256": "<64-hex>",
"profile": "sanitized-gh-https-v1",
"protect_hfs_enabled": true,
"protect_ntfs_enabled": true,
"protocol_restricted": true,
"signing_disabled": true,
"textconv_disabled": true,
"transport_url_sha256": "<64-hex>"
},
"isolation": "no-network-minimal-env-checkout-only",
"risk_acceptance_sha256": null
},
"head_branch": "<head-branch>",
"kind": "remediation",
"operations": [
"edit-files",
"run-validation",
"commit",
"push",
"reply-to-codex-threads",
"request-codex-review",
"update-automation"
],
"paths": ["<sorted/repository-relative/path>"],
"path_records": [
{
"inside_worktree": true,
"lstat_chain_sha256": "<64-hex>",
"outside_git_metadata": true,
"path": "<matching-repository-relative-path>",
"resolved_target_sha256": "<64-hex>"
}
],
"policy_schema_version": 1,
"pr_number": 42,
"remote_name": "<validated-remote-name>",
"repository": "<lowercase-owner/lowercase-repo>",
"request_key": "<comment-id>:<requested-40-hex-sha>",
"review_request_body_template": "@codex please review this fix.\n<!-- github-codex-pr-loop-request run=<same-run-id> round=<fix-rounds-plus-one> sha={{FINAL_HEAD_SHA}} -->",
"review_request_body_template_sha256": "<64-hex>",
"responses": [],
"run_id": "<32-lowercase-hex>",
"version": 1
}Sort thread snapshots by id and paths lexicographically. Paths must be unique,
normalized, and repository-relative. path_records use the exact initial
record schema, sort by path, and match paths one-for-one. Recompute and
require identical records immediately before each edit and staging operation.
Preserve command execution order. If there
are no commands, omit run-validation from operations; otherwise the operation
list must be exactly the order shown. actionable_threads must have exactly the
snapshot's actionable_codex_threads entries.
The review-request template uses the same one-placeholder rules as the initial
template, but its canonical marker round is fix_rounds + 1. Hash the template
inside the remediation plan before editing. After the approved fix is committed
and pushed, substitute only the verified new full head SHA, verify the rendered
body, post it as the next canonical request, and persist that final SHA as the
new request head.
Use explicit-risk-acceptance for execution.isolation only after the user
accepts the exact unavailable isolation guarantees and commands. In that case,
risk_acceptance_sha256 is the lowercase SHA-256 of the canonical acceptance
record. The isolated form requires null.
The execution object must exactly match the independent execution_boundary
in the evaluator snapshot. Construct its git object by following
git-execution-boundary.md. The safe profile requires every protection boolean
to be true, no active clean filters, the gh-cli-https strategy, and exact
filesystem read/write scope digests. Use
explicit-risk-acceptance-v1 only with explicit-risk-acceptance; enumerate the
actual protection booleans and filters and bind the acceptance digest.
remote_name must equal the validated initial remote and current recurring
state; a changed or ambiguous remote invalidates the plan.
{
"base_branch": "<base-branch>",
"base_sha": "<requested-40-hex-base-sha>",
"current_head_sha": "<requested-40-hex-sha>",
"eligible_threads": [],
"gates": {
"approvals": "<pass-fail-pending-or-unknown>",
"branch_protection": "<pass-fail-pending-or-unknown>",
"checks": "<pass-fail-pending-or-unknown>",
"mergeability": "<pass-fail-pending-or-unknown>"
},
"head_branch": "<head-branch>",
"kind": "finalization",
"non_codex_rooted_threads": 0,
"operations": ["delete-owned-automation"],
"policy_schema_version": 1,
"pr_is_draft": false,
"pr_number": 42,
"remote_name": "<validated-remote-name>",
"repository": "<lowercase-owner/lowercase-repo>",
"request_key": "<comment-id>:<requested-40-hex-sha>",
"responses": [],
"run_id": "<32-lowercase-hex>",
"version": 1
}Each eligible_threads entry uses the same exact thread schema and sorting as a
remediation thread. Its length must equal eligible_codex_threads. Prepend
mark-draft-ready to operations when pr_is_draft is true. Then append
resolve-codex-threads when eligible threads exist. Always end with
delete-owned-automation. The four gate values, draft state, non-Codex count,
qualifying responses, and operation list must match the evaluator snapshot.
The remote name, base branch, head branch, and base SHA must match the canonical
request checkpoint. Any current remote, base-tip, or PR branch drift invalidates
the plan and forbids GitHub mutation or Automation cleanup.
The first finalization plan authorizes the listed GitHub operations but never
produces merge readiness or immediate cleanup. After those operations, build a
fresh finalization manifest from a new snapshot; it must have a false
pr_is_draft, no eligible threads, and only delete-owned-automation. Preserve the
original claimed request key/hash separately. Only finish-finalization from
that post-mutation snapshot permits deletion and fresh merge-gate reporting.