Skip to content

What is the execution logic of the agent in Codex? This is the result extracted by me using AIL (AI-Language). #1

Description

@pyted

Codex Agent Execution Logic

Analyzed from: codex-main (OpenAI Codex CLI - Rust implementation)

Key source files:

codex-rs/core/src/tasks/regular.rs - main turn loop

codex-rs/core/src/codex.rs - core run_turn / run_sampling_request

codex-rs/core/src/tools/orchestrator.rs - tool approval + sandbox + retry

codex-rs/core/src/memories/phase1.rs - memory extraction (RAG phase 1)

codex-rs/core/src/memories/phase2.rs - memory consolidation (RAG phase 2)

─────────────────────────────────────────────────────────────

Data Types

─────────────────────────────────────────────────────────────

type TaskInput:
user_query: str
attachments: list[str] = []
context_items: list[str] = [] # injected background context

type TurnConfig:
collaboration_mode: str # "auto" | "plan" | "suggest"
reasoning_effort: str # "low" | "medium" | "high"
max_retries: int = 3
sandbox_policy: str # "none" | "container" | "vm"

type ApprovalDecision:
kind: str # "skip" | "approved" | "forbidden" | "needs_approval"
reason: str = ""

type ToolCall:
tool_name: str
arguments: dict
call_id: str

type ToolResult:
call_id: str
output: str
is_error: bool = false
needs_followup: bool = false

type MemoryRecord:
raw_memory: str
rollout_summary: str
rollout_slug: str
usage_count: int = 0

type QualityScore:
relevance: float
completeness: float

─────────────────────────────────────────────────────────────

Tools & Skills

─────────────────────────────────────────────────────────────

use tool shell_exec(cmd: str) -> str
use tool file_read(path: str) -> str
use tool file_write(path: str, content: str) -> str
use tool vector_search(query: str, top_k: int) -> list[MemoryRecord]
use tool rerank(query: str, docs: list[MemoryRecord]) -> list[MemoryRecord]
use tool redact_secrets(text: str) -> str
use tool db_claim_jobs(phase: int) -> list[str] # returns rollout ids
use tool db_load_rollout(rollout_id: str) -> list[str] # returns rollout items
use tool db_persist_memory(rollout_id: str, record: MemoryRecord) -> bool
use tool db_compute_watermark() -> str

use skill spawn_sub_agent(config: dict) -> str # returns agent_id
use skill run_consolidation_agent(memory_root: str) -> str

─────────────────────────────────────────────────────────────

Phase 1 — Memory Extraction (background, on session startup)

Ref: memories/phase1.rs

Runs in parallel across eligible rollouts, extracts structured memory

─────────────────────────────────────────────────────────────

def extract_memories_phase1() -> int: # returns count of processed rollouts
rollout_ids = db_claim_jobs(phase=1)
if len(rollout_ids) == 0:
return 0

processed = 0

# Parallel extraction with concurrency cap (CONCURRENCY_LIMIT in phase1.rs)
results = []
for rollout_id in rollout_ids:                       # buffered concurrency in Rust impl
    result = extract_single_rollout(rollout_id)
    results.append(result)

for r in results:
    if r != "failed":
        processed += 1

return processed

def extract_single_rollout(rollout_id: str) -> str:
items = db_load_rollout(rollout_id)
if len(items) == 0:
return "failed"

prompt p_extract = """
    You are a memory extraction agent.
    Analyze the following conversation rollout and extract:
    1. raw_memory: key facts, decisions, and patterns worth remembering
    2. rollout_summary: a concise summary of what happened
    3. rollout_slug: a short identifier phrase

    Rollout items:
    {items}

    Output valid JSON matching: {{ raw_memory, rollout_summary, rollout_slug }}
"""

try:
    raw_output = @ask(p_extract)
    record = @extract(raw_output, type=MemoryRecord)

    # Redact secrets before persistence (phase1.rs line ~290)
    record.raw_memory      = redact_secrets(record.raw_memory)
    record.rollout_summary = redact_secrets(record.rollout_summary)

    db_persist_memory(rollout_id, record)
    return "ok"
fallback:
    return "failed"

─────────────────────────────────────────────────────────────

Phase 2 — Memory Consolidation (background, serialized global job)

Ref: memories/phase2.rs

Claims a global job, syncs artifacts, spawns a consolidation sub-agent

─────────────────────────────────────────────────────────────

def consolidate_memories_phase2(memory_root: str) -> bool:
# Only one phase2 job runs at a time (global claim in DB)
job_claimed = db_claim_jobs(phase=2)
if len(job_claimed) == 0:
return false

