diff --git a/notes/epiphany-core-harness-surfaces.md b/notes/epiphany-core-harness-surfaces.md index 31786959..49b5fbf2 100644 --- a/notes/epiphany-core-harness-surfaces.md +++ b/notes/epiphany-core-harness-surfaces.md @@ -47,7 +47,7 @@ The rule is: | `thread/epiphany/promote` | verifier gate | landed | Rejects or applies candidates through the durable update path. | | `thread/epiphany/stateUpdated` | notification | landed | Emits updated projected state, source, revision, and changed fields after successful update/promote writes. | | `thread/epiphany/scene` | read-only reflection | landed, live-smoked | Compact client scene derived from authoritative Epiphany state, including checkpoint summary reflection. | -| `thread/epiphany/jobs` | read-only reflection | landed, live-smoked | Derived indexing, remap, verification, and specialist-progress slots from typed state and retrieval summaries. | +| `thread/epiphany/jobs` | read-only reflection | landed, live-smoked | Derived indexing, remap, verification, and specialist-progress slots from typed state and retrieval summaries, with durable `jobBindings` plus live runtime `agent_jobs` overlay when a real owner exists. | | `thread/epiphany/freshness` | read-only reflection | landed, live-smoked | Retrieval and graph freshness lens derived from retrieval summaries plus graph frontier/churn state, with watcher-backed invalidation inputs for loaded threads. | | `thread/epiphany/context` | read-only reflection | landed, live-smoked | Targeted state shard for graph nodes/edges, active frontier, graph checkpoint, investigation checkpoint, observations, and evidence. | | `thread/epiphany/pressure` | read-only reflection | landed, live-smoked | Context-pressure gauge derived from token telemetry and recorded auto-compact/context limits. | @@ -107,6 +107,7 @@ The important categories are: - graph frontier and checkpoint - retrieval summary - investigation checkpoint +- job bindings - scratch - observations - recent evidence @@ -129,8 +130,8 @@ Metaphor is compression after source context. It is not decoration for guesses. These are not landed yet: -- live typed job/progress state for running indexing, remap, verification, and specialist work - `thread/epiphany/jobsUpdated` or equivalent progress notifications +- an Epiphany-owned long-running job launcher beyond binding to existing runtime `agent_jobs` - richer evidence-range and graph-shard inspection beyond the landed context shard - automatic watcher-driven graph/retrieval/invariant invalidation policy on top of the landed freshness reflection - automatic tool-output observation promotion @@ -146,10 +147,11 @@ and a verification story. The first read-only job/progress reflection is landed as `thread/epiphany/jobs`. It reports derived slots for retrieval indexing, graph remap, invariant -verification, and future specialist work. It does not start, schedule, persist, -or notify jobs. +verification, and specialist work, and can overlay durable `jobBindings` onto +live runtime `agent_jobs` snapshots. It does not start, schedule, create, or +notify jobs. -The next missing organ is live observable long-running work. +The next missing organ is live progress notification for bound runtime work. Future live job state should describe work like: @@ -166,8 +168,10 @@ Minimum useful fields: - `scope` - `ownerRole` - `status` +- `runtimeAgentJobId` - `itemsProcessed` - `itemsTotal` +- `activeThreadIds` - `progressNote` - `lastCheckpointAt` - `blockingReason` diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 3b006b1a..fe811258 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -23,7 +23,7 @@ The spine exists in seventeen live paths: - explicit state updates: app-server routes `thread/epiphany/update` through a loaded `CodexThread` update method that rejects malformed appended observation/evidence graph records and structurally invalid replacement fields, including investigation checkpoints that cite missing evidence, mutates live `SessionState` only after validation, 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:4620), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:376), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:552), and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:44). - live state notifications and write responses: 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; direct `thread/epiphany/update` and accepted `thread/epiphany/promote` responses expose the same revision/change metadata, accepted promotions always report `Evidence` because verifier evidence is appended even when the patch evidence list is empty, and successful write response/notification states now use the same client-visible live projection as `thread/read`, including retrieval-summary backfill when durable state has no persisted retrieval metadata, in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:1113), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4354), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4419), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4428), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4555), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4608), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4652), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4693), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11327), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11370). - scene projection: app-server declares experimental read-only `thread/epiphany/scene`, reads the same live/stored Epiphany state view used by `thread/read`, and derives a compact client scene with objective, active subgoal, invariant status counts, graph focus/counts, retrieval status, investigation-checkpoint summary counts, observation/evidence summaries, churn, live/stored source, and available control-plane actions without mutating or persisting state in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:340), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:3924), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4115), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:10757). -- job/progress reflection: app-server declares experimental read-only `thread/epiphany/jobs`, reads the same thread view as scene, optionally fills retrieval state for loaded threads with no Epiphany state, and derives four non-authoritative progress slots for retrieval indexing, graph remap, invariant verification, and future specialist work without scheduling, notifying, mutating, or persisting jobs in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:345), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4132), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4150), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11354). +- job/progress reflection: app-server declares experimental read-only `thread/epiphany/jobs`, reads the same thread view as scene, optionally fills retrieval state for loaded threads with no Epiphany state, and derives four non-authoritative progress slots for retrieval indexing, graph remap, invariant verification, and specialist work. Durable `job_bindings` can now overlay those slots onto live runtime `agent_jobs` snapshots so the surface can show real owner/progress/thread information without scheduling, notifying, mutating, or persisting runtime jobs in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:347), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4142), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4176), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11998). - freshness reflection: app-server declares experimental read-only `thread/epiphany/freshness`, reads the same live/stored thread view used by scene/jobs/context, fills live retrieval state for loaded threads, and derives one bounded retrieval/graph freshness lens plus, for loaded threads, watcher-backed invalidation inputs with watched root, changed paths, mapped graph-node hits, active-frontier hits, and revision/source identity without mutating, scheduling, notifying, or performing automatic semantic invalidation in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4167), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4259), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:19), [epiphany_invalidation.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/epiphany_invalidation.rs:67), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4219), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11073). - targeted context reflection: app-server declares experimental read-only `thread/epiphany/context`, reads the same live/stored Epiphany state view used by scene/jobs, and derives a bounded state shard containing selected graph nodes/edges, links, active frontier, checkpoint, the full durable investigation-checkpoint packet, observations, direct evidence, linked evidence, and missing requested ids without retrieval, proposal, promotion, notification, mutation, or persistence in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:355), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4253), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4261), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11152). - context-pressure reflection: core token telemetry carries `model_auto_compact_token_limit` beside total/last usage and `model_context_window`; app-server declares experimental read-only `thread/epiphany/pressure`, reads live token usage or the latest stored rollout `TokenCount`, and derives `unknown`/`low`/`elevated`/`high`/`critical` pressure plus a non-acting compaction-prep recommendation without mutating Epiphany state or scheduling compaction in [protocol.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/protocol/src/protocol.rs:2237), [turn_context.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/turn_context.rs:103), [session/mod.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/mod.rs:2716), [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:360), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4361), [token_usage_replay.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/token_usage_replay.rs:75), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4306), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11028). @@ -996,16 +996,16 @@ A client sends experimental `thread/epiphany/jobs` with: ### Plain-language role -Job projection is the second Phase 6 reflection boundary. It gives clients a typed answer to "what Epiphany work is visible from here?" without creating a background scheduler, durable job table, or GUI-owned progress model. +Job projection is the second Phase 6 reflection boundary. It gives clients a typed answer to "what Epiphany work is visible from here?" without creating a background scheduler, GUI-owned progress model, or a second durable runtime job table. -The surface currently reports four derived slots: +The surface currently reports four base slots: - retrieval indexing - graph remap - invariant/evidence verification -- future specialist work +- specialist work -The important boundary is that these are reflections over existing state. Retrieval status comes from the same retrieval summary machinery used by scene/thread reads. Remap pressure comes from graph frontier dirty paths, open questions/gaps, and churn freshness. Verification pressure comes from invariant status counts. Specialist work is explicitly `unavailable` until a scheduler exists, which is intentionally better than making clients invent one in the upholstery. +The important boundary is that these are reflections over existing state. Retrieval status comes from the same retrieval summary machinery used by scene/thread reads. Remap pressure comes from graph frontier dirty paths, open questions/gaps, and churn freshness. Verification pressure comes from invariant status counts. Durable `jobBindings` in authoritative Epiphany state can then attach owner/scope/linkage metadata and, when they point at a real runtime `agent_jobs` record, overlay live status, progress counts, runtime job id, and active worker thread ids. If the runtime seam is missing or the referenced job is gone, the slot blocks honestly instead of improvising a secret scheduler. Protocol shape: @@ -1027,15 +1027,17 @@ Response shape: - optional `progressNote` - optional `lastCheckpointAtUnixSeconds` - optional `blockingReason` + - optional `runtimeAgentJobId` + - `activeThreadIds` - `linkedSubgoalIds` - `linkedGraphNodeIds` Code refs: -- [app-server-protocol/common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:344) -- [app-server-protocol/v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4100) -- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4114) -- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:10734) +- [app-server-protocol/common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:347) +- [app-server-protocol/v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4142) +- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4176) +- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11998) ### Mechanism @@ -1046,23 +1048,24 @@ The app-server handler: 3. calls `read_thread_view(..., include_turns: false)` to reuse the live/stored Epiphany projection path. 4. if the thread is loaded and no Epiphany retrieval summary is present, asks the loaded thread for retrieval state so the index slot can still be honest before typed Epiphany state exists. 5. returns `stateRevision` only when authoritative Epiphany state exists. -6. passes the optional state and optional retrieval override into `map_epiphany_jobs`. +6. opens a live runtime state-db seam from the loaded thread when possible and falls back to direct state-runtime lookup for read-only agent-job resolution. +7. passes the optional state, optional retrieval override, and optional runtime job snapshots into `map_epiphany_jobs`. `map_epiphany_jobs`: -1. returns a `retrieval-index` slot from retrieval status: ready becomes idle, stale becomes needed, indexing becomes running, unavailable stays unavailable. -2. returns a `graph-remap` slot that is blocked without Epiphany state and needed when frontier dirty paths, open questions/gaps, or stale graph freshness are visible. -3. returns a `verification` slot that is blocked without Epiphany state and needed when any invariant status is not accepting. -4. returns a `specialist-work` slot as unavailable with an explicit blocking reason because specialist scheduling is not landed. -5. copies active subgoal and graph-node ids into relevant slots so clients can link progress pressure back to the visible model. +1. returns base `retrieval-index`, `graph-remap`, `verification`, and `specialist-work` slots from retrieval state, frontier/churn state, invariant counts, and the no-scheduler boundary. +2. copies active subgoal and graph-node ids into relevant slots so clients can link progress pressure back to the visible model. +3. if authoritative typed state carries `jobBindings`, matches those bindings onto the base slots by id/kind. +4. when a binding carries `runtime_agent_job_id` and the state runtime can resolve it, overlays live runtime `agent_jobs` status, progress counts, progress note, runtime job id, and active worker thread ids onto the slot. +5. when the state runtime is unavailable or the referenced runtime job is missing, blocks the bound slot with an explicit reason instead of pretending the work is running. ### Output -The response is a typed progress board derived from the current Epiphany machine. The Phase 6 jobs smoke proved that missing-state jobs can still reflect retrieval while graph/remap and verification stay blocked, ready-state jobs expose revision identity and needed remap/verification pressure, no `thread/epiphany/stateUpdated` notification is emitted by the read, and final thread state revision does not change. +The response is a typed progress board derived from the current Epiphany machine. The Phase 6 jobs smoke proved that missing-state jobs can still reflect retrieval while graph/remap and verification stay blocked, ready-state jobs expose revision identity and needed remap/verification pressure, a bound specialist runtime job surfaces `running` plus its runtime job id and active worker thread id, no `thread/epiphany/stateUpdated` notification is emitted by the read, and final thread state revision does not change. ### Invariant -Job projection is read-only. It does not start indexing, remap graphs, verify invariants, schedule specialists, mutate `SessionState`, append rollout items, or emit progress notifications. It is a board on the wall, not a payroll department with a fake mustache. +Job projection is read-only. It does not start indexing, create runtime jobs, remap graphs, verify invariants, schedule specialists, mutate `SessionState`, append rollout items, or emit progress notifications. It is a board on the wall, not a payroll department with a fake mustache. ## Flow 15: Targeted Context Shard Control Flow @@ -1275,7 +1278,7 @@ These are deliberately not shipped yet: - no automatic watcher-driven graph or semantic invalidation. - no code-intelligence graph. - no specialist-agent scheduler. -- no live long-running job execution or `thread/epiphany/jobsUpdated` progress notification. +- no `thread/epiphany/jobsUpdated` progress notification or Epiphany-owned long-running job executor. - no GUI implementation beyond the read-only scene, jobs, freshness, context, and pressure projection surfaces. - no durable retrieval-summary write from `thread/epiphany/retrieve`. diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index 2f3bd259..fe5a28ad 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -57,7 +57,7 @@ The landed machine now has: - reusable Phase 5 app-server smoke coverage in `tools/epiphany_phase5_smoke.py` - a first Phase 6 read-only reflection surface through `thread/epiphany/scene` - live Phase 6 scene app-server smoke coverage in `tools/epiphany_phase6_scene_smoke.py` -- read-only Phase 6 job/progress reflection through `thread/epiphany/jobs` +- read-only Phase 6 job/progress reflection through `thread/epiphany/jobs`, with durable `jobBindings` plus live runtime `agent_jobs` overlay - live Phase 6 jobs app-server smoke coverage in `tools/epiphany_phase6_jobs_smoke.py` - read-only Phase 6 retrieval/graph freshness reflection plus watcher-backed invalidation inputs through `thread/epiphany/freshness` - live Phase 6 freshness app-server smoke coverage in `tools/epiphany_phase6_freshness_smoke.py` @@ -82,7 +82,7 @@ These boundaries are more important than the individual method names: - Durable typed state writes go through `thread/epiphany/update` or accepted `thread/epiphany/promote`. - `thread/epiphany/index` may update the semantic retrieval catalog, but it is not a hidden Epiphany-state writer. - `thread/epiphany/scene` is a client reflection, not a second source of truth. -- `thread/epiphany/jobs` is a derived reflection, not a scheduler or durable job store. +- `thread/epiphany/jobs` is a derived reflection over retrieval summaries, typed `jobBindings`, and optional runtime `agent_jobs` snapshots, not a scheduler or durable runtime job store. - `thread/epiphany/freshness` is a derived reflection, not automatic watcher-driven invalidation, a mutation gate, or a hidden refresh scheduler. - `thread/epiphany/context` is a targeted reflection, not a state writer or hidden proposal engine. - `thread/epiphany/pressure` is a context-pressure reflection, not an automatic compactor, scheduler, or CRRC coordinator. @@ -123,7 +123,7 @@ and notify typed state. It can. The next unknowns are: -- how to expose live long-running job progress without making the GUI authoritative +- how live job progress notifications should work now that `thread/epiphany/jobs` can bind to real runtime owners without making the GUI authoritative - how the landed watcher-backed invalidation telemetry should be consumed without turning freshness into a secret worker - how the landed reorientation verdict should be consumed by future automatic CRRC without becoming ceremony machinery - how much automatic CRRC coordination belongs in runtime once pressure, freshness, watcher, checkpoint, and reorientation signals all exist @@ -135,8 +135,8 @@ Phase 6 should grow observable harness state outward from the typed spine. Useful candidates: -1. Add real long-running job ownership/progress state so `thread/epiphany/jobs` and future notifications can reflect something real. -2. Add live job progress notifications only after there is a real long-running owner to report. +1. Add live job progress notifications now that durable `jobBindings` can point at real runtime owners. +2. Decide whether future Epiphany-owned long-running work should keep parasitizing runtime `agent_jobs` or needs a stricter fork-owned launcher seam. 3. Add targeted scene/jobs/context/pressure/reorient fields only when a client or smoke exposes a real gap. Do not spend Phase 6 polishing Phase 5 out of anxiety. The Phase 5 smoke harness diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index b31b269a..156d00f8 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -28,7 +28,7 @@ Do not trust this file for the exact live HEAD. Always check git. - Do not copy exact branch or HEAD from this note. Run `git status --short --branch` and `git log --oneline -5`. - Phase 1 through Phase 5 are complete enough. -- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/pressure`, and `thread/epiphany/reorient`; freshness carries watcher-backed invalidation inputs for loaded threads, and reorient turns checkpoint plus freshness/pressure/watcher signals into a read-only resume-versus-regather verdict. +- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/pressure`, and `thread/epiphany/reorient`; jobs can now overlay durable `jobBindings` onto live runtime `agent_jobs` progress for loaded threads, freshness carries watcher-backed invalidation inputs, and reorient turns checkpoint plus freshness/pressure/watcher signals into a read-only resume-versus-regather verdict. - Durable in-flight investigation checkpointing is now landed in authoritative typed state, writable through `thread/epiphany/update` or accepted `thread/epiphany/promote`, rendered into the prompt, and reflected through scene/context. - The repo is an Epiphany fork of Codex, not a Codex preset. - `vendor/codex` is tracked directly, not a submodule. @@ -63,7 +63,7 @@ The current spine: - verifier-backed promotion through `thread/epiphany/promote` - successful-write notification through `thread/epiphany/stateUpdated` - read-only compact reflection through `thread/epiphany/scene` -- read-only job/progress reflection through `thread/epiphany/jobs` +- read-only job/progress reflection through `thread/epiphany/jobs`, with durable `jobBindings` plus live runtime `agent_jobs` overlay - read-only retrieval/graph freshness reflection plus watcher-backed invalidation inputs through `thread/epiphany/freshness` - read-only targeted state-shard reflection through `thread/epiphany/context` - read-only context-pressure reflection through `thread/epiphany/pressure` @@ -208,12 +208,14 @@ watcher, and investigation-checkpoint signals to decide whether a checkpoint still deserves `resume` or has to admit `regather`. Also keep the guardrail in mind: a read-only reorientation verdict is still not -automatic CRRC. Runtime coordination, next-action execution, and long-running -job ownership are still future work. +automatic CRRC. Runtime coordination and next-action execution are still future +work even though read-only job ownership/progress reflection now has a real +runtime seam. -When the user asks to continue, the next likely organ is real long-running job -ownership/progress state so `thread/epiphany/jobs` and future notifications can -reflect something real, with automatic runtime CRRC still waiting beyond that +When the user asks to continue, the next likely organ is live +`thread/epiphany/jobsUpdated` or equivalent progress notification now that +durable `jobBindings` can point at real runtime owners through +`thread/epiphany/jobs`, with automatic runtime CRRC still waiting beyond that boundary. Live `thread/epiphany/scene`, `thread/epiphany/jobs`, @@ -228,7 +230,7 @@ guardrails, not the next organs. - specialist-agent scheduling - GUI-as-source-of-truth - automatic runtime CRRC coordinator acting on the landed reorientation verdict -- live long-running job execution or `thread/epiphany/jobsUpdated` +- Epiphany-owned long-running job execution or `thread/epiphany/jobsUpdated` - broad event stream beyond the landed state update notification The machine is good enough to move outward. Do not sand the same edge until the diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 83d30106..f83f5897 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -42,3 +42,4 @@ {"ts": "2026-04-26T06:01:53+00:00", "type": "design", "status": "accepted", "note": "Epiphany publication stance is built-in-the-open rather than indiscriminately FOSS: use FOSS where viable, source-available licensing where economics or capability risk demand it, and commercial terms where organizations extract real enterprise value."} {"ts": "2026-04-26T06:51:04+00:00", "type": "verification", "status": "ok", "note": "Phase 6 watcher-backed invalidation inputs landed inside read-only thread/epiphany/freshness. Loaded threads now expose runtime watcher telemetry with watched root, changed paths, mapped graph-node/frontier hits, and no state mutation, scheduling, notification, or automatic semantic invalidation. Focused protocol/app-server tests, schema fixture regeneration/checks, app-server build, and tools/epiphany_phase6_invalidation_smoke.py plus tools/epiphany_phase6_freshness_smoke.py passed."} {"ts": "2026-04-26T10:45:36+00:00", "type": "verification", "status": "ok", "note": "Phase 6 bounded CRRC reorientation policy landed as read-only thread/epiphany/reorient. The surface consumes checkpoint, freshness, watcher, and pressure signals to return resume-versus-regather with explicit checkpoint-path/frontier reasons, without mutation, compaction, scheduling, notification, or silent continuation. Focused protocol/schema checks, app-server mapper tests/build, and tools/epiphany_phase6_reorient_smoke.py plus tools/epiphany_phase6_scene_smoke.py passed."} +{"ts": "2026-04-26T11:30:59+00:00", "type": "verification", "status": "ok", "note": "Phase 6 jobs reflection now carries durable jobBindings in EpiphanyThreadState and overlays real runtime agent_jobs progress in read-only thread/epiphany/jobs, including runtime job ids, worker thread ids, and progress counts. The live seam required direct state-runtime lookup rather than rollout get_state_db backfill. Focused protocol/core/app-server tests, schema fixture checks, app-server build, and tools/epiphany_phase6_jobs_smoke.py passed."} diff --git a/state/map.yaml b/state/map.yaml index 6cce7321..e88ad8bd 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -73,14 +73,14 @@ accepted_design: - Context pressure must become an explicit runtime signal for automatic CRRC; inference from transcript vibes, user warnings, or shrinking safe scope is useful only as a temporary human/agent heuristic. - The harness now has an in-flight investigation checkpoint packet in durable typed state; automatic CRRC still needs runtime logic that reads the packet and decides whether to resume or force reorientation and re-gathering after compaction. - GUI/client work must reflect or steer typed state rather than manufacture a second source of truth. - - `thread/epiphany/jobs` is the first read-only job/progress reflection: it derives indexing, remap, verification, and specialist slots from existing state; it is not a scheduler or durable job store. + - `thread/epiphany/jobs` is a read-only job/progress reflection: it derives indexing, remap, verification, and specialist slots from authoritative state and retrieval summaries, and can overlay durable `job_bindings` onto live runtime `agent_jobs` snapshots when a real owner exists; it is not a scheduler, notification stream, or durable runtime job table. - `thread/epiphany/freshness` is the first read-only freshness surface: it derives retrieval and graph staleness from live retrieval summaries plus frontier/churn state, and for loaded threads reflects watcher-backed invalidation inputs with changed paths plus mapped graph/frontier hits, without mutating, scheduling, or performing automatic semantic invalidation. - `thread/epiphany/context` is the first targeted read-only state-shard surface: it selects graph nodes/edges, active frontier, checkpoint, observations, and evidence from authoritative typed state without mutating or notifying. - `thread/epiphany/pressure` is the first read-only context-pressure surface: it derives pressure from token telemetry and auto-compact/context limits without compacting, scheduling, mutating, or notifying. - `thread/epiphany/reorient` is the first bounded CRRC policy surface: it derives a read-only `resume` versus `regather` verdict from checkpoint, freshness, watcher, and pressure signals without mutating state, compacting, scheduling, or silently continuing work. - Epiphany safety doctrine is to separate cognition from actuation, keep authority explicit, make interruption sacred, stage capability release, and treat operator misuse as a normal design input rather than a rude hypothetical. - Epiphany is built in the open, but publication mode is not one-size-fits-all: use FOSS where viable, source-available licensing where economics or capability risk demand it, and commercial terms where organizations extract real enterprise value. - - Live long-running job execution, progress notifications, and specialist scheduling are still future work. + - Epiphany-owned long-running job execution, live job progress notifications, and specialist scheduling are still future work. - Handoff, plan, harness-surface, and scratch files must have distinct jobs and must not become duplicate brains. - A machine is perfect when every remaining part visibly deserves to exist; this applies to persistent memory too. - Evidence is not an activity feed; repeated "I just did this" entries should be cut or distilled when git already proves the work. @@ -123,5 +123,5 @@ rejected_paths: reason: persistent state that accumulates every local truth becomes memory cruft and weakens rehydration current_status: - summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, freshness, targeted context, context-pressure reflection, watcher-backed invalidation inputs, durable investigation checkpointing, and bounded CRRC reorientation policy are landed. `thread/epiphany/reorient` now consumes checkpoint, freshness, watcher, and pressure signals to return a read-only `resume` versus `regather` verdict with explicit checkpoint-path/frontier reasons, but it still does not mutate state, compact, schedule, notify, or silently continue work. Focused protocol/app-server checks plus scene and reorientation smokes cover the seam. Investigation checkpoints still live in authoritative typed state, flow through the prompt, and reflect through scene/context without adding a second writer or fake scheduler. Persistent state was cut down so map, handoff, plan, harness surfaces, scratch, and evidence each carry one job instead of becoming a Jenga tower; evidence is now a distilled ledger rather than an activity feed. The global AGENTS Epiphany core command says persistent state is the agent's mind and explicitly permits the agent to ask for self-modification when memory or workflow makes it worse. The continuity goal is to bank the fire before the dark so compaction becomes less like death and more like sleep. - next_action: After compaction, rehydrate and reorient. If the user asks to continue, the next likely Phase 6 organ is real long-running job ownership/progress state so `thread/epiphany/jobs` and future notifications can reflect something real, while automatic runtime CRRC remains future work that may eventually consume the landed read-only reorientation verdict. Do not restart the completed pressure, freshness, watcher-invalidation, investigation-checkpoint, or reorientation slices unless a concrete regression appears. + summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, freshness, targeted context, context-pressure reflection, watcher-backed invalidation inputs, durable investigation checkpointing, and bounded CRRC reorientation policy are landed. `thread/epiphany/jobs` now carries durable `job_bindings` in authoritative typed state and overlays real runtime `agent_jobs` progress for bound work, including runtime job id, worker thread ids, and progress counts, without becoming a scheduler or notification stream. `thread/epiphany/reorient` still consumes checkpoint, freshness, watcher, and pressure signals to return a read-only `resume` versus `regather` verdict with explicit checkpoint-path/frontier reasons, but it does not mutate state, compact, schedule, notify, or silently continue work. Focused protocol/core/app-server checks plus the live jobs smoke cover the seam. Investigation checkpoints still live in authoritative typed state, flow through the prompt, and reflect through scene/context without adding a second writer or fake scheduler. Persistent state was cut down so map, handoff, plan, harness surfaces, scratch, and evidence each carry one job instead of becoming a Jenga tower; evidence is now a distilled ledger rather than an activity feed. The global AGENTS Epiphany core command says persistent state is the agent's mind and explicitly permits the agent to ask for self-modification when memory or workflow makes it worse. The continuity goal is to bank the fire before the dark so compaction becomes less like death and more like sleep. + next_action: After compaction, rehydrate and reorient. If the user asks to continue, the next likely Phase 6 organ is live `thread/epiphany/jobsUpdated` or equivalent progress notification now that durable `job_bindings` can point at real runtime owners, while automatic runtime CRRC remains future work that may eventually consume the landed read-only reorientation verdict. Do not restart the completed pressure, freshness, watcher-invalidation, investigation-checkpoint, reorientation, or landed jobs-binding slice unless a concrete regression appears. diff --git a/tools/epiphany_phase6_jobs_smoke.py b/tools/epiphany_phase6_jobs_smoke.py index 672b740a..067c05f8 100644 --- a/tools/epiphany_phase6_jobs_smoke.py +++ b/tools/epiphany_phase6_jobs_smoke.py @@ -3,6 +3,8 @@ from __future__ import annotations import argparse import json from pathlib import Path +import sqlite3 +import time from typing import Any from epiphany_phase5_smoke import AppServerClient @@ -25,7 +27,115 @@ JOBS_CODE_REF = { } -def job_surface_patch() -> dict[str, Any]: +def locate_state_db(codex_home: Path) -> Path: + candidates = sorted(codex_home.glob("state_*.sqlite")) + if candidates: + return candidates[-1] + fallback = sorted( + path + for path in codex_home.glob("*.sqlite") + if "logs" not in path.name.lower() + ) + if fallback: + return fallback[-1] + raise FileNotFoundError(f"no state db found under {codex_home}") + + +def seed_agent_job(codex_home: Path, job_id: str, running_thread_id: str) -> None: + db_path = locate_state_db(codex_home) + now = int(time.time()) + connection = sqlite3.connect(db_path) + try: + with connection: + connection.execute( + """ + INSERT INTO agent_jobs ( + id, name, status, instruction, output_schema_json, input_headers_json, + input_csv_path, output_csv_path, auto_export, created_at, updated_at, + started_at, completed_at, last_error + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + job_id, + "epiphany-specialist-smoke", + "running", + "Smoke-seeded runtime job for Epiphany jobs reflection.", + None, + "[]", + str(codex_home / "input.csv"), + str(codex_home / "output.csv"), + 0, + now - 120, + now, + now - 90, + None, + None, + ), + ) + connection.executemany( + """ + INSERT INTO agent_job_items ( + job_id, item_id, row_index, source_id, row_json, status, assigned_thread_id, + attempt_count, result_json, last_error, created_at, updated_at, completed_at, + reported_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + [ + ( + job_id, + "item-completed", + 0, + "row-0", + '{"row":0}', + "completed", + None, + 1, + '{"accepted":true}', + None, + now - 120, + now - 30, + now - 30, + now - 30, + ), + ( + job_id, + "item-running", + 1, + "row-1", + '{"row":1}', + "running", + running_thread_id, + 1, + None, + None, + now - 120, + now, + None, + None, + ), + ( + job_id, + "item-pending", + 2, + "row-2", + '{"row":2}', + "pending", + None, + 0, + None, + None, + now - 120, + now - 10, + None, + None, + ), + ], + ) + finally: + connection.close() + + +def job_surface_patch(runtime_agent_job_id: str) -> dict[str, Any]: return { "objective": "Expose read-only Epiphany job/progress reflection without creating a scheduler.", "activeSubgoalId": "phase6-jobs-smoke", @@ -68,6 +178,18 @@ def job_surface_patch() -> dict[str, Any]: "dirty_paths": ["app-server/src/codex_message_processor.rs"], "open_question_ids": ["q-live-progress"], }, + "jobBindings": [ + { + "id": "specialist-work", + "kind": "specialist", + "scope": "role-scoped specialist work", + "owner_role": "epiphany-harness", + "runtime_agent_job_id": runtime_agent_job_id, + "linked_subgoal_ids": ["phase6-jobs-smoke"], + "linked_graph_node_ids": ["job-surface"], + "progress_note": "Bound to a real runtime specialist job.", + } + ], "churn": { "understanding_status": "ready", "diff_pressure": "low", @@ -126,8 +248,17 @@ def assert_ready_jobs(response: dict[str, Any]) -> None: require(verification["itemsTotal"] == 2, "verification should count total invariants") specialist = job_by_id(response["jobs"], "specialist-work") - require(specialist["status"] == "unavailable", "specialist scheduler should remain explicit non-flow") - require("blockingReason" in specialist, "specialist non-flow should explain itself") + require(specialist["status"] == "running", "bound specialist job should report real runtime progress") + require( + specialist["runtimeAgentJobId"] == "job-specialist-smoke", + "specialist runtime job id should surface through jobs reflection", + ) + require(specialist["itemsProcessed"] == 1, "specialist job should count completed+failed items") + require(specialist["itemsTotal"] == 3, "specialist job should count total runtime items") + require( + specialist["activeThreadIds"] == ["worker-thread-specialist"], + "specialist job should expose active worker thread ids", + ) def run_smoke(args: argparse.Namespace) -> dict[str, Any]: @@ -160,6 +291,7 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: ) assert started is not None thread_id = started["thread"]["id"] + seed_agent_job(codex_home, "job-specialist-smoke", "worker-thread-specialist") missing_notification_start = len(client.notifications) missing_response = client.send("thread/epiphany/jobs", {"threadId": thread_id}) @@ -174,7 +306,11 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: update_notification_start = len(client.notifications) update = client.send( "thread/epiphany/update", - {"threadId": thread_id, "expectedRevision": 0, "patch": job_surface_patch()}, + { + "threadId": thread_id, + "expectedRevision": 0, + "patch": job_surface_patch("job-specialist-smoke"), + }, ) assert update is not None require(update["revision"] == 1, "job smoke patch should advance revision to 1") @@ -213,6 +349,13 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "verificationItemsTotal": job_by_id( ready_response["jobs"], "verification" )["itemsTotal"], + "specialistStatus": job_by_id(ready_response["jobs"], "specialist-work")["status"], + "specialistRuntimeAgentJobId": job_by_id( + ready_response["jobs"], "specialist-work" + )["runtimeAgentJobId"], + "specialistActiveThreadIds": job_by_id( + ready_response["jobs"], "specialist-work" + )["activeThreadIds"], "jobsNotificationCount": client.count_notifications( "thread/epiphany/stateUpdated", start_index=jobs_notification_start, diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 8db277bb..a78eca8a 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1159,6 +1159,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -3849,6 +3911,15 @@ } ] }, + "jobBindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": [ + "array", + "null" + ] + }, "mode": { "anyOf": [ { 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 4b680ac2..90a6b7b0 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 @@ -1419,6 +1419,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1703,6 +1765,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", @@ -3799,6 +3867,7 @@ "graphCheckpoint", "scratch", "investigationCheckpoint", + "jobBindings", "observations", "evidence", "churn", 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 7b28e28d..da665e68 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 @@ -8268,6 +8268,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/v2/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -8552,6 +8614,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/v2/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", @@ -15146,6 +15214,12 @@ }, "ThreadEpiphanyJob": { "properties": { + "activeThreadIds": { + "items": { + "type": "string" + }, + "type": "array" + }, "blockingReason": { "type": [ "string", @@ -15202,6 +15276,12 @@ "null" ] }, + "runtimeAgentJobId": { + "type": [ + "string", + "null" + ] + }, "scope": { "type": "string" }, @@ -15231,7 +15311,11 @@ "enum": [ "idle", "needed", + "pending", "running", + "completed", + "failed", + "cancelled", "blocked", "unavailable" ], @@ -16125,6 +16209,7 @@ "graphCheckpoint", "scratch", "investigationCheckpoint", + "jobBindings", "observations", "evidence", "churn", @@ -16246,6 +16331,15 @@ } ] }, + "jobBindings": { + "items": { + "$ref": "#/definitions/v2/EpiphanyJobBinding" + }, + "type": [ + "array", + "null" + ] + }, "mode": { "anyOf": [ { 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 b7fe1f14..6bd15637 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 @@ -4828,6 +4828,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -5112,6 +5174,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", @@ -13033,6 +13101,12 @@ }, "ThreadEpiphanyJob": { "properties": { + "activeThreadIds": { + "items": { + "type": "string" + }, + "type": "array" + }, "blockingReason": { "type": [ "string", @@ -13089,6 +13163,12 @@ "null" ] }, + "runtimeAgentJobId": { + "type": [ + "string", + "null" + ] + }, "scope": { "type": "string" }, @@ -13118,7 +13198,11 @@ "enum": [ "idle", "needed", + "pending", "running", + "completed", + "failed", + "cancelled", "blocked", "unavailable" ], @@ -14012,6 +14096,7 @@ "graphCheckpoint", "scratch", "investigationCheckpoint", + "jobBindings", "observations", "evidence", "churn", @@ -14133,6 +14218,15 @@ } ] }, + "jobBindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": [ + "array", + "null" + ] + }, "mode": { "anyOf": [ { 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 87bbf77d..8dc7a0e5 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 @@ -418,6 +418,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -702,6 +764,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", @@ -792,6 +860,7 @@ "graphCheckpoint", "scratch", "investigationCheckpoint", + "jobBindings", "observations", "evidence", "churn", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 4f4682b7..93f162ee 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -866,6 +866,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1150,6 +1212,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 675c0c9e..7ff05008 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -807,6 +807,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1091,6 +1153,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 89ba7d54..70fef008 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -807,6 +807,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1091,6 +1153,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index d50894b3..c331ef96 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -807,6 +807,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1091,6 +1153,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index fa989937..0bbf9db2 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -866,6 +866,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1150,6 +1212,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 3b0c102f..30667de3 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -807,6 +807,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1091,6 +1153,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 1dc5664d..7b6dc19a 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -866,6 +866,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1150,6 +1212,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index b87c3a3a..a707bcdc 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -807,6 +807,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1091,6 +1153,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 40b11046..3d140d30 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -807,6 +807,68 @@ ], "type": "string" }, + "EpiphanyJobBinding": { + "properties": { + "blocking_reason": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/EpiphanyJobKind" + }, + "linked_graph_node_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "linked_subgoal_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owner_role": { + "type": "string" + }, + "progress_note": { + "type": [ + "string", + "null" + ] + }, + "runtime_agent_job_id": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + } + }, + "required": [ + "id", + "kind", + "owner_role", + "scope" + ], + "type": "object" + }, + "EpiphanyJobKind": { + "enum": [ + "indexing", + "remap", + "verification", + "specialist" + ], + "type": "string" + }, "EpiphanyModeState": { "properties": { "kind": { @@ -1091,6 +1153,12 @@ } ] }, + "job_bindings": { + "items": { + "$ref": "#/definitions/EpiphanyJobBinding" + }, + "type": "array" + }, "last_updated_turn_id": { "type": [ "string", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyJobBinding.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyJobBinding.ts new file mode 100644 index 00000000..c0a6dbbf --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyJobBinding.ts @@ -0,0 +1,6 @@ +// 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. +import type { EpiphanyJobKind } from "./EpiphanyJobKind"; + +export type EpiphanyJobBinding = { id: string, kind: EpiphanyJobKind, scope: string, owner_role: string, runtime_agent_job_id?: string | null, linked_subgoal_ids?: Array, linked_graph_node_ids?: Array, progress_note?: string | null, blocking_reason?: string | null, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyJobKind.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyJobKind.ts new file mode 100644 index 00000000..6b89f8b1 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyJobKind.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 EpiphanyJobKind = "indexing" | "remap" | "verification" | "specialist"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts index 2acf0394..9c3ab3d7 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/EpiphanyThreadState.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type EpiphanyThreadState = { revision: bigint, objective?: string | null, active_subgoal_id?: string | null, subgoals?: Array, invariants?: Array, graphs?: EpiphanyGraphs, graph_frontier?: EpiphanyGraphFrontier | null, graph_checkpoint?: EpiphanyGraphCheckpoint | null, retrieval?: EpiphanyRetrievalState | null, scratch?: EpiphanyScratchPad | null, investigation_checkpoint?: EpiphanyInvestigationCheckpoint | null, observations?: Array, recent_evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, last_updated_turn_id?: string | null, }; +export type EpiphanyThreadState = { revision: bigint, objective?: string | null, active_subgoal_id?: string | null, subgoals?: Array, invariants?: Array, graphs?: EpiphanyGraphs, graph_frontier?: EpiphanyGraphFrontier | null, graph_checkpoint?: EpiphanyGraphCheckpoint | null, retrieval?: EpiphanyRetrievalState | null, scratch?: EpiphanyScratchPad | null, investigation_checkpoint?: EpiphanyInvestigationCheckpoint | null, job_bindings?: Array, observations?: Array, recent_evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, last_updated_turn_id?: string | null, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts index 53e9cabb..fc476024 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -25,6 +25,8 @@ export type { EpiphanyGraphs } from "./EpiphanyGraphs"; export type { EpiphanyInvariant } from "./EpiphanyInvariant"; export type { EpiphanyInvestigationCheckpoint } from "./EpiphanyInvestigationCheckpoint"; export type { EpiphanyInvestigationDisposition } from "./EpiphanyInvestigationDisposition"; +export type { EpiphanyJobBinding } from "./EpiphanyJobBinding"; +export type { EpiphanyJobKind } from "./EpiphanyJobKind"; export type { EpiphanyModeState } from "./EpiphanyModeState"; export type { EpiphanyObservation } from "./EpiphanyObservation"; export type { EpiphanyRetrievalStatus } from "./EpiphanyRetrievalStatus"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJob.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJob.ts index 1efee0f3..c5212e83 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJob.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJob.ts @@ -4,4 +4,4 @@ import type { ThreadEpiphanyJobKind } from "./ThreadEpiphanyJobKind"; import type { ThreadEpiphanyJobStatus } from "./ThreadEpiphanyJobStatus"; -export type ThreadEpiphanyJob = { id: string, kind: ThreadEpiphanyJobKind, scope: string, ownerRole: string, status: ThreadEpiphanyJobStatus, itemsProcessed?: number | null, itemsTotal?: number | null, progressNote?: string | null, lastCheckpointAtUnixSeconds?: bigint | null, blockingReason?: string | null, linkedSubgoalIds?: Array, linkedGraphNodeIds?: Array, }; +export type ThreadEpiphanyJob = { id: string, kind: ThreadEpiphanyJobKind, scope: string, ownerRole: string, status: ThreadEpiphanyJobStatus, runtimeAgentJobId?: string | null, itemsProcessed?: number | null, itemsTotal?: number | null, progressNote?: string | null, lastCheckpointAtUnixSeconds?: bigint | null, blockingReason?: string | null, activeThreadIds?: Array, linkedSubgoalIds?: Array, linkedGraphNodeIds?: Array, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJobStatus.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJobStatus.ts index 6c582a9d..caf52792 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJobStatus.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyJobStatus.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ThreadEpiphanyJobStatus = "idle" | "needed" | "running" | "blocked" | "unavailable"; +export type ThreadEpiphanyJobStatus = "idle" | "needed" | "pending" | "running" | "completed" | "failed" | "cancelled" | "blocked" | "unavailable"; 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 index 6ea139a1..39d42215 100644 --- 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 @@ -2,4 +2,4 @@ // 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" | "investigationCheckpoint" | "observations" | "evidence" | "churn" | "mode"; +export type ThreadEpiphanyStateUpdatedField = "objective" | "activeSubgoalId" | "subgoals" | "invariants" | "graphs" | "graphFrontier" | "graphCheckpoint" | "scratch" | "investigationCheckpoint" | "jobBindings" | "observations" | "evidence" | "churn" | "mode"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts index 2c0c55b0..3a748a6f 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyUpdatePatch.ts @@ -8,9 +8,10 @@ import type { EpiphanyGraphFrontier } from "../EpiphanyGraphFrontier"; import type { EpiphanyGraphs } from "../EpiphanyGraphs"; import type { EpiphanyInvariant } from "../EpiphanyInvariant"; import type { EpiphanyInvestigationCheckpoint } from "../EpiphanyInvestigationCheckpoint"; +import type { EpiphanyJobBinding } from "../EpiphanyJobBinding"; import type { EpiphanyModeState } from "../EpiphanyModeState"; import type { EpiphanyObservation } from "../EpiphanyObservation"; import type { EpiphanyScratchPad } from "../EpiphanyScratchPad"; import type { EpiphanySubgoal } from "../EpiphanySubgoal"; -export type ThreadEpiphanyUpdatePatch = { objective?: string | null, activeSubgoalId?: string | null, subgoals?: Array | null, invariants?: Array | null, graphs?: EpiphanyGraphs | null, graphFrontier?: EpiphanyGraphFrontier | null, graphCheckpoint?: EpiphanyGraphCheckpoint | null, scratch?: EpiphanyScratchPad | null, investigationCheckpoint?: EpiphanyInvestigationCheckpoint | null, observations?: Array, evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, }; +export type ThreadEpiphanyUpdatePatch = { objective?: string | null, activeSubgoalId?: string | null, subgoals?: Array | null, invariants?: Array | null, graphs?: EpiphanyGraphs | null, graphFrontier?: EpiphanyGraphFrontier | null, graphCheckpoint?: EpiphanyGraphCheckpoint | null, scratch?: EpiphanyScratchPad | null, investigationCheckpoint?: EpiphanyInvestigationCheckpoint | null, jobBindings?: Array | null, observations?: Array, evidence?: Array, churn?: EpiphanyChurnState | null, mode?: EpiphanyModeState | null, }; 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 d6122045..08b3ff0e 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 @@ -1820,6 +1820,7 @@ mod tests { scope: "/workspace".to_string(), owner_role: "epiphany-core".to_string(), status: v2::ThreadEpiphanyJobStatus::Needed, + runtime_agent_job_id: None, items_processed: Some(12), items_total: None, progress_note: Some( @@ -1827,6 +1828,7 @@ mod tests { ), last_checkpoint_at_unix_seconds: Some(1_744_500_000), blocking_reason: None, + active_thread_ids: Vec::new(), linked_subgoal_ids: vec!["phase-6".to_string()], linked_graph_node_ids: vec!["retrieval".to_string()], }], 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 124805c3..c07c9a6b 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 @@ -69,6 +69,7 @@ use codex_protocol::protocol::EpiphanyGraphs as CoreEpiphanyGraphs; use codex_protocol::protocol::EpiphanyInvariant as CoreEpiphanyInvariant; use codex_protocol::protocol::EpiphanyInvestigationCheckpoint as CoreEpiphanyInvestigationCheckpoint; use codex_protocol::protocol::EpiphanyInvestigationDisposition as CoreEpiphanyInvestigationDisposition; +use codex_protocol::protocol::EpiphanyJobBinding as CoreEpiphanyJobBinding; use codex_protocol::protocol::EpiphanyModeState as CoreEpiphanyModeState; use codex_protocol::protocol::EpiphanyObservation as CoreEpiphanyObservation; use codex_protocol::protocol::EpiphanyRetrievalStatus as CoreEpiphanyRetrievalStatus; @@ -4291,7 +4292,11 @@ pub enum ThreadEpiphanyJobKind { pub enum ThreadEpiphanyJobStatus { Idle, Needed, + Pending, Running, + Completed, + Failed, + Cancelled, Blocked, Unavailable, } @@ -4307,6 +4312,9 @@ pub struct ThreadEpiphanyJob { pub status: ThreadEpiphanyJobStatus, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] + pub runtime_agent_job_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] pub items_processed: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] @@ -4321,6 +4329,8 @@ pub struct ThreadEpiphanyJob { #[ts(optional = nullable)] pub blocking_reason: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub active_thread_ids: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub linked_subgoal_ids: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub linked_graph_node_ids: Vec, @@ -4727,6 +4737,9 @@ pub struct ThreadEpiphanyUpdatePatch { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub investigation_checkpoint: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub job_bindings: Option>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub observations: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -4780,6 +4793,7 @@ pub enum ThreadEpiphanyStateUpdatedField { GraphCheckpoint, Scratch, InvestigationCheckpoint, + JobBindings, Observations, Evidence, Churn, 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 d4329fc2..78d14734 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 @@ -402,6 +402,8 @@ use codex_protocol::protocol::ConversationStartParams; use codex_protocol::protocol::ConversationStartTransport; use codex_protocol::protocol::ConversationTextParams; use codex_protocol::protocol::EpiphanyInvestigationDisposition; +use codex_protocol::protocol::EpiphanyJobBinding; +use codex_protocol::protocol::EpiphanyJobKind as CoreEpiphanyJobKind; use codex_protocol::protocol::EpiphanyRetrievalState; use codex_protocol::protocol::EpiphanyRetrievalStatus; use codex_protocol::protocol::EpiphanyThreadState; @@ -429,6 +431,9 @@ use codex_rmcp_client::perform_oauth_login_return_url; use codex_rollout::state_db::StateDbHandle; use codex_rollout::state_db::get_state_db; use codex_rollout::state_db::reconcile_rollout; +use codex_state::AgentJobItemStatus; +use codex_state::AgentJobProgress; +use codex_state::AgentJobStatus; use codex_state::StateRuntime; use codex_state::ThreadMetadata; use codex_state::ThreadMetadataBuilder; @@ -3398,7 +3403,7 @@ impl CodexMessageProcessor { let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok(); let mut state_db_ctx = loaded_thread.as_ref().and_then(|thread| thread.state_db()); if state_db_ctx.is_none() { - state_db_ctx = get_state_db(&self.config).await; + state_db_ctx = open_state_db_for_direct_thread_lookup(&self.config).await; } let Some(state_db_ctx) = state_db_ctx else { self.send_internal_error( @@ -4212,6 +4217,15 @@ impl CodexMessageProcessor { } else { None }; + let mut state_db_ctx = loaded_thread.as_ref().and_then(|thread| thread.state_db()); + if state_db_ctx.is_none() { + state_db_ctx = open_state_db_for_direct_thread_lookup(&self.config).await; + } + let job_resolution = load_epiphany_agent_job_snapshots( + state_db_ctx.as_ref(), + thread.epiphany_state.as_ref(), + ) + .await; let response = ThreadEpiphanyJobsResponse { thread_id, @@ -4224,7 +4238,11 @@ impl CodexMessageProcessor { .epiphany_state .as_ref() .map(|epiphany_state| epiphany_state.revision), - jobs: map_epiphany_jobs(thread.epiphany_state.as_ref(), retrieval_override.as_ref()), + jobs: map_epiphany_jobs( + thread.epiphany_state.as_ref(), + retrieval_override.as_ref(), + &job_resolution, + ), }; self.outgoing.send_response(request_id, response).await; } @@ -4821,6 +4839,7 @@ impl CodexMessageProcessor { graph_checkpoint: patch.graph_checkpoint, scratch: patch.scratch, investigation_checkpoint: patch.investigation_checkpoint, + job_bindings: patch.job_bindings, observations: patch.observations, evidence, churn: patch.churn, @@ -4909,6 +4928,7 @@ impl CodexMessageProcessor { graph_checkpoint: patch.graph_checkpoint, scratch: patch.scratch, investigation_checkpoint: patch.investigation_checkpoint, + job_bindings: patch.job_bindings, observations: patch.observations, evidence: patch.evidence, churn: patch.churn, @@ -11899,16 +11919,271 @@ fn extend_unique_strings(target: &mut Vec, values: impl IntoIterator, +} + +#[derive(Debug, Clone)] +struct EpiphanyAgentJobSnapshot { + status: AgentJobStatus, + progress: AgentJobProgress, + updated_at_unix_seconds: i64, + last_error: Option, + active_thread_ids: Vec, +} + +async fn load_epiphany_agent_job_snapshots( + state_db_ctx: Option<&StateDbHandle>, + state: Option<&EpiphanyThreadState>, +) -> EpiphanyAgentJobResolution { + let Some(state) = state else { + return EpiphanyAgentJobResolution::default(); + }; + + let Some(state_db_ctx) = state_db_ctx else { + return EpiphanyAgentJobResolution { + available: false, + snapshots_by_binding_id: HashMap::new(), + }; + }; + + let mut resolution = EpiphanyAgentJobResolution { + available: true, + snapshots_by_binding_id: HashMap::new(), + }; + + for binding in &state.job_bindings { + let Some(runtime_agent_job_id) = binding.runtime_agent_job_id.as_deref() else { + continue; + }; + + let Ok(Some(job)) = state_db_ctx.get_agent_job(runtime_agent_job_id).await else { + continue; + }; + let Ok(progress) = state_db_ctx + .get_agent_job_progress(runtime_agent_job_id) + .await + else { + continue; + }; + let active_thread_ids = state_db_ctx + .list_agent_job_items( + runtime_agent_job_id, + Some(AgentJobItemStatus::Running), + /*limit*/ Some(64), + ) + .await + .map(|items| { + unique_strings(items.into_iter().filter_map(|item| item.assigned_thread_id)) + }) + .unwrap_or_default(); + + resolution.snapshots_by_binding_id.insert( + binding.id.clone(), + EpiphanyAgentJobSnapshot { + status: job.status, + progress, + updated_at_unix_seconds: job.updated_at.timestamp(), + last_error: job.last_error, + active_thread_ids, + }, + ); + } + + resolution +} + fn map_epiphany_jobs( state: Option<&EpiphanyThreadState>, retrieval_override: Option<&EpiphanyRetrievalState>, + job_resolution: &EpiphanyAgentJobResolution, ) -> Vec { - vec![ + let mut jobs = vec![ map_epiphany_index_job(state, retrieval_override), map_epiphany_remap_job(state), map_epiphany_verification_job(state), map_epiphany_specialist_job(), - ] + ]; + + let Some(state) = state else { + return jobs; + }; + + for binding in &state.job_bindings { + let snapshot = job_resolution + .snapshots_by_binding_id + .get(binding.id.as_str()); + let replacement = if let Some(existing) = jobs.iter().find(|job| job.id == binding.id) { + overlay_epiphany_job_binding(existing.clone(), binding, snapshot, job_resolution) + } else { + map_epiphany_bound_job(binding, snapshot, job_resolution) + }; + + if let Some(existing) = jobs.iter_mut().find(|job| job.id == binding.id) { + *existing = replacement; + } else { + jobs.push(replacement); + } + } + + jobs +} + +fn map_epiphany_bound_job( + binding: &EpiphanyJobBinding, + snapshot: Option<&EpiphanyAgentJobSnapshot>, + job_resolution: &EpiphanyAgentJobResolution, +) -> ThreadEpiphanyJob { + overlay_epiphany_job_binding( + ThreadEpiphanyJob { + id: binding.id.clone(), + kind: map_core_epiphany_job_kind(binding.kind), + scope: binding.scope.clone(), + owner_role: binding.owner_role.clone(), + status: if binding.blocking_reason.is_some() { + ThreadEpiphanyJobStatus::Blocked + } else { + ThreadEpiphanyJobStatus::Idle + }, + runtime_agent_job_id: binding.runtime_agent_job_id.clone(), + items_processed: None, + items_total: None, + progress_note: None, + last_checkpoint_at_unix_seconds: None, + blocking_reason: None, + active_thread_ids: Vec::new(), + linked_subgoal_ids: binding.linked_subgoal_ids.clone(), + linked_graph_node_ids: binding.linked_graph_node_ids.clone(), + }, + binding, + snapshot, + job_resolution, + ) +} + +fn overlay_epiphany_job_binding( + mut job: ThreadEpiphanyJob, + binding: &EpiphanyJobBinding, + snapshot: Option<&EpiphanyAgentJobSnapshot>, + job_resolution: &EpiphanyAgentJobResolution, +) -> ThreadEpiphanyJob { + job.kind = map_core_epiphany_job_kind(binding.kind); + job.scope = binding.scope.clone(); + job.owner_role = binding.owner_role.clone(); + job.runtime_agent_job_id = binding.runtime_agent_job_id.clone(); + if !binding.linked_subgoal_ids.is_empty() { + job.linked_subgoal_ids = binding.linked_subgoal_ids.clone(); + } + if !binding.linked_graph_node_ids.is_empty() { + job.linked_graph_node_ids = binding.linked_graph_node_ids.clone(); + } + if let Some(progress_note) = binding.progress_note.clone() { + job.progress_note = Some(progress_note); + } + if let Some(blocking_reason) = binding.blocking_reason.clone() { + job.blocking_reason = Some(blocking_reason); + } + + if let Some(snapshot) = snapshot { + job.status = map_thread_epiphany_job_status(snapshot.status); + job.items_processed = Some(progress_processed_items(&snapshot.progress)); + job.items_total = Some(progress_total_items(&snapshot.progress)); + job.progress_note = Some(render_agent_job_progress_note(snapshot)); + job.last_checkpoint_at_unix_seconds = Some(snapshot.updated_at_unix_seconds); + job.active_thread_ids = snapshot.active_thread_ids.clone(); + job.blocking_reason = match snapshot.status { + AgentJobStatus::Failed => snapshot.last_error.clone().or(job.blocking_reason), + AgentJobStatus::Cancelled => Some( + snapshot + .last_error + .clone() + .unwrap_or_else(|| "Bound runtime agent job was cancelled.".to_string()), + ), + _ => None, + }; + return job; + } + + if let Some(runtime_agent_job_id) = binding.runtime_agent_job_id.as_deref() { + job.status = ThreadEpiphanyJobStatus::Blocked; + job.blocking_reason = Some(if job_resolution.available { + format!( + "Bound runtime agent job {:?} was not found in state runtime.", + runtime_agent_job_id + ) + } else { + "State runtime is unavailable, so bound runtime agent jobs cannot be resolved." + .to_string() + }); + } + + job +} + +fn map_core_epiphany_job_kind(kind: CoreEpiphanyJobKind) -> ThreadEpiphanyJobKind { + match kind { + CoreEpiphanyJobKind::Indexing => ThreadEpiphanyJobKind::Indexing, + CoreEpiphanyJobKind::Remap => ThreadEpiphanyJobKind::Remap, + CoreEpiphanyJobKind::Verification => ThreadEpiphanyJobKind::Verification, + CoreEpiphanyJobKind::Specialist => ThreadEpiphanyJobKind::Specialist, + } +} + +fn map_thread_epiphany_job_status(status: AgentJobStatus) -> ThreadEpiphanyJobStatus { + match status { + AgentJobStatus::Pending => ThreadEpiphanyJobStatus::Pending, + AgentJobStatus::Running => ThreadEpiphanyJobStatus::Running, + AgentJobStatus::Completed => ThreadEpiphanyJobStatus::Completed, + AgentJobStatus::Failed => ThreadEpiphanyJobStatus::Failed, + AgentJobStatus::Cancelled => ThreadEpiphanyJobStatus::Cancelled, + } +} + +fn progress_total_items(progress: &AgentJobProgress) -> u32 { + u32::try_from(progress.total_items).unwrap_or(u32::MAX) +} + +fn progress_processed_items(progress: &AgentJobProgress) -> u32 { + u32::try_from( + progress + .completed_items + .saturating_add(progress.failed_items), + ) + .unwrap_or(u32::MAX) +} + +fn render_agent_job_progress_note(snapshot: &EpiphanyAgentJobSnapshot) -> String { + let progress = &snapshot.progress; + match snapshot.status { + AgentJobStatus::Pending => format!( + "Runtime agent job is pending with {} pending, {} running, {} completed, and {} failed item(s).", + progress.pending_items, + progress.running_items, + progress.completed_items, + progress.failed_items + ), + AgentJobStatus::Running => format!( + "Runtime agent job is running with {} pending, {} running, {} completed, and {} failed item(s).", + progress.pending_items, + progress.running_items, + progress.completed_items, + progress.failed_items + ), + AgentJobStatus::Completed => format!( + "Runtime agent job completed all {} item(s).", + progress.total_items + ), + AgentJobStatus::Failed => format!( + "Runtime agent job failed with {} completed and {} failed item(s).", + progress.completed_items, progress.failed_items + ), + AgentJobStatus::Cancelled => format!( + "Runtime agent job was cancelled with {} pending, {} completed, and {} failed item(s).", + progress.pending_items, progress.completed_items, progress.failed_items + ), + } } fn map_epiphany_index_job( @@ -11928,11 +12203,13 @@ fn map_epiphany_index_job( scope: "workspace".to_string(), owner_role: "epiphany-core".to_string(), status: ThreadEpiphanyJobStatus::Unavailable, + runtime_agent_job_id: None, items_processed: None, items_total: None, progress_note: None, last_checkpoint_at_unix_seconds: None, blocking_reason: Some("Retrieval state is unavailable for this thread.".to_string()), + active_thread_ids: Vec::new(), linked_subgoal_ids, linked_graph_node_ids, }; @@ -11959,6 +12236,7 @@ fn map_epiphany_index_job( scope: retrieval.workspace_root.display().to_string(), owner_role: "epiphany-core".to_string(), status: epiphany_job_status_from_retrieval_status(retrieval.status), + runtime_agent_job_id: None, items_processed: retrieval.indexed_file_count, items_total: None, progress_note: Some(progress_note), @@ -11966,6 +12244,7 @@ fn map_epiphany_index_job( blocking_reason: (retrieval.status == EpiphanyRetrievalStatus::Unavailable).then(|| { "Indexing requires a readable workspace and configured retrieval backend.".to_string() }), + active_thread_ids: Vec::new(), linked_subgoal_ids, linked_graph_node_ids, } @@ -12024,11 +12303,13 @@ fn map_epiphany_remap_job(state: Option<&EpiphanyThreadState>) -> ThreadEpiphany } else { ThreadEpiphanyJobStatus::Idle }, + runtime_agent_job_id: None, items_processed: None, items_total: None, progress_note: Some(progress_note), last_checkpoint_at_unix_seconds: None, blocking_reason: None, + active_thread_ids: Vec::new(), linked_subgoal_ids: epiphany_active_subgoal_ids(Some(state)), linked_graph_node_ids: epiphany_active_graph_node_ids(Some(state)), } @@ -12069,11 +12350,13 @@ fn map_epiphany_verification_job(state: Option<&EpiphanyThreadState>) -> ThreadE scope: "invariants/evidence".to_string(), owner_role: "epiphany-harness".to_string(), status, + runtime_agent_job_id: None, items_processed: Some(verified), items_total: Some(total), progress_note: Some(progress_note), last_checkpoint_at_unix_seconds: None, blocking_reason: None, + active_thread_ids: Vec::new(), linked_subgoal_ids: epiphany_active_subgoal_ids(Some(state)), linked_graph_node_ids: epiphany_active_graph_node_ids(Some(state)), } @@ -12086,6 +12369,7 @@ fn map_epiphany_specialist_job() -> ThreadEpiphanyJob { scope: "role-scoped specialist work".to_string(), owner_role: "epiphany-harness".to_string(), status: ThreadEpiphanyJobStatus::Unavailable, + runtime_agent_job_id: None, items_processed: None, items_total: None, progress_note: None, @@ -12094,6 +12378,7 @@ fn map_epiphany_specialist_job() -> ThreadEpiphanyJob { "Specialist scheduling is not landed; this slot prevents clients from inventing private scheduler state." .to_string(), ), + active_thread_ids: Vec::new(), linked_subgoal_ids: Vec::new(), linked_graph_node_ids: Vec::new(), } @@ -12111,11 +12396,13 @@ fn epiphany_blocked_state_job( scope: scope.to_string(), owner_role: "epiphany-harness".to_string(), status: ThreadEpiphanyJobStatus::Blocked, + runtime_agent_job_id: None, items_processed: None, items_total: None, progress_note: None, last_checkpoint_at_unix_seconds: None, blocking_reason: Some(blocking_reason.to_string()), + active_thread_ids: Vec::new(), linked_subgoal_ids: Vec::new(), linked_graph_node_ids: Vec::new(), } @@ -12165,6 +12452,7 @@ fn thread_epiphany_patch_has_state_replacements(patch: &ThreadEpiphanyUpdatePatc || patch.graph_checkpoint.is_some() || patch.scratch.is_some() || patch.investigation_checkpoint.is_some() + || patch.job_bindings.is_some() || patch.churn.is_some() || patch.mode.is_some() } @@ -12266,6 +12554,9 @@ fn epiphany_update_patch_changed_fields( if patch.investigation_checkpoint.is_some() { fields.push(ThreadEpiphanyStateUpdatedField::InvestigationCheckpoint); } + if patch.job_bindings.is_some() { + fields.push(ThreadEpiphanyStateUpdatedField::JobBindings); + } if !patch.observations.is_empty() { fields.push(ThreadEpiphanyStateUpdatedField::Observations); } @@ -14490,7 +14781,7 @@ mod tests { ..Default::default() }; - let jobs = map_epiphany_jobs(Some(&state), None); + let jobs = map_epiphany_jobs(Some(&state), None, &EpiphanyAgentJobResolution::default()); assert_eq!(jobs.len(), 4); assert_eq!(jobs[0].id, "retrieval-index"); @@ -14527,7 +14818,11 @@ mod tests { ..Default::default() }; - let jobs = map_epiphany_jobs(None, Some(&retrieval)); + let jobs = map_epiphany_jobs( + None, + Some(&retrieval), + &EpiphanyAgentJobResolution::default(), + ); assert_eq!(jobs[0].status, ThreadEpiphanyJobStatus::Idle); assert_eq!(jobs[0].items_processed, Some(7)); @@ -14535,6 +14830,102 @@ mod tests { assert_eq!(jobs[2].status, ThreadEpiphanyJobStatus::Blocked); } + #[test] + fn map_epiphany_jobs_overlays_bound_runtime_agent_job_progress() { + let state = codex_protocol::protocol::EpiphanyThreadState { + job_bindings: vec![codex_protocol::protocol::EpiphanyJobBinding { + id: "specialist-work".to_string(), + kind: codex_protocol::protocol::EpiphanyJobKind::Specialist, + scope: "role-scoped specialist work".to_string(), + owner_role: "epiphany-harness".to_string(), + runtime_agent_job_id: Some("job-specialist-1".to_string()), + linked_subgoal_ids: vec!["phase-6".to_string()], + linked_graph_node_ids: vec!["job-surface".to_string()], + progress_note: Some("Bound to a real runtime job.".to_string()), + blocking_reason: None, + }], + ..Default::default() + }; + let resolution = EpiphanyAgentJobResolution { + available: true, + snapshots_by_binding_id: HashMap::from([( + "specialist-work".to_string(), + EpiphanyAgentJobSnapshot { + status: AgentJobStatus::Running, + progress: AgentJobProgress { + total_items: 3, + pending_items: 1, + running_items: 1, + completed_items: 1, + failed_items: 0, + }, + updated_at_unix_seconds: 1_744_600_000, + last_error: None, + active_thread_ids: vec!["worker-thread-1".to_string()], + }, + )]), + }; + + let jobs = map_epiphany_jobs(Some(&state), None, &resolution); + let specialist = jobs + .iter() + .find(|job| job.id == "specialist-work") + .expect("specialist slot should exist"); + + assert_eq!(specialist.status, ThreadEpiphanyJobStatus::Running); + assert_eq!( + specialist.runtime_agent_job_id.as_deref(), + Some("job-specialist-1") + ); + assert_eq!(specialist.items_processed, Some(1)); + assert_eq!(specialist.items_total, Some(3)); + assert_eq!( + specialist.active_thread_ids, + vec!["worker-thread-1".to_string()] + ); + assert_eq!(specialist.linked_subgoal_ids, vec!["phase-6".to_string()]); + assert_eq!( + specialist.linked_graph_node_ids, + vec!["job-surface".to_string()] + ); + } + + #[test] + fn map_epiphany_jobs_blocks_missing_bound_runtime_agent_job() { + let state = codex_protocol::protocol::EpiphanyThreadState { + job_bindings: vec![codex_protocol::protocol::EpiphanyJobBinding { + id: "specialist-work".to_string(), + kind: codex_protocol::protocol::EpiphanyJobKind::Specialist, + scope: "role-scoped specialist work".to_string(), + owner_role: "epiphany-harness".to_string(), + runtime_agent_job_id: Some("job-missing".to_string()), + linked_subgoal_ids: Vec::new(), + linked_graph_node_ids: Vec::new(), + progress_note: None, + blocking_reason: None, + }], + ..Default::default() + }; + let resolution = EpiphanyAgentJobResolution { + available: true, + snapshots_by_binding_id: HashMap::new(), + }; + + let jobs = map_epiphany_jobs(Some(&state), None, &resolution); + let specialist = jobs + .iter() + .find(|job| job.id == "specialist-work") + .expect("specialist slot should exist"); + + assert_eq!(specialist.status, ThreadEpiphanyJobStatus::Blocked); + assert!( + specialist + .blocking_reason + .as_deref() + .is_some_and(|reason| reason.contains("not found in state runtime")) + ); + } + #[test] fn epiphany_update_patch_changed_fields_reports_patch_surface() { let fields = epiphany_update_patch_changed_fields(&ThreadEpiphanyUpdatePatch { @@ -14542,6 +14933,7 @@ mod tests { graphs: Some(Default::default()), graph_frontier: Some(Default::default()), investigation_checkpoint: Some(Default::default()), + job_bindings: Some(Vec::new()), observations: vec![Default::default()], evidence: vec![Default::default()], churn: Some(Default::default()), @@ -14555,6 +14947,7 @@ mod tests { ThreadEpiphanyStateUpdatedField::Graphs, ThreadEpiphanyStateUpdatedField::GraphFrontier, ThreadEpiphanyStateUpdatedField::InvestigationCheckpoint, + ThreadEpiphanyStateUpdatedField::JobBindings, ThreadEpiphanyStateUpdatedField::Observations, ThreadEpiphanyStateUpdatedField::Evidence, ThreadEpiphanyStateUpdatedField::Churn, diff --git a/vendor/codex/codex-rs/core/src/codex_thread.rs b/vendor/codex/codex-rs/core/src/codex_thread.rs index 158d57fa..0a94b4ff 100644 --- a/vendor/codex/codex-rs/core/src/codex_thread.rs +++ b/vendor/codex/codex-rs/core/src/codex_thread.rs @@ -32,6 +32,7 @@ use codex_protocol::protocol::EpiphanyGraphFrontier; use codex_protocol::protocol::EpiphanyGraphs; use codex_protocol::protocol::EpiphanyInvariant; use codex_protocol::protocol::EpiphanyInvestigationCheckpoint; +use codex_protocol::protocol::EpiphanyJobBinding; use codex_protocol::protocol::EpiphanyModeState; use codex_protocol::protocol::EpiphanyObservation; use codex_protocol::protocol::EpiphanyRetrievalState; @@ -112,6 +113,7 @@ pub struct EpiphanyStateUpdate { pub graph_checkpoint: Option, pub scratch: Option, pub investigation_checkpoint: Option, + pub job_bindings: Option>, pub observations: Vec, pub evidence: Vec, pub churn: Option, @@ -129,6 +131,7 @@ impl EpiphanyStateUpdate { && self.graph_checkpoint.is_none() && self.scratch.is_none() && self.investigation_checkpoint.is_none() + && self.job_bindings.is_none() && self.observations.is_empty() && self.evidence.is_empty() && self.churn.is_none() @@ -645,10 +648,44 @@ fn epiphany_state_update_validation_errors( } } + if let Some(job_bindings) = update.job_bindings.as_ref() { + errors.extend(validate_epiphany_job_bindings(job_bindings)); + } + errors.extend(epiphany_state_replacement_validation_errors(state, update)); errors } +fn validate_epiphany_job_bindings(job_bindings: &[EpiphanyJobBinding]) -> Vec { + let mut errors = Vec::new(); + let mut seen_ids = HashSet::<&str>::new(); + + for binding in job_bindings { + require_nonempty_update(&binding.id, "job_binding.id", &mut errors); + require_nonempty_update(&binding.scope, "job_binding.scope", &mut errors); + require_nonempty_update(&binding.owner_role, "job_binding.owner_role", &mut errors); + + if let Some(runtime_agent_job_id) = binding.runtime_agent_job_id.as_deref() { + require_nonempty_update( + runtime_agent_job_id, + "job_binding.runtime_agent_job_id", + &mut errors, + ); + } + if let Some(progress_note) = binding.progress_note.as_deref() { + require_nonempty_update(progress_note, "job_binding.progress_note", &mut errors); + } + if let Some(blocking_reason) = binding.blocking_reason.as_deref() { + require_nonempty_update(blocking_reason, "job_binding.blocking_reason", &mut errors); + } + if !binding.id.is_empty() && !seen_ids.insert(binding.id.as_str()) { + errors.push(format!("duplicate job binding id {:?}", binding.id)); + } + } + + errors +} + fn epiphany_state_replacement_validation_errors( state: &EpiphanyThreadState, update: &EpiphanyStateUpdate, @@ -734,6 +771,9 @@ fn apply_epiphany_state_update( if let Some(checkpoint) = update.investigation_checkpoint { state.investigation_checkpoint = Some(checkpoint); } + if let Some(job_bindings) = update.job_bindings { + state.job_bindings = job_bindings; + } if let Some(churn) = update.churn { state.churn = Some(churn); } @@ -760,6 +800,7 @@ mod epiphany_update_tests { use super::*; use codex_protocol::protocol::EpiphanyGraph; use codex_protocol::protocol::EpiphanyGraphNode; + use codex_protocol::protocol::EpiphanyJobKind; fn evidence(id: &str) -> EpiphanyEvidenceRecord { EpiphanyEvidenceRecord { @@ -782,6 +823,20 @@ mod epiphany_update_tests { } } + fn job_binding(id: &str) -> EpiphanyJobBinding { + EpiphanyJobBinding { + id: id.to_string(), + kind: EpiphanyJobKind::Specialist, + scope: "role-scoped specialist work".to_string(), + owner_role: "epiphany-harness".to_string(), + runtime_agent_job_id: Some(format!("agent-job-{id}")), + linked_subgoal_ids: vec!["phase-6".to_string()], + linked_graph_node_ids: vec!["job-surface".to_string()], + progress_note: Some("Bound to a real runtime agent job.".to_string()), + blocking_reason: None, + } + } + #[test] fn apply_epiphany_state_update_replaces_typed_fields_and_prepends_evidence() { let mut state = EpiphanyThreadState { @@ -847,6 +902,33 @@ mod epiphany_update_tests { ); } + #[test] + fn apply_epiphany_state_update_replaces_job_bindings() { + let mut state = EpiphanyThreadState { + revision: 2, + job_bindings: vec![job_binding("old")], + ..Default::default() + }; + + apply_epiphany_state_update( + &mut state, + EpiphanyStateUpdate { + job_bindings: Some(vec![job_binding("new")]), + ..Default::default() + }, + Some("turn-jobs".to_string()), + ); + + assert_eq!(state.revision, 3); + assert_eq!(state.job_bindings.len(), 1); + assert_eq!(state.job_bindings[0].id, "new"); + assert_eq!( + state.job_bindings[0].runtime_agent_job_id.as_deref(), + Some("agent-job-new") + ); + assert_eq!(state.last_updated_turn_id.as_deref(), Some("turn-jobs")); + } + #[test] fn validate_epiphany_state_update_accepts_observation_linked_to_existing_evidence() { let state = EpiphanyThreadState { @@ -998,6 +1080,67 @@ mod epiphany_update_tests { .any(|error| error.contains("investigation checkpoint cites missing evidence id")) ); } + + #[test] + fn validate_epiphany_state_update_rejects_invalid_job_bindings() { + let update = EpiphanyStateUpdate { + job_bindings: Some(vec![ + job_binding("dup"), + job_binding("dup"), + EpiphanyJobBinding { + id: String::new(), + kind: EpiphanyJobKind::Verification, + scope: String::new(), + owner_role: String::new(), + runtime_agent_job_id: Some(String::new()), + linked_subgoal_ids: Vec::new(), + linked_graph_node_ids: Vec::new(), + progress_note: Some(String::new()), + blocking_reason: Some(String::new()), + }, + ]), + ..Default::default() + }; + + let errors = + epiphany_state_update_validation_errors(&EpiphanyThreadState::default(), &update); + + assert!( + errors + .iter() + .any(|error| error.contains("duplicate job binding id")) + ); + assert!( + errors + .iter() + .any(|error| error.contains("job_binding.id must not be empty")) + ); + assert!( + errors + .iter() + .any(|error| error.contains("job_binding.scope must not be empty")) + ); + assert!( + errors + .iter() + .any(|error| error.contains("job_binding.owner_role must not be empty")) + ); + assert!( + errors + .iter() + .any(|error| error.contains("job_binding.runtime_agent_job_id must not be empty")) + ); + assert!( + errors + .iter() + .any(|error| error.contains("job_binding.progress_note must not be empty")) + ); + assert!( + errors + .iter() + .any(|error| error.contains("job_binding.blocking_reason must not be empty")) + ); + } } fn pending_message_input_item(message: &ResponseItem) -> CodexResult { diff --git a/vendor/codex/codex-rs/core/src/session/tests.rs b/vendor/codex/codex-rs/core/src/session/tests.rs index a855f87b..c7ff4489 100644 --- a/vendor/codex/codex-rs/core/src/session/tests.rs +++ b/vendor/codex/codex-rs/core/src/session/tests.rs @@ -337,6 +337,7 @@ fn sample_epiphany_state_for_prompt() -> EpiphanyThreadState { open_gap_ids: vec!["gap-resume-proof".to_string()], }), investigation_checkpoint: None, + job_bindings: Vec::new(), scratch: Some(EpiphanyScratchPad { summary: Some("Inject the state as a developer fragment, not as a user message.".to_string()), hypothesis: Some("Keeping the block bounded will help resume without making prompt sludge.".to_string()), diff --git a/vendor/codex/codex-rs/protocol/src/protocol.rs b/vendor/codex/codex-rs/protocol/src/protocol.rs index c739de0c..3466e1f9 100644 --- a/vendor/codex/codex-rs/protocol/src/protocol.rs +++ b/vendor/codex/codex-rs/protocol/src/protocol.rs @@ -3021,6 +3021,9 @@ pub struct EpiphanyThreadState { #[ts(type = "EpiphanyInvestigationCheckpoint | null")] pub investigation_checkpoint: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[ts(type = "Array")] + pub job_bindings: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] #[ts(type = "Array")] pub observations: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -3296,6 +3299,39 @@ pub struct EpiphanyInvestigationCheckpoint { pub evidence_ids: Vec, } +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case")] +pub enum EpiphanyJobKind { + Indexing, + Remap, + Verification, + Specialist, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)] +pub struct EpiphanyJobBinding { + pub id: String, + pub kind: EpiphanyJobKind, + pub scope: String, + pub owner_role: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(type = "string | null")] + pub runtime_agent_job_id: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[ts(type = "Array")] + pub linked_subgoal_ids: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[ts(type = "Array")] + pub linked_graph_node_ids: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(type = "string | null")] + pub progress_note: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(type = "string | null")] + pub blocking_reason: Option, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)] pub struct EpiphanyObservation { pub id: String, @@ -5722,6 +5758,7 @@ mod tests { next_probe: Some("Replay after rollback and compaction".to_string()), notes: vec!["Do not touch prompts yet".to_string()], }), + job_bindings: Vec::new(), observations: vec![EpiphanyObservation { id: "obs-1".to_string(), summary: "TurnContext already gives us the persistence seam".to_string(),