Files
codex/codex-rs/core/session.md
T
2025-10-06 15:53:38 +01:00

10 KiB
Raw Blame History

Sub‑Sessions: Spawn and Await Child Conversations

This document proposes a first design to let a model spawn a new conversation (a new “session”) from within an existing codex-core session, and later await its completion to retrieve the final assistant message.

Goals

  • Give the model two new capabilities via tools:
    • create_session(session_type, prompt) -> { session_id }
    • wait_session(session_id, timeout_ms) -> { result }
  • The spawned child session runs independently and returns ONLY its last assistant message as the result.
  • Allow the caller to customize the child’s developer prompt, model, and tools.
  • Keep parent and child conversations isolated in history, state, and rollout.
  • Make it easy to add specialized child session profiles (e.g., linter fixer, math solver).

Non-goals (initial version):

  • Cross-session message streaming to the parent while the child is running.
  • Bidirectional piping of tool output between sessions.
  • Multi-turn orchestration inside the child beyond the standard Codex task loop.

User-Facing Interface (tools)

  • create_session

    • Inputs:
      • session_type: SessionType — an enum string (phase 1 presets below). In phase 1, this maps to a fixed developer prompt + model; tools are inherited from the parent’s normal configuration.
      • prompt: String — initial user message for the child.
    • Output: { session_id: String } — a UUID v4 formatted string. Internally this will be the child ConversationId.
  • wait_session

    • Inputs:
      • session_id: String
      • timeout_ms: i32 — total time to wait; <= 0 means “do not wait”.
    • Output: { result: String } — last assistant message of the child session. If the child failed or timed out, return a tool error.
  • cancel_session

    • Inputs:
      • session_id: String
    • Output: { cancelled: boolean } — true if the child was still running and is now cancelled. Errors if unknown session_id.

Notes:

  • These are exposed as function tools so the model can orchestrate sub‑workflows.
  • create_session returns immediately after queuing the child run.
  • wait_session may block the current turn until completion or timeout.

High‑Level Flow

  1. Parent model calls create_session(...) during a turn.
  2. Core spawns a new Codex conversation using a derived Config:
    • Phase 1 mapping from session_type enum to a profile with:
      • base_instructions override set from the profile’s developer prompt.
      • model set from the profile’s configured model.
    • Tools: in phase 1, inherit the parent’s normal tool configuration unchanged.
    • Inherit cwd, sandbox_policy, and telemetry from parent.
    • Approval policy: in phase 1, child sessions run without interactive approvals (see “Approvals & Safety (Phase 1)” below).
  3. Core starts the child by submitting a single UserInput (or UserTurn) with prompt.
  4. A background driver consumes the child’s events until TaskComplete, capturing last_agent_message.
  5. create_session returns { session_id } to the parent turn.
  6. Later, the parent calls wait_session(session_id, timeout_ms) to obtain the { result } string (the child’s final assistant message), or a timeout/error.
  7. The parent may cancel a running child with cancel_session(session_id).

Architecture

New Runtime Service: SubsessionManager

Add a per‑session orchestrator responsible for child conversations:

  • Stored on SessionServices as subsessions: SubsessionManager.
  • Holds HashMap<ConversationId, ChildState> guarded by tokio::sync::Mutex.
  • Spawns and monitors child conversations; exposes:
    • spawn_child(config, prompt) -> ConversationId
    • wait_child(id, timeout) -> Result<String, ChildError>
    • cancel_child(id) -> bool
    • abort_all_children() for cleanup on parent drop/interrupt.

Child state lifecycle:

  • Pending { handle } — background task running; handle joins to finish.
  • Done { result: Option<String> } — captured last assistant message; None if no assistant message was produced.
  • Failed { error } — terminal error captured as string.

Using Existing Building Blocks

  • Conversation creation: reuse ConversationManager::new_conversation(config) internally or a lighter inline Codex::spawn, then wrap in CodexConversation.
  • Child run loop: submit a UserInput/UserTurn with the provided prompt and consume events until EventMsg::TaskComplete(TaskCompleteEvent); use the embedded last_agent_message as the result. Fall back to scanning the final turn’s ResponseItems if needed.
  • Rollout: each child conversation records its own rollout file via its own RolloutRecorder. For origin, see “SessionSource for Sub‑Sessions” below.
  • Parent observability: use EventMsg::BackgroundEvent to optionally notify the parent UI when a child is created/completed.

Session types (Phase 1)

Represented as an enum string from the model, mapped server‑side in codex-core to profiles:

  • tester — a strict, concise test‑writing assistant.
  • mathematician — a reasoning‑optimized assistant focused on math problems.
  • linter_fixer — an assistant focused on fixing lint issues.
  • default — fallback; mirrors parent’s model and uses a small generic task prompt.

Each profile supplies:

  • developer_instructions: String — appended as base instructions override.
  • model_name: String — full model id to use in the child.

Tools are inherited from the parent in phase 1; we will add per‑profile tool curation in phase 2.

Tools and Configuration Mapping (Phase 2 – future)

We will add optional per‑profile tool surface selection by mapping a profile’s tools to a ToolsConfig subset (mirroring core/src/tools/spec.rs). MCP tool allow‑lists would also be supported.

Error Handling and Timeouts

  • create_session errors if the child fails to spawn; otherwise always returns a session_id.
  • wait_session:
    • If timeout_ms <= 0, behave as a non‑blocking check: return an error if not completed; otherwise return the result.
    • If timed out, return a tool error like: "session {id} did not complete within {timeout_ms}ms".
    • If the child task fails, return the captured error string.

Isolation, Security, and Policies

  • The child inherits the parent’s cwd and sandbox_policy. Shell execution remains sandboxed as in the parent.
  • The child’s tool surface is restricted to SessionType.tools.
  • No history is shared; the child starts with initial context built from its own ConfigureSession only (environment context and developer instructions).

Approvals & Safety (Phase 1)

  • Child sessions do not request interactive approvals in phase 1.
  • Implementation: force the child’s approval_policy to a non‑interactive mode (e.g., equivalent of “never escalate”), regardless of the parent session’s policy.
  • We can add opt‑in approval behaviors to specific session types in phase 2.

Events and Rollout

  • Each child conversation has its own rollout path under sessions/YYYY/MM/DD/... with its own SessionMeta and TurnContext items.
  • Parent session emit BackgroundEvent messages:
    • On create: "spawned child session {id} with model {model_name}".
    • On complete: "child session {id} completed".
    • On cancel: child session {id} cancelled.

SessionSource for Sub‑Sessions

Today, SessionMeta.source distinguishes origins like Cli, VSCode, Exec, and Mcp (see protocol/src/protocol.rs). Adding SessionSource::SubSession would:

  • Let rollouts clearly identify runs that were spawned by another session, enabling filtering, analytics, and UI affordances (e.g., “show only child runs”).
  • Help group parent/child runs in future UX without relying on naming conventions or directory structure.

Trade‑offs:

  • Requires updating the protocol crate (Rust + generated TS) and any consumers that switch over SessionSource.
  • Backward compatibility: default unknown values to Unknown in older clients; new servers can safely emit subsession.

Phase 1 proposal: keep using an existing source (e.g., Exec) for minimal surface change, but reserve the enum value and wire it shortly after to avoid churn across downstreams.

Relationship to Review Threads (Phase 3)

  • Review mode today uses an isolated in‑memory thread (no parent history) inside the same session/task, then emits ExitedReviewMode with structured output.
  • Sub‑sessions generalize isolation by giving a fully separate conversation with its own lifecycle, model, and tool surface, and an explicit await mechanism.
  • We can later re‑implement review as a pre‑configured SessionType template if desired. (phase 3)

Testing Strategy

  • Unit tests for SubsessionManager:
    • Spawns a child, captures last_agent_message, handles failure.
    • Timeout behavior and non‑blocking checks.
    • abort_all_children() on parent drop/interrupt.
  • Integration tests exercising the tools:
    • Model calls create_session then wait_session and receives the child result.
    • Cancellation via cancel_session.
    • Deeper subsession spawns a subsession itself.

Incremental Implementation Plan (Phase 1)

  1. Add subsessions module to codex-core with SubsessionManager on SessionServices; APIs: spawn/wait/cancel/abort_all.
  2. Implement background driver that runs a child conversation to TaskComplete and stores { result }.
  3. Add tool specs and handlers:
    • create_session(session_type, prompt) → spawn via profile mapping and return id.
    • wait_session(session_id, timeout_ms) → await completion with timeout and return result.
    • cancel_session(session_id) → cancel a running child.
  4. Phase 1: inherit tools from parent; force approval policy to non‑interactive.
  5. Emit optional BackgroundEvent diagnostics.
  6. Add enum profiles and developer prompts for initial types (tester, mathematician, linter_fixer, default).

Module Layout (Phase 1)

Inside codex-core:

  • core/src/subsessions/mod.rs — manager, child state, profile mappings.
  • core/src/tools/handlers/subsessions.rs — handlers for create_session, wait_session, cancel_session.
  • Minimal changes in core/src/state/service.rs to attach the manager.
  • No changes required to tui for phase 1; no UI exposure.

Coding Guidelines Note

  • Reuse existing primitives and flows (Codex spawn, submission loop, rollout).
  • Prefer refactoring shared logic over duplicating or introducing ad‑hoc hacks; any unavoidable interim workaround should be accompanied by a clear TODO comment and a follow‑up refactor task.