[PR #5600] [MERGED] feat(environment): apply evidence-gated write effects to the served state #6989

Closed
opened 2026-11-01 21:44:31 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5600
Author: @earakely-scale
Created: 10/20/2026
Status: ✅ Merged
Merged: 10/22/2026
Merged by: @morluto

Base: main ← Head: feat/environment-state-writes


📝 Commits (10+)

  • 6c8f4f7 feat(environment): render more read tools from the state and describe responses
  • 378a109 fix(environment): keep rendered documents and quoted samples inside their budgets
  • cfb42dd feat(environment): apply evidence-gated write effects to the served state
  • 5d0e096 fix(environment): bound and validate write state history
  • 5b42469 feat(environment): render more read tools from the state and describe responses
  • 54c8934 fix(environment): keep rendered documents and quoted samples inside their budgets
  • 19b50ea feat(environment): apply evidence-gated write effects to the served state
  • a057ea9 fix(environment): bound and validate write state history
  • f79f1e1 fix(environment): bound additional collection history
  • 4a678f1 Merge remote-tracking branch 'live-5600/feat/environment-state-writes' into codex/blockers-5600-live

📊 Changes

57 files changed (+12919 additions, -199 deletions)

View changed files

📝 .agents/skills/reverse-engineer-anything/references/evidence-workflows.md (+10 -6)
📝 docs/agent-environments.md (+87 -38)
📝 docs/cli.md (+2 -1)
➕ docs/specs/agent-environments/state-writes/plan.md (+45 -0)
➕ docs/specs/agent-environments/state-writes/spec.md (+427 -0)
➕ docs/specs/agent-environments/state-writes/tasks.md (+22 -0)
➕ docs/specs/agent-environments/state-writes/verification.md (+376 -0)
📝 docs/testing.md (+1 -1)
📝 docs/web-network-captures.md (+56 -11)
📝 src/application/environment/environmentBundle.test.ts (+4 -2)
📝 src/application/environment/environmentBundle.ts (+5 -4)
📝 src/application/environment/environmentState.test.ts (+110 -1)
📝 src/application/environment/environmentState.ts (+80 -30)
📝 src/application/environment/environmentStateAnswer.test.ts (+21 -0)
📝 src/application/environment/environmentStateAnswer.ts (+160 -47)
➕ src/application/environment/environmentStateCollections.ts (+50 -0)
📝 src/application/environment/environmentStateDocument.ts (+15 -0)
📝 src/application/environment/environmentStateStore.ts (+323 -25)
➕ src/application/environment/environmentStateWriteAnswer.test.ts (+941 -0)
➕ src/application/environment/environmentStateWriteAnswer.ts (+535 -0)

...and 37 more files

📄 Description

Summary

An environment served from its state now also serves the write tools whose effects the capture itself confirms:

  • a create adds an entity;
  • an update changes the members the call sends;
  • a delete removes the entity;
  • a document write changes a document.

Later reads, lists and data/get show the change, and data/reset undoes it.

The environment-state view decides which write tools are served. It replays the capture's own timeline on the extracted initial state and checks every later read the served state would answer. A write tool is served only when a later read confirms its effect and none contradicts it. Every other write tool stays unserved, with its reason.

Refs morluto/rea#4933 (design discussion).

Stacked on #5351 and #5352. Both are merged into this branch at their current heads (5a61853a and b4b7ebc7), and main is merged in. Until they land, the diff against main includes them; the new work is in:

  • b9fbcd70: implementation;
  • 84291a05: fixes and docs;
  • cbb7b613: tests;
  • the spec folder docs/specs/agent-environments/state-writes/ (spec, plan, tasks, verification).

Problem and expected behavior

Serving from the state was read-only, so every task an environment could support was a lookup. For training, agents need to change the environment, and graders need to check the resulting state rather than the agent's reply.

The expected behaviour:

  • write tools whose effect the capture confirms apply it to the live state, deterministically;
  • the environment answers as the application recorded;
  • every other write tool stays unserved and says why.

Change and scope

  • Write plans (environment-state view).
    • writes holds one plan per state-changing tool, write_clock the instant the served clock starts from.
    • Effects are classified as create, update, delete, document or none by the state type or document the path names.
    • Each plan is built from the tool's first successful call. Field rules explain the written entity:
      • echo, copy, path value, slug;
      • generated identity (sequence, UUID or token);
      • clock in its recorded format;
      • sequence, unique, template, constant.
    • Body members are accepted (stored as sent), fixed (accepted only with the recorded value, because the app transforms them) or required.
    • The response shape is entity, identity, empty or recorded; the insert position is learned from the recorded list order.
  • Gate. The view replays the timeline on a copy of the state:
    • modelled writes apply their effect;
    • an unmodelled write below an entity's path leaves that entity unknown until a read shows it again, and failures it may explain are checked only on the members the modelled writes sent;
    • reads are compared as the read gate compares them, and a 400, 404, 410 or null body counts as absence;
    • plans not served carry a reason: no-state-target, no-call, body-unusable, no-observation, unexplained-change, contradicted, side-effect, unconfirmed or incomplete-entities.
  • Serving.
    • In state mode the catalog adds the served write tools, with descriptions of their effect and body members.
    • A call applies its effect atomically to the live store and answers with a recorded status and a rendered body; refused calls leave the state unchanged.
    • Clock, sequences, UUIDs and tokens are deterministic. Sequences don't reuse deleted values. data/reset and data/add restart them.
    • Reads of a missing entity answer the recorded 400, 404 or 410.
  • List reads. A list line that only pages (limit, offset, …) and listed the whole collection backs membership: "all", and an argument-less list call answers from it. Without this, created entities never appear in many apps' lists.
  • Not included:
    • side effects on counters, relations or copies (a tool that shows them is not served);
    • inputs the app transforms (only their recorded value is accepted);
    • referential rules on delete;
    • filtered or paged list membership;
    • write paths the manifest left literal.

Follow the contribution scope.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the environment-state view (src/domain/analysisView/environmentStateWrite*.ts, EnvironmentStateWriteSimulation.ts) and the environment serve path (src/application/environment/environmentStateWrite*.ts, environmentToolAnswer.ts, the store).
  • Contribution scope fit and linked maintainer decision: a further agent-environment increment of #4933.
  • CLI and MCP/tool-catalog contract:
    • in state mode the catalog adds the served write tools (readOnlyHint: false);
    • environment-state results gain writes and write_clock;
    • list views may back all membership on paging-only lines;
    • --replay-only is unchanged.
  • Provider, bridge, target-format, or platform compatibility: none.
  • Evidence, artifact, provenance, or reconstruction contract:
    • written and updated entities cite no records;
    • write answers carry state provenance (create, update, delete, document-write, missing).
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): a served write changes only the environment's in-memory state. There is no new network activity.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): the inspect_analysis_view description gains one sentence; mcp-catalog:check passes.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

Tests.

  • Values (94): clocks, UUIDs, tokens, slugs, delimited tokens.
  • Field rules and response shapes (101).
  • Shared apply functions (52).
  • The gate through the view (16): every reason; delete confirmation by 400, 404 or null; learned insert rules; unmodelled writes; observations from later reads.
  • Serving (14): creates, updates and deletes; refusals that leave the state unchanged; determinism across reset and load; missing entities; the catalog; paging-only lines.
  • An MCP boundary test through the built CLI.

50 showcase captures (45 HAR inspections, 5 browser scenarios). 31 write tools served in 16 captures (10 creates, 11 updates, 10 deletes). Of the remaining 152 state-changing tools, 102 name no state type and 50 are modelled but not served, each with its reason. For example:

  • deleted Airbyte sources and EspoCRM tasks stay readable;
  • Immich item reads show fields the create response lacked;
  • Sonarr lists new tags in a second collection;
  • Linkding's bundle create and patch are checked only by a list taken after the bundle was deleted.

An in-process smoke applied all 31 served tools and read every created or updated entity back from the state.

End to end on a randomly chosen published environment (NetBox). The environment was exported with this branch and run in AgentEnv framework 0.9.1334. Four write tasks were graded by env_outcome_verifier through data/get, so the grade comes from the state, not the agent's reply:

Task gpt-oss-120b GLM-5.3-Flash
Create a site in the Copper Valley region 1.0 1.0
Update a seeded site 1.0 1.0
Delete one seeded site 1.0 1.0
Create a site, then update it 1.0 1.0

The bundle's 12 retrieval tasks: 12/12 for both models.

Against the live application. NetBox 4.7.3 ran with the capture's own seed, and one scenario ran against the live API and the served environment. 11 of 13 steps were equal, with only created/last_updated normalised:

  • generated ids 5, 6 and 7 (not reusing 5), URLs, region objects;
  • name order after creates, patch results, the 404 body.

The difference is NetBox's 409 when deleting a site that devices reference: the served delete removes the entity alone. The capture never showed that rule, and it is listed as not modelled.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.
Representative output (NetBox capture, built CLI, MCP client)
tools 42 (39 reads + post_dcim_sites, patch_dcim_sites_by_id, delete_dcim_sites_by_id)
list before: Blue Heron Kitchen Closet, Copperfield Annex, Lighthouse Rota Exchange, Saltmarsh Depot Edge   state {view: list, membership: all}
post_dcim_sites {name: Tide Gauge Annex, ...} -> 201, state {view: create, id: "5"}, url .../api/dcim/sites/5/, created 2026-10-20T05:48:41.000000Z
get_dcim_sites_by_id {id: "5"} -> state {view: item}; get_dcim_sites {} -> 5 sites, count 5
post_dcim_sites {..., status: "active"} -> isError: "Member status is accepted only with the value the capture sent ..."
patch_dcim_sites_by_id {id: "5", body: {description, facility}} -> state {view: update}, last_updated +3 s
delete_dcim_sites_by_id {id: "5"} -> 204; get_dcim_sites_by_id {id: "5"} -> 404 {"detail": "No Site matches the given query."}, state {view: missing}
next create -> id 6 (not 5); data/reset -> {}; same first create -> id 5, same timestamp

Validation performed

At cbb7b613 and again after merging the base branches and main:

  • Static checks: npm run build:cached, check:fast, format:check, knip, scan:todos, docs:check, verify:test-discovery and mcp-catalog:check passed.

  • Duplication: npm run jscpd at 3.44%, under the 3.5% gate.

  • Domain and services: npx vitest run --project domain --project services: 7442 passed, 5 pre-existing skips.

  • Boundaries: npm run test:prepare, then npm run test:focused -- the write MCP boundary, the serve MCP, CLI and process boundaries, the export CLI, the analysis-view CLI and MCP and the compact schema boundaries, and the contract description tests: 72 passed.

  • After the latest main merge (only the shared description string conflicted): the contracts, analysis-view and environment tests (857 passed) and those boundaries (53) passed again.

  • Review round (head df2bded1): merged #5351 at c00fde41, #5352 at f6c756aa and main at 1629266e (trial merge with current main is clean), then fixed the 11 Greptile findings in 82556cd5, 138ea4b1 and 6d21b62e (the batch-limit one was #5351 code, fixed there in c00fde41). check:fast, format:check, knip, scan:todos, docs:check, verify:test-discovery, verify:module-boundaries and mcp-catalog:check passed; jscpd 3.47%; domain and services 7463 passed (5 pre-existing skips); the focused boundary set 75 passed; conformance 279 passed. The pinned replay card digest moved only because #5352 quotes summary field names (un-quoting reproduces the old digest). The stricter gate left the write verdicts over the 50 showcase captures unchanged plan by plan, and the in-process smoke again applied all 33 served write tools.

  • Review round 2 (head 7056f5ff): fixed Greptile's 5 follow-up findings in 2086da6c (checked lines matched as serving matches calls, increments and slugs held for every call, insert rules still learned after an item read, the gate running serving's document body check, decoded parent path values on served creates); each new test fails on df2bded1. Gates as above passed again (domain and services 7468, 5 pre-existing skips; boundaries 75; conformance 279; jscpd 3.47%); verdicts over the 50 captures unchanged plan by plan; smoke 33/33.

  • Review round 3 (head db8df45f): fixed Greptile's 2 findings on round 2 in db8df45f: checked lines keep the path as spelled (replay prefers verbatim spellings), and later slug rules are checked against the values earlier calls recorded. Both new tests fail on 7056f5ff. Gates passed again (check:fast, format, knip, docs, discovery, boundaries, catalog; domain and services 7470 with 5 pre-existing skips; boundaries 75; conformance 279; jscpd 3.46%); verdicts over the 50 captures unchanged plan by plan; smoke 33/33.

  • Review round 4 (head 5327e598): a read's checked line is now chosen as replay chooses the line for its call (verbatim or decoded parameters, verbatim first, then recorded order), and a list is compared from that line's collection; the new test fails on db8df45f. Gates passed again (domain and services 7471 with 5 pre-existing skips; boundaries 75; conformance 279; jscpd 3.46%); verdicts unchanged plan by plan; smoke 33/33.

  • Review round 5 (head b6c3b168): Greptile's review of #5699, which builds on this branch, found four more gate and serving gaps, fixed here.

    • A document write at another path is unmodelled.
    • A create picks the verbatim-spelled collection.
    • A missing entity is answered with a recorded 2xx null.
    • data/add refuses a document whose data/get form would pass 32 MiB.
    • Each new test fails on 5327e598.
    • Gates passed again (domain and services 7475, 5 pre-existing skips; boundaries 75; conformance; jscpd 3.46%), the verdicts over the 50 captures were unchanged, and the smoke ran 33/33.
  • Follow-up (head f2b95351): a 2xx null now answers a missing entity only for reads, so a write of a missing entity never reports success (Greptile). Its test fails on b6c3b168; domain and services 7476 passed, and the smoke ran 33/33.

  • Review round 6 (head fc403d98): merged the serving branch at 25b6622d and 6b8db198 and the coverage branch at 665112e9, which bring main 335c042d. The contract text conflicted only where this branch adds its write sentence; in the store this branch's own size check was kept. Gates passed (domain and services 7605, 5 pre-existing skips; boundaries 75; conformance; jscpd 3.45%), the verdicts over the 50 captures are unchanged, and the smoke ran 33/33.

  • Review round 7 (head 1d1c4787): fixed Greptile's 4 findings on round 6 in 1d1c4787. A read whose body no longer holds the entity or list, or that shows an entity without its required fields, now contradicts the writes it checks. A write whose effect a later write replaces is no longer confirmed by reads of that later state. Sequences skip values the state started with. An emptied collection keeps its spelling for creates. The same commit fixes Greptile's report on #5699 that the gate confirmed an entity without a field serving requires. All 5 new tests fail on fc403d98. Gates passed (check:fast, format, knip, todos, docs, discovery, module boundaries, catalog, no oxlint warnings; domain and services 7610, 5 pre-existing skips; boundaries 75; conformance; jscpd 3.45%). Over the 50 captures, only Linkding changed: its bundle create and patch are now unconfirmed, because the one list that checked them was taken after the bundle was deleted. That leaves 31 served write tools (10 creates, 11 updates, 10 deletes), and the smoke ran 31/31.

  • Review round 8 (head dc5e2aad): fixed Greptile's 2 findings on round 7.

    • An update's effect is every field it sent or changed, derived ones included, and an increment adds to its field rather than overwriting it. A PATCH that bumps a version stays confirmed after a PUT that keeps the version.
    • A reset or load starts from the collection spellings of the state it installs.
    • Both new tests fail on 1d1c4787. Gates passed: domain and services 7612 (5 pre-existing skips), boundaries 75, conformance 279, jscpd 3.45%, no oxlint warnings.
    • The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 9 (head fe8ab584): fixed Greptile's 3 findings on round 8; two were raised on #5699 against this branch's code.

    • Later writes erase the fields they overwrite from earlier effects, and an effect with nothing left is dropped.
    • A recorded outcome erases what earlier writes changed on its entity.
    • A reset or load keeps the Evidence's own collection spellings.
    • All 3 new tests fail on dc5e2aad. Gates passed: domain and services 7615 (5 pre-existing skips), boundaries 75, conformance 279, jscpd 3.45%, no oxlint warnings.
    • The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 10 (head 73c0e9b5): fixed Greptile's finding on #5699 about this branch's store: a create prefers a held collection over an equally fitting Evidence-only spelling. Its test fails on fe8ab584. Gates passed: domain and services 7616, boundaries 75, conformance, jscpd 3.45%, no oxlint warnings. Verdicts are unchanged and the smoke ran 31/31.

    • fe8ab584's CI run failed only in the PE-resources acceptance test (tests/acceptance/applications/peResources.test.ts), which timed out at 30 s. This branch does not touch it, and it passes on main.
  • Review round 11 (head 6d28aa78): fixed six findings Greptile raised on #5787 against this branch's code, in 9f513d07:

    • a read with no query on a whole list is checked on the paging line serving answers it from, and a line whose query repeats a name is never checked;
    • a read made while a write to its targets was still running checks nothing;
    • a later call that stores a member otherwise than the plan refuses the plan;
    • a collection the capture listed empty keeps its spelling for creates;
    • containsDelimited no longer overflows the stack on wide arrays;
    • the guide counts the confirmed write tools among the served ones.
    • Attribution moves to environmentStateWriteAttribution.ts and the create's collection choice to environmentStateCollections.ts, to stay under the line limit.
    • Merged morluto's b2244622 (main and the export-failure CLI test) in 6d28aa78.
    • All six new tests fail on 73c0e9b5. Gates passed: domain and services 7667 (5 pre-existing skips); boundaries 76 plus the new export-inputs CLI test (4); conformance 279; jscpd 3.44%; no oxlint warnings.
    • The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 12 (head 3580d85c): fixed five findings Greptile raised here and one on #5699 against this branch's code, and merged main d8a9673f:

    • a passing read confirms only the writes it shows something of;
    • document writes join the effect tracking, so a replacement overwrites an earlier merge;
    • a read a write's effect was learned from still contradicts the other writes it exercises;
    • an item read names its entity as serving does, percent-decoded spellings included;
    • a line repeating a query name with one value stays checkable;
    • a call naming a page the collection has outgrown answers from its recording (page-exceeded), and the gate checks nothing by it.
    • Each new test fails on the previous head. The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 13 (head 43d98ea9): fixed four findings Greptile raised on #5787 against this branch's code:

    • a create that answers with its id alone is observed only through a read of that id;
    • a later call that first sends the path's id adds the identity member to the plan;
    • the copy search walks a request body by index, so 500,000 zeros take 74 ms rather than 63 s;
    • the sent values of fixed members a plan keeps count against the view's allowance.
    • Each new test fails on the previous head. The verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 14 (head 325c128f): fixed six waves of findings Greptile raised against this branch's code, here and on #5699, #5787 and #5800, in 264ce410, 0ae64400, 466ed8b4, 7cef2fb2, 45a5c8b9 and 325c128f:

    • a passing read that shows nothing of a write leaves it pending, and interference is weighed per write and per target the read covers where the tool's effect survives, so a write made after an unmodelled one is still contradicted, by an item read as well, while one an unmodelled write followed is checked on its members only;
    • a read begun while a write to an unknown entity still ran neither checks nor resynchronises it;
    • a later create must keep the forms serving generates: sequences in order, clocks in their format, unique values as UUIDs;
    • a document replacement below the root overwrites only its object, later document calls write at the plan's object, and only document pointers cover the members under them;
    • item reads, updates and deletes name their entity as serving does, decoding the id once;
    • an update's identity member never takes a rule; a plan that uses a member as both identity and fixed member, or fixes it at different values, is refused, as is an update a later read shows dropped a field (a write's own response may leave fields out);
    • a _start–_end range bounds a page as limit does, and a zero size is no bound by itself only where the line's recording listed items under it, the line's other bounds still applying;
    • a derived state data/get would write past 32 MiB is not served.
    • The paging rules moved to environmentStatePaging.ts (6257de93) and the document write to environmentStateWriteDocument.ts, to keep files under the line limit.
    • Each new test fails on its previous head. Gates passed on 325c128f: check:fast, format, knip, todos, docs, discovery, module boundaries and catalog, with no oxlint warnings in the changed files; domain and services 7748 (5 pre-existing skips); environment boundaries 63; conformance 265; jscpd 3.44%.
    • The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 15 (head 717ac2c4): morluto restacked this branch onto main after #5352 and added fixes through 3b34bbd7 (5d00ff3c). Merged main 8413ec08 in 717ac2c4, with no conflict.

    • Gates passed: check:fast, format, knip, todos, docs, discovery, module boundaries and catalog; domain and services 7827 (5 pre-existing skips); environment boundaries 64; jscpd 3.47%.
    • CI shard 3 failed on 5d00ff3c in the client-configuration diagnostics acceptance test's JVM cleanup (cleanup_incomplete). That test passes locally.
    • The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31.
  • Review round 16 (head 3f1f825e): fixed the Greptile findings raised against this branch's code on the stacked PRs.

    • e923fc73 and 75d7bb02, replayed onto the restacked branch:
      • an older change elsewhere no longer excuses a newer write that a failed list shows undone;
      • an update explains a field it added when the observing view had shown the entity;
      • a create is tied to the collection serving picks;
      • write descriptions quote member names and stay within the description budget;
      • a request body is indexed once per explanation, so the wide-body test runs in about 60 ms instead of 36.8 s.
    • ccdf091a:
      • a failed read that is no absence leaves an unknown entity unknown;
      • volatile fields check no write;
      • a call on an unknown entity adds nothing to a built plan;
      • a later update that does not answer with its entity is observed only by a read that shows every learned clock.
    • 1c979337 fills a create's path fields from the collection serving selects.
    • 90d90227:
      • unmodelled writes name entities and collections as modelled writes do;
      • an update's answer counts the fields its rules write;
      • a member that a later call first stores keeps that call's JSON type (the body's new kinds).
    • Merged main 4a4bbab3 in 2a125b71, which brings main's fix for the client-configuration cleanup_incomplete CI failure. Merged morluto's 53a78662 and 329aeedc/756c6312 in 3f1f825e; where fixes overlapped, one copy is kept.
    • Each new test fails on its previous head. Gates passed on 3f1f825e: check:fast, format, knip, todos, docs, discovery, module boundaries and catalog; domain and services 7880 (5 pre-existing skips); environment boundaries 64; jscpd 3.41%. The smoke ran 31/31.
    • One plan changed across the 50 captures. Gitea's issue PATCH is still served, but no longer accepts state or stamps closed_at. The only call that closed the issue came after an unmodelled comment delete under it, so that call is no longer learned from (ccdf091a).

Details, including the per-capture verdict table, are in docs/specs/agent-environments/state-writes/verification.md.

Compatibility, safety, and release

  • In state mode the served catalog gains the confirmed write tools.
  • --replay-only and the replay fallback are unchanged.
  • The replay card pinned by the serve MCP boundary changed with #5352's response summaries; its digest now matches the combined branch.
  • Published manifests built by an older REA whose tool names no longer match the current view fall back to replay, as #5351 designed; rebuilding a bundle regenerates them.
  • Size: about 4,000 lines of source and 2,500 of tests on top of #5351 and #5352. If wanted, a natural split is the gate (the view's writes) first and serving second.
  • Gates ran on Node v22.23.1; the pinned 24.18.0 is not installed on this machine.

Author readiness

Human owner: @earakely-scale

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code

Current blocker verification

The combined branch retains the author's 7de20bc9e fixes for sent-member provenance, negative sequence continuation, deleted-target absence checks and encoded update observations. Target resolution prefers live entities before removed-target fallback, and observation matching uses the same owner. Distinct tests cover encoded collection aliases and the exact resolved raw ID rather than deriving a new identity from the path. The earlier generation-exhaustion refusal remains typed and precedes mutation/write commitment.

The additional collection-history fix indexes baseline collection spellings once at restart/load/reset and budgets only newly generated path history. Admission happens before mutation; rejected insertions leave state unchanged. Loaded source collections and sequence facts remain available after deletion/reinsertion. A canonical index removes the prior scan of all baseline entities on every new path. The 1 MiB allowance is deterministic logical accounting for generated history (UTF-8 keys plus explicit bookkeeping allowances), not a total-store or measured OS-memory bound.

The combined source passed 116/116 focused tests, check:fast, format:check and git diff --check using Node 24.18.0/npm 11.16.0. docs:check also passed on the final tree (README consistency 5/5), and the pre-push fast gate passed. No provider was executed.

Independent review covered these owner changes. For the history-index change only, a matched 1,000 insert/delete sample recorded baseline 14.57 ms / 23,867 microseconds CPU versus candidate 9.98 ms / 16,914 microseconds CPU; these are host-specific samples, not a universal speed claim, and the later correctness fixes were not separately benchmarked. Vitest/V8 RSS was noisy; process-family memory and agent-token costs were not measured. The documentation records the limits. This pass is not a new full review of the entire reconstructed PR or a real-provider run. Normal merges preserve the author's intervening branch updates; no history rewrite was used.

Additional owner-boundary verification

Update observations now carry the selected read view's field coverage. A summary that does not cover a required learned clock cannot displace useful partial write-response facts. Facts outside a later view remain available from the response; omission counts as removal only within the read's declared coverage. Missing generated values remain absent in simulation rather than becoming synthetic nulls. This keeps incomplete evidence explicit while preserving the timing and clock-rule checks.

The fix was normally merged with the author's intervening restack, preserving both histories. On exact live head 5d00ff3c, 218 focused simulation, explanation and application tests passed using Node 24.18.0. check:fast, format:check, diff checks and the push gate passed. Regressions cover an abbreviated item view after a richer partial write response, covered removals, and incomplete clock observations. The history-budget and earlier target/provenance fixes remain. No provider execution or new resource measurements were performed.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/5600 **Author:** [@earakely-scale](https://github.com/earakely-scale) **Created:** 10/20/2026 **Status:** ✅ Merged **Merged:** 10/22/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `feat/environment-state-writes` --- ### 📝 Commits (10+) - [`6c8f4f7`](https://github.com/morluto/rea/commit/6c8f4f728f0d9bbc84d9e1ce3975cafe3d9a1954) feat(environment): render more read tools from the state and describe responses - [`378a109`](https://github.com/morluto/rea/commit/378a109cc62edda924510c24122386ee69b06917) fix(environment): keep rendered documents and quoted samples inside their budgets - [`cfb42dd`](https://github.com/morluto/rea/commit/cfb42ddde0a576fa0325b008262aa54e951c5d7a) feat(environment): apply evidence-gated write effects to the served state - [`5d0e096`](https://github.com/morluto/rea/commit/5d0e0969f5c801f27f6ea9b9c582286fdba561fe) fix(environment): bound and validate write state history - [`5b42469`](https://github.com/morluto/rea/commit/5b42469bd63cb5c71414607db5f001e902e07714) feat(environment): render more read tools from the state and describe responses - [`54c8934`](https://github.com/morluto/rea/commit/54c8934995a3c8771cb40a8bf562342b5af6f472) fix(environment): keep rendered documents and quoted samples inside their budgets - [`19b50ea`](https://github.com/morluto/rea/commit/19b50ea668ca54f53effbe29b537ac0353dd661b) feat(environment): apply evidence-gated write effects to the served state - [`a057ea9`](https://github.com/morluto/rea/commit/a057ea978497a87c67ab22eb4d35971c780062ca) fix(environment): bound and validate write state history - [`f79f1e1`](https://github.com/morluto/rea/commit/f79f1e1833f604aef157dd920519b1577ed921c4) fix(environment): bound additional collection history - [`4a678f1`](https://github.com/morluto/rea/commit/4a678f1a8f529ac5ffea8ed65841736ccbc7ebdc) Merge remote-tracking branch 'live-5600/feat/environment-state-writes' into codex/blockers-5600-live ### 📊 Changes **57 files changed** (+12919 additions, -199 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/reverse-engineer-anything/references/evidence-workflows.md` (+10 -6) 📝 `docs/agent-environments.md` (+87 -38) 📝 `docs/cli.md` (+2 -1) ➕ `docs/specs/agent-environments/state-writes/plan.md` (+45 -0) ➕ `docs/specs/agent-environments/state-writes/spec.md` (+427 -0) ➕ `docs/specs/agent-environments/state-writes/tasks.md` (+22 -0) ➕ `docs/specs/agent-environments/state-writes/verification.md` (+376 -0) 📝 `docs/testing.md` (+1 -1) 📝 `docs/web-network-captures.md` (+56 -11) 📝 `src/application/environment/environmentBundle.test.ts` (+4 -2) 📝 `src/application/environment/environmentBundle.ts` (+5 -4) 📝 `src/application/environment/environmentState.test.ts` (+110 -1) 📝 `src/application/environment/environmentState.ts` (+80 -30) 📝 `src/application/environment/environmentStateAnswer.test.ts` (+21 -0) 📝 `src/application/environment/environmentStateAnswer.ts` (+160 -47) ➕ `src/application/environment/environmentStateCollections.ts` (+50 -0) 📝 `src/application/environment/environmentStateDocument.ts` (+15 -0) 📝 `src/application/environment/environmentStateStore.ts` (+323 -25) ➕ `src/application/environment/environmentStateWriteAnswer.test.ts` (+941 -0) ➕ `src/application/environment/environmentStateWriteAnswer.ts` (+535 -0) _...and 37 more files_ </details> ### 📄 Description ## Summary An environment served from its state now also serves the write tools whose effects the capture itself confirms: - a create adds an entity; - an update changes the members the call sends; - a delete removes the entity; - a document write changes a document. Later reads, lists and `data/get` show the change, and `data/reset` undoes it. The `environment-state` view decides which write tools are served. It replays the capture's own timeline on the extracted initial state and checks every later read the served state would answer. A write tool is served only when a later read confirms its effect and none contradicts it. Every other write tool stays unserved, with its reason. Refs morluto/rea#4933 (design discussion). **Stacked on #5351 and #5352.** Both are merged into this branch at their current heads (5a61853a and b4b7ebc7), and main is merged in. Until they land, the diff against main includes them; the new work is in: - b9fbcd70: implementation; - 84291a05: fixes and docs; - cbb7b613: tests; - the spec folder `docs/specs/agent-environments/state-writes/` (spec, plan, tasks, verification). ## Problem and expected behavior Serving from the state was read-only, so every task an environment could support was a lookup. For training, agents need to change the environment, and graders need to check the resulting state rather than the agent's reply. The expected behaviour: - write tools whose effect the capture confirms apply it to the live state, deterministically; - the environment answers as the application recorded; - every other write tool stays unserved and says why. ## Change and scope - **Write plans (environment-state view).** - `writes` holds one plan per state-changing tool, `write_clock` the instant the served clock starts from. - Effects are classified as `create`, `update`, `delete`, `document` or `none` by the state type or document the path names. - Each plan is built from the tool's first successful call. Field rules explain the written entity: - echo, copy, path value, slug; - generated identity (sequence, UUID or token); - clock in its recorded format; - sequence, unique, template, constant. - Body members are accepted (stored as sent), fixed (accepted only with the recorded value, because the app transforms them) or required. - The response shape is `entity`, `identity`, `empty` or `recorded`; the insert position is learned from the recorded list order. - **Gate.** The view replays the timeline on a copy of the state: - modelled writes apply their effect; - an unmodelled write below an entity's path leaves that entity unknown until a read shows it again, and failures it may explain are checked only on the members the modelled writes sent; - reads are compared as the read gate compares them, and a 400, 404, 410 or `null` body counts as absence; - plans not served carry a reason: `no-state-target`, `no-call`, `body-unusable`, `no-observation`, `unexplained-change`, `contradicted`, `side-effect`, `unconfirmed` or `incomplete-entities`. - **Serving.** - In state mode the catalog adds the served write tools, with descriptions of their effect and body members. - A call applies its effect atomically to the live store and answers with a recorded status and a rendered body; refused calls leave the state unchanged. - Clock, sequences, UUIDs and tokens are deterministic. Sequences don't reuse deleted values. `data/reset` and `data/add` restart them. - Reads of a missing entity answer the recorded 400, 404 or 410. - **List reads.** A list line that only pages (`limit`, `offset`, …) and listed the whole collection backs `membership: "all"`, and an argument-less list call answers from it. Without this, created entities never appear in many apps' lists. - **Not included:** - side effects on counters, relations or copies (a tool that shows them is not served); - inputs the app transforms (only their recorded value is accepted); - referential rules on delete; - filtered or paged list membership; - write paths the manifest left literal. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). ## Contract and boundary impact - Semantic owner and earliest changed stage: the `environment-state` view (`src/domain/analysisView/environmentStateWrite*.ts`, `EnvironmentStateWriteSimulation.ts`) and the environment serve path (`src/application/environment/environmentStateWrite*.ts`, `environmentToolAnswer.ts`, the store). - Contribution scope fit and linked maintainer decision: a further agent-environment increment of #4933. - CLI and MCP/tool-catalog contract: - in state mode the catalog adds the served write tools (`readOnlyHint: false`); - `environment-state` results gain `writes` and `write_clock`; - list views may back `all` membership on paging-only lines; - `--replay-only` is unchanged. - Provider, bridge, target-format, or platform compatibility: none. - Evidence, artifact, provenance, or reconstruction contract: - written and updated entities cite no records; - write answers carry `state` provenance (`create`, `update`, `delete`, `document-write`, `missing`). - Target selection, authority, upstream behavior, and cleanup details (if applicable): a served write changes only the environment's in-memory state. There is no new network activity. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): the `inspect_analysis_view` description gains one sentence; `mcp-catalog:check` passes. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage **Tests.** - Values (94): clocks, UUIDs, tokens, slugs, delimited tokens. - Field rules and response shapes (101). - Shared apply functions (52). - The gate through the view (16): every reason; delete confirmation by 400, 404 or `null`; learned insert rules; unmodelled writes; observations from later reads. - Serving (14): creates, updates and deletes; refusals that leave the state unchanged; determinism across reset and load; missing entities; the catalog; paging-only lines. - An MCP boundary test through the built CLI. **50 showcase captures** (45 HAR inspections, 5 browser scenarios). 31 write tools served in 16 captures (10 creates, 11 updates, 10 deletes). Of the remaining 152 state-changing tools, 102 name no state type and 50 are modelled but not served, each with its reason. For example: - deleted Airbyte sources and EspoCRM tasks stay readable; - Immich item reads show fields the create response lacked; - Sonarr lists new tags in a second collection; - Linkding's bundle create and patch are checked only by a list taken after the bundle was deleted. An in-process smoke applied all 31 served tools and read every created or updated entity back from the state. **End to end on a randomly chosen published environment (NetBox).** The environment was exported with this branch and run in AgentEnv framework 0.9.1334. Four write tasks were graded by `env_outcome_verifier` through `data/get`, so the grade comes from the state, not the agent's reply: | Task | gpt-oss-120b | GLM-5.3-Flash | | --- | --- | --- | | Create a site in the Copper Valley region | 1.0 | 1.0 | | Update a seeded site | 1.0 | 1.0 | | Delete one seeded site | 1.0 | 1.0 | | Create a site, then update it | 1.0 | 1.0 | The bundle's 12 retrieval tasks: 12/12 for both models. **Against the live application.** NetBox 4.7.3 ran with the capture's own seed, and one scenario ran against the live API and the served environment. 11 of 13 steps were equal, with only `created`/`last_updated` normalised: - generated ids 5, 6 and 7 (not reusing 5), URLs, region objects; - name order after creates, patch results, the 404 body. The difference is NetBox's 409 when deleting a site that devices reference: the served delete removes the entity alone. The capture never showed that rule, and it is listed as not modelled. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. <details><summary>Representative output (NetBox capture, built CLI, MCP client)</summary> ``` tools 42 (39 reads + post_dcim_sites, patch_dcim_sites_by_id, delete_dcim_sites_by_id) list before: Blue Heron Kitchen Closet, Copperfield Annex, Lighthouse Rota Exchange, Saltmarsh Depot Edge state {view: list, membership: all} post_dcim_sites {name: Tide Gauge Annex, ...} -> 201, state {view: create, id: "5"}, url .../api/dcim/sites/5/, created 2026-10-20T05:48:41.000000Z get_dcim_sites_by_id {id: "5"} -> state {view: item}; get_dcim_sites {} -> 5 sites, count 5 post_dcim_sites {..., status: "active"} -> isError: "Member status is accepted only with the value the capture sent ..." patch_dcim_sites_by_id {id: "5", body: {description, facility}} -> state {view: update}, last_updated +3 s delete_dcim_sites_by_id {id: "5"} -> 204; get_dcim_sites_by_id {id: "5"} -> 404 {"detail": "No Site matches the given query."}, state {view: missing} next create -> id 6 (not 5); data/reset -> {}; same first create -> id 5, same timestamp ``` </details> ## Validation performed At cbb7b613 and again after merging the base branches and main: - **Static checks:** `npm run build:cached`, `check:fast`, `format:check`, `knip`, `scan:todos`, `docs:check`, `verify:test-discovery` and `mcp-catalog:check` passed. - **Duplication:** `npm run jscpd` at 3.44%, under the 3.5% gate. - **Domain and services:** `npx vitest run --project domain --project services`: 7442 passed, 5 pre-existing skips. - **Boundaries:** `npm run test:prepare`, then `npm run test:focused --` the write MCP boundary, the serve MCP, CLI and process boundaries, the export CLI, the analysis-view CLI and MCP and the compact schema boundaries, and the contract description tests: 72 passed. - After the latest main merge (only the shared description string conflicted): the contracts, analysis-view and environment tests (857 passed) and those boundaries (53) passed again. - **Review round (head df2bded1):** merged #5351 at c00fde41, #5352 at f6c756aa and main at 1629266e (trial merge with current main is clean), then fixed the 11 Greptile findings in 82556cd5, 138ea4b1 and 6d21b62e (the batch-limit one was #5351 code, fixed there in c00fde41). `check:fast`, `format:check`, `knip`, `scan:todos`, `docs:check`, `verify:test-discovery`, `verify:module-boundaries` and `mcp-catalog:check` passed; `jscpd` 3.47%; domain and services 7463 passed (5 pre-existing skips); the focused boundary set 75 passed; conformance 279 passed. The pinned replay card digest moved only because #5352 quotes summary field names (un-quoting reproduces the old digest). The stricter gate left the write verdicts over the 50 showcase captures unchanged plan by plan, and the in-process smoke again applied all 33 served write tools. - **Review round 2 (head 7056f5ff):** fixed Greptile's 5 follow-up findings in 2086da6c (checked lines matched as serving matches calls, increments and slugs held for every call, insert rules still learned after an item read, the gate running serving's document body check, decoded parent path values on served creates); each new test fails on df2bded1. Gates as above passed again (domain and services 7468, 5 pre-existing skips; boundaries 75; conformance 279; `jscpd` 3.47%); verdicts over the 50 captures unchanged plan by plan; smoke 33/33. - **Review round 3 (head db8df45f):** fixed Greptile's 2 findings on round 2 in db8df45f: checked lines keep the path as spelled (replay prefers verbatim spellings), and later slug rules are checked against the values earlier calls recorded. Both new tests fail on 7056f5ff. Gates passed again (`check:fast`, format, knip, docs, discovery, boundaries, catalog; domain and services 7470 with 5 pre-existing skips; boundaries 75; conformance 279; `jscpd` 3.46%); verdicts over the 50 captures unchanged plan by plan; smoke 33/33. - **Review round 4 (head 5327e598):** a read's checked line is now chosen as replay chooses the line for its call (verbatim or decoded parameters, verbatim first, then recorded order), and a list is compared from that line's collection; the new test fails on db8df45f. Gates passed again (domain and services 7471 with 5 pre-existing skips; boundaries 75; conformance 279; `jscpd` 3.46%); verdicts unchanged plan by plan; smoke 33/33. - **Review round 5 (head b6c3b168):** Greptile's review of #5699, which builds on this branch, found four more gate and serving gaps, fixed here. - A document write at another path is unmodelled. - A create picks the verbatim-spelled collection. - A missing entity is answered with a recorded 2xx `null`. - `data/add` refuses a document whose `data/get` form would pass 32 MiB. - Each new test fails on 5327e598. - Gates passed again (domain and services 7475, 5 pre-existing skips; boundaries 75; conformance; `jscpd` 3.46%), the verdicts over the 50 captures were unchanged, and the smoke ran 33/33. - **Follow-up (head f2b95351):** a 2xx `null` now answers a missing entity only for reads, so a write of a missing entity never reports success (Greptile). Its test fails on b6c3b168; domain and services 7476 passed, and the smoke ran 33/33. - **Review round 6 (head fc403d98):** merged the serving branch at 25b6622d and 6b8db198 and the coverage branch at 665112e9, which bring main 335c042d. The contract text conflicted only where this branch adds its write sentence; in the store this branch's own size check was kept. Gates passed (domain and services 7605, 5 pre-existing skips; boundaries 75; conformance; `jscpd` 3.45%), the verdicts over the 50 captures are unchanged, and the smoke ran 33/33. - **Review round 7 (head 1d1c4787):** fixed Greptile's 4 findings on round 6 in 1d1c4787. A read whose body no longer holds the entity or list, or that shows an entity without its required fields, now contradicts the writes it checks. A write whose effect a later write replaces is no longer confirmed by reads of that later state. Sequences skip values the state started with. An emptied collection keeps its spelling for creates. The same commit fixes Greptile's report on #5699 that the gate confirmed an entity without a field serving requires. All 5 new tests fail on fc403d98. Gates passed (`check:fast`, format, knip, todos, docs, discovery, module boundaries, catalog, no oxlint warnings; domain and services 7610, 5 pre-existing skips; boundaries 75; conformance; `jscpd` 3.45%). Over the 50 captures, only Linkding changed: its bundle create and patch are now unconfirmed, because the one list that checked them was taken after the bundle was deleted. That leaves 31 served write tools (10 creates, 11 updates, 10 deletes), and the smoke ran 31/31. - **Review round 8 (head dc5e2aad):** fixed Greptile's 2 findings on round 7. - An update's effect is every field it sent or changed, derived ones included, and an increment adds to its field rather than overwriting it. A PATCH that bumps a version stays confirmed after a PUT that keeps the version. - A reset or load starts from the collection spellings of the state it installs. - Both new tests fail on 1d1c4787. Gates passed: domain and services 7612 (5 pre-existing skips), boundaries 75, conformance 279, `jscpd` 3.45%, no oxlint warnings. - The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 9 (head fe8ab584):** fixed Greptile's 3 findings on round 8; two were raised on #5699 against this branch's code. - Later writes erase the fields they overwrite from earlier effects, and an effect with nothing left is dropped. - A recorded outcome erases what earlier writes changed on its entity. - A reset or load keeps the Evidence's own collection spellings. - All 3 new tests fail on dc5e2aad. Gates passed: domain and services 7615 (5 pre-existing skips), boundaries 75, conformance 279, `jscpd` 3.45%, no oxlint warnings. - The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 10 (head 73c0e9b5):** fixed Greptile's finding on #5699 about this branch's store: a create prefers a held collection over an equally fitting Evidence-only spelling. Its test fails on fe8ab584. Gates passed: domain and services 7616, boundaries 75, conformance, `jscpd` 3.45%, no oxlint warnings. Verdicts are unchanged and the smoke ran 31/31. - fe8ab584's CI run failed only in the PE-resources acceptance test (`tests/acceptance/applications/peResources.test.ts`), which timed out at 30 s. This branch does not touch it, and it passes on main. - **Review round 11 (head 6d28aa78):** fixed six findings Greptile raised on #5787 against this branch's code, in 9f513d07: - a read with no query on a whole list is checked on the paging line serving answers it from, and a line whose query repeats a name is never checked; - a read made while a write to its targets was still running checks nothing; - a later call that stores a member otherwise than the plan refuses the plan; - a collection the capture listed empty keeps its spelling for creates; - `containsDelimited` no longer overflows the stack on wide arrays; - the guide counts the confirmed write tools among the served ones. - Attribution moves to `environmentStateWriteAttribution.ts` and the create's collection choice to `environmentStateCollections.ts`, to stay under the line limit. - Merged morluto's b2244622 (main and the export-failure CLI test) in 6d28aa78. - All six new tests fail on 73c0e9b5. Gates passed: domain and services 7667 (5 pre-existing skips); boundaries 76 plus the new export-inputs CLI test (4); conformance 279; `jscpd` 3.44%; no oxlint warnings. - The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 12 (head 3580d85c):** fixed five findings Greptile raised here and one on #5699 against this branch's code, and merged main d8a9673f: - a passing read confirms only the writes it shows something of; - document writes join the effect tracking, so a replacement overwrites an earlier merge; - a read a write's effect was learned from still contradicts the other writes it exercises; - an item read names its entity as serving does, percent-decoded spellings included; - a line repeating a query name with one value stays checkable; - a call naming a page the collection has outgrown answers from its recording (`page-exceeded`), and the gate checks nothing by it. - Each new test fails on the previous head. The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 13 (head 43d98ea9):** fixed four findings Greptile raised on #5787 against this branch's code: - a create that answers with its id alone is observed only through a read of that id; - a later call that first sends the path's id adds the identity member to the plan; - the copy search walks a request body by index, so 500,000 zeros take 74 ms rather than 63 s; - the sent values of fixed members a plan keeps count against the view's allowance. - Each new test fails on the previous head. The verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 14 (head 325c128f):** fixed six waves of findings Greptile raised against this branch's code, here and on #5699, #5787 and #5800, in 264ce410, 0ae64400, 466ed8b4, 7cef2fb2, 45a5c8b9 and 325c128f: - a passing read that shows nothing of a write leaves it pending, and interference is weighed per write and per target the read covers where the tool's effect survives, so a write made after an unmodelled one is still contradicted, by an item read as well, while one an unmodelled write followed is checked on its members only; - a read begun while a write to an unknown entity still ran neither checks nor resynchronises it; - a later create must keep the forms serving generates: sequences in order, clocks in their format, unique values as UUIDs; - a document replacement below the root overwrites only its object, later document calls write at the plan's object, and only document pointers cover the members under them; - item reads, updates and deletes name their entity as serving does, decoding the id once; - an update's identity member never takes a rule; a plan that uses a member as both identity and fixed member, or fixes it at different values, is refused, as is an update a later read shows dropped a field (a write's own response may leave fields out); - a `_start`–`_end` range bounds a page as `limit` does, and a zero size is no bound by itself only where the line's recording listed items under it, the line's other bounds still applying; - a derived state `data/get` would write past 32 MiB is not served. - The paging rules moved to `environmentStatePaging.ts` (6257de93) and the document write to `environmentStateWriteDocument.ts`, to keep files under the line limit. - Each new test fails on its previous head. Gates passed on 325c128f: `check:fast`, format, knip, todos, docs, discovery, module boundaries and catalog, with no oxlint warnings in the changed files; domain and services 7748 (5 pre-existing skips); environment boundaries 63; conformance 265; `jscpd` 3.44%. - The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 15 (head 717ac2c4):** morluto restacked this branch onto main after #5352 and added fixes through 3b34bbd7 (5d00ff3c). Merged main 8413ec08 in 717ac2c4, with no conflict. - Gates passed: `check:fast`, format, knip, todos, docs, discovery, module boundaries and catalog; domain and services 7827 (5 pre-existing skips); environment boundaries 64; `jscpd` 3.47%. - CI shard 3 failed on 5d00ff3c in the client-configuration diagnostics acceptance test's JVM cleanup (`cleanup_incomplete`). That test passes locally. - The 50-capture verdicts are unchanged plan by plan, and the smoke ran 31/31. - **Review round 16 (head 3f1f825e):** fixed the Greptile findings raised against this branch's code on the stacked PRs. - e923fc73 and 75d7bb02, replayed onto the restacked branch: - an older change elsewhere no longer excuses a newer write that a failed list shows undone; - an update explains a field it added when the observing view had shown the entity; - a create is tied to the collection serving picks; - write descriptions quote member names and stay within the description budget; - a request body is indexed once per explanation, so the wide-body test runs in about 60 ms instead of 36.8 s. - ccdf091a: - a failed read that is no absence leaves an unknown entity unknown; - volatile fields check no write; - a call on an unknown entity adds nothing to a built plan; - a later update that does not answer with its entity is observed only by a read that shows every learned clock. - 1c979337 fills a create's path fields from the collection serving selects. - 90d90227: - unmodelled writes name entities and collections as modelled writes do; - an update's answer counts the fields its rules write; - a member that a later call first stores keeps that call's JSON type (the body's new `kinds`). - Merged main 4a4bbab3 in 2a125b71, which brings main's fix for the client-configuration `cleanup_incomplete` CI failure. Merged morluto's 53a78662 and 329aeedc/756c6312 in 3f1f825e; where fixes overlapped, one copy is kept. - Each new test fails on its previous head. Gates passed on 3f1f825e: `check:fast`, format, knip, todos, docs, discovery, module boundaries and catalog; domain and services 7880 (5 pre-existing skips); environment boundaries 64; `jscpd` 3.41%. The smoke ran 31/31. - One plan changed across the 50 captures. Gitea's issue PATCH is still served, but no longer accepts `state` or stamps `closed_at`. The only call that closed the issue came after an unmodelled comment delete under it, so that call is no longer learned from (ccdf091a). Details, including the per-capture verdict table, are in `docs/specs/agent-environments/state-writes/verification.md`. ## Compatibility, safety, and release - In state mode the served catalog gains the confirmed write tools. - `--replay-only` and the replay fallback are unchanged. - The replay card pinned by the serve MCP boundary changed with #5352's response summaries; its digest now matches the combined branch. - Published manifests built by an older REA whose tool names no longer match the current view fall back to replay, as #5351 designed; rebuilding a bundle regenerates them. - Size: about 4,000 lines of source and 2,500 of tests on top of #5351 and #5352. If wanted, a natural split is the gate (the view's `writes`) first and serving second. - Gates ran on Node v22.23.1; the pinned 24.18.0 is not installed on this machine. ## Author readiness Human owner: @earakely-scale - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- maintainer-blocker-review --> ### Current blocker verification The combined branch retains the author's `7de20bc9e` fixes for sent-member provenance, negative sequence continuation, deleted-target absence checks and encoded update observations. Target resolution prefers live entities before removed-target fallback, and observation matching uses the same owner. Distinct tests cover encoded collection aliases and the exact resolved raw ID rather than deriving a new identity from the path. The earlier generation-exhaustion refusal remains typed and precedes mutation/write commitment. The additional collection-history fix indexes baseline collection spellings once at restart/load/reset and budgets only newly generated path history. Admission happens before mutation; rejected insertions leave state unchanged. Loaded source collections and sequence facts remain available after deletion/reinsertion. A canonical index removes the prior scan of all baseline entities on every new path. The 1 MiB allowance is deterministic logical accounting for generated history (UTF-8 keys plus explicit bookkeeping allowances), not a total-store or measured OS-memory bound. The combined source passed 116/116 focused tests, `check:fast`, `format:check` and `git diff --check` using Node 24.18.0/npm 11.16.0. `docs:check` also passed on the final tree (README consistency 5/5), and the pre-push fast gate passed. No provider was executed. Independent review covered these owner changes. For the history-index change only, a matched 1,000 insert/delete sample recorded baseline 14.57 ms / 23,867 microseconds CPU versus candidate 9.98 ms / 16,914 microseconds CPU; these are host-specific samples, not a universal speed claim, and the later correctness fixes were not separately benchmarked. Vitest/V8 RSS was noisy; process-family memory and agent-token costs were not measured. The documentation records the limits. This pass is not a new full review of the entire reconstructed PR or a real-provider run. Normal merges preserve the author's intervening branch updates; no history rewrite was used. <!-- final-owner-followup --> ### Additional owner-boundary verification Update observations now carry the selected read view's field coverage. A summary that does not cover a required learned clock cannot displace useful partial write-response facts. Facts outside a later view remain available from the response; omission counts as removal only within the read's declared coverage. Missing generated values remain absent in simulation rather than becoming synthetic nulls. This keeps incomplete evidence explicit while preserving the timing and clock-rule checks. The fix was normally merged with the author's intervening restack, preserving both histories. On exact live head `5d00ff3c`, 218 focused simulation, explanation and application tests passed using Node 24.18.0. `check:fast`, `format:check`, diff checks and the push gate passed. Regressions cover an abbreviated item view after a richer partial write response, covered removals, and incomplete clock observations. The history-budget and earlier target/provenance fixes remain. No provider execution or new resource measurements were performed. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:44:31 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6989
No description provided.