Remove legacy Epiphany state from Codex protocol
This commit is contained in:
parent
d6ebed2c01
commit
febef826c0
20 changed files with 147 additions and 828 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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
1
vendor/codex/codex-rs/Cargo.lock
generated
vendored
|
|
@ -2394,7 +2394,6 @@ dependencies = [
|
|||
"dirs",
|
||||
"dunce",
|
||||
"env-flags",
|
||||
"epiphany-state-model",
|
||||
"eventsource-stream",
|
||||
"futures",
|
||||
"http 1.4.0",
|
||||
|
|
|
|||
|
|
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
vendor/codex/codex-rs/core/Cargo.toml
vendored
1
vendor/codex/codex-rs/core/Cargo.toml
vendored
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
391
vendor/codex/codex-rs/core/src/session/tests.rs
vendored
391
vendor/codex/codex-rs/core/src/session/tests.rs
vendored
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
25
vendor/codex/codex-rs/protocol/src/protocol.rs
vendored
25
vendor/codex/codex-rs/protocol/src/protocol.rs
vendored
|
|
@ -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]
|
||||
|
|
|
|||
4
vendor/codex/codex-rs/rollout/src/list.rs
vendored
4
vendor/codex/codex-rs/rollout/src/list.rs
vendored
|
|
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
1
vendor/codex/codex-rs/rollout/src/policy.rs
vendored
1
vendor/codex/codex-rs/rollout/src/policy.rs
vendored
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
{
|
||||
|
|
|
|||
5
vendor/codex/codex-rs/state/src/extract.rs
vendored
5
vendor/codex/codex-rs/state/src/extract.rs
vendored
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue