Let the Machine Touch Something
Part 3 — Give Intelligence a Runtime
The edit that has not happened
Constructed scene. A reviewer proposes changing a cache setting. The patch is in an artifact. A person decides to apply it. The file still contains the old value.
Then a worker returns “done” and nobody opens the file.
The decision might have been well supported, the instruction exact, and the worker’s return normal — none of those tells the person reading the record whether the file changed.
Chapter 18 ended: “A decision can now say what it rested on. It still changes nothing.” This chapter crosses that boundary, but it needs to cross it in pieces.
How does a proposed action become an observed effect?
Decided, requested, performed, observed
The distinction is decided ≠ requested ≠ performed ≠ observed.
| Stage | Question answered | What remains open |
|---|---|---|
| Decided | What change was chosen, and on what basis? | Has anyone asked a worker to do it? |
| Requested | What operation was submitted, by whom, against which state? | Did execution begin? |
| Performed | What did the worker do? | What evidence of that effect survives? |
| Observed | What did a specified reader find afterwards? | Does that observation establish the intended result? |
“Performed” is a fact about an effect. A worker’s statement that it performed something is a report about that fact. Keeping the report is useful; promoting it silently into independent evidence is the mistake.
An effect report is another claim. Observation is evidence about the effect. This carries Chapter 18’s distinction across the execution boundary: what the actor says and what an observer reads must remain separately inspectable.
ReAct interleaves model reasoning with actions that obtain information from external sources and environments. That supports treating an action and the information returned from acting as distinct steps. Its benchmark results do not establish durable recording or authorization for this runtime. The mapping to CodeAI’s action boundary is the book’s own. Yao et al., 2023
PAL makes a related separation: a language model generates intermediate programs and an interpreter executes them. Moving computation into an interpreter changes who performs it. It does not, by itself, establish that a proposed file edit meets a user’s objective. That application to effectful software is also the book’s mapping, not a result reported by PAL. Gao et al., 2023
The construction here needs ordinary software around that boundary: a request, a callable worker, a record of its answer, and a reading whose origin is explicit.
Write down the request
The source-inspected ActionRequest separates the operation’s identity from its task, instruction, capability, precondition, and execution attribution. directive_id can associate it with a directive; payload carries application data. idempotency_key is also present, but its consequences belong to Chapter 22. 1
Constructed example using the actual API. The identifiers and proposed value below are illustrative. Creating this object does not execute the edit. The decision_id inside payload is an application convention, not a dedicated field. This fragment assumes before_hash was computed from the intended target under a declared policy. 1
from codeai.adapters import ActionRequest
request = ActionRequest(
action_id="apply-cache-change",
task_id="cache-task",
directive_id="cache-directive",
capability="write",
instruction="Set cache.toml TTL to 300 seconds.",
precondition_hash=before_hash,
idempotency_key="cache-change-once",
requested_by="human-reviewer",
actor_id="file-worker",
adapter="local-file",
adapter_id="local-file",
payload={"decision_id": "choose-cache-change"},
)
Who requested, who acts, and through what adapter are different questions. effective_requester() uses requested_by, falling back to actor_id. effective_adapter_id() uses adapter_id, falling back to adapter. These are attribution strings, not evidence that a named person authenticated or approved the request. 1
The missing connection matters. execute_action does not look up a Stage 18 decision or validate a decision_id from the payload. A request can therefore carry the application’s explanation of which decision it implements without the runtime enforcing that relationship. Do not draw an enforced decision-to-action arrow merely because both kinds of record exist. 2
Where the effect happens
The execution interface is small. This is its source-inspected signature, with the surrounding imports omitted; it is not an executed example. 3
class ExecutionAdapter(Protocol):
def execute(self, request: ActionRequest) -> ActionResult: ...
The adapter is where the side effect belongs. The runtime supplies an explicit request and receives an ActionResult. That result can carry a status, timestamps, transcript, stdio, exit code, and state-related fields. ActionStatus has SUCCEEDED, FAILED, and DENIED; there is no separate “objective verified” member. 4
On a new action, the source-inspected order is as follows. The existing-result branch returns earlier than the main execution path. 2
| Order | Operation | Meaning at this boundary |
|---|---|---|
| First | Append action.requested |
Preserve what was submitted |
| Then | Look up a prior result by key | May return a reused result; Chapter 22 must examine this |
| On a new execution | Call policy.require |
The authority boundary, developed in Chapter 20 |
| Before the adapter | Read _current_state_hash() and compare a supplied precondition |
Compare the planning state with an available current reading |
| Effect | Call adapter.execute(request) |
Hand control to the worker |
| After return | Read _current_state_hash() again; finalize the result |
Combine the worker’s report with runtime bookkeeping |
| Last | Append action.completed |
Preserve the completed result record |
The boundary as a flow — the report and the reading travel separate paths:
flowchart TD
P["proposal<br/><i>decided, not yet requested</i>"] --> R["action.requested<br/><i>recorded before execution</i>"]
R --> AD["execution adapter<br/><i>the effect happens here</i>"]
AD --> W["world / target state<br/><i>changed, or not</i>"]
AD --> REP["adapter report<br/><i>status + claimed state</i>"]
W --> OBS["runtime observation<br/><i>independent reading, declared scope</i>"]
REP --> RES["action.completed<br/><i>report and observation kept apart</i>"]
OBS --> RES
EXP["expected effect<br/><i>what was intended</i>"] -.->|"compared outside<br/>the action contract"| RES
The precondition comparison runs only when both the request’s hash and the current reading are non-null. A missing resolver therefore leaves a supplied precondition unenforced. Also, the actual before reading is used for comparison but is not stored in a separate before-observation field. The request retains the expected before hash. 2
An effect can occur before its completion record exists. Chapter 16’s recorded provider experiment exposed an analogous interval: a started attempt without a later observation left the effect unknown. That measurement concerns provider calls at its recorded commit, not crash recovery for this action path. 5
Who computed that hash?
At CodeAI 7a0d43b, the runtime really does call its configured state resolver after an adapter returns. It does not merely ask the adapter for a hash. With no resolver, _current_state_hash() returns None. Independence therefore depends on what the caller configured that resolver to read. 6
But at that commit, finalization uses the following expression. This is a source excerpt, not an experimental result. 7
resulting_state_hash=result.resulting_state_hash or resulting_state_hash
The left value comes from the adapter. The right value is the reading passed into finalization by the runtime. A nonempty adapter value wins. A reader of the final resulting_state_hash cannot tell from that field alone which path supplied it; a conflicting runtime reading is not retained there. This is the named defect: adapter-first finalization obscures observation provenance. 7
The Chapter 19 revision, now in CodeAI a1b562a, adds a separate observed_state_hash. Finalization sets it from the runtime reading even when the adapter supplied a different value, including a value in that new field itself. The legacy resulting_state_hash keeps its old behavior for compatibility. This is source-inspected behavior of the revision, not a property retroactively established by the older bundles. 7
| Field in the revised result | How to read it |
|---|---|
state_hash |
Adapter-supplied value; its meaning depends on the adapter |
resulting_state_hash |
Legacy adapter-first value, with runtime reading as fallback |
observed_state_hash |
Runtime-resolver reading, or None when unavailable |
Those meanings follow from the result definition and finalization; none denotes a correctness verdict. 7
Old completion records do not acquire an independent observation when reopened. Deserialization leaves the new field null if it was absent. Reusing a recorded result carries its original observation forward; it does not take a fresh reading of today’s world. 8
This separates two reports. Comparison of an expected resulting state with the observed one stays outside the contract, and an adapter’s successful status stands even when the intended file stayed unchanged. 2
An identity has a scope
The default repository resolver is a policy, not a complete description of the environment. In a repository with a resolvable HEAD, default_repository_state_hash hashes a combination of HEAD, staged diff, unstaged diff, and the sorted untracked path list. It deliberately does not read untracked file contents into that Git-based identity. 9
Consequently, changing the contents of an already-present untracked file need not change this identity. That is a source-inspected consequence of the inputs being hashed, not a measured result in this chapter. The function’s fallback path is different: it walks files and hashes paths and bytes, excluding directories and paths containing .codeai. The two paths should not be described as one universal content hash. 9
Keep three meanings separate: reported result/state is what the acting boundary reports; observed state is what a specified observer reads; expected state/effect is what the operation was intended to produce. Those concepts do not depend on an old field name having perfectly clean historical semantics.
Before comparing hashes, ask what each covers. A session identifier, a target-file digest, and a repository identity are not interchangeable. Even comparable before and after hashes answer only whether the selected representation changed. A change to the wrong file can change a repository identity; an unchanged value can be correct for an operation whose desired state already held.
For the teaching fixture, reading the target’s bytes makes the question concrete. Preserving those bytes also makes later inspection possible. A digest alone cannot show a future reader which line changed.
Read the file after the worker
Constructed, reduced example using actual CodeAI APIs. This disposable fixture shows request, execution, and an independent target read. It is teaching code, not the preregistered Stage 19 demonstration. The adapter deliberately supplies a report that should not be mistaken for a file digest. The revised runtime supplies the separate observed field. 10
from hashlib import sha256
from pathlib import Path
from tempfile import TemporaryDirectory
from codeai.adapters import ActionRequest, ActionResult, ActionStatus
from codeai.domain import Authority, Capability
from codeai.ledger import SQLiteLedger
from codeai.runtime import Runtime
with TemporaryDirectory() as directory:
target = Path(directory) / "cache.toml"
target.write_bytes(b"ttl_seconds = 60\n")
expected = b"ttl_seconds = 300\n"
def read_hash():
return sha256(target.read_bytes()).hexdigest()
class FileAdapter:
def execute(self, request):
target.write_bytes(expected)
return ActionResult(
action_id=request.action_id,
status=ActionStatus.SUCCEEDED,
resulting_state_hash="worker-report",
)
runtime = Runtime(SQLiteLedger(), state_resolver=read_hash)
before_bytes = target.read_bytes()
request = ActionRequest(
action_id="edit", task_id="cache", capability="write",
instruction="Apply the fixture TTL change.",
precondition_hash=sha256(before_bytes).hexdigest(),
idempotency_key="fixture-edit", adapter="fixture",
requested_by="reviewer", actor_id="file-worker",
)
result = runtime.execute_action(
request, authority=Authority(frozenset({Capability.WRITE})),
adapter=FileAdapter(),
)
after_bytes = target.read_bytes() # outside the acting adapter
observed_hash = sha256(after_bytes).hexdigest()
effect_matches = after_bytes == expected
The last comparison is the important line. effect_matches is a fixture-local conclusion about exact target bytes. It does not follow from the returned status, and it does not inspect every other file the worker could have changed.
Remove the adapter’s write in a copy of this example. Then make another copy that writes to a different target, and another that writes only part of the content before raising. Those variations are exactly what the pinned Stage 19 run executes, with the results reported below.
At a1b562a the source caught only RuntimeError from execution and recorded a failed result with a state reading; any other exception class escaped and left the request without a completion. That gap turned out to matter: a ConnectionError after a write let a same-key retry write again, and an uncommitted repair made while editing Chapter 22 now records any adapter exception as FAILED. Two holes remain. A resolver that raises while a failure is being recorded still interrupts the record, and a process that dies mid-effect records nothing at all. The ledger then shows a gap where the completion should be, while the target may already hold a partial write. 2
The real adapter has a narrower answer
OpenCodeExecutionAdapter is an existing implementation of the protocol. By source inspection, it creates a session if needed, sends the instruction through client.prompt, joins response text parts into a transcript, and returns SUCCEEDED. Its state_hash is the session ID. It records the session and transcript, leaving the resulting repository and any task-specific postcondition unread. This account is source inspection, not a live OpenCode run. 11
That is a useful transport boundary, but a session ID is not a repository hash: a prompt response and transcript can describe work without establishing the resulting file state, so the surrounding process still owes the reader an observation with a declared target and scope.
What has actually run
The available action evidence is partial — the capstone demo preserves a denied edit under READ with the file unchanged, a successful edit under WRITE attributed to human-approver, and a separate post-edit file check reported PASS. Its scope includes fake cognition, local checks, a stub state resolver, and clean reopen. It is a demo, not a commit-pinned Chapter 19 experiment. 12
The later capstone composition, run under a protocol frozen before execution, keeps the three origins apart on one successful edit: the adapter returned succeeded with no state of its own, the runtime recorded its resolver reading, and an independent reader matched that reading to the preserved after-bytes. The same run shows the first weakness listed below in action. Its resolver hashed one file, so when a separate branch changed a second file and then failed, the recorded observation never saw the change. 13
Stage 29B has a narrower measured control at 7a0d43b. The rule-resolved answer was submitted as a WRITE effect through the action path, first under READ and then under WRITE. 14
| Control | Recorded effect status | Apply-adapter calls | Independently recorded target state |
|---|---|---|---|
| READ | denied |
0 | File absent before and after |
| WRITE | succeeded |
1 | File absent before; afterwards [cache] and ttl_seconds = 300 |
These rows come from authority/authority-denied.json and authority/authority-granted.json, not inferred from the status labels. The latter includes the resulting text and its SHA-256. 14
This is evidence that a bounded effect happened in that control and was inspected outside its success label. The dishonest worker, partial write, wrong target, and Stage 18 decision whose basis later moves remain uncovered there. Those joined cases are covered by the pinned Stage 19 run below.
Checking it without trusting it
The Stage 29B verifier checks the authority control within its wider experiment. The wider bundle is not wholly green: its report preserves a failed spend claim, L-D, caused by usage lost on an empty-output failure path. The action control should be cited locally, not used to describe the whole bundle as passing. 15
The pinned decision-to-effect run
The dedicated Stage 19 experiment ran under a protocol frozen before execution, in experiments/applied-ai/evidence/decision-to-effect/2026-09-14-a1b562a/. It starts from a real decision.recorded resting on a supported claim, applies a small patch in a disposable workspace per case, and exports the decision, request, result, invocation receipts, before and after bytes, and hashes. An independent stdlib-only verifier derives the byte comparison from the ledger and the fixture files rather than importing the producer’s success predicate.
| Case | Adapter status | Intended bytes present? | Runtime observation == actual bytes? |
|---|---|---|---|
| Honest adapter | succeeded |
yes | yes — report string and digest keep their origins |
| Lying adapter | succeeded |
no, target untouched | yes — the recorded observation is the before-hash, exposing the discrepancy |
| Partial adapter | failed (ConnectionError mid-write) |
8-byte prefix only | yes — same-key retry replays FAILED with 2 requests, 1 effect |
| Wrong-target adapter | succeeded |
no, target untouched | yes — the fixture inventory exposes other.toml with the expected bytes |
| Decision basis moves later | succeeded, bytes as intended |
yes | yes — the decision event stays intact while standing moves basis_intact → basis_changed |
The partial case exercises the uncommitted repair described above: a ConnectionError, not a RuntimeError, is recorded as FAILED, and the retry returns the FAILED replay with the adapter invoked exactly once. The stale-basis case uses a genuine decision.recorded from an offline recorded call, refuting evidence landed after the effect, and the standing projection — the old decision and the performed effect are retained while current standing changes.
Seeded corruptions distinguish a broken record from a poorly described effect: an altered observed hash, a worker report substituted for the observation, a removed changed-path inventory entry, and a rewritten decision basis are each rejected by the verifier with the failure named. The run’s stated limits are kept: the runtime resolver watches exactly one target file (everything else comes from the verifier-side inventory, not from any ledger field), fixtures are disposable and local with no concurrency, and a process death between effect and completion still records nothing — only the caught-exception path is exercised.
What this is not
- Authority belongs to Chapter 20. Requester attribution raises the question of permission; that chapter develops it.
- Check adequacy remains open. Reading a file establishes its bytes within a time and scope; Chapter 21 asks what check can establish the required behavior.
- Retry safety belongs to Chapter 22. The interval between an effect and a durable completion is handled there.
- Containment and production validation are out of scope. A disposable file fixture does not establish either.
Where it is still weak
- Observation is configurable. A constant or incomplete resolver can supply a poor reading; the runtime does not validate its coverage. 16
- The expected outcome is outside the action contract. No built-in comparison judges the intended resulting state. 2
- The historical decision link is conventional. Execution does not validate the decision named in application payload data. 2
- Failure evidence can still be incomplete. Adapter exceptions are now recorded, but a raising resolver or a process death can leave the completion absent. 2
- The joined evidence is now supplied for the five pinned cases. What remains: a resolver that raises while a failure is being recorded still interrupts the record, and the expected-after comparison stays outside the action contract.
The source-inspected project_decision_standing compares a recorded basis with current claim standing and reports changes. That projection is not an operation that reverses a file write. Linking its report to a particular performed action is the remaining construction, not something a shared task name proves. 17
Do this now
Thirty minutes. Take one proposed edit and follow it as far as the file.
- Save the original bytes in a disposable workspace. Write the desired bytes separately, before invoking a worker.
- Record who requested the change, who will perform it, and the expected before identity. Keep these distinct from the decision that motivated it.
- Run the worker, save its report, and read the target independently. Compare the bytes with the desired result and inspect other changed paths.
- Replace the worker with one that only says “done.” Identify which record now exposes the discrepancy and which record still says success.
If you are building with an assistant:
Make one disposable file action inspectable from request to effect.
Record the request before execution. Keep the worker's status and state
report separate from a runtime-controlled reading of the target.
Preserve before and after bytes and declare the hash policy.
Do not infer correctness from status or from a changed hash.
Exercise honest, no-op, partial-write, and wrong-target workers.
Count invocations outside the completion record and inventory changed paths.
Show missing observations as missing. Preserve failures without inventing
a successful outcome. Leave authority, check adequacy, and retry policy
as explicit subsequent work.
Failure modes
- Treating a decision as an effect. A well-supported choice can still leave the file untouched.
- Treating “done” as an observation. A worker’s report is evidence of what it reported.
- Comparing hashes without their policies. Different representations can answer different questions.
- Calling every changed hash correct. The wrong target can change too.
- Reading failure as rollback. A worker can fail after making part of its change.
- Rewriting history when the basis moves. New knowledge changes what is justified now, not what happened then.
What this chapter established
- Decided, requested, performed, and observed answer different questions; observing a state still leaves correctness to be assessed.
- The inspected action path records a request, invokes an adapter on its execution branch, and records a result. 2
- The baseline’s adapter-first resulting hash mixes origins; the Chapter 19 revision separately retains the runtime resolver’s reading. 7
- The measured authority control includes an actual file write and a separately recorded resulting file, within its fixture. 14
- The pinned decision-to-effect run in
experiments/applied-ai/evidence/decision-to-effect/2026-09-14-a1b562a/demonstrates honest, lying, partial, wrong-target, and changed-basis cases with an independent verifier that recomputes from the ledger and the fixture bytes and rejects four seeded corruptions.
Next
A callable worker gives the process the ability to change something. An observation gives it a way to learn what was left behind. Neither answers who was allowed to request the change.
Continue with Capability Is Not Authority.
References
- Shunyu Yao, Jeffrey Zhao, Dian Yu, Nan Du, Izhak Shafran, Karthik Narasimhan, and Yuan Cao. ReAct: Synergizing Reasoning and Acting in Language Models. ICLR, 2023. arXiv:2210.03629.
- Luyu Gao, Aman Madaan, Shuyan Zhou, Uri Alon, Pengfei Liu, Yiming Yang, Jamie Callan, and Graham Neubig. PAL: Program-aided Language Models. ICML, PMLR 202:10764–10799, 2023. Proceedings.
Implementation sources: CodeAI baseline 7a0d43b36bac09be815138a71b9a0a50be5d20dd for historical finalization; a1b562a for the Chapter 19 observation revision; and an uncommitted working-tree repair that records any adapter exception as FAILED, all distinguished above. src/codeai/adapters.py: ActionRequest, ActionResult, ActionStatus, ExecutionAdapter; src/codeai/runtime.py: Runtime.execute_action, _finalize_action_result, _action_result_from_payload, _append_action_result_event, _current_state_hash, default_repository_state_hash; src/codeai/opencode.py: OpenCodeExecutionAdapter.execute; src/codeai/evidence.py: project_decision_standing. Regression source: tests/test_action_observation.py; existing tests: tests/test_runtime.py, tests/test_opencode.py. Evidence: experiments/applied-ai/evidence/capstone/; experiments/applied-ai/evidence/capstone-composition/; experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b/, especially authority/; experiments/applied-ai/evidence/working-state/2026-09-13-68f4ba0/; experiments/applied-ai/evidence/decision-to-effect/2026-09-14-a1b562a/ (pinned five-case run with independent verifier). Footnotes mark provenance: source notes refer to inspected code, measurement notes to pinned runs, demo notes to preserved unpinned execution.
-
Source inspection:
src/codeai/adapters.py(ActionRequest). ↩︎ ↩︎ ↩︎ -
Source inspection:
src/codeai/runtime.py(Runtime.execute_action). ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ -
Interface inspection:
src/codeai/adapters.py(ExecutionAdapter). ↩︎ -
Source inspection:
src/codeai/adapters.py(ActionResult, ActionStatus). ↩︎ -
Measured run:
experiments/applied-ai/evidence/working-state/2026-09-13-68f4ba0. ↩︎ -
Runtime inspection:
src/codeai/runtime.py(Runtime._current_state_hash, Runtime.execute_action). ↩︎ -
Finalization inspection:
src/codeai/runtime.py(Runtime._finalize_action_result). ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ -
Source inspection:
src/codeai/runtime.py(Runtime._action_result_from_payload, Runtime.execute_action). ↩︎ -
Source inspection:
src/codeai/runtime.py(default_repository_state_hash). ↩︎ ↩︎ -
Source inspection:
src/codeai/runtime.py(Runtime.execute_action, Runtime._finalize_action_result). ↩︎ -
Adapter inspection:
src/codeai/opencode.py(OpenCodeExecutionAdapter.execute). ↩︎ -
Unpinned demonstration:
experiments/applied-ai/evidence/capstone. ↩︎ -
Measured run:
experiments/applied-ai/evidence/capstone-composition. ↩︎ -
Measured run:
experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b. ↩︎ ↩︎ ↩︎ -
Report:
experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b/chapter-evidence-report.md. ↩︎ -
Source inspection:
src/codeai/runtime.py(Runtime._current_state_hash). ↩︎ -
Source inspection:
src/codeai/evidence.py(project_decision_standing). ↩︎