# Load top-N stage-1 outputs ranked by usage_count + last_usage
candidates = vector_search("all memories", top_k=50)
top_records = rerank("recency and relevance", candidates)[:20]

# Sync local artifact files
raw_memories_content = ""
for rec in top_records:
    raw_memories_content = rec.raw_memory + "\n\n---\n\n" + raw_memories_content

file_write(memory_root + "/raw_memories.md", raw_memories_content)

for rec in top_records:
    file_write(
        memory_root + "/rollout_summaries/" + rec.rollout_slug + ".md",
        rec.rollout_summary
    )

# Spawn a restricted internal consolidation agent (phase2.rs line ~131)
# Config: no network, workspace-write-only sandbox, no recursive memory gen
run_consolidation_agent(memory_root)

watermark = db_compute_watermark()
memory.save(watermark, key="phase2:watermark")

return true

─────────────────────────────────────────────────────────────

Tool Orchestrator — Approval + Sandbox + Retry

Ref: tools/orchestrator.rs

Called for every tool the model wants to invoke

─────────────────────────────────────────────────────────────

def orchestrate_tool(call: ToolCall, config: TurnConfig) -> ToolResult:

# ── Step 1: Approval ────────────────────────────────────────
approval = get_approval(call, config)

if approval.kind == "forbidden":
    return ToolResult(
        call_id  = call.call_id,
        output   = "Tool call rejected: " + approval.reason,
        is_error = true
    )

if approval.kind == "needs_approval":
    decision = @ask_user("Allow tool '{call.tool_name}' with args {call.arguments}?")
    if @judge("user rejected the tool call: {decision}"):
        return ToolResult(
            call_id  = call.call_id,
            output   = "User rejected tool: " + decision,
            is_error = true
        )

# ── Step 2: First Sandbox Attempt ───────────────────────────
sandbox = pick_sandbox(config.sandbox_policy)
first_result = run_in_sandbox(call, sandbox)

if not first_result.is_error:
    return first_result

# ── Step 3: Escalation on Sandbox Denial ────────────────────
# orchestrator.rs lines ~228-359
if @judge("first attempt failed due to sandbox denial: {first_result.output}"):
    if approval.kind != "approved":
        # Re-ask user before escalating sandbox
        escalate_decision = @ask_user(
            "Tool '{call.tool_name}' was denied by sandbox. Allow with elevated permissions?"
        )
        if @judge("user rejected escalation: {escalate_decision}"):
            return first_result

    # Retry with no sandbox (escalated)
    escalated_result = run_in_sandbox(call, sandbox="none")
    return escalated_result

return first_result

def get_approval(call: ToolCall, config: TurnConfig) -> ApprovalDecision:
if config.sandbox_policy == "skip_approval":
return ApprovalDecision(kind="skip")

# Forbidden tools check
if @judge("tool '{call.tool_name}' is in the forbidden list for config {config}"):
    return ApprovalDecision(kind="forbidden", reason="policy")

return ApprovalDecision(kind="needs_approval")

def run_in_sandbox(call: ToolCall, sandbox: str) -> ToolResult:
try:
output = shell_exec(call.tool_name + " " + str(call.arguments))
return ToolResult(call_id=call.call_id, output=output)
fallback:
return ToolResult(call_id=call.call_id, output="execution failed", is_error=true)

─────────────────────────────────────────────────────────────

Single Turn Execution

Ref: codex.rs run_turn() + try_run_sampling_request()

One model call + all resulting tool calls

─────────────────────────────────────────────────────────────

def run_turn(
session_messages: list[str],
pending_input: str,
config: TurnConfig,
memories: list[MemoryRecord]
) -> (str, bool): # (agent_reply, needs_followup)

with context(system="You are Codex, an AI coding agent.") as ctx:

    # Inject retrieved memories as background context (RAG)
    if len(memories) > 0:
        ctx.remember("Relevant memories from past sessions:\n" + str(memories))

    # Inject skills/plugins available this turn (codex.rs ~line 6065)
    ctx.remember("Available tools: shell_exec, file_read, file_write, ...")

    # Inject conversation history
    for msg in session_messages:
        ctx.remember(msg)

    # ── Reasoning Phase ─────────────────────────────────────
    # In "plan" mode, model outputs a proposed plan before acting
    if config.collaboration_mode == "plan":
        prompt p_plan = """
            The user wants: {pending_input}
            First, think through what steps are needed.
            Output a numbered plan before taking any actions.
        """
        plan_text = @ask(p_plan)
        @confirm("Proceed with this plan?\n\n" + plan_text)

    # ── Sampling Request + Stream Processing ─────────────────
    # codex.rs try_run_sampling_request() ~line 7520
    # The model streams tokens; tool calls are extracted as they arrive

    prompt p_main = """
        User request: {pending_input}
        Think carefully and use the available tools to accomplish the task.
        Cite sources when drawing on retrieved context.
    """

    retry max=3:
        agent_reply = @ask(p_main, model="gpt-4o")
        @validate(agent_reply, "reply is not empty and addresses the user request")

    # Extract all tool calls the model wants to make
    tool_calls = @extract(agent_reply, "list of tool calls", type=list[ToolCall])

    # ── Parallel Tool Execution ──────────────────────────────
    # codex.rs FuturesOrdered ~line 7552 — tools run concurrently
    tool_results = []
    if len(tool_calls) > 0:
        results_raw = []
        for call in tool_calls:    # Rust uses FuturesOrdered for true parallelism
            r = orchestrate_tool(call, config)
            results_raw.append(r)
        tool_results = results_raw

    # ── Determine Follow-up Need ─────────────────────────────
    needs_followup = @judge(
        "any tool result indicates more work is needed or user input is required: {tool_results}"
    )

    # If tools produced output, synthesize final reply
    if len(tool_results) > 0:
        prompt p_synthesize = """
            Original request: {pending_input}
            Tool results:
            {tool_results}

            Synthesize a final, complete response to the user.
            Be specific about what was done and what the outcome is.
        """
        retry max=3:
            agent_reply = @ask(p_synthesize)
            @validate(agent_reply, "answer is grounded in tool results, no fabrication")

    return agent_reply, needs_followup

─────────────────────────────────────────────────────────────

Main Agent Entry Point

Ref: tasks/regular.rs RegularTask::run()

Outer loop: keeps processing pending inputs until none remain

─────────────────────────────────────────────────────────────

def run_codex_agent(task: TaskInput, config: TurnConfig) -> str:

# ── RAG: Retrieve relevant memories before starting ──────────
# Phase 1 and 2 run in background at session startup;
# here we perform runtime retrieval for this specific query.
candidate_memories = vector_search(task.user_query, top_k=10)
memories = rerank(task.user_query, candidate_memories)[:5]

session_messages: list[str] = []
pending_input = task.user_query
last_reply    = ""

# ── Outer Loop: process all pending inputs ───────────────────
# regular.rs lines 67-81
loop max=20 until @judge("no more pending input and task is complete: {pending_input}"):

    try:
        # Run a single model + tool-execution turn
        reply, needs_followup = run_turn(
            session_messages = session_messages,
            pending_input    = pending_input,
            config           = config,
            memories         = memories
        )

        last_reply = reply
        session_messages.append("assistant: " + reply)

        # Quality gate (codex.rs stop hooks ~line 6324)
        quality = @eval(reply, type={"relevance": float, "completeness": float})
        if quality.relevance < 0.7 or quality.completeness < 0.7:
            improvement = @ask("Improve this reply, current scores: {quality}\n\nReply: {reply}")
            last_reply = improvement
            session_messages.append("assistant (revised): " + improvement)

        if not needs_followup:
            break

        # If model is waiting for more user input, ask
        pending_input = @ask_user("Agent needs more information. Please respond:")
        session_messages.append("user: " + pending_input)

    fallback:
        # Turn-level error recovery (codex.rs ~line 6792)
        error_reply = @ask("The previous turn encountered an error. Summarize what was attempted and suggest next steps.")
        last_reply  = error_reply
        break

# ── Final Summary Output ─────────────────────────────────────
prompt p_summary = """
    Task: {task.user_query}
    Final result: {last_reply}

    Provide a concise completion summary:
    - What was accomplished
    - Any files changed or commands run
    - Any caveats or follow-up actions needed
"""
summary = @ask(p_summary)

# Persist to memory for future sessions (runtime memory.save)
memory.save(
    "Task: " + task.user_query + "\nResult: " + summary,
    key  = "session:" + task.user_query[:30],
    tags = ["history", "completed"]
)

@show(summary)
return summary

─────────────────────────────────────────────────────────────

Session Bootstrap — wires everything together

Ref: codex.rs session init + memories startup

─────────────────────────────────────────────────────────────

def bootstrap_session(task: TaskInput, config: TurnConfig) -> str:

# Background memory jobs run concurrently with main task
# (phase1 and phase2 are fire-and-forget at session start)
# In Rust these are spawned as tokio tasks; modeled here as parallel:
parallel:
    _m1 = extract_memories_phase1()
    _m2 = consolidate_memories_phase2(memory_root="/home/user/.codex/memories")
    result = run_codex_agent(task, config)

return result

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions