diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 280daeaa..ad73b688 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -21,7 +21,7 @@ The spine exists in eleven live paths: - explicit map/churn proposals: app-server routes read-only `thread/epiphany/propose` through a loaded-thread handler into `epiphany-core` so verified observations with code refs and accepting recent evidence can be selected explicitly or auto-selected as a bounded path cluster, prioritize the strongest selected observation, focus or extend architecture graph nodes, rescue unanchored graph nodes through strict semantic overlap, carry linked dataflow nodes and incident graph edges into the frontier, update churn candidates with match-kind-aware map-delta judgment and pressure, and still avoid mutation until promotion in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4244) and [proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:115). - explicit promotion gates: app-server routes `thread/epiphany/promote` through a loaded-thread handler into `epiphany-core` policy evaluation, rejects failed verifier evidence without mutation, applies accepted candidates through the same durable update path, and now treats medium/high/expanded/broadening/semantic churn deltas as needing explicit rationale plus stronger verifier evidence whose kind is token-matched rather than substring-matched in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4316) and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33). - explicit state updates: app-server routes `thread/epiphany/update` through a loaded `CodexThread` update method that mutates live `SessionState`, bumps the revision, and persists an immediate `RolloutItem::EpiphanyState` snapshot in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4416) and [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:373). -- live state notifications: app-server declares experimental `thread/epiphany/stateUpdated` and emits it with the updated typed state plus a typed `source` after successful direct updates and accepted promotions, but not after rejected promotions, in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:1097), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4059), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4417), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4494). +- live state notifications: app-server declares experimental `thread/epiphany/stateUpdated` and emits it with the updated typed state, typed `source`, event-level `revision`, and typed `changedFields` after successful direct updates and accepted promotions, but not after rejected promotions, in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:1097), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4059), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4379), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4464), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:10509). - retrieval/indexing: app-server routes `thread/epiphany/retrieve` and `thread/epiphany/index` through loaded `CodexThread` host methods into `epiphany-core` in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4046), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4122), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:438), and [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:456). The thick Epiphany-owned implementation is mostly outside vendored Codex now: @@ -80,7 +80,7 @@ flowchart TD ZA --> ZB["Apply typed patch and increment revision"] ZB --> B ZB --> ZC["Persist immediate RolloutItem::EpiphanyState"] - ZB --> ZD["Emit thread/epiphany/stateUpdated notification"] + ZB --> ZD["Emit thread/epiphany/stateUpdated notification with source, revision, changedFields"] ZD --> Q ZC --> K T --> V["epiphany-core retrieve_workspace"] @@ -97,7 +97,7 @@ What this means in plain English: - A loaded thread can ask for a typed observation/evidence proposal without mutating state. - A loaded thread can ask a verifier-backed promotion gate to reject or apply that proposal. - A loaded thread can now accept explicit typed state patches and persist them immediately. -- Successful direct updates and accepted promotions now notify clients that typed state changed. +- Successful direct updates and accepted promotions now notify clients that typed state changed, which revision landed, and which top-level state fields changed. - Retrieval and indexing are typed side paths hanging off loaded threads. - Retrieval remains read-only; state mutation has its own control-plane door. - Map/churn proposal is also read-only; accepted graph/churn edits still go through promotion and update. @@ -129,7 +129,7 @@ This is the same flow in the language Epiphany is supposed to make the model car | Proposal | Verified observations with code refs and accepting recent evidence can produce a candidate graph frontier/churn patch without writing it. The caller can provide ids, or the proposal engine can choose a small ready path cluster, then reuse existing map nodes, focus linked context, and report pressure from the actual map delta. | The clerk overlays tracing paper on the old blueprint before drawing a new wall through the plumbing, choosing only the relevant marked pages before measuring how much load the sketch would put on the frame. | | Promotion | A verifier-backed gate can reject a proposal without mutation or send it through the durable update path. | The foreman stamps the pencil draft before the clerk reaches for the red pen. | | State updates | A loaded thread can accept explicit typed patches that append observations/evidence and replace bounded map/scratch/churn fields. | The clerk finally has the red pen, but still writes only on the ledger page the form allows. | -| State notifications | Successful direct updates and accepted promotions publish the updated typed state to app-server clients, with a source label saying which control-plane door caused it. | The clerk rings the little counter bell only after the ledger ink is dry, and the bell tag says whether this was direct filing or foreman-approved promotion. | +| State notifications | Successful direct updates and accepted promotions publish the updated typed state to app-server clients, with source, revision, and changed-field labels saying which control-plane door caused it and which ledger sections changed. | The clerk rings the little counter bell only after the ledger ink is dry, and the bell tag says whether this was direct filing or foreman-approved promotion, which page number landed, and which tabs were touched. | ## Flow 1: State Shape @@ -572,8 +572,9 @@ The app-server handler: 3. maps the app-server patch DTO into `EpiphanyStateUpdate`. 4. calls `CodexThread.epiphany_update_state`. 5. returns the updated `EpiphanyThreadState`. -6. emits `thread/epiphany/stateUpdated` with `source: "update"` and the same updated state. -7. reports empty patches and revision mismatches as invalid requests before notification. +6. computes `changedFields` from the accepted patch surface. +7. emits `thread/epiphany/stateUpdated` with `source: "update"`, event-level `revision`, typed `changedFields`, and the same updated state. +8. reports empty patches and revision mismatches as invalid requests before notification. `CodexThread.epiphany_update_state`: @@ -600,7 +601,7 @@ Code refs: ### Output -The response contains the updated `epiphanyState`. The durable side effect is a rollout `EpiphanyState` snapshot. The live side effect is a `thread/epiphany/stateUpdated` notification carrying `source: "update"` plus the same updated state to app-server clients. +The response contains the updated `epiphanyState`. The durable side effect is a rollout `EpiphanyState` snapshot. The live side effect is a `thread/epiphany/stateUpdated` notification carrying `source: "update"`, event-level `revision`, typed `changedFields`, and the same updated state to app-server clients. ### Invariant @@ -827,7 +828,7 @@ The app-server handler: 6. maps the patch into `EpiphanyStateUpdate`. 7. calls `CodexThread.epiphany_update_state`. 8. returns the updated `EpiphanyThreadState`. -9. emits `thread/epiphany/stateUpdated` with `source: "promote"` only for the accepted path. +9. emits `thread/epiphany/stateUpdated` with `source: "promote"`, event-level `revision`, and typed `changedFields` only for the accepted path. `evaluate_promotion` currently enforces a deliberately small but now delta-aware policy: @@ -855,7 +856,7 @@ Code refs: ### Output -Rejected promotions return reasons and no state. Accepted promotions return the updated `epiphanyState` from the durable update path and emit `thread/epiphany/stateUpdated` with `source: "promote"` plus that state. +Rejected promotions return reasons and no state. Accepted promotions return the updated `epiphanyState` from the durable update path and emit `thread/epiphany/stateUpdated` with `source: "promote"`, event-level `revision`, typed `changedFields`, plus that state. ### Invariant @@ -889,7 +890,7 @@ stored Epiphany state -> successful writes emit thread/epiphany/stateUpdated ``` -The current remaining missing organ is not the red pen, not the first chewing motion, and not the first state-change bell. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it can auto-select a bounded evidence-backed observation cluster when ids are omitted, requires accepting recent evidence behind selected observations, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports match-kind-aware churn pressure from the actual proposal shape. Distillation now also has the first source-output-aware teeth for tool/model summaries. Promotion now notices risky deltas instead of just checking the shape of the envelope, treats graph expansion as risky even if the patch tries to mumble "low pressure" while dragging a new wall into the blueprint, and token-matches strong verifier kinds so `contest` cannot sneak past as `test`. Successful update/promote writes now emit `thread/epiphany/stateUpdated` with `source: "update"` or `source: "promote"` so clients no longer need to poll blindly or infer the cause from timing. Richer app-server smoke coverage now exists in `tools/epiphany_phase5_smoke.py`; the remaining missing organs are watcher/freshness inputs and eventually role-scoped specialist ownership without silently auto-promoting anything. +The current remaining missing organ is not the red pen, not the first chewing motion, and not the first state-change bell. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it can auto-select a bounded evidence-backed observation cluster when ids are omitted, requires accepting recent evidence behind selected observations, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports match-kind-aware churn pressure from the actual proposal shape. Distillation now also has the first source-output-aware teeth for tool/model summaries. Promotion now notices risky deltas instead of just checking the shape of the envelope, treats graph expansion as risky even if the patch tries to mumble "low pressure" while dragging a new wall into the blueprint, and token-matches strong verifier kinds so `contest` cannot sneak past as `test`. Successful update/promote writes now emit `thread/epiphany/stateUpdated` with `source: "update"` or `source: "promote"`, event-level revision, and typed changed fields so clients no longer need to poll blindly, infer the cause from timing, or diff the whole state to know which top-level sections moved. Richer app-server smoke coverage now exists in `tools/epiphany_phase5_smoke.py`; the remaining missing organs are watcher/freshness inputs and eventually role-scoped specialist ownership without silently auto-promoting anything. ```text model/tool observations @@ -901,7 +902,7 @@ model/tool observations -> live stateUpdated notification ``` -The typed write path exists, the first deterministic observation/evidence distillation path exists, source-output-aware distillation now summarizes noisy tool/model output into typed evidence with final result/error/finished lines ahead of generic warnings, the first deterministic map/churn proposal path exists, proposal-quality hardening now auto-selects bounded observation clusters when ids are omitted, requires accepting recent evidence, reuses existing graph nodes by observed code refs, prioritizes stronger selected observations, rescues unanchored graph nodes by strict semantic overlap, focuses linked frontier context, reports match-kind-aware map-delta pressure, and the first verifier-backed promotion gate exists. The first map/churn safety layer also exists: promotion can reject state replacement patches that are not tied to explicit observations/evidence, that contain structurally broken subgoal, invariant, graph, frontier, checkpoint, or churn fields, that try to promote risky churn without a warning and stronger verifier evidence, that claim a graph expansion is low-pressure to dodge the warning gate, or that rely on substring verifier-kind accidents. The first live state notification layer also exists: accepted writes publish updated typed state; rejected promotions stay quiet. +The typed write path exists, the first deterministic observation/evidence distillation path exists, source-output-aware distillation now summarizes noisy tool/model output into typed evidence with final result/error/finished lines ahead of generic warnings, the first deterministic map/churn proposal path exists, proposal-quality hardening now auto-selects bounded observation clusters when ids are omitted, requires accepting recent evidence, reuses existing graph nodes by observed code refs, prioritizes stronger selected observations, rescues unanchored graph nodes by strict semantic overlap, focuses linked frontier context, reports match-kind-aware map-delta pressure, and the first verifier-backed promotion gate exists. The first map/churn safety layer also exists: promotion can reject state replacement patches that are not tied to explicit observations/evidence, that contain structurally broken subgoal, invariant, graph, frontier, checkpoint, or churn fields, that try to promote risky churn without a warning and stronger verifier evidence, that claim a graph expansion is low-pressure to dodge the warning gate, or that rely on substring verifier-kind accidents. The first live state notification layer also exists: accepted writes publish updated typed state plus event-level revision and typed changed fields; rejected promotions stay quiet. In natural language: current Epiphany can preserve a map, show a map, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified evidence-backed observations with code refs, choose a small coherent observation set when the caller does not provide one, favor the strongest selected observation when drafting that candidate, reject or accept a verified candidate, apply explicit map/evidence/churn edits, notify clients after successful writes, and sanity-check those edits before they hit the ledger. It still does not ingest fresh tool/model output into richer map deltas automatically, invalidate stale graph regions, or coordinate specialist ownership. Teeth are installed; chewing is supervised; the first bell rings; the next slices are about taste, not jawbones. @@ -920,7 +921,7 @@ Current tests cover the landed flows at useful seams: - retrieval ranking, fallback, stale manifest detection, and mocked Qdrant/Ollama indexing in `epiphany-core`. - app-server protocol serde for `thread/epiphany/retrieve`, `thread/epiphany/index`, `thread/epiphany/distill`, `thread/epiphany/propose`, `thread/epiphany/promote`, `thread/epiphany/update`, and `thread/epiphany/stateUpdated`. - app-server mapping of core retrieval/index summaries into protocol responses. -- reusable app-server smoke for the richer Phase 5 chain in `tools/epiphany_phase5_smoke.py`, including update/promote `thread/epiphany/stateUpdated` notifications. +- reusable app-server smoke for the richer Phase 5 chain in `tools/epiphany_phase5_smoke.py`, including update/promote `thread/epiphany/stateUpdated` notifications with source, revision, and changed-field checks. Useful commands: diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index 4b976b6a..7327ec14 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -2,7 +2,7 @@ ## Status -Updated on 2026-04-25 after adding the first live typed Epiphany state notification seam on top of the richer Phase 5 app-server chain. +Updated on 2026-04-25 after hardening the first live typed Epiphany state notification seam with event-level revision and typed changed-field metadata. This note tracks Epiphany as a fork of Codex with an opinionated modeling architecture. The point is not to offer Codex another collaboration preset. The point is to make the harness force the model to carry explicit structure about the codebase, the active subgoal, the evidence trail, and the machine it is modifying. @@ -28,10 +28,10 @@ This note tracks Epiphany as a fork of Codex with an opinionated modeling archit - an expansion-freshness promotion hardening slice that prevents a graph-expansion patch from bypassing warning requirements by falsely claiming low `diff_pressure` - a token-aware verifier-kind promotion hardening slice that prevents substring accidents such as `contest` from satisfying the risky-delta `test` verifier-kind requirement - a reusable richer Phase 5 app-server smoke harness at `tools/epiphany_phase5_smoke.py` that verifies source-output distillation, read-only proposal, risky-delta rejection, expanded-low-pressure rejection, substring verifier-kind rejection, and accepted promotion through the loaded-thread seam -- a first live typed-state notification slice: experimental `thread/epiphany/stateUpdated` fires after successful direct `thread/epiphany/update` and accepted `thread/epiphany/promote`, carrying the updated `epiphanyState` and a typed `source` while rejected promotions remain silent and non-mutating +- a first live typed-state notification slice: experimental `thread/epiphany/stateUpdated` fires after successful direct `thread/epiphany/update` and accepted `thread/epiphany/promote`, carrying the updated `epiphanyState`, typed `source`, event-level `revision`, and typed `changedFields` while rejected promotions remain silent and non-mutating - current phase: post-Phase-5 live typed state/read-surface hardening -The next job is no longer to prove the retriever exists, sketch the persistent follow-up in prose, invent the first red-pen path, build the first observation proposal surface, make promotion notice broken map/churn replacements, ship the first read-only map/churn proposal surface, teach proposal to avoid duplicating already-mapped code surfaces, make proposal frontier focus follow existing graph links, make selected observations evidence-backed with non-haunted churn pressure, rescue unanchored graph nodes with strict semantic matching, let proposal choose a bounded ready observation set when ids are omitted, make proposal pressure understand match kinds, make the distiller summarize noisy source output, make source-output salience prefer final results over generic warnings, make promotion notice risky deltas, make expansion freshness resistant to low-pressure underreporting, prevent substring verifier-kind accidents, prove that richer chain over app-server by hand, or add the first live state notification after successful writes. Those parts are landed or live-smoked. The next job is to use the reusable smoke harness as a guardrail before adding the next smallest source-grounded notification/read/proposal/promotion rule, and to keep `thread/epiphany/retrieve`, `thread/epiphany/distill`, and `thread/epiphany/propose` out of the durable-writer business. +The next job is no longer to prove the retriever exists, sketch the persistent follow-up in prose, invent the first red-pen path, build the first observation proposal surface, make promotion notice broken map/churn replacements, ship the first read-only map/churn proposal surface, teach proposal to avoid duplicating already-mapped code surfaces, make proposal frontier focus follow existing graph links, make selected observations evidence-backed with non-haunted churn pressure, rescue unanchored graph nodes with strict semantic matching, let proposal choose a bounded ready observation set when ids are omitted, make proposal pressure understand match kinds, make the distiller summarize noisy source output, make source-output salience prefer final results over generic warnings, make promotion notice risky deltas, make expansion freshness resistant to low-pressure underreporting, prevent substring verifier-kind accidents, prove that richer chain over app-server by hand, add the first live state notification after successful writes, or make that notification say which revision and state sections changed. Those parts are landed or live-smoked. The next job is to use the reusable smoke harness as a guardrail before adding the next smallest source-grounded notification/read/proposal/promotion rule, and to keep `thread/epiphany/retrieve`, `thread/epiphany/distill`, and `thread/epiphany/propose` out of the durable-writer business. ## Summary @@ -241,8 +241,10 @@ Current bounded shape of that follow-up: - it carries `threadId` plus the full updated `epiphanyState` - it carries `source: "update"` after successful direct `thread/epiphany/update` - it carries `source: "promote"` after accepted `thread/epiphany/promote` +- it carries event-level `revision` so clients can sequence updates without cracking open the full state first +- it carries typed `changedFields` derived from the accepted patch surface so clients can react to top-level state-section changes without diffing the entire `epiphanyState` - it does not fire for rejected promotions, because rejection still does not mutate state -- the richer app-server smoke now asserts direct update publishes source/revision `update`/`1` and accepted promotion publishes source/revision `promote`/`2` +- the richer app-server smoke now asserts direct update publishes source/revision/changed-fields `update`/`1`/`observations,evidence` and accepted promotion publishes source/revision/changed-fields `promote`/`2`/`graphs,graphFrontier,observations,evidence,churn` Follow-up addition now landed after the update surface: @@ -294,7 +296,7 @@ Add tests for: ## Immediate Next Step -Treat the current retrieval baseline and explicit indexing follow-up as landed Phase 4. Treat the explicit update path, distillation proposal path, read-only map/churn proposal path, verifier-backed promotion gate, structural map/churn promotion validation, graph-node reuse, linked frontier focus, evidence-backed selection, map-delta churn pressure, selected-observation prioritization, strict unanchored-node semantic reuse, automatic bounded observation-set selection, match-kind-aware map-delta judgment, source-output-aware observation distillation, source-output salience ranking, risky-delta promotion policy, expansion-freshness promotion hardening, token-aware verifier-kind promotion hardening, reusable richer app-server smoke harness, and first sourced `thread/epiphany/stateUpdated` notification seam as the active baseline. The next machine gap is no longer "can Epiphany retrieve code?", "can it propose one durable observation patch?", "can it draft one bounded map/churn candidate?", "can it avoid duplicating an existing graph node when an observation points at already-mapped code?", "can it focus linked graph context?", "can it reject selected observations that are not backed by accepting recent evidence?", "can it use graph language when no concrete refs exist yet?", "can it choose a proposal-ready observation set when ids are omitted?", "can it distinguish exact refinement from same-path broadening or semantic anchoring?", "can it summarize noisy tool/model output as typed evidence?", "can it rank final result/error/finished lines above generic warnings?", "can promotion policy reason over richer deltas without silently writing them?", "can expansion freshness resist low-pressure underreporting?", "can verifier kind matching avoid substring accidents?", "does the richer Phase 5 chain behave through the app-server seam?", or "can successful writes notify a client what changed state and why?" It can. +Treat the current retrieval baseline and explicit indexing follow-up as landed Phase 4. Treat the explicit update path, distillation proposal path, read-only map/churn proposal path, verifier-backed promotion gate, structural map/churn promotion validation, graph-node reuse, linked frontier focus, evidence-backed selection, map-delta churn pressure, selected-observation prioritization, strict unanchored-node semantic reuse, automatic bounded observation-set selection, match-kind-aware map-delta judgment, source-output-aware observation distillation, source-output salience ranking, risky-delta promotion policy, expansion-freshness promotion hardening, token-aware verifier-kind promotion hardening, reusable richer app-server smoke harness, and sourced/revisioned/changed-field `thread/epiphany/stateUpdated` notification seam as the active baseline. The next machine gap is no longer "can Epiphany retrieve code?", "can it propose one durable observation patch?", "can it draft one bounded map/churn candidate?", "can it avoid duplicating an existing graph node when an observation points at already-mapped code?", "can it focus linked graph context?", "can it reject selected observations that are not backed by accepting recent evidence?", "can it use graph language when no concrete refs exist yet?", "can it choose a proposal-ready observation set when ids are omitted?", "can it distinguish exact refinement from same-path broadening or semantic anchoring?", "can it summarize noisy tool/model output as typed evidence?", "can it rank final result/error/finished lines above generic warnings?", "can promotion policy reason over richer deltas without silently writing them?", "can expansion freshness resist low-pressure underreporting?", "can verifier kind matching avoid substring accidents?", "does the richer Phase 5 chain behave through the app-server seam?", or "can successful writes notify a client what changed state and why?" It can. 1. treat the current verified-and-landed query-time hybrid retriever as the Phase 4 slice 1 baseline 2. treat the verified and live-smoked `thread/epiphany/index` slice as the bounded persistent-semantic follow-up diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 5cc05887..fe99faf1 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -33,6 +33,7 @@ Recent anchor commits: - `fbd4dc2` - added repo-local app-server test stack configuration after tracing Windows stack pressure - `cabad31` - added experimental `thread/epiphany/stateUpdated` notifications after successful update/promote writes - `4c770e9` - labeled Epiphany state update notifications with typed `source` values +- latest slice - hardened `thread/epiphany/stateUpdated` with event-level `revision` and typed `changedFields` Do not trust this note for the exact current HEAD; use `git log --oneline -1` if you need the live commit. @@ -87,14 +88,14 @@ Current verified typed state/promotion layer: - experimental loaded-thread-only `thread/epiphany/distill` - experimental loaded-thread-only `thread/epiphany/promote` - experimental loaded-thread-only `thread/epiphany/propose` -- experimental `thread/epiphany/stateUpdated` notifications after successful direct updates and accepted promotions, with typed `source` values of `update` or `promote` +- experimental `thread/epiphany/stateUpdated` notifications after successful direct updates and accepted promotions, with typed `source` values of `update` or `promote`, event-level `revision`, and typed `changedFields` - typed patch shape for observations, evidence, objective, subgoals, invariants, graphs, graph frontier/checkpoint, scratch, churn, and mode - deterministic observation/evidence proposal generation in `epiphany-core` - observation distillation now keeps noisy tool/command/shell/model output bounded by selecting salient output lines, prioritizing final result/failure/error/finished lines above generic warnings, and defaulting evidence kind to `tool-output` or `model-output` when the caller did not provide one - deterministic map/churn proposal generation in `epiphany-core`, now with first-pass graph-node reuse by exact code-ref, same-path overlap, or deterministic path-node id before new candidate nodes are created, strict semantic reuse for unanchored architecture nodes, linked frontier focus through graph links and named incident edges, evidence-backed selected observations via accepting `recent_evidence`, selected-observation prioritization, automatic bounded observation selection when ids are omitted, and match-kind-aware churn pressure derived from exact-ref refinement, same-path broadening, deterministic-node reuse, semantic anchoring, or new candidate surfaces - verifier-backed promotion policy evaluation in `epiphany-core`, now rejecting risky churn deltas unless they carry explicit warning rationale and strong verifier evidence kind; graph expansion is risky even if a patch understates `diff_pressure`, and strong verifier kinds are token-matched instead of substring-matched - core `CodexThread.epiphany_update_state(...)` applies the patch to live `SessionState`, bumps revision, persists `RolloutItem::EpiphanyState`, and flushes rollout -- app-server now emits `thread/epiphany/stateUpdated` after the direct update path and after accepted promotion return an updated state; notifications identify their `source`, and rejected promotions still do not mutate state or emit an update notification +- app-server now emits `thread/epiphany/stateUpdated` after the direct update path and after accepted promotion return an updated state; notifications identify their `source`, expose event-level `revision`, list typed `changedFields`, and rejected promotions still do not mutate state or emit an update notification - replay helpers in both `codex-core` and `epiphany-core` accept an out-of-band Epiphany snapshot before the first real user turn so seed/update snapshots survive resume - retrieval remains read-only and indexing remains the only persistent semantic write path @@ -203,7 +204,7 @@ What is wired right now: - `thread/epiphany/promote` is the first explicit promotion gate; it rejects failed verifier evidence without mutation and applies accepted candidates through the update path - `thread/epiphany/promote` now has a first richer map/churn safety layer: state replacement patches must carry explicit observations and patch evidence, and subgoal/invariant/graph/frontier/checkpoint/churn structure is validated before the durable update path is called - `thread/epiphany/update` is the first explicit durable Epiphany state write surface; it rejects empty patches, supports optional revision matching, appends observations/evidence, replaces bounded typed fields, increments state revision, and persists immediately -- `thread/epiphany/stateUpdated` is the first live typed-state notification surface; it fires only after successful `thread/epiphany/update` and accepted `thread/epiphany/promote`, carrying the full updated `epiphanyState` plus `source: "update"` or `source: "promote"` +- `thread/epiphany/stateUpdated` is the first live typed-state notification surface; it fires only after successful `thread/epiphany/update` and accepted `thread/epiphany/promote`, carrying the full updated `epiphanyState`, event-level `revision`, typed `changedFields`, plus `source: "update"` or `source: "promote"` - live hydrated `thread.epiphanyState` reads now backfill a lightweight retrieval summary when the thread already has Epiphany state but no persisted retrieval summary yet - basic protocol/core/app-server unit tests were added for the new types and mapping layer - the current pushed baseline additionally wires: @@ -360,17 +361,17 @@ If a future session wakes up from compaction and starts bluffing, this is the pa - persistent semantic indexing now exists behind explicit `thread/epiphany/index` - Phase 5 semantic distillation/promotion is active: - dedicated experimental `thread/epiphany/update`, `thread/epiphany/distill`, and `thread/epiphany/promote` exist and are live-smoked - - experimental `thread/epiphany/stateUpdated` now exists and is live-smoked for both direct update and accepted promote paths, including source labels + - experimental `thread/epiphany/stateUpdated` now exists and is live-smoked for both direct update and accepted promote paths, including source labels, event-level revision, and typed changed fields - structural map/churn promotion validation is landed in `epiphany-core` - proposal selection now rejects observations without accepting `recent_evidence`, and churn pressure now reflects match-kind-aware map-delta shape - proposal can now omit `observationIds` and let `epiphany-core` choose a bounded coherent path cluster from existing verified, evidence-backed observations using source/evidence quality plus graph-frontier focus - proposal churn now distinguishes exact-ref refinements from same-path broadening, deterministic-node reuse, semantic unanchored-node reuse, and new candidate surfaces before setting graph freshness and diff pressure - distillation now summarizes noisy tool/command/shell/model output into salient typed evidence rather than preserving raw output sludge as the summary, and final result/failure/error/finished lines outrank generic warnings unless warning is the only central signal - promotion now rejects medium/high/expanded/broadening/semantic/update churn deltas that lack a warning, carry weak generic verifier evidence, or rely on substring verifier-kind accidents such as `contest` - - richer Phase 5 app-server smoke is now reproducible through `tools/epiphany_phase5_smoke.py`; it verifies `shell-tool` distillation to `tool-output`, read-only proposal, update notification source/revision `update`/1, risky-churn rejection without warning, expanded-low-pressure rejection, substring verifier-kind rejection, weak verifier-kind rejection, accepted promotion only with warning plus strong verifier evidence, and promote notification source/revision `promote`/2 + - richer Phase 5 app-server smoke is now reproducible through `tools/epiphany_phase5_smoke.py`; it verifies `shell-tool` distillation to `tool-output`, read-only proposal, update notification source/revision/changed-fields `update`/1/`observations,evidence`, risky-churn rejection without warning, expanded-low-pressure rejection, substring verifier-kind rejection, weak verifier-kind rejection, accepted promotion only with warning plus strong verifier evidence, and promote notification source/revision/changed-fields `promote`/2/`graphs,graphFrontier,observations,evidence,churn` - the Epiphany algorithmic map has been semantically audited flow-by-flow against the cited code; stale line anchors were fixed, and the current typed spine still describes a coherent machine - the next bounded follow-up should use the richer smoke harness as a guardrail before the next source-grounded proposal/promotion hardening slice -- There is now a first live `thread/epiphany/stateUpdated` notification seam for successful update/promote writes; there is still no broader watcher/job/specialist event stream. +- There is now a first live `thread/epiphany/stateUpdated` notification seam for successful update/promote writes; it includes source, revision, and changed-field metadata, but there is still no broader watcher/job/specialist event stream. - The next phase is **not** GUI work. - The current pushed baseline also extracted most Epiphany-owned implementation into `epiphany-core`: - prompt rendering, rollout replay, and retrieval/indexing logic are now repo-owned @@ -517,4 +518,4 @@ cmd /c "\"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxi The repo is in a good state. -Phase 1, Phase 2, the minimal Phase 3 typed read surface, Phase 4 retrieval/indexing/core-extraction, and the current Phase 5 distill/propose/promote/update baseline are landed and verified. `vendor/codex` is first-class in the parent repo now, with the heavier Epiphany organs living in `epiphany-core`. Successful direct updates and accepted promotions now emit experimental `thread/epiphany/stateUpdated` notifications carrying the updated typed state and source labels. Read-only distillation now summarizes noisy tool/model output into typed evidence and ranks final result/error/finished lines above generic warnings, read-only proposal can auto-select a bounded evidence-backed observation cluster and report match-kind-aware map-delta pressure, promotion rejects risky deltas without warning plus strong verifier evidence, expansion freshness cannot bypass warning requirements by claiming low pressure, and verifier kinds are token-matched so `contest` does not pass as `test`. The current Epiphany algorithmic map has been source-audited against the code flow it describes. The richer Phase 5 chain is now live-smoked through app-server by `tools/epiphany_phase5_smoke.py`, including update notification source/revision `update`/1, expanded-low-pressure and substring-verifier rejection checks, accepted promotion, and promote notification source/revision `promote`/2. The next clean move is the next bounded live typed-state/read-surface hardening slice, with that smoke harness as a guardrail; not GUI paint, watcher magic, or re-proving retrieval. Pre-compaction persistence is now an explicit design rule, not a lucky habit. +Phase 1, Phase 2, the minimal Phase 3 typed read surface, Phase 4 retrieval/indexing/core-extraction, and the current Phase 5 distill/propose/promote/update baseline are landed and verified. `vendor/codex` is first-class in the parent repo now, with the heavier Epiphany organs living in `epiphany-core`. Successful direct updates and accepted promotions now emit experimental `thread/epiphany/stateUpdated` notifications carrying the updated typed state, source labels, event-level revision, and typed changed fields. Read-only distillation now summarizes noisy tool/model output into typed evidence and ranks final result/error/finished lines above generic warnings, read-only proposal can auto-select a bounded evidence-backed observation cluster and report match-kind-aware map-delta pressure, promotion rejects risky deltas without warning plus strong verifier evidence, expansion freshness cannot bypass warning requirements by claiming low pressure, and verifier kinds are token-matched so `contest` does not pass as `test`. The current Epiphany algorithmic map has been source-audited against the code flow it describes. The richer Phase 5 chain is now live-smoked through app-server by `tools/epiphany_phase5_smoke.py`, including update notification source/revision/changed-fields `update`/1/`observations,evidence`, expanded-low-pressure and substring-verifier rejection checks, accepted promotion, and promote notification source/revision/changed-fields `promote`/2/`graphs,graphFrontier,observations,evidence,churn`. The next clean move is the next bounded live typed-state/read-surface hardening slice, with that smoke harness as a guardrail; not GUI paint, watcher magic, or re-proving retrieval. Pre-compaction persistence is now an explicit design rule, not a lucky habit. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index c5934c20..f50d7f1c 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -137,3 +137,5 @@ {"ts": "2026-04-25T04:56:41+00:00", "type": "verification", "status": "ok", "note": "Verified sourced stateUpdated notifications with py_compile for tools/epiphany_phase5_smoke.py, cargo fmt for codex-app-server-protocol/app-server, targeted cargo test -p codex-app-server-protocol --lib thread_epiphany, targeted cargo test -p codex-app-server --lib map_epiphany_, regenerated stable app-server protocol schema fixtures, full cargo test -p codex-app-server-protocol, rebuilt codex-app-server, and reran tools/epiphany_phase5_smoke.py. Smoke observed updateNotificationSource=update with revision 1 and promoteNotificationSource=promote with revision 2."} {"ts": "2026-04-25T04:56:56+00:00", "type": "implementation", "status": "ok", "note": "Hardened the live Epiphany state notification contract. thread/epiphany/stateUpdated now carries a typed source field: update for direct thread/epiphany/update writes and promote for accepted thread/epiphany/promote writes; rejected promotions remain non-mutating and silent."} {"ts": "2026-04-25T05:00:55+00:00", "type": "verification", "status": "ok", "note": "Verified Compact-Rehydrate-Reorient-Continue continuity after sourced stateUpdated notifications. Branch main is clean/synced at 4c770e9, state/map.yaml, fresh-workspace-handoff, implementation plan, algorithmic map, and evidence all agree on the current baseline: thread/epiphany/stateUpdated emits typed source values update/promote with revisions 1/2 in smoke. Next action remains the next bounded live typed-state/read-surface hardening slice; keep retrieval/distill/propose read-only and avoid GUI, watcher, or specialist-agent machinery."} +{"ts": "2026-04-25T05:24:57+00:00", "type": "implementation", "status": "ok", "note": "Hardened the live Epiphany stateUpdated notification contract. thread/epiphany/stateUpdated now carries event-level revision and typed changedFields derived from the accepted update/promote patch surface, while rejected promotions remain non-mutating and silent."} +{"ts": "2026-04-25T05:25:05+00:00", "type": "verification", "status": "ok", "note": "Verified revisioned changed-field stateUpdated notifications with py_compile for tools/epiphany_phase5_smoke.py, cargo fmt for codex-app-server-protocol/app-server, targeted cargo test -p codex-app-server-protocol --lib thread_epiphany, targeted cargo test -p codex-app-server --lib epiphany_update_patch_changed_fields, regenerated stable app-server protocol schema fixtures, full cargo test -p codex-app-server-protocol, rebuilt codex-app-server, and reran tools/epiphany_phase5_smoke.py. Smoke observed update changedFields observations/evidence at revision 1 and promote changedFields graphs/graphFrontier/observations/evidence/churn at revision 2."} diff --git a/state/map.yaml b/state/map.yaml index 3cbaf20e..a3ba8aa4 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -108,7 +108,7 @@ accepted_design: - harden source-output distillation salience ranking incrementally: final test result, failure, error, exit-code, passed, and finished lines outrank generic warnings when compressing noisy tool output, while warning-only output still preserves the warning as the central signal - harden promotion policy incrementally: `thread/epiphany/promote` should reject risky churn deltas unless they carry explicit warning rationale and strong verifier evidence kind, treating graph expansion, broadening, semantic anchoring, updates, and medium/high pressure as risky even when a patch tries to understate pressure; strong verifier kinds are token-matched so substring accidents such as `contest` do not satisfy `test` - live-smoke the richer Phase 5 app-server chain with a reusable repo-local harness: source-output distillation produced `tool-output` with final result lines ahead of generic warnings, proposal stayed read-only, risky churn rejected without warning, expansion-with-low-pressure churn rejected without warning, risky churn rejected with weak or substring verifier evidence, and accepted risky churn persisted only with warning plus strong verifier evidence - - harden the first live Epiphany state notification seam: experimental `thread/epiphany/stateUpdated` publishes the updated typed state after successful direct `thread/epiphany/update` and accepted `thread/epiphany/promote`, includes a typed `source` of `update` or `promote`, while rejected promotions still do not mutate state or emit update notifications + - harden the first live Epiphany state notification seam: experimental `thread/epiphany/stateUpdated` publishes the updated typed state after successful direct `thread/epiphany/update` and accepted `thread/epiphany/promote`, includes a typed `source` of `update` or `promote`, event-level `revision`, and typed `changedFields`, while rejected promotions still do not mutate state or emit update notifications rejected_paths: - name: pinned_map_only @@ -125,5 +125,5 @@ rejected_paths: reason: audited candidates were either terminal-only, stale/aspirational, or incoherent wrappers; the comparison clones were removed from vendor to avoid building on untrusted drift current_status: - summary: Workspace initialized with protocol files, typed state, handoff notes, architecture maps, and a rolling implementation plan against vendored Codex. Phase 1 durable state, Phase 2 prompt integration, the minimal Phase 3 typed read surface, and Phase 4 retrieval/indexing/core-extraction are landed on `main`. Phase 5 semantic distillation/promotion/proposal is now active and chewed enough for the first live typed-state stream seam: the current baseline includes explicit Qdrant indexing with BM25 fallback, typed `thread/epiphany/update`, read-only `thread/epiphany/distill`, read-only `thread/epiphany/propose`, verifier-backed `thread/epiphany/promote`, experimental `thread/epiphany/stateUpdated` notifications after successful direct updates and accepted promotions with typed `source` values of `update` or `promote`, structural map/churn promotion validation, source-output-aware distillation with result/error/finished lines prioritized over generic warnings, graph-node reuse, linked frontier focus, evidence-backed proposal selection, selected-observation prioritization, strict unanchored-node semantic reuse, automatic bounded observation selection, match-kind-aware map-delta churn pressure, risky-delta promotion policy in `epiphany-core`, expansion-freshness promotion hardening so new map surfaces cannot bypass warning requirements by claiming low pressure, token-aware verifier-kind matching so substring accidents cannot satisfy strong verifier evidence, a reusable richer Phase 5 app-server smoke harness, and thin vendored Codex host seams. The richer smoke now verifies source-output distillation, read-only proposal, update notification source/revision `update`/1, risky-delta rejection, expanded-low-pressure rejection, weak-verifier rejection, substring-verifier rejection, accepted promotion, and promote notification source/revision `promote`/2 through the app-server seam. The Windows app-server stack-pressure regression was narrowed to test-thread stack size; repo-local Cargo config sets `RUST_MIN_STACK=67108864`, and boxed-future request-dispatch experiments were removed after config-only verification passed. The repo also has source-available/commercial licensing plus a CLA gate for external contributions. + summary: Workspace initialized with protocol files, typed state, handoff notes, architecture maps, and a rolling implementation plan against vendored Codex. Phase 1 durable state, Phase 2 prompt integration, the minimal Phase 3 typed read surface, and Phase 4 retrieval/indexing/core-extraction are landed on `main`. Phase 5 semantic distillation/promotion/proposal is now active and chewed enough for the first live typed-state stream seam: the current baseline includes explicit Qdrant indexing with BM25 fallback, typed `thread/epiphany/update`, read-only `thread/epiphany/distill`, read-only `thread/epiphany/propose`, verifier-backed `thread/epiphany/promote`, experimental `thread/epiphany/stateUpdated` notifications after successful direct updates and accepted promotions with typed `source` values of `update` or `promote`, event-level `revision`, typed `changedFields`, structural map/churn promotion validation, source-output-aware distillation with result/error/finished lines prioritized over generic warnings, graph-node reuse, linked frontier focus, evidence-backed proposal selection, selected-observation prioritization, strict unanchored-node semantic reuse, automatic bounded observation selection, match-kind-aware map-delta churn pressure, risky-delta promotion policy in `epiphany-core`, expansion-freshness promotion hardening so new map surfaces cannot bypass warning requirements by claiming low pressure, token-aware verifier-kind matching so substring accidents cannot satisfy strong verifier evidence, a reusable richer Phase 5 app-server smoke harness, and thin vendored Codex host seams. The richer smoke now verifies source-output distillation, read-only proposal, update notification source/revision/changed-fields `update`/1/`observations,evidence`, risky-delta rejection, expanded-low-pressure rejection, weak-verifier rejection, substring-verifier rejection, accepted promotion, and promote notification source/revision/changed-fields `promote`/2/`graphs,graphFrontier,observations,evidence,churn` through the app-server seam. The Windows app-server stack-pressure regression was narrowed to test-thread stack size; repo-local Cargo config sets `RUST_MIN_STACK=67108864`, and boxed-future request-dispatch experiments were removed after config-only verification passed. The repo also has source-available/commercial licensing plus a CLA gate for external contributions. next_action: Continue the next bounded live typed-state/read-surface hardening slice from the now-notified update/promote seam; keep retrieval/distillation/proposal read-only, keep accepted writes routed through promotion/update, and avoid GUI, watcher, or specialist-agent machinery. diff --git a/tools/epiphany_phase5_smoke.py b/tools/epiphany_phase5_smoke.py index 8e9d5b25..ef3042e1 100644 --- a/tools/epiphany_phase5_smoke.py +++ b/tools/epiphany_phase5_smoke.py @@ -296,6 +296,14 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: update_notification["params"]["epiphanyState"]["revision"] == 1, "update notification should publish revision 1", ) + require( + update_notification["params"]["revision"] == 1, + "update notification should expose revision at the event level", + ) + require( + update_notification["params"]["changedFields"] == ["observations", "evidence"], + "update notification should identify appended observation/evidence fields", + ) propose = client.send("thread/epiphany/propose", {"threadId": thread_id}) assert propose is not None @@ -477,6 +485,15 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: promote_notification["params"]["epiphanyState"]["revision"] == 2, "promote notification should publish revision 2", ) + require( + promote_notification["params"]["revision"] == 2, + "promote notification should expose revision at the event level", + ) + require( + promote_notification["params"]["changedFields"] + == ["graphs", "graphFrontier", "observations", "evidence", "churn"], + "promote notification should identify promoted graph/evidence/churn fields", + ) require( final_state["churn"]["diff_pressure"] == "medium", "accepted state should preserve risky churn pressure", @@ -498,9 +515,8 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "distillEvidenceKind": evidence["kind"], "distillSummary": evidence["summary"], "updateNotificationSource": update_notification["params"]["source"], - "updateNotificationRevision": update_notification["params"]["epiphanyState"][ - "revision" - ], + "updateNotificationRevision": update_notification["params"]["revision"], + "updateNotificationChangedFields": update_notification["params"]["changedFields"], "proposalExpectedRevision": propose["expectedRevision"], "proposalObservationId": proposal_patch["observations"][0]["id"], "proposalChurn": proposal_patch["churn"], @@ -516,8 +532,9 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "accepted": accepted["accepted"], "finalRevision": final_state["revision"], "promoteNotificationSource": promote_notification["params"]["source"], - "promoteNotificationRevision": promote_notification["params"]["epiphanyState"][ - "revision" + "promoteNotificationRevision": promote_notification["params"]["revision"], + "promoteNotificationChangedFields": promote_notification["params"][ + "changedFields" ], "finalChurn": final_state["churn"], "graphNodeCount": len( diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 94df07ca..ab1e1b1d 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3712,11 +3712,39 @@ ], "type": "object" }, + "ThreadEpiphanyStateUpdatedField": { + "enum": [ + "objective", + "activeSubgoalId", + "subgoals", + "invariants", + "graphs", + "graphFrontier", + "graphCheckpoint", + "scratch", + "observations", + "evidence", + "churn", + "mode" + ], + "type": "string" + }, "ThreadEpiphanyStateUpdatedNotification": { "properties": { + "changedFields": { + "items": { + "$ref": "#/definitions/ThreadEpiphanyStateUpdatedField" + }, + "type": "array" + }, "epiphanyState": { "$ref": "#/definitions/EpiphanyThreadState" }, + "revision": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, "source": { "$ref": "#/definitions/ThreadEpiphanyStateUpdatedSource" }, @@ -3725,7 +3753,9 @@ } }, "required": [ + "changedFields", "epiphanyState", + "revision", "source", "threadId" ], diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 3974e2c5..e733df5c 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -14966,12 +14966,40 @@ ], "type": "object" }, + "ThreadEpiphanyStateUpdatedField": { + "enum": [ + "objective", + "activeSubgoalId", + "subgoals", + "invariants", + "graphs", + "graphFrontier", + "graphCheckpoint", + "scratch", + "observations", + "evidence", + "churn", + "mode" + ], + "type": "string" + }, "ThreadEpiphanyStateUpdatedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "changedFields": { + "items": { + "$ref": "#/definitions/v2/ThreadEpiphanyStateUpdatedField" + }, + "type": "array" + }, "epiphanyState": { "$ref": "#/definitions/v2/EpiphanyThreadState" }, + "revision": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, "source": { "$ref": "#/definitions/v2/ThreadEpiphanyStateUpdatedSource" }, @@ -14980,7 +15008,9 @@ } }, "required": [ + "changedFields", "epiphanyState", + "revision", "source", "threadId" ], diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index be4960b1..67374656 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -12853,12 +12853,40 @@ ], "type": "object" }, + "ThreadEpiphanyStateUpdatedField": { + "enum": [ + "objective", + "activeSubgoalId", + "subgoals", + "invariants", + "graphs", + "graphFrontier", + "graphCheckpoint", + "scratch", + "observations", + "evidence", + "churn", + "mode" + ], + "type": "string" + }, "ThreadEpiphanyStateUpdatedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "changedFields": { + "items": { + "$ref": "#/definitions/ThreadEpiphanyStateUpdatedField" + }, + "type": "array" + }, "epiphanyState": { "$ref": "#/definitions/EpiphanyThreadState" }, + "revision": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, "source": { "$ref": "#/definitions/ThreadEpiphanyStateUpdatedSource" }, @@ -12867,7 +12895,9 @@ } }, "required": [ + "changedFields", "epiphanyState", + "revision", "source", "threadId" ], diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json index eab68010..3d7efa0e 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadEpiphanyStateUpdatedNotification.json @@ -705,6 +705,23 @@ ], "type": "string" }, + "ThreadEpiphanyStateUpdatedField": { + "enum": [ + "objective", + "activeSubgoalId", + "subgoals", + "invariants", + "graphs", + "graphFrontier", + "graphCheckpoint", + "scratch", + "observations", + "evidence", + "churn", + "mode" + ], + "type": "string" + }, "ThreadEpiphanyStateUpdatedSource": { "enum": [ "update", @@ -714,9 +731,20 @@ } }, "properties": { + "changedFields": { + "items": { + "$ref": "#/definitions/ThreadEpiphanyStateUpdatedField" + }, + "type": "array" + }, "epiphanyState": { "$ref": "#/definitions/EpiphanyThreadState" }, + "revision": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, "source": { "$ref": "#/definitions/ThreadEpiphanyStateUpdatedSource" }, @@ -725,7 +753,9 @@ } }, "required": [ + "changedFields", "epiphanyState", + "revision", "source", "threadId" ], diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts new file mode 100644 index 00000000..dd2052ff --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedField.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadEpiphanyStateUpdatedField = "objective" | "activeSubgoalId" | "subgoals" | "invariants" | "graphs" | "graphFrontier" | "graphCheckpoint" | "scratch" | "observations" | "evidence" | "churn" | "mode"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedNotification.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedNotification.ts index f58d1cd2..a6dd67c6 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedNotification.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyStateUpdatedNotification.ts @@ -2,6 +2,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { EpiphanyThreadState } from "../EpiphanyThreadState"; +import type { ThreadEpiphanyStateUpdatedField } from "./ThreadEpiphanyStateUpdatedField"; import type { ThreadEpiphanyStateUpdatedSource } from "./ThreadEpiphanyStateUpdatedSource"; -export type ThreadEpiphanyStateUpdatedNotification = { threadId: string, source: ThreadEpiphanyStateUpdatedSource, epiphanyState: EpiphanyThreadState, }; +export type ThreadEpiphanyStateUpdatedNotification = { threadId: string, source: ThreadEpiphanyStateUpdatedSource, revision: bigint, changedFields: Array, epiphanyState: EpiphanyThreadState, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 71c4b71e..2cdb6358 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -321,6 +321,7 @@ export type { ThreadEpiphanyRetrieveIndexSummary } from "./ThreadEpiphanyRetriev export type { ThreadEpiphanyRetrieveResult } from "./ThreadEpiphanyRetrieveResult"; export type { ThreadEpiphanyRetrieveResultKind } from "./ThreadEpiphanyRetrieveResultKind"; export type { ThreadEpiphanyRetrieveShardSummary } from "./ThreadEpiphanyRetrieveShardSummary"; +export type { ThreadEpiphanyStateUpdatedField } from "./ThreadEpiphanyStateUpdatedField"; export type { ThreadEpiphanyStateUpdatedNotification } from "./ThreadEpiphanyStateUpdatedNotification"; export type { ThreadEpiphanyStateUpdatedSource } from "./ThreadEpiphanyStateUpdatedSource"; export type { ThreadEpiphanyUpdatePatch } from "./ThreadEpiphanyUpdatePatch"; diff --git a/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs b/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs index daf43afb..ab07649c 100644 --- a/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1889,6 +1889,11 @@ mod tests { v2::ThreadEpiphanyStateUpdatedNotification { thread_id: "thr_123".to_string(), source: v2::ThreadEpiphanyStateUpdatedSource::Update, + revision: 7, + changed_fields: vec![ + v2::ThreadEpiphanyStateUpdatedField::Objective, + v2::ThreadEpiphanyStateUpdatedField::Evidence, + ], epiphany_state: codex_protocol::protocol::EpiphanyThreadState { revision: 7, objective: Some("Keep the map live".to_string()), @@ -1902,6 +1907,8 @@ mod tests { "params": { "threadId": "thr_123", "source": "update", + "revision": 7, + "changedFields": ["objective", "evidence"], "epiphanyState": { "revision": 7, "objective": "Keep the map live" @@ -2515,6 +2522,8 @@ mod tests { v2::ThreadEpiphanyStateUpdatedNotification { thread_id: "thr_123".to_string(), source: v2::ThreadEpiphanyStateUpdatedSource::Promote, + revision: 0, + changed_fields: vec![v2::ThreadEpiphanyStateUpdatedField::Churn], epiphany_state: codex_protocol::protocol::EpiphanyThreadState::default(), }, ); diff --git a/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs b/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs index da0d42e9..45827cf0 100644 --- a/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4059,6 +4059,8 @@ pub struct ThreadEpiphanyUpdateResponse { pub struct ThreadEpiphanyStateUpdatedNotification { pub thread_id: String, pub source: ThreadEpiphanyStateUpdatedSource, + pub revision: u64, + pub changed_fields: Vec, pub epiphany_state: CoreEpiphanyThreadState, } @@ -4070,6 +4072,24 @@ pub enum ThreadEpiphanyStateUpdatedSource { Promote, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub enum ThreadEpiphanyStateUpdatedField { + Objective, + ActiveSubgoalId, + Subgoals, + Invariants, + Graphs, + GraphFrontier, + GraphCheckpoint, + Scratch, + Observations, + Evidence, + Churn, + Mode, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs index 5f98a503..909760b5 100644 --- a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs +++ b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs @@ -160,6 +160,7 @@ use codex_app_server_protocol::ThreadEpiphanyRetrieveResponse; use codex_app_server_protocol::ThreadEpiphanyRetrieveResult; use codex_app_server_protocol::ThreadEpiphanyRetrieveResultKind; use codex_app_server_protocol::ThreadEpiphanyRetrieveShardSummary; +use codex_app_server_protocol::ThreadEpiphanyStateUpdatedField; use codex_app_server_protocol::ThreadEpiphanyStateUpdatedNotification; use codex_app_server_protocol::ThreadEpiphanyStateUpdatedSource; use codex_app_server_protocol::ThreadEpiphanyUpdateParams; @@ -4375,6 +4376,7 @@ impl CodexMessageProcessor { return; } + let changed_fields = epiphany_update_patch_changed_fields(&patch); let mut evidence = patch.evidence; evidence.push(verifier_evidence); let update = EpiphanyStateUpdate { @@ -4419,6 +4421,8 @@ impl CodexMessageProcessor { ThreadEpiphanyStateUpdatedNotification { thread_id: thread_uuid.to_string(), source: ThreadEpiphanyStateUpdatedSource::Promote, + revision: epiphany_state.revision, + changed_fields, epiphany_state, }, )) @@ -4457,6 +4461,7 @@ impl CodexMessageProcessor { } }; + let changed_fields = epiphany_update_patch_changed_fields(&patch); let update = EpiphanyStateUpdate { expected_revision, objective: patch.objective, @@ -4497,6 +4502,8 @@ impl CodexMessageProcessor { ThreadEpiphanyStateUpdatedNotification { thread_id: thread_uuid.to_string(), source: ThreadEpiphanyStateUpdatedSource::Update, + revision: epiphany_state.revision, + changed_fields, epiphany_state, }, )) @@ -10499,6 +10506,49 @@ fn map_epiphany_retrieve_result( } } +fn epiphany_update_patch_changed_fields( + patch: &ThreadEpiphanyUpdatePatch, +) -> Vec { + let mut fields = Vec::new(); + if patch.objective.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Objective); + } + if patch.active_subgoal_id.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::ActiveSubgoalId); + } + if patch.subgoals.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Subgoals); + } + if patch.invariants.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Invariants); + } + if patch.graphs.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Graphs); + } + if patch.graph_frontier.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::GraphFrontier); + } + if patch.graph_checkpoint.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::GraphCheckpoint); + } + if patch.scratch.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Scratch); + } + if !patch.observations.is_empty() { + fields.push(ThreadEpiphanyStateUpdatedField::Observations); + } + if !patch.evidence.is_empty() { + fields.push(ThreadEpiphanyStateUpdatedField::Evidence); + } + if patch.churn.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Churn); + } + if patch.mode.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::Mode); + } + fields +} + pub(crate) fn summary_to_thread( summary: ConversationSummary, fallback_cwd: &AbsolutePathBuf, @@ -11754,6 +11804,31 @@ mod tests { Ok(()) } + #[test] + fn epiphany_update_patch_changed_fields_reports_patch_surface() { + let fields = epiphany_update_patch_changed_fields(&ThreadEpiphanyUpdatePatch { + objective: Some("Keep the event readable".to_string()), + graphs: Some(Default::default()), + graph_frontier: Some(Default::default()), + observations: vec![Default::default()], + evidence: vec![Default::default()], + churn: Some(Default::default()), + ..Default::default() + }); + + assert_eq!( + fields, + vec![ + ThreadEpiphanyStateUpdatedField::Objective, + ThreadEpiphanyStateUpdatedField::Graphs, + ThreadEpiphanyStateUpdatedField::GraphFrontier, + ThreadEpiphanyStateUpdatedField::Observations, + ThreadEpiphanyStateUpdatedField::Evidence, + ThreadEpiphanyStateUpdatedField::Churn, + ] + ); + } + #[tokio::test] async fn aborting_pending_request_clears_pending_state() -> Result<()> { let thread_id = ThreadId::from_string("bfd12a78-5900-467b-9bc5-d3d35df08191")?;