Remove legacy Epiphany state from Codex protocol

This commit is contained in:
Meta Vens 2026-07-12 06:03:17 +01:00
commit febef826c0
20 changed files with 147 additions and 828 deletions

View file

@ -1931,6 +1931,13 @@ listing, state extraction, and agent control. Next remove the named variant
entirely by making app-server's migration quarantine read raw rollout lines,
recognize only `type=epiphany_state`, and strictly decode ordinary Codex
lifecycle items needed for rollback-aware migration.
That deletion is complete: Codex `RolloutItem` and every core consumer have no
Epiphany legacy variant. App-server migration alone scans raw JSONL for the
exact old tag, validates typed payloads fail-closed, and combines them with
strictly decoded lifecycle items for rollback-aware recovery. Four migration
tests pass; obsolete core fixtures and its state-model dev dependency are gone.
Next audit app-server `epiphany_automation` and route dispatch for surviving
Codex-owned coordinator or scheduler decisions.
## Immediate Re-entry Instruction

View file

@ -342,3 +342,15 @@ The deletion path is narrower: app-server's migration quarantine should read
raw rollout lines, recognize only `type=epiphany_state`, decode its payload,
and combine those migration records with strictly decoded Codex lifecycle items.
Then `LegacyEpiphanyState` can leave Codex protocol and all core consumers.
That final deletion is complete. `RolloutItem` has no Epiphany variant, and
protocol/core/rollout/state/app-server-protocol contain no legacy-state match.
App-server's migration quarantine reads raw JSONL, recognizes only the exact
historical `type=epiphany_state` tag, validates its payload into native
`EpiphanyStateItem`, and strictly decodes ordinary `RolloutLine` items needed
for turn and rollback semantics. Unknown ordinary lines remain parse failures
for the normal Codex loader; the migration scan ignores them without teaching
Codex global tolerance. Four quarantine tests prove raw-tag recognition,
latest-surviving selection, rollback rejection, and malformed-payload refusal.
Obsolete core legacy fixtures and the final core state-model dev dependency are
deleted.

Binary file not shown.

View file

@ -659,4 +659,4 @@ current_status:
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. Gjallar readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
next_action: Delete `LegacyEpiphanyState` from `RolloutItem` by moving raw-line recognition into app-server's migration quarantine. The quarantine reader should recognize only `type=epiphany_state`, decode that payload fail-closed, strictly decode other lines as normal `RolloutLine` lifecycle items for rollback/turn boundaries, and preserve Codex's existing unknown/malformed parse-error behavior everywhere else.
next_action: Audit vendored app-server `epiphany_automation` and remaining Epiphany route dispatch as possible surviving coordinator/scheduler authority. Native coordinator/status/mutation/read organs now own live state and processes; retain only explicit client compatibility projection/actuation, and delete any automatic Codex turn-boundary or token-pressure behavior that can still decide Epiphany work.

1
vendor/codex/codex-rs/Cargo.lock generated vendored
View file

@ -2394,7 +2394,6 @@ dependencies = [
"dirs",
"dunce",
"env-flags",
"epiphany-state-model",
"eventsource-stream",
"futures",
"http 1.4.0",

View file

@ -232,9 +232,7 @@ impl ThreadHistoryBuilder {
RolloutItem::EventMsg(event) => self.handle_event(event),
RolloutItem::Compacted(payload) => self.handle_compacted(payload),
RolloutItem::ResponseItem(item) => self.handle_response_item(item),
RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::SessionMeta(_) => {}
RolloutItem::TurnContext(_) | RolloutItem::SessionMeta(_) => {}
}
}

View file

@ -1,16 +1,12 @@
use std::path::Path;
use super::legacy_epiphany_rollout::latest_legacy_epiphany_state;
pub(super) use super::legacy_epiphany_rollout::load_epiphany_state_from_rollout_path;
use codex_core::CodexThread;
use codex_core::RolloutRecorder;
use epiphany_state_model::EpiphanyThreadState;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::TokenUsageInfo;
use epiphany_codex_bridge::mutation_service::EpiphanyMutationHost;
use epiphany_codex_bridge::mutation_service::load_authoritative_accepted_state;
use epiphany_codex_bridge::pressure::EpiphanyTokenUsageSnapshot;
use epiphany_codex_bridge::state::client_visible_epiphany_state_for_paths;
use epiphany_codex_bridge::state::thread_state_mirror_id_from_rollout_path;
use epiphany_state_model::EpiphanyThreadState;
pub(super) struct EpiphanyCodexThreadHost<'a> {
thread: &'a CodexThread,
@ -55,24 +51,6 @@ impl EpiphanyMutationHost for EpiphanyCodexThreadHost<'_> {
}
}
pub(super) async fn load_epiphany_state_from_rollout_path(
rollout_path: &Path,
) -> std::result::Result<Option<EpiphanyThreadState>, String> {
let items = match RolloutRecorder::get_rollout_history(rollout_path)
.await
.map_err(|err| {
format!(
"failed to load rollout `{}` for Epiphany state: {err}",
rollout_path.display()
)
})? {
InitialHistory::New | InitialHistory::Cleared => Vec::new(),
InitialHistory::Forked(items) => items,
InitialHistory::Resumed(resumed) => resumed.history,
};
latest_legacy_epiphany_state(&items)
}
pub(super) async fn live_thread_epiphany_state(
thread: &CodexThread,
) -> Option<EpiphanyThreadState> {

View file

@ -1,7 +1,16 @@
use std::path::Path;
use codex_protocol::models::ResponseItem;
use epiphany_state_model::EpiphanyThreadState;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::RolloutLine;
use epiphany_state_model::EpiphanyStateItem;
use epiphany_state_model::EpiphanyThreadState;
enum LegacyReplayItem {
Codex(RolloutItem),
Epiphany(EpiphanyStateItem),
}
#[derive(Default)]
struct LegacySegment {
@ -28,8 +37,8 @@ fn is_out_of_band(segment: &LegacySegment) -> bool {
segment.turn_id.is_none() && !segment.user_turn && segment.state.is_some()
}
pub(super) fn latest_legacy_epiphany_state(
items: &[RolloutItem],
fn latest_legacy_epiphany_state(
items: &[LegacyReplayItem],
) -> Result<Option<EpiphanyThreadState>, String> {
let mut state = None;
let mut rollbacks = 0usize;
@ -46,15 +55,15 @@ pub(super) fn latest_legacy_epiphany_state(
}
match item {
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(event)) => {
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::ThreadRolledBack(event))) => {
rollbacks = rollbacks
.saturating_add(usize::try_from(event.num_turns).unwrap_or(usize::MAX));
}
RolloutItem::EventMsg(EventMsg::TurnComplete(event)) => {
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::TurnComplete(event))) => {
let segment = segment.get_or_insert_with(LegacySegment::default);
segment.turn_id.get_or_insert_with(|| event.turn_id.clone());
}
RolloutItem::EventMsg(EventMsg::TurnAborted(event)) => {
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::TurnAborted(event))) => {
if let Some(turn_id) = &event.turn_id {
segment
.get_or_insert_with(LegacySegment::default)
@ -62,17 +71,16 @@ pub(super) fn latest_legacy_epiphany_state(
.get_or_insert_with(|| turn_id.clone());
}
}
RolloutItem::EventMsg(EventMsg::UserMessage(_)) => {
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::UserMessage(_))) => {
segment.get_or_insert_with(LegacySegment::default).user_turn = true;
}
RolloutItem::ResponseItem(ResponseItem::Message { role, .. }) if role == "user" => {
LegacyReplayItem::Codex(RolloutItem::ResponseItem(ResponseItem::Message {
role,
..
})) if role == "user" => {
segment.get_or_insert_with(LegacySegment::default).user_turn = true;
}
RolloutItem::LegacyEpiphanyState(payload) => {
let item: epiphany_state_model::EpiphanyStateItem =
serde_json::from_value(payload.clone()).map_err(|error| {
format!("invalid legacy Epiphany rollout payload: {error}")
})?;
LegacyReplayItem::Epiphany(item) => {
let segment = segment.get_or_insert_with(LegacySegment::default);
if segment.turn_id.is_none() {
segment.turn_id = item.turn_id.clone();
@ -83,7 +91,7 @@ pub(super) fn latest_legacy_epiphany_state(
segment.state = Some(item.state.clone());
}
}
RolloutItem::EventMsg(EventMsg::TurnStarted(event)) => {
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::TurnStarted(event))) => {
if segment.as_ref().is_some_and(|segment| {
compatible(segment.turn_id.as_deref(), Some(event.turn_id.as_str()))
}) && let Some(segment) = segment.take()
@ -91,11 +99,7 @@ pub(super) fn latest_legacy_epiphany_state(
finish(segment, &mut state, &mut rollbacks);
}
}
RolloutItem::ResponseItem(_)
| RolloutItem::EventMsg(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::SessionMeta(_) => {}
LegacyReplayItem::Codex(_) => {}
}
}
@ -105,15 +109,49 @@ pub(super) fn latest_legacy_epiphany_state(
Ok(state)
}
pub(super) async fn load_epiphany_state_from_rollout_path(
path: &Path,
) -> Result<Option<EpiphanyThreadState>, String> {
let text = tokio::fs::read_to_string(path)
.await
.map_err(|error| format!("failed to read rollout `{}`: {error}", path.display()))?;
let mut items = Vec::new();
for line in text.lines().filter(|line| !line.trim().is_empty()) {
if let Some(item) = parse_replay_line(line)? {
items.push(item);
}
}
latest_legacy_epiphany_state(&items)
}
fn parse_replay_line(line: &str) -> Result<Option<LegacyReplayItem>, String> {
let Ok(value) = serde_json::from_str::<serde_json::Value>(line) else {
return Ok(None);
};
if value.get("type").and_then(serde_json::Value::as_str) == Some("epiphany_state") {
let payload = value
.get("payload")
.cloned()
.ok_or_else(|| "legacy Epiphany rollout item is missing payload".to_string())?;
let item = serde_json::from_value(payload)
.map_err(|error| format!("invalid legacy Epiphany rollout payload: {error}"))?;
Ok(Some(LegacyReplayItem::Epiphany(item)))
} else {
Ok(serde_json::from_value::<RolloutLine>(value)
.ok()
.map(|line| LegacyReplayItem::Codex(line.item)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use codex_protocol::config_types::ModeKind;
use epiphany_state_model::EpiphanyStateItem;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::TurnCompleteEvent;
use codex_protocol::protocol::TurnStartedEvent;
use codex_protocol::protocol::UserMessageEvent;
use epiphany_state_model::EpiphanyStateItem;
fn state(id: &str) -> EpiphanyThreadState {
EpiphanyThreadState {
@ -123,34 +161,37 @@ mod tests {
}
}
fn turn(id: &str, state: EpiphanyThreadState) -> Vec<RolloutItem> {
fn turn(id: &str, state: EpiphanyThreadState) -> Vec<LegacyReplayItem> {
vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: id.to_string(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: ModeKind::Default,
})),
RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
message: id.to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
})),
RolloutItem::LegacyEpiphanyState(
serde_json::to_value(EpiphanyStateItem {
turn_id: Some(id.to_string()),
state,
})
.expect("serialize legacy Epiphany payload"),
),
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: id.to_string(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
})),
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::TurnStarted(
TurnStartedEvent {
turn_id: id.to_string(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: ModeKind::Default,
},
))),
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::UserMessage(
UserMessageEvent {
message: id.to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
))),
LegacyReplayItem::Epiphany(EpiphanyStateItem {
turn_id: Some(id.to_string()),
state,
}),
LegacyReplayItem::Codex(RolloutItem::EventMsg(EventMsg::TurnComplete(
TurnCompleteEvent {
turn_id: id.to_string(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
))),
]
}
@ -168,18 +209,43 @@ mod tests {
let first = state("one");
let mut items = turn("one", first.clone());
items.extend(turn("two", state("two")));
items.push(RolloutItem::EventMsg(EventMsg::ThreadRolledBack(
ThreadRolledBackEvent { num_turns: 1 },
items.push(LegacyReplayItem::Codex(RolloutItem::EventMsg(
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 1 }),
)));
assert_eq!(latest_legacy_epiphany_state(&items), Ok(Some(first)));
}
#[test]
fn rejects_malformed_legacy_state_payload() {
let items = vec![RolloutItem::LegacyEpiphanyState(serde_json::json!({
"turn_id": "not-the-legacy-shape"
}))];
let line = serde_json::json!({
"timestamp": "2026-07-12T00:00:00Z",
"type": "epiphany_state",
"payload": { "turn_id": "not-the-legacy-shape" }
});
assert!(parse_replay_line(&line.to_string()).is_err());
}
assert!(latest_legacy_epiphany_state(&items).is_err());
#[test]
fn recognizes_only_the_historical_raw_line_tag() {
let expected = state("raw-line");
let line = serde_json::json!({
"timestamp": "2026-07-12T00:00:00Z",
"type": "epiphany_state",
"payload": {
"turn_id": "raw-line",
"state": expected
}
});
let Some(LegacyReplayItem::Epiphany(item)) =
parse_replay_line(&line.to_string()).expect("parse historical line")
else {
panic!("expected quarantined Epiphany migration item");
};
assert_eq!(item.turn_id.as_deref(), Some("raw-line"));
assert_eq!(item.state, state("raw-line"));
assert!(parse_replay_line(r#"{"type":"future_unknown","payload":{}}"#)
.expect("unknown line should remain nonfatal to migration scan")
.is_none());
}
}

View file

@ -140,7 +140,6 @@ windows-sys = { version = "0.52", features = [
codex-shell-escalation = { workspace = true }
[dev-dependencies]
epiphany-state-model = { path = "../../../../epiphany-state-model" }
assert_cmd = { workspace = true }
assert_matches = { workspace = true }
codex-otel = { workspace = true }

View file

@ -119,7 +119,6 @@ fn keep_forked_rollout_item(item: &RolloutItem) -> bool {
// A forked child gets its own runtime config, including spawned-agent
// instructions, so it must establish a fresh context diff baseline.
RolloutItem::TurnContext(_) => false,
RolloutItem::LegacyEpiphanyState(_) => true,
RolloutItem::Compacted(_) | RolloutItem::EventMsg(_) | RolloutItem::SessionMeta(_) => true,
}
}

View file

@ -182,7 +182,6 @@ impl Session {
}
}
}
RolloutItem::LegacyEpiphanyState(_) => {}
RolloutItem::EventMsg(EventMsg::TurnStarted(event)) => {
// `TurnStarted` is the oldest boundary of the active reverse segment.
if active_segment.as_ref().is_some_and(|active_segment| {
@ -275,7 +274,6 @@ impl Session {
}
RolloutItem::EventMsg(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::SessionMeta(_) => {}
}
}

View file

@ -6,20 +6,12 @@ use codex_protocol::ThreadId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::CompactedItem;
use epiphany_state_model::EpiphanyStateItem;
use epiphany_state_model::EpiphanyThreadState;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::ResumedHistory;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
fn legacy_epiphany_state(item: EpiphanyStateItem) -> RolloutItem {
RolloutItem::LegacyEpiphanyState(
serde_json::to_value(item).expect("serialize legacy Epiphany rollout payload"),
)
}
fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message {
id: None,
@ -44,16 +36,6 @@ fn assistant_message(text: &str) -> ResponseItem {
}
}
fn sample_epiphany_state(id: &str) -> EpiphanyThreadState {
EpiphanyThreadState {
revision: 1,
objective: Some(format!("Objective for {id}")),
active_subgoal_id: Some(format!("subgoal-{id}")),
last_updated_turn_id: Some(id.to_string()),
..Default::default()
}
}
fn inter_agent_assistant_message(text: &str) -> ResponseItem {
let communication = InterAgentCommunication::new(
AgentPath::root(),
@ -114,162 +96,6 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
assert!(session.reference_context_item().await.is_none());
}
#[tokio::test]
async fn record_initial_history_resumed_accepts_legacy_epiphany_state_items() {
let (session, turn_context) = make_session_and_context().await;
let first_context_item = turn_context.to_turn_context_item();
let first_turn_id = first_context_item
.turn_id
.clone()
.expect("turn context should have turn_id");
let second_turn_id = "turn-2".to_string();
let first_epiphany = sample_epiphany_state("turn-1");
let second_epiphany = sample_epiphany_state("turn-2");
let rollout_items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(
codex_protocol::protocol::TurnStartedEvent {
turn_id: first_turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
},
)),
RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
message: "turn 1 user".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
)),
RolloutItem::TurnContext(first_context_item),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(first_turn_id.clone()),
state: first_epiphany,
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(
codex_protocol::protocol::TurnCompleteEvent {
turn_id: first_turn_id,
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
)),
RolloutItem::EventMsg(EventMsg::TurnStarted(
codex_protocol::protocol::TurnStartedEvent {
turn_id: second_turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
},
)),
RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
message: "turn 2 user".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
)),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(second_turn_id.clone()),
state: second_epiphany.clone(),
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(
codex_protocol::protocol::TurnCompleteEvent {
turn_id: second_turn_id,
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
)),
];
session
.record_initial_history(InitialHistory::Resumed(ResumedHistory {
conversation_id: ThreadId::default(),
history: rollout_items,
rollout_path: PathBuf::from("resumed.jsonl"),
}))
.await;
let _ = second_epiphany;
}
#[tokio::test]
async fn record_initial_history_resumed_accepts_out_of_band_epiphany_state_before_first_turn() {
let (session, _turn_context) = make_session_and_context().await;
let epiphany_state = sample_epiphany_state("control-plane-update");
session
.record_initial_history(InitialHistory::Resumed(ResumedHistory {
conversation_id: ThreadId::default(),
history: vec![legacy_epiphany_state(EpiphanyStateItem {
turn_id: None,
state: epiphany_state.clone(),
})],
rollout_path: PathBuf::from("resumed.jsonl"),
}))
.await;
let _ = epiphany_state;
}
#[tokio::test]
async fn record_initial_history_resumed_accepts_out_of_band_epiphany_state_after_completed_turn() {
let (session, _turn_context) = make_session_and_context().await;
let turn_id = "turn-before-control-plane-update".to_string();
let turn_state = sample_epiphany_state("turn-before-control-plane-update");
let control_plane_state = sample_epiphany_state("control-plane-update");
session
.record_initial_history(InitialHistory::Resumed(ResumedHistory {
conversation_id: ThreadId::default(),
history: vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(
codex_protocol::protocol::TurnStartedEvent {
turn_id: turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
},
)),
RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
message: "before control-plane update".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
)),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(turn_id.clone()),
state: turn_state,
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(
codex_protocol::protocol::TurnCompleteEvent {
turn_id,
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
)),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: None,
state: control_plane_state.clone(),
}),
],
rollout_path: PathBuf::from("resumed.jsonl"),
}))
.await;
let _ = control_plane_state;
}
#[tokio::test]
async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lifecycle_turn_with_missing_turn_context_id()
{
@ -454,88 +280,6 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com
);
}
#[tokio::test]
async fn reconstruct_history_ignores_legacy_epiphany_items_during_rollback() {
let (session, turn_context) = make_session_and_context().await;
let first_context_item = turn_context.to_turn_context_item();
let first_turn_id = first_context_item
.turn_id
.clone()
.expect("turn context should have turn_id");
let rolled_back_turn_id = "rolled-back-turn".to_string();
let rollout_items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(
codex_protocol::protocol::TurnStartedEvent {
turn_id: first_turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
},
)),
RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
message: "turn 1 user".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
)),
RolloutItem::TurnContext(first_context_item),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(first_turn_id.clone()),
state: sample_epiphany_state("turn-1"),
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(
codex_protocol::protocol::TurnCompleteEvent {
turn_id: first_turn_id,
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
)),
RolloutItem::EventMsg(EventMsg::TurnStarted(
codex_protocol::protocol::TurnStartedEvent {
turn_id: rolled_back_turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
},
)),
RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
message: "turn 2 user".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
)),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(rolled_back_turn_id.clone()),
state: sample_epiphany_state("turn-2"),
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(
codex_protocol::protocol::TurnCompleteEvent {
turn_id: rolled_back_turn_id,
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
)),
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(
codex_protocol::protocol::ThreadRolledBackEvent { num_turns: 1 },
)),
];
let reconstructed = session
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
.await;
assert!(reconstructed.previous_turn_settings.is_some());
}
#[tokio::test]
async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_incomplete_turn() {
let (session, turn_context) = make_session_and_context().await;
@ -1271,63 +1015,6 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
);
}
#[tokio::test]
async fn reconstruct_history_ignores_legacy_epiphany_items_during_compaction() {
let (session, turn_context) = make_session_and_context().await;
let previous_context_item = turn_context.to_turn_context_item();
let previous_turn_id = previous_context_item
.turn_id
.clone()
.expect("turn context should have turn_id");
let epiphany_state = sample_epiphany_state("turn-before-compaction");
let rollout_items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(
codex_protocol::protocol::TurnStartedEvent {
turn_id: previous_turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
},
)),
RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
message: "seed".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
},
)),
RolloutItem::TurnContext(previous_context_item),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(previous_turn_id.clone()),
state: epiphany_state.clone(),
}),
RolloutItem::EventMsg(EventMsg::TurnComplete(
codex_protocol::protocol::TurnCompleteEvent {
turn_id: previous_turn_id,
last_agent_message: None,
completed_at: None,
duration_ms: None,
time_to_first_token_ms: None,
},
)),
RolloutItem::Compacted(CompactedItem {
message: "summary after compaction".to_string(),
replacement_history: Some(vec![assistant_message("summary after compaction")]),
}),
];
let reconstructed = session
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
.await;
assert_eq!(
reconstructed.history,
vec![assistant_message("summary after compaction")]
);
}
#[tokio::test]
async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_turn_for_compaction_accounting()
{

View file

@ -75,24 +75,6 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::ConversationAudioParams;
use codex_protocol::protocol::CreditsSnapshot;
use epiphany_state_model::EpiphanyChurnState;
use epiphany_state_model::EpiphanyCodeRef;
use epiphany_state_model::EpiphanyEvidenceRecord;
use epiphany_state_model::EpiphanyGraph;
use epiphany_state_model::EpiphanyGraphCheckpoint;
use epiphany_state_model::EpiphanyGraphEdge;
use epiphany_state_model::EpiphanyGraphFrontier;
use epiphany_state_model::EpiphanyGraphLink;
use epiphany_state_model::EpiphanyGraphNode;
use epiphany_state_model::EpiphanyGraphs;
use epiphany_state_model::EpiphanyInvariant;
use epiphany_state_model::EpiphanyModeKind;
use epiphany_state_model::EpiphanyModeState;
use epiphany_state_model::EpiphanyObservation;
use epiphany_state_model::EpiphanyScratchPad;
use epiphany_state_model::EpiphanyStateItem;
use epiphany_state_model::EpiphanySubgoal;
use epiphany_state_model::EpiphanyThreadState;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::InterAgentCommunication;
@ -147,12 +129,6 @@ use std::time::Duration as StdDuration;
mod guardian_tests;
fn legacy_epiphany_state(item: EpiphanyStateItem) -> RolloutItem {
RolloutItem::LegacyEpiphanyState(
serde_json::to_value(item).expect("serialize legacy Epiphany rollout payload"),
)
}
#[test]
fn codex_session_has_no_epiphany_state_custody() {
let session_state = include_str!("../state/session.rs");
@ -169,262 +145,11 @@ fn codex_session_has_no_epiphany_state_custody() {
assert!(!codex_thread.contains("pub async fn epiphany_"));
assert!(!core_lib.contains("epiphany_rollout"));
assert!(!protocol.contains("pub use epiphany_state_model::"));
assert!(protocol.contains("LegacyEpiphanyState(serde_json::Value)"));
assert!(protocol.contains("#[serde(rename = \"epiphany_state\")]"));
assert!(!protocol.contains("LegacyEpiphanyState"));
assert!(!protocol.contains("epiphany_state"));
assert!(!protocol_manifest.contains("epiphany-state-model"));
}
fn sample_epiphany_state_for_prompt() -> EpiphanyThreadState {
EpiphanyThreadState {
revision: 7,
objective: Some("Keep the prompt path oriented around the real thread state.".to_string()),
active_subgoal_id: Some("phase2-prompt".to_string()),
subgoals: vec![
EpiphanySubgoal {
id: "phase2-prompt".to_string(),
title: "Inject the structured state into build_initial_context.".to_string(),
status: "in_progress".to_string(),
summary: Some("Wire a compact developer fragment into the initial prompt bundle.".to_string()),
},
EpiphanySubgoal {
id: "phase2-tests".to_string(),
title: "Add tests that catch prompt sludge.".to_string(),
status: "pending".to_string(),
summary: Some("Prove inclusion, omission, and bounded rendering.".to_string()),
},
],
invariants: vec![
EpiphanyInvariant {
id: "canon".to_string(),
description: "Prompt text must not silently redefine the durable state.".to_string(),
status: "active".to_string(),
rationale: Some("We want the model reading state, not cosplaying authorship of it.".to_string()),
},
EpiphanyInvariant {
id: "bounded".to_string(),
description: "The injected summary must stay compact and deterministic.".to_string(),
status: "active".to_string(),
rationale: Some("The whole point is to avoid turning the prompt into another haunted attic.".to_string()),
},
],
graphs: EpiphanyGraphs {
architecture: EpiphanyGraph {
nodes: vec![
EpiphanyGraphNode {
id: "arch-session".to_string(),
title: "Session loop".to_string(),
purpose: "Owns the turn setup and initial prompt bundle.".to_string(),
mechanism: Some("Builds developer/contextual-user fragments before the first model request.".to_string()),
metaphor: Some("The foreman lining tools up before the crew starts swinging.".to_string()),
status: Some("verified".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/session/mod.rs"),
start_line: Some(2433),
end_line: Some(2617),
symbol: Some("build_initial_context".to_string()),
note: Some("Phase 2 injection seam".to_string()),
}],
},
EpiphanyGraphNode {
id: "arch-context".to_string(),
title: "Context fragments".to_string(),
purpose: "Render marked developer and contextual-user fragments.".to_string(),
mechanism: Some("Small typed renderers turn state into bounded prompt sections.".to_string()),
metaphor: Some("The bins on the wall where the right parts live.".to_string()),
status: Some("verified".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/context/epiphany_state_instructions.rs"),
start_line: Some(1),
end_line: Some(220),
symbol: Some("EpiphanyStateInstructions".to_string()),
note: Some("New renderer".to_string()),
}],
},
],
edges: vec![EpiphanyGraphEdge {
source_id: "arch-session".to_string(),
target_id: "arch-context".to_string(),
kind: "renders".to_string(),
id: Some("edge-session-context".to_string()),
label: Some("injects".to_string()),
mechanism: Some("Calls the Epiphany renderer when thread state exists.".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/session/mod.rs"),
start_line: Some(2502),
end_line: Some(2510),
symbol: Some("build_initial_context".to_string()),
note: Some("Epiphany insertion slot".to_string()),
}],
}],
},
dataflow: EpiphanyGraph {
nodes: vec![
EpiphanyGraphNode {
id: "flow-state".to_string(),
title: "Epiphany state snapshot".to_string(),
purpose: "Carries durable objective, graph, evidence, and churn state across turns.".to_string(),
mechanism: Some("Lives on SessionState and comes back during resume.".to_string()),
metaphor: Some("The map pinned to the workshop wall so nobody wanders off whistling.".to_string()),
status: Some("verified".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/state/session.rs"),
start_line: Some(36),
end_line: Some(121),
symbol: Some("SessionState::epiphany_state".to_string()),
note: Some("Stored thread state".to_string()),
}],
},
EpiphanyGraphNode {
id: "flow-prompt".to_string(),
title: "Prompt fragment".to_string(),
purpose: "Turns the durable state into a bounded developer instruction block.".to_string(),
mechanism: Some("Summarizes only active and recent state with line-anchored refs.".to_string()),
metaphor: Some("A folded field guide, not the entire filing cabinet.".to_string()),
status: Some("in_progress".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/context/epiphany_state_instructions.rs"),
start_line: Some(1),
end_line: Some(220),
symbol: Some("render_epiphany_state".to_string()),
note: Some("Summary shaping".to_string()),
}],
},
],
edges: vec![EpiphanyGraphEdge {
source_id: "flow-state".to_string(),
target_id: "flow-prompt".to_string(),
kind: "summarized-as".to_string(),
id: Some("edge-state-prompt".to_string()),
label: Some("bounded summary".to_string()),
mechanism: Some("Keep frontier, evidence, and churn in view without dumping raw JSON.".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/context/epiphany_state_instructions.rs"),
start_line: Some(32),
end_line: Some(220),
symbol: Some("render_epiphany_state".to_string()),
note: Some("Prompt shaping".to_string()),
}],
}],
},
links: vec![EpiphanyGraphLink {
dataflow_node_id: "flow-prompt".to_string(),
architecture_node_id: "arch-context".to_string(),
relationship: Some("implemented-by".to_string()),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/context/epiphany_state_instructions.rs"),
start_line: Some(1),
end_line: Some(220),
symbol: Some("EpiphanyStateInstructions".to_string()),
note: Some("Renderer implementation".to_string()),
}],
}],
},
graph_frontier: Some(EpiphanyGraphFrontier {
active_node_ids: vec!["arch-session".to_string(), "flow-prompt".to_string()],
active_edge_ids: vec!["edge-session-context".to_string()],
open_question_ids: vec!["q-boundary".to_string()],
open_gap_ids: vec!["gap-resume-proof".to_string()],
dirty_paths: vec![
PathBuf::from("core/src/session/mod.rs"),
PathBuf::from("core/src/context/epiphany_state_instructions.rs"),
],
}),
graph_checkpoint: Some(EpiphanyGraphCheckpoint {
checkpoint_id: "phase2-checkpoint".to_string(),
graph_revision: 7,
summary: Some("Prompt integration is the current frontier; GUI and retrieval stay dark.".to_string()),
frontier_node_ids: vec!["arch-session".to_string(), "flow-prompt".to_string()],
open_question_ids: vec!["q-boundary".to_string()],
open_gap_ids: vec!["gap-resume-proof".to_string()],
}),
investigation_checkpoint: None,
job_bindings: Vec::new(),
acceptance_receipts: Vec::new(),
runtime_links: 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()),
next_probe: Some("Verify resumed sessions still inject the latest surviving snapshot.".to_string()),
notes: vec![
"Do not dump raw JSON.".to_string(),
"Do not invent a public toggle yet.".to_string(),
],
}),
observations: vec![
EpiphanyObservation {
id: "obs-session-seam".to_string(),
summary: "build_initial_context already composes developer fragments in one place.".to_string(),
source_kind: "code-read".to_string(),
status: "accepted".to_string(),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/session/mod.rs"),
start_line: Some(2433),
end_line: Some(2617),
symbol: Some("build_initial_context".to_string()),
note: Some("Prompt assembly".to_string()),
}],
evidence_ids: vec!["ev-session-read".to_string()],
},
EpiphanyObservation {
id: "obs-state-seam".to_string(),
summary: "Phase 1 already restores Epiphany state before future turns build context.".to_string(),
source_kind: "test".to_string(),
status: "accepted".to_string(),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/session/rollout_reconstruction.rs"),
start_line: Some(1),
end_line: Some(220),
symbol: Some("RolloutReconstruction".to_string()),
note: Some("Resume path".to_string()),
}],
evidence_ids: vec!["ev-resume-read".to_string()],
},
],
recent_evidence: vec![
EpiphanyEvidenceRecord {
id: "ev-session-read".to_string(),
kind: "research".to_string(),
status: "ok".to_string(),
summary: "Confirmed the prompt builder already has a clean insertion point after collaboration-mode instructions.".to_string(),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/session/mod.rs"),
start_line: Some(2498),
end_line: Some(2511),
symbol: Some("build_initial_context".to_string()),
note: Some("Insertion window".to_string()),
}],
},
EpiphanyEvidenceRecord {
id: "ev-resume-read".to_string(),
kind: "test".to_string(),
status: "ok".to_string(),
summary: "Phase 1 tests already prove the latest surviving Epiphany snapshot is restored on resume.".to_string(),
code_refs: vec![EpiphanyCodeRef {
path: PathBuf::from("core/src/session/rollout_reconstruction_tests.rs"),
start_line: Some(110),
end_line: Some(191),
symbol: Some("record_initial_history_resumed_hydrates_epiphany_state_from_latest_surviving_turn".to_string()),
note: Some("Resume proof".to_string()),
}],
},
],
churn: Some(EpiphanyChurnState {
understanding_status: "stable".to_string(),
diff_pressure: "low".to_string(),
graph_freshness: Some("fresh".to_string()),
warning: Some("Keep the fragment bounded; do not turn it into another transcript dump.".to_string()),
unexplained_writes: Some(0),
}),
retrieval: None,
mode: Some(EpiphanyModeState {
name: "epiphany".to_string(),
kind: Some(EpiphanyModeKind::Default),
}),
planning: Default::default(),
last_updated_turn_id: Some("phase2-turn".to_string()),
}
}
struct InstructionsTestCase {
slug: &'static str,
expects_apply_patch_description: bool,
@ -577,10 +302,6 @@ fn developer_input_texts(items: &[ResponseItem]) -> Vec<&str> {
.collect()
}
fn joined_developer_input_text(items: &[ResponseItem]) -> String {
developer_input_texts(items).join("\n\n")
}
fn user_input_texts(items: &[ResponseItem]) -> Vec<&str> {
items
.iter()
@ -5152,7 +4873,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
}
#[tokio::test]
async fn record_context_updates_does_not_persist_epiphany_state() {
async fn record_context_updates_persists_turn_context() {
let (session, turn_context) = make_session_and_context().await;
let config = session.get_config().await;
let recorder = RolloutRecorder::new(
@ -5195,59 +4916,6 @@ async fn record_context_updates_does_not_persist_epiphany_state() {
.iter()
.any(|item| matches!(item, RolloutItem::TurnContext(_)))
);
assert!(
!resumed
.history
.iter()
.any(|item| matches!(item, RolloutItem::LegacyEpiphanyState(_)))
);
}
#[tokio::test]
async fn record_context_updates_and_set_reference_context_item_skips_epiphany_state_when_none() {
let (session, turn_context) = make_session_and_context().await;
let config = session.get_config().await;
let recorder = RolloutRecorder::new(
config.as_ref(),
RolloutRecorderParams::new(
ThreadId::default(),
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
Vec::new(),
EventPersistenceMode::Limited,
),
/*state_db_ctx*/ None,
/*state_builder*/ None,
)
.await
.expect("create rollout recorder");
let rollout_path = recorder.rollout_path().to_path_buf();
{
let mut rollout = session.services.rollout.lock().await;
*rollout = Some(recorder);
}
session
.record_context_updates_and_set_reference_context_item(&turn_context)
.await;
session.ensure_rollout_materialized().await;
session.flush_rollout().await.expect("rollout should flush");
let InitialHistory::Resumed(resumed) = RolloutRecorder::get_rollout_history(&rollout_path)
.await
.expect("read rollout history")
else {
panic!("expected resumed rollout history");
};
assert!(
!resumed
.history
.iter()
.any(|item| matches!(item, RolloutItem::LegacyEpiphanyState(_))),
"epiphany state should not be persisted when session state is empty"
);
}
#[tokio::test]
@ -5323,59 +4991,6 @@ async fn build_initial_context_prepends_model_switch_message() {
assert!(text.contains("<model_switch>"));
}
#[tokio::test]
async fn build_initial_context_omits_epiphany_state_block_when_absent() {
let (session, turn_context) = make_session_and_context().await;
let initial_context = session.build_initial_context(&turn_context).await;
let developer_text = joined_developer_input_text(&initial_context);
assert!(!developer_text.contains("<epiphany_state>"));
}
#[tokio::test]
async fn build_initial_context_does_not_inject_resumed_epiphany_state() {
let (session, turn_context) = make_session_and_context().await;
let turn_context_item = turn_context.to_turn_context_item();
let turn_id = turn_context_item
.turn_id
.clone()
.expect("turn context should have turn_id");
let epiphany_state = sample_epiphany_state_for_prompt();
session
.record_initial_history(InitialHistory::Resumed(ResumedHistory {
conversation_id: ThreadId::default(),
history: vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: turn_id.clone(),
started_at: None,
model_context_window: Some(128_000),
collaboration_mode_kind: ModeKind::Default,
})),
RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
message: "resume the thread".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
})),
RolloutItem::TurnContext(turn_context_item),
legacy_epiphany_state(EpiphanyStateItem {
turn_id: Some(turn_id),
state: epiphany_state,
}),
],
rollout_path: PathBuf::from("/tmp/phase2-resume.jsonl"),
}))
.await;
let initial_context = session.build_initial_context(&turn_context).await;
let developer_text = joined_developer_input_text(&initial_context);
assert!(!developer_text.contains("<epiphany_state>"));
assert!(!developer_text.contains("Checkpoint: `phase2-checkpoint` (graph revision 7)"));
}
#[tokio::test]
async fn record_context_updates_and_set_reference_context_item_persists_full_reinjection_to_rollout()
{

View file

@ -94,8 +94,6 @@ pub const ENVIRONMENT_CONTEXT_OPEN_TAG: &str = "<environment_context>";
pub const ENVIRONMENT_CONTEXT_CLOSE_TAG: &str = "</environment_context>";
pub const COLLABORATION_MODE_OPEN_TAG: &str = "<collaboration_mode>";
pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
pub const EPIPHANY_STATE_OPEN_TAG: &str = "<epiphany_state>";
pub const EPIPHANY_STATE_CLOSE_TAG: &str = "</epiphany_state>";
pub const REALTIME_CONVERSATION_OPEN_TAG: &str = "<realtime_conversation>";
pub const REALTIME_CONVERSATION_CLOSE_TAG: &str = "</realtime_conversation>";
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
@ -2943,8 +2941,6 @@ pub enum RolloutItem {
ResponseItem(ResponseItem),
Compacted(CompactedItem),
TurnContext(TurnContextItem),
#[serde(rename = "epiphany_state")]
LegacyEpiphanyState(serde_json::Value),
EventMsg(EventMsg),
}
@ -5103,27 +5099,6 @@ mod tests {
Ok(())
}
#[test]
fn legacy_epiphany_state_envelope_preserves_opaque_payload() -> Result<()> {
let payload = json!({
"turn_id": "turn-123",
"state": {
"revision": 7,
"objective": "historical compatibility only"
}
});
let item = RolloutItem::LegacyEpiphanyState(payload.clone());
let encoded = serde_json::to_value(&item)?;
assert_eq!(encoded["type"], "epiphany_state");
assert_eq!(encoded["payload"], payload);
let reparsed: RolloutItem = serde_json::from_value(encoded)?;
let RolloutItem::LegacyEpiphanyState(reparsed_payload) = reparsed else {
panic!("expected opaque legacy Epiphany rollout payload");
};
assert_eq!(reparsed_payload, payload);
Ok(())
}
/// Serialize Event to verify that its JSON representation has the expected
/// amount of nesting.
#[test]

View file

@ -1127,9 +1127,6 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result<HeadTai
RolloutItem::TurnContext(_) => {
// Not included in `head`; skip.
}
RolloutItem::LegacyEpiphanyState(_) => {
// Not included in `head`; skip.
}
RolloutItem::Compacted(_) => {
// Not included in `head`; skip.
}
@ -1186,7 +1183,6 @@ pub async fn read_head_for_summary(path: &Path) -> io::Result<Vec<serde_json::Va
}
RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::EventMsg(_) => {}
}
}

View file

@ -73,7 +73,6 @@ pub fn builder_from_items(
RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::EventMsg(_) => None,
}) && let Some(builder) = builder_from_session_meta(session_meta, rollout_path)
{
@ -128,7 +127,6 @@ pub async fn extract_metadata_from_rollout(
RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::EventMsg(_) => None,
}),
parse_errors,

View file

@ -18,7 +18,6 @@ pub fn is_persisted_response_item(item: &RolloutItem, mode: EventPersistenceMode
// Persist Codex executive markers so we can analyze flows (e.g., compaction, API turns).
RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::SessionMeta(_) => true,
}
}

View file

@ -880,9 +880,6 @@ impl RolloutRecorder {
RolloutItem::TurnContext(item) => {
items.push(RolloutItem::TurnContext(item));
}
RolloutItem::LegacyEpiphanyState(item) => {
items.push(RolloutItem::LegacyEpiphanyState(item));
}
RolloutItem::EventMsg(_ev) => {
items.push(RolloutItem::EventMsg(_ev));
}
@ -1846,7 +1843,6 @@ async fn resume_candidate_matches_cwd(
RolloutItem::SessionMeta(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::EventMsg(_) => None,
})
{

View file

@ -22,7 +22,7 @@ pub fn apply_rollout_item(
RolloutItem::TurnContext(turn_ctx) => apply_turn_context(metadata, turn_ctx),
RolloutItem::EventMsg(event) => apply_event_msg(metadata, event),
RolloutItem::ResponseItem(item) => apply_response_item(metadata, item),
RolloutItem::Compacted(_) | RolloutItem::LegacyEpiphanyState(_) => {}
RolloutItem::Compacted(_) => {}
}
if metadata.model_provider.is_empty() {
metadata.model_provider = default_provider.to_string();
@ -38,8 +38,7 @@ pub fn rollout_item_affects_thread_metadata(item: &RolloutItem) -> bool {
) => true,
RolloutItem::EventMsg(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::LegacyEpiphanyState(_) => false,
| RolloutItem::Compacted(_) => false,
}
}

View file

@ -973,7 +973,6 @@ pub(super) fn extract_dynamic_tools(items: &[RolloutItem]) -> Option<Option<Vec<
RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::EventMsg(_) => None,
})
}
@ -984,7 +983,6 @@ pub(super) fn extract_memory_mode(items: &[RolloutItem]) -> Option<String> {
RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::LegacyEpiphanyState(_)
| RolloutItem::EventMsg(_) => None,
})
}