Skip to content

Technical

How Elpis
works.

The full project write-up—context engineering, continuity, memory, providers, work graphs, and the evidence behind them.

Never lose a thread again.

You run an agent inside Elpis, and it becomes Elpis.

Elpis is an open-source fork of OpenAI's Codex CLI that keeps the proven execution foundation while adding explicit context control, durable continuity, auditable pruning, and provider-neutral ownership around the model loop.

Linux verification License Telemetry

InstallFeaturesEvaluationDocs

Contents

Quickstart

Linux x86_64 preview:

curl -fsSL https://raw.githubusercontent.com/MasihMoafi/Elpis/main/scripts/install-elpis.sh | bash && ~/.local/bin/elpis

The installer picks the right binary for your machine and installs RTK, which powers shell-output filtering. On first launch, choose a provider and sign in or enter its API key.

v0.1.2 is the current release.

What is Elpis

Elpis is a provider-neutral coding-agent environment. The selected model or runtime performs inference; Elpis owns the surrounding working state: context admission, continuity, memory, permissions, tools, evidence, and the terminal interface.

It starts from OpenAI's Apache-2.0 Codex CLI and preserves its execution foundation — terminal UI, patches, permissions, sandboxing, sessions, and tool lifecycle — while adding a continuity- first control layer around it. Change the provider without throwing away the project context. Nothing about the project has to be explained twice.

Different paths. Same roots. One shared project.

Why Elpis

Long sessions fill up with transcripts, file reads, searches, command output, and dead ends. The useful state gets buried in the story of how the agent reached it, while every request pays for more context.

Agents using excessive tool calls and tokens to gather redundant context

Elpis separates the active working set from durable evidence. The next request receives a small, inspectable context; the exact record stays on disk and can be retrieved when it is needed.

Three paired configured historical runs with automatic pruning enabled under the superseded high-frequency setup used one byte-identical prompt, the same model, and the same source commit on both arms. In those runs, peak context per request fell 47–65%; median context stabilized at 26.6–27.1%. Codex peaked above 90% of the window in each run, while Elpis stayed safely bounded in the green zone.

Elpis never modifies a model's own output or a request already in flight. Pruning rewrites only harness-supplied tool output, using a separate model instance sequenced against the main agent.

Core Features

Context engineering

Context is a budgeted working set, not a dumped transcript. Elpis makes admission visible and uses a layered pipeline to keep useful findings while removing disposable exploration:

Elpis Context Architecture

Layer What it does When
1. RTK shell-output filtering Compacts supported command output before it reaches the model. Before the agent sees it
2. Deterministic safety cap Bounds exceptionally large tool results. This is inherited from Codex. Before the agent sees it
3. Ace pruning — Experimental Selectively rewrites eligible old tool evidence toward a safe working-set target, preserving the latest context and an evidence pointer. Manual /prune or /force-prune; automatic pressure cycling only in a conversation started with the default-off setting enabled

/prune and /force-prune are explicit manual Ace actions and do not rewrite user instructions, assistant messages, or model reasoning. /compact immediately runs Codex native compaction; it is independent of Ace pruning. Automatic native compaction uses the model-window threshold and usable-window headroom. Automatic Ace pruning is Experimental and off by default; /settings saves its value for the next conversation.

Ace Pruning and Context Lifecycle

What a pruning decision looks like

Task 1 Context Flow and Pruning Lifecycle

One real pass from disk. A search command whose raw output ran to 18,930 characters — close to 5,000 tokens carried across requests:

Before — what the model was carrying:

Script completed · Wall time 0.1 seconds · Output:

tui/src/external_agent_config_migration.rs:800:   item_type: …ItemType::AgentsMd,
tui/src/external_agent_config_migration_flow.rs:75: …ItemType::AgentsMd
tui/src/theme_picker.rs:283:  fn theme_picker_subtitle(home: …) -> String
tui/src/theme_picker.rs:392:     subtitle: Some(theme_picker_subtitle(
tui/src/theme_picker.rs:605:     let subtitle = theme_picker_subtitle(…, Some(200));
tui/src/theme_picker.rs:617:     let subtitle = theme_picker_subtitle(…, Some(140));
tui/src/app_event.rs:152:        OpenAgentPicker,
… roughly two hundred more lines of the same shape …

After — what the model carries on the next request:

[Ace pruned 231 lines of ripgrep output (18,930 chars → 248 chars).
Findings:
- Found ItemType::AgentsMd in external_agent_config_migration.rs:800
- Found theme_picker_subtitle definitions in theme_picker.rs:283,392,605,617
- Full raw output preserved in rollout evidence: rollout://sess-01j8/tool-14.log]

Context Ledger and observability

The Context Ledger (Tab; during an active turn, Alt+C always toggles it) lists admitted goals, rules, memory, and other portable sources with their byte sizes and capped character-derived estimates. Toggling a row writes admission.toml, which controls what the next turn receives.

Development rules are ordinary Markdown Ledger rows, not skills: newly discovered rules start included and an explicit exclusion persists. A nonempty configured development-rule root list replaces the managed fallback; an empty list uses it. Elpis leaves ordinary and bundled skills off by product default, while deliberate user configuration can enable a selected skill. Enabled skills expose compact metadata and keep their bodies lazy; /skills shows available candidates and their origins, but mentions and the model-visible list contain enabled skills only. The Ledger has no skills-catalog token row; its per-source estimates are not tokenizer measurements.

The Context Ledger listing admitted instruction files with their token counts and included state

/context answers a different question: where the window went. It displays token usage by user messages, agent responses, tool calls, system prompt, Development rules, and free space, alongside available backtrack checkpoints.

The /context view showing token usage by category and available backtrack checkpoints

Sessions and continuity

Keep the working context across model switches, compaction, and restarts:

  • GOAL.md holds the current task. It is carried into each request, stays visible across compaction, and is editable during a run.
  • ES.md is an event-derived executive summary. It records modified files, commands run, blockers, and next steps, and is updated as the run progresses.
  • Exact resume continues an existing thread with its full history, using the provider-native session when one is available.
  • Lean continuation starts a clean thread from the current GOAL.md, ES.md, and active rules. This sheds old exploration without losing the objective.

Memory

Durable memory is one Markdown file, MEMORY.md, in the Elpis memory directory (derived from CODEX_HOME). The Context Ledger discovers it and lists it as a row, switched off until you admit it: like every optional row, memory does not reach the model unasked.

  • One visible file. Plain text. Read it, edit it, commit it to git, or delete it.
  • Admitted in the open. Because it is a Ledger row, you can always see whether memory reached the model, switch it on when you want it, and drop it when you do not.
  • Retrieval beyond that file is your choice. Register an MCP server — for example rag-mcp-lancedb — and Elpis will use it.

Elpis previously ran an extraction, consolidation, and promotion pipeline. It was removed because it did not work: across two threshold settings it produced zero durable promotions, every sweep landing one recall short of the gate. Memory that rewrites itself in the background without appearing anywhere is the failure mode the Ledger row exists to prevent.

Deterministic work graphs

A coordinator can fan work out to several agents under an engine that validates the plan before anything runs. This is Elpis's own; it is not part of the Codex foundation.

Elpis deterministic work graph

The coordinator submits a complete task graph — tasks, dependencies, write scopes, acceptance criteria, and environments. Elpis then owns the scheduling:

  • Cycles cannot be scheduled. Kahn's topological algorithm proves the graph is acyclic and rejects it otherwise, so no worker is created for a plan that could only deadlock.
  • Write conflicts are caught by construction. Path-prefix intersection detects overlapping write scopes, and all writable tasks in one environment are serialized even when their declared prefixes do not overlap.
  • Verification is not optional. A writable task without a directly dependent verify task in the same environment is rejected before dispatch.
  • Evidence gates progress. Dependent work is released only after an accepted result; a failed, cancelled, or blocked prerequisite blocks its descendants.

Elpis never creates, merges, rebases, deletes, or pushes branches or worktrees. Preparing and integrating them stays coordinator-owned, because those operations change durable user state and deserve deliberate review.

Off by default. Enable with enable_fanout = true under [features]; there is no slash command. Full rules and the graph schema are in docs/WORK_GRAPHS.md.

Bring your own provider

Elpis is not tied to a single model vendor:

  • OpenAI: GPT-4o, GPT-5.6-Luna, o1, o3, and compatible endpoints.
  • Anthropic: Claude 3.5 Sonnet, Claude 3 Opus, Claude 3.5 Haiku.
  • Google: Gemini 2.0 Flash, Gemini 1.5 Pro.
  • Local & self-hosted: Ollama, vLLM, and any OpenAI-compatible server.

Switch models mid-session without restarting. The working context, goal, and session memory are preserved across provider boundaries.

Integrations and tools

Extend Elpis with external capabilities that stay in their own processes through MCP:

  • Workspace retrieval: rag-mcp-lancedb provides local LanceDB/Tantivy search over your documents.
  • Voice transcription: WhisperType provides local speech-to-text without adding its model/runtime dependencies to Elpis core.

Privacy and ownership

Telemetry is off by default and no analytics are uploaded unless you explicitly configure an exporter. Bring your own provider keys. Durable Elpis state is local files and SQLite that you can inspect, edit, export, or delete.

Evaluation status

The published evaluation reports three paired, byte-identical configured historical workloads with automatic pruning enabled under the superseded high-frequency setup, on gpt-5.6-luna (258,400 token context window).

RQ1: Context Reduction & Operating Hygiene

Across those configured historical runs, Elpis maintained working sets within safe operational thresholds.

Peak Context Utilization

In those configured historical runs, Codex expanded into the critical danger zone (>90% window) in every run, forcing 3 emergency compactions. Elpis maintained peak window utilization at 32.5–49.5%, achieving a 47–65% reduction in peak context footprint:

Peak Context Window Utilization (Elpis vs. Codex)

Input Token Distribution & Interquartile Stability

In those configured historical runs, Codex suffered wide distribution variance as transcripts accumulated, while Elpis tightly stabilized median token input at 68.8k–69.6k tokens (26.6%–27.0% of the window):

Input Tokens per Model Call (Interquartile Range & Median across 3 Runs)

Trajectory Dynamics across Context Health Bands

When normalized across the request lifecycle (0% to 100% completion), Codex exhibits unbounded monotonic growth until emergency rollover occurs. The Elpis trace shown here is a configured historical run with automatic pruning enabled under the superseded high-frequency setup; it is not current default behavior:

Normalized Task-Progress View (0%–100% Sequence Overlay)

Operating Zone Breakdown

Across those configured historical requests, Elpis spent over 95% of its operating lifespan inside the safe and healthy bands, with zero requests entering the critical danger zone:

Context operating zones by run

RQ2 & RQ3: Target Retention & Task Quality

  • RQ2 (Information Retention): In benchmark audits testing recall of key file paths, schemas, and error signatures after pruning, 100% of tested targets (6/6) were retained intact in active context.
  • RQ3 (Task Performance): Not established. The executed runs are incomplete and unreplicated, so they do not support a comparative correctness claim in either direction. No per-arm score is reported, and there is no evidence that pruning improves task completion or output quality.

RQ4: Pruning Overhead & Token Economics

Pruning adds an auxiliary model call sequenced against the main agent, and rewriting history invalidates the provider's cached prefix. Both costs are real. The figures below are configured historical runs with automatic pruning enabled under the superseded high-frequency setup; they bound that configuration's penalty rather than describe the current default: 730,810 auxiliary tokens spent to reclaim 605,377 context tokens (0.83 reclaimed per spent token).

What Pruning Spent to Hold That Window (41-Pass Breakdown)

RQ5: Forensic Auditability

Every pruning event produces an immutable audit record on disk under ~/.elpis/logs/pruning/. In full forensic reconstruction evaluations, 7 of 9 properties were completely recoverable from disk, 2 partial, and 0 absent.

Research Question Empirical Finding
RQ1 — Context Efficiency Historical superseded high-frequency setup: peak reduction of 47–65%; median context stabilized at 26.6–27.1% of the 258k window.
RQ2 — Information Retention 6/6 tested post-prune targets preserved intact (100% retention).
RQ3 — Task Performance Not established. The available runs do not support a comparative correctness claim.
RQ4 — Pruning Economics Penalty established, current magnitude open. The measured figures describe a superseded high-frequency configuration.
RQ5 — Forensic Auditability 7/9 properties fully recoverable from local rollout evidence; 0 lost records.

Documentation

License

Apache-2.0.

The execution foundation — terminal UI, patches, permissions, sandboxing, and sessions — derives from OpenAI's Apache-2.0 Codex CLI. Elpis extends that foundation with context admission and pruning, continuity checkpoints, auditable evidence, and provider control. Codex-derived source retains its upstream notices under codex-rs/.

Elpis Context Sovereignty & 4-Layer Pruning Pipeline

Elpis enforces Context Sovereignty: the principle that context is a strictly budgeted working set, not a dumped chat transcript. The user maintains live visibility and explicit control over every byte admitted to the agent's context window.


1. Systemic Role in Elpis

Context management acts as the primary gatekeeper between raw workspace/session events and the active model inference loop:


2. The 4-Layer Pruning Pipeline

Long agent sessions accumulate dead ends, voluminous search results, and repetitive file reads. Elpis separates working context from durable evidence.

Layer 3 is a single trigger, run as a gated cycle rather than continuously. An earlier "steady" trigger also fired on backlog size alone, independently of how full the window was; it was removed because it produced runs of tiny passes inside the healthy 20-30% band, and every pass discards the reusable prompt-cache prefix past its first rewritten item. The case it was meant to cover -- a single turn that balloons past the boundary without ever ending -- is already handled here, because the eligible region is cut by recency rather than at a turn boundary. See cache-friendly-pruning.md.

Pipeline Layer Comparison

Layer Trigger Scope Behavior Failure Recovery
1. RTK Filter Tool execution Shell output (rg, git status, find) Compacts raw command output using pattern filters before the agent sees it. Fallback to unfiltered output on tool error.
2. Safety Cap Tool execution All raw tool outputs Hard-truncates exceptionally large output blobs to protect context limits. Inherited from Codex, unchanged. Preserves header & footer with truncation notice.
3. Ace Pressure Cycle Exact model-window use reaches 30% (70% remaining), and a previous cycle has since been seen below 30% Oldest eligible tool exploration, including the turn still running, but never a sealed epoch Selects only enough old tool evidence to target roughly 20% use (80% remaining); the newest 10% of the window stays verbatim. Useful results become a compact conclusion plus an evidence pointer; dead ends leave working context entirely. It reaches into the current turn, because a single tool-driven turn can cross the boundary without ever ending. One cycle gets at most 2 Ace passes, spent back to back; the cycle then closes and cannot reopen until use has been measured below 30% and has climbed back to it. Each applied pass seals its region with an epoch marker that later passes may not touch. A failed pass changes nothing. Once the cycle's 2-pass budget is spent, or when nothing reclaimable remains at this boundary, Elpis requests native compaction rather than let the window drift toward the model's hard limit.

All three layers ship with Elpis. Layer 1 runs through RTK, which is a separate binary: scripts/install-elpis.sh installs it alongside Elpis (skip with ELPIS_SKIP_RTK=1), and on a launch that finds rtk on PATH with no ~/.elpis/hooks.json of your own, Elpis writes the PreToolUse hook that calls rtk hook claude. It then passes the normal startup hook review before it can run. An existing hooks.json is never modified, so {"hooks":{}} opts out permanently, and Elpis's hook runtime (codex-rs/hooks/src/events/pre_tool_use.rs) is what accepts RTK's rewrite response.

The Ace pass runs between model follow-ups as well as at the end of a turn, so one long-running tool-driven turn cannot skip the trigger. Each pass records which trigger fired (manual or pressure) in its manifest and report. OpenAI-backed passes use Luna at maximal reasoning effort (PRUNE_REASONING_EFFORT = ReasoningEffort::Max). Every successful pass immediately recomputes the working history estimate and writes prune_report.md alongside the session logs (codex-rs/core/src/session/context_prune_audit.rs). When a pressure pass runs during an active turn, it keeps the newest 10% of the window verbatim so the current follow-up observations remain intact while older evidence is pruned.

/prune runs the Ace pass on demand across eligible tool evidence from completed turns. It keeps user and assistant messages, the current turn, and durable rollout evidence. /compact is Elpis-owned conservative cleanup. It first runs the audited tool-evidence pass, then asks Luna Max to mark older whole conversation messages as KEEP or DELETE. The latest turn is protected; kept content is copied verbatim; incomplete, malformed, or uncertain decisions leave conversation history unchanged. A successful deletion starts a new window while the raw transcript remains intact. An explicit custom compact_prompt retains the upstream summary path as an opt-out. The Context Ledger's exact used-token number is authoritative after either path.

Ace pass audit trail

Every applied Ace pass writes an immutable audit before the working history changes. If that audit cannot be written, Elpis keeps the working history and does not record the pass as applied.

Elpis immutable audit trail

You do not have to go looking for these: prune_report.md renders ace.json and manifest.json as clickable links (context_prune_audit.rs). The audit deliberately omits the system prompt, skills, and transcript, so it stays readable.


3. Context Lifetimes

Every item admitted into Elpis context carries an explicit lifetime:

Lifetime Tier Scope & Source Files Eviction & Survival Behavior
1. Durable Lifetime AGENTS.md rules, active GOAL.md, MEMORY.md, explicit user constraints Survives across compaction, model switches, and restarts.
2. Task Lifetime Standing decisions, modified files, blockers, ES.md checkpoint Survives across turns within the task; summarized into ES.md on task transition.
3. Turn Lifetime Terminal reads, searches, directory listings, build outputs, git diffs Expires immediately after the active turn question is answered; raw output is preserved in disk logs (rollout:// evidence).
  1. Durable: Survives across compaction, model switches, and restarts.
  2. Task: Survives across turn execution within the current task; summarized into ES.md upon task transition.
  3. Turn: Expires immediately after the active turn question is answered. Raw output is evicted from working context, leaving behind an exact evidence pointer (rollout ID / log path).

4. Context Ledger (Tab / Alt+C) & admission.toml

Elpis provides interactive context admission control in the TUI:

  • Context Ledger Panel (Tab or Alt+C): A side panel shown by default, listing every admitted portable context source with exact byte sizes and the percentage of the model context window in use. It is 52 columns wide, narrowing to a proportional slice on smaller terminals so the composer keeps room. While a turn is running, Tab defers to the composer's queue-the-draft action; Alt+C always toggles the ledger.
  • admission.toml Control: Toggling a row in the ledger writes ~/.elpis/context/workspaces/<workspace>/admission.toml, which dynamically governs next-turn admission for:
    • GOAL.md (Active Goal)
    • ES.md (Executive Summary)
    • Global & project-level AGENTS.md rules
    • Individual portable development rules installed by Elpis (~/.elpis/skills/dev/*.md)

Elpis embeds and installs its portable development rules on first launch and refreshes the managed files when the binary changes. The installed directory is the single default source; a project-sibling skills/dev is not scanned, so a development checkout cannot double-admit the same rules. Machine-specific additions remain opt-in through ELPIS_DEV_SKILLS_DIRS.

The Context Ledger listing admitted instruction files with their token counts and included state

/context — where the window went

The ledger answers what is admitted. /context answers what filled the window: token usage as a grid broken down by category — user messages, agent responses, tool calls, system prompt, skills, and free space — alongside the backtrack checkpoints available via Esc Esc. The two are separate surfaces and neither replaces the other.

/context showing token usage as a grid, broken down by category, with available backtrack checkpoints

Context Accounting Contract

Elpis exposes one single source of truth for context measurement:

  • Displayed percentages explicitly state whether they mean used or remaining.
  • The percentage is computed against the model's own context window — used tokens over context window (codex-rs/tui/src/chatwidget/context_ledger.rs) — never against transcript length.
  • It is reported in the Context Ledger. The persistent identity header carries product, model, and location only (Elpis · model {model} · location {cwd}); the inherited footer status line is deliberately suppressed so there is exactly one place to read the number.
  • /usage enumerates admitted sources, byte sizes, and lifetime reasons.

5. Systemic Inter-Dependencies

  • Integration with Sessions: the admitted GOAL.md and ES.md sources are exactly what lean continuation carries into a fresh thread; see Sessions.
  • Integration with Memory: durable memory is a separate subsystem. It reads completed rollout transcripts from disk rather than hooking into compaction, so it does not depend on when a thread compacts. A PreCompact hook event is available if you want to run your own work at that moment.
  • Integration with Providers: admitted context is normalized across provider wire formats while evidence pointers are preserved; see Providers.

Session Continuity

Elpis keeps work, goals, decisions, and evidence alive across restarts, model switches, and thread compaction without forcing the model to re-read an ever-growing transcript.


1. Two Continuation Modes

Elpis separates the model provider's native thread from its own provider-neutral session state, which gives two ways to continue work:

Exact resume Lean continuation
What continues The provider's native thread, with its accumulated history. A fresh thread, re-anchored from portable checkpoints.
History source Full native thread history (thread_id). GOAL.md + ES.md + applicable rule files.
Token footprint Grows with raw turn history until compaction. Bounded by the per-source character caps in codex-rs/core/src/elpis_context.rs.
Provider mobility Bound to the originating provider thread. Provider-neutral — the checkpoint is plain Markdown.
Evidence Provider transcript on disk. Provider transcript on disk, plus the checkpoint.

Open decision. The threshold at which Elpis should switch automatically from exact resume to lean continuation is listed under Deferred Decisions in GUIDE.md. Today the portable checkpoint is contributed to thread context on every thread start; there is no automatic tier-switching state machine.


2. How Lean Continuation Is Delivered

Continuity is a context contribution, not a separate replay path. ElpisContinuityExtension (codex-rs/app-server/src/extensions.rs) implements ContextContributor; on thread context assembly it calls build_continuity_prompt (codex-rs/core/src/elpis_context.rs) and injects the result as a separate developer prompt fragment.

build_continuity_prompt reads only the sources currently admitted in the Context Ledger, so anything you toggle off in the ledger stops being carried forward on the next turn.


Elpis session continuity modes

3. Portable Checkpoint Layout

Portable session state lives independently of provider threads:

The <workspace> segment is a slug derived from the working directory plus a short hash, so separate checkouts never share a checkpoint.

GOAL.md

Written by write_goal (codex-rs/tui/src/elpis_context.rs):

# Elpis Goal

- Workspace: `/path/to/project`
- Thread: `<thread_id>`
- Status: <status>
- Updated: <unix_timestamp>

## Objective

<objective text>

ES.md

Written by write_session_checkpoint in the same module, from the completed turn's own items — command executions and patch applications — rather than from a model-generated summary:

# Elpis Session Checkpoint

- Workspace: `/path/to/project`
- Thread: `<thread_id>`
- Turn: `<turn_id>`
- Status: <status>
- Updated: <unix_timestamp>
- Goal: [GOAL.md](GOAL.md) when present

## Latest Result

<final agent message for the turn, or "No final agent result was recorded.">

## Changed Files

- `path/to/file.rs` (modified)

## Commands

- `cargo test` (exit 0)

## Exact Evidence

- Full turn remains in the provider transcript.

Both files are written to a temporary path and renamed into place, so a crash mid-write cannot leave a truncated checkpoint.


4. Failure Behavior

If writing ES.md fails, the turn still completes: Elpis logs a warning and surfaces Turn completed, but Elpis could not save ES.md: <error> in the transcript (codex-rs/tui/src/app/app_server_events.rs). Continuity degrades visibly rather than silently, but it does not abort the turn.


  • Context admission — which checkpoint sources are carried forward is controlled in the Context Ledger; see Context.
  • Memory — durable cross-session facts live in MEMORY.md, switchable in the Context Ledger.
  • Providers — because checkpoints are plain Markdown, switching provider mid-task does not discard them; see Providers.

Elpis Memory Architecture

Elpis uses curated, size-bounded local memory to preserve recurring project knowledge and user preferences across sessions—without turning past conversation history into prompt baggage.


1. Overview & Core Philosophy

In Elpis, durable memory is distinct from session transcripts. While raw transcripts remain intact on disk as exact evidence, active context is kept small.

  • Memory is curated: Only stable project facts, user preferences, standing decisions, and proven procedures enter long-term memory.
  • Memory carries provenance: Every entry records its source thread and context.
  • Recall is a discovery aid: Historical memory entries are treated as hypotheses. If a fact can drift or change, Elpis verifies it against the current workspace before relying on it.

What you control

Memory is off by default and yours to switch on. Three independent controls govern it, all reachable from /memories in the TUI:

Control Config key Default Effect when off
Memory subsystem features.memories off Nothing is extracted, consolidated, or recalled. The pipeline below never starts.
Recall memories.use_memories on Memory instructions are not injected into prompts; stored memory is not consulted.
Writing memories.generate_memories on New threads are recorded as memory_mode = "disabled" and never become memory candidates.

Two further guarantees hold regardless of those settings:

  • The agent has no memory-write tool. Dedicated memory tools are gated behind memories.dedicated_tools, which defaults to off. MEMORY.md is written by the phase-2 consolidation pass described below, not by the agent you are talking to.
  • Nothing is deleted without a trace. Faded and removed entries append to archive.md, and if that write fails the operation is aborted rather than completed.

2. Two-Stage Memory Pipeline

Memory processing in Elpis operates in two distinct background stages:

Raw Transcript Rollout Stage 1 Extraction (stores candidates & recall queries) Phase 2 Consolidation (distills verified knowledge into MEMORY.md under a 6h cooldown).

  1. Stage 1 (Extraction — memory_stage1):

    • Extracts candidate memory items from raw thread rollouts.
    • Stores normalized outputs in stage1_outputs.
    • Tracks usage_count, last_usage timestamps, and stage1_recall_queries (thread_id, query_key, recalled_at) to measure recall frequency and query diversity.
  2. Phase 2 (Global Consolidation — memory_consolidate_global):

    • Attempts to run on each launch and stops unless the last success is more than 6 hours old and the candidate set changed.
    • Marks a candidate promotable only when it clears both thresholds: recalled at least twice, across at least two distinct query contexts. There is no weighted score — the two conditions are absolute.
    • Hands a sandboxed consolidation agent the changed inputs; that agent, not the pipeline, edits MEMORY.md, and it is instructed to skip anything the thresholds marked ineligible.

    Measured reality: on a live install this produced zero promotions in five days — 104 extractions, 60 candidates, no change to MEMORY.md. The threshold was 3 recalls at the time and is now 2. Until a promotion is observed, treat this stage as unproven.


3. Data & File Layout

Elpis memory state is strictly separated from upstream runtime state and stored under ~/.elpis/:

Path Purpose Behavior
~/.elpis/memories/MEMORY.md Curated durable long-term memory. Reaches the model through the Context Ledger, where it is listed and switchable like GOAL.md and ES.md.
~/.elpis/memories/memory_summary.md Compact index of stored memory. Injected into developer instructions when memory is on, capped at 2,500 tokens. This — not MEMORY.md — is what the pipeline puts in the prompt.
~/.elpis/memories/raw_memories.md Every extracted candidate. The journal. Grows unbounded until consolidation prunes it; 210 KB on a five-day-old install.
~/.elpis/memories/archive.md Searchable append-only archive. Receives deleted or age-faded memories prior to baseline reset. Fail-closed on write error.
~/.elpis/state/memories_1.sqlite SQLite state database. Stores stage1_outputs, stage1_recall_queries, job queues, and promotion metadata.
~/.elpis/context/workspaces/<workspace>/GOAL.md Active goal checkpoint. Survives restarts, model switches, and thread compaction.
~/.elpis/context/workspaces/<workspace>/ES.md Session checkpoint, rewritten after each completed turn. Records the turn's latest result, changed files, and commands run. See Sessions.

4. Provenance & Live Workspace Re-Verification

Memory in Elpis is not treated as undeniable truth:

  • Provenance: Every recalled memory item retains its original source attribution (thread_id, timestamp, query key).
  • Workspace Re-Verification: When an entry describes workspace state (e.g., file paths, build flags, dependencies, or function signatures), Elpis treats the memory entry as a discovery pointer. Before making changes based on a recalled memory, Elpis inspects the live workspace to confirm the fact remains true.

5. How Memory Fits the Context Lifecycle

Memory fits into a strict 3-tiered context lifetime model:

Lifetime        Scope                    Examples
--------------------------------------------------------------------------------------
durable         Global rules & memory     AGENTS.md, GOAL.md, MEMORY.md
task            Active thread state       ES.md, decisions, changed paths, verification
turn            Transient exploration     File reads, rg outputs, command execution
  • Post-Turn Pruning (Ace): After a turn finishes, transient turn-level exploration (searches, directory listings, raw command outputs) is pruned from the next request, leaving only compact conclusions and evidence pointers. See Context.
  • Independent of compaction: Memory extraction reads completed rollout transcripts from disk, so it neither runs at compaction time nor depends on it. Transcripts are already durable evidence by the time stage 1 sees them.
  • Fail-Closed Archive Reset: When resetting or pruning memory baselines, faded entries must append to archive.md. If the archive write fails, the reset is aborted to prevent data loss (codex-rs/memories/write/src/workspace.rs).

6. Evals

Memory is judged by behavior, not by whether its plumbing runs. The eval plants a fact that appears nowhere except durable memory, runs a real turn against a mock model, and inspects the request that left for the model.

Eval Location Proves
Recall codex-rs/app-server/tests/suite/v2/memory_recall.rs A fact in MEMORY.md reaches the model, and switching MEMORY.md off in the Context Ledger removes it.

The negative half carries the weight. A recall test that only checks the fact arrived passes just as well when every file on disk is admitted unconditionally — which is not memory working. Both halves have been verified to fail when the behavior they describe is broken.

Run it with:

CODEX_SKIP_BWRAP_BUILD=1 cargo test --manifest-path codex-rs/Cargo.toml \
  -p codex-app-server --test all v2::memory

Any change to how memory is stored, promoted, or recalled must keep this eval passing, and a new memory behavior needs its own eval before the behavior is written.


7. Inspection & Verification

You can inspect and verify memory behavior using the following surfaces:

  1. Context & Memory Usage: Run /usage in the TUI to see currently admitted memory sources, byte sizes, and reasons.
  2. Context Ledger: The ledger sidebar is shown by default and toggles with Tab or Alt+C. MEMORY.md is listed there from the start, like GOAL.md and ES.md, so durable memory is visible and switchable without being added by hand.
  3. Database Audit: Inspect ~/.elpis/state/memories_1.sqlite using SQLite:
    SELECT thread_id, usage_count, datetime(last_usage, 'unixepoch') FROM stage1_outputs;
    SELECT thread_id, query_key, datetime(recalled_at, 'unixepoch') FROM stage1_recall_queries;
  4. Archive Safety: Inspect ~/.elpis/memories/archive.md to review historical faded or deleted memory entries preserved prior to baseline resets.

Provider-Neutral Architecture & Model Adapters

Elpis maintains a Provider-Neutral Architecture: Elpis owns context admission, durable memory, session continuity, permissions, tool execution, and the TUI interface. The selected provider owns inference.


Elpis provider-neutral architecture

1. Systemic Architecture


2. Supported Provider Routes & Protocols

Provider ID API Base URL Credential Env Variable Native Wire Protocol Default Model
openai https://api.openai.com/v1 OPENAI_API_KEY / OAuth OpenAI Responses API gpt-5.4
openrouter https://openrouter.ai/api/v1 OPENROUTER_API_KEY OpenAI Responses Compatibility openai/gpt-5.4
anthropic https://api.anthropic.com/v1 ANTHROPIC_API_KEY Anthropic Messages API claude-sonnet-4-6
google-gemini https://generativelanguage.googleapis.com/v1beta GEMINI_API_KEY Gemini GenerateContent API gemini-3.5-flash
amazon-bedrock https://bedrock-mantle.us-east-1.api.aws/openai/v1 AWS credentials OpenAI Responses API openai.gpt-5.* model IDs
ollama http://localhost:11434/v1 none OpenAI Responses API served locally
lmstudio http://localhost:1234/v1 none OpenAI Responses API served locally

ollama and lmstudio point at a local inference server, so no key is required and no request leaves the machine. Their port and base URL can be overridden with the experimental CODEX_OSS_PORT and CODEX_OSS_BASE_URL environment variables (codex-rs/model-provider-info/src/lib.rs).


2.1 Bring Your Own Key (BYOK) & Zero-API Local Testing

Elpis supports Bring Your Own Key (BYOK) across all major foundation providers and local engines. Setting environment variables or selecting model IDs dynamically switches active provider routing and UI display.

1. Bring Your Own Key (BYOK) Setup

  • Anthropic / Claude:
    export ANTHROPIC_API_KEY="sk-ant-..."
    elpis --model claude-3-7-sonnet-20250219
  • OpenRouter (All Models / Universal Routing):
    export OPENROUTER_API_KEY="sk-or-..."
    elpis --model anthropic/claude-3.7-sonnet
  • Google Gemini:
    export GEMINI_API_KEY="..."
    elpis --model-provider google-gemini --model gemini-2.5-pro
  • OpenAI API Key:
    export OPENAI_API_KEY="sk-proj-..."
    elpis --model gpt-4o

2. Testing UI Model Banner Without Paid API Keys

You can verify model switching, UI model banner rendering, and TUI state changes without an active paid API key using any of the following:

  1. Local Engines (Ollama / LMStudio - No API Key Required):
    elpis --model-provider ollama --model llama3
    # or
    elpis --model-provider lmstudio --model local-model
  2. OpenRouter Free Tier Models:
    elpis --model tencent/hy3:free
  3. Interactive TUI Model Picker: Inside Elpis, type /model at any time to open the model & reasoning tier picker and verify that the active model name updates immediately in the upper header banner.

3. Provider Wire Protocol Translation

Elpis translates canonical turn objects into vendor-native HTTP payloads and translates vendor stream chunks back into unified ResponseEvent streams (codex-rs/core/src/chat_completions.rs):

Canonical Elpis Event OpenAI Responses Anthropic Messages Gemini GenerateContent
System Rules / Prompt instructions / system system array systemInstruction object
User Message user role item user role content block user role parts
Assistant Message assistant role item assistant role content block model role parts
Tool Declaration tools JSON schema tools JSON schema tools.functionDeclarations
Tool Call Output function_call item tool_use block functionCall part
Tool Result Input function_call_output tool_result block functionResponse part
Header Auth Authorization: Bearer <key> x-api-key: <key> x-goog-api-key: <key>

4. Compatibility Launcher Aliases

--provider accepts ten values in total: the direct routes openai, openrouter, anthropic, google-gemini, amazon-bedrock, ollama, and lmstudio, plus three compatibility aliases that route through OpenRouter:

Launcher Command Actual Provider Model Target Description
--provider anthropic anthropic claude-sonnet-4-6 Direct Native Anthropic API routing
--provider google-gemini google-gemini gemini-3.5-flash Direct Native Google Gemini API routing
--provider claude openrouter ~anthropic/claude-sonnet-latest OpenRouter compatibility route
--provider gemini openrouter ~google/gemini-pro-latest OpenRouter compatibility route
--provider gemini-flash openrouter ~google/gemini-flash-latest OpenRouter compatibility route

5. Security & Authentication Isolation

  1. Credential Isolation: Credentials are read strictly from their designated environment variable (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY). Native keys are never forwarded to OpenRouter or cross-contaminated.
  2. Provider Switch Mobility: Switching providers (/model) changes the active inference engine, but does not discard Elpis workspace context, GOAL.md, ES.md checkpoints, or memory state.

Anthropic sends its key only as x-api-key along with anthropic-version: 2023-06-01; Gemini sends its key only as x-goog-api-key; OpenAI and OpenRouter keep Authorization: Bearer.


6. Stream Translation Behavior

Beyond the request/response mapping above, the native adapters also translate streamed text, tool calls, vendor errors, token usage, model and version identifiers, and completion state back into the unified event stream. Dropping the response stream cancels the parser and releases the upstream response body, and provider stream-idle timeouts surface as stream errors.

The static native catalogs are supplied to the model manager, so /model uses the native provider's own default model instead of attempting an OpenAI /models request.


7. Honest Protocol Limitations

The native boundary rejects unsupported history and tool shapes rather than silently approximating them.

  • Text and function tools are supported. Image inputs and image-bearing tool results are rejected, even though both vendors have image-capable APIs.
  • OpenAI Responses-only items — encrypted reasoning state, remote compaction controls, custom/freeform tools, tool-search items, built-in web search, image generation, and namespace tools — are not translated.
  • Vendor-native thinking/reasoning signatures, citations, prompt-cache controls, structured-output strictness, Anthropic server tools, and Gemini built-in tools/code execution are not preserved.
  • Anthropic requests use an explicit max_tokens of 8192, because the canonical request has no provider-neutral output-token limit.
  • Gemini emits only the first candidate. Repeated full function-call chunks are de-duplicated.
  • The canonical completion event exposes end_turn, not a raw vendor finish-reason. Known finish reasons are mapped explicitly; unknown ones remain unknown. A parsed tool call always maps to end_turn = false.
  • Native stream reconnection is not attempted after partial output. HTTP and SSE failures go to the existing provider error path.
  • Live vendor acceptance of both native adapters is still pending.

8. Manual Smoke Tests

Anthropic:

export ANTHROPIC_API_KEY='...'
cargo run -p codex-tui --bin elpis -- --provider anthropic
# In the TUI: run /model and confirm Claude Sonnet 4.6 is listed, then ask for a simple
# answer and a task that invokes a local function tool.

Gemini:

export GEMINI_API_KEY='...'
cargo run -p codex-tui --bin elpis -- --provider google-gemini
# In the TUI: run /model and confirm Gemini 3.5 Flash is listed, then exercise text and a
# function-tool turn.

Compatibility route:

export OPENROUTER_API_KEY='...'
cargo run -p codex-tui --bin elpis -- --provider claude
# Confirm logs/config show model_provider=openrouter and the compatibility model alias.

Do not run these on the maintainer's workstation; use the remote Rust workflow.

Workspace Retrieval

Semantic search over your own files — ask a question in plain language, get back relevant excerpts with their source paths, instead of reading files one at a time or loading a whole tree into the context window.

Elpis does not provide this. It lets you plug it in.

Retrieval means embedding models, a vector store, and — depending on the reranker — many gigabytes of weights. A Qwen reranker alone is around 8GB. None of that fits in a terminal binary, and none of it should be downloaded on a user's behalf. Elpis has no retrieval engine and no /rag command; it has MCP, which is the general mechanism for exactly this.

How to add it

Register any MCP server that exposes a retrieval tool. ~/.codex/config.toml:

[mcp_servers.rag]
command = "/absolute/path/to/.venv/bin/python"
args = ["/absolute/path/to/server.py"]

Restart Elpis and run /mcp to confirm it connected. From then on the agent calls it the way it calls any other tool — ask for what you want in plain language and name a folder if you want the search scoped.

rag-mcp is a ready-made local implementation: hybrid BM25 plus vector search with reranking, everything on-device, no API keys. Its readme carries the current setup steps. Any MCP server meeting the same contract works equally well, including one backed by a hosted embedding API — that choice, and its cost, is yours.

Why it is not built in

The engine used to live in this repository. It pinned PyTorch, which is roughly 2.5GB, and that single fact made retrieval unreachable from every binary install: you cannot ship it in a release artifact, and you cannot ask someone who ran a one-line installer to wait for a multi-gigabyte download. Moving the engine out did not reduce what Elpis can do — it moved the cost to the person who decides to pay it.

The rule that follows: never add a machine-learning dependency to this repository. See docs/SHIPPING_RULES.md.

What Elpis still owns

  • Context pruning — excerpts a retrieval tool returns are tool output like any other, so they are pruned after the turn rather than accumulating (see context.md).
  • Memory — durable memory may record a useful query or search strategy, never the retrieved document bodies (see memory.md).
  • Startup — an MCP server runs in its own process. Its imports, model loading, and indexing cost cannot reach the TUI's launch path no matter how heavy the engine is.