Overview#
The Codex extension system exposes contributor traits that allow features and external integrations to hook into the session, thread, turn, and tool execution lifecycle. The Goals system is a first-class built-in extension that uses these same hooks to implement persistent, resumable agent objectives with token-budget tracking.
As of v0.133.0, Goals are enabled by default — the feature is registered as Stage::Stable with default_enabled: true , no longer behind an experimental flag.
Key entry points:
- Extension API —
codex-rs/ext/extension-api/src/contributors.rs: all contributor trait definitions - Tool lifecycle metadata —
codex-rs/ext/extension-api/src/contributors/tool_lifecycle.rs - Goals runtime —
codex-rs/core/src/goals.rs - Goals storage —
codex-rs/state/src/runtime/goals.rs - Goals extension —
codex-rs/ext/goal/src/extension.rs
Extension Contributor Lifecycle Hooks#
All hooks are defined as async traits in contributors.rs. Each callback receives scoped ExtensionData stores (session_store, thread_store, turn_store) for managing extension-private state without touching core runtime objects.
Thread scope — ThreadLifecycleContributor #
| Callback | When called |
|---|---|
on_thread_start | After thread-scoped extension stores are created |
on_thread_resume | After runtime is reconstructed from persisted history |
on_thread_idle | After the host drains all pending thread work |
on_thread_stop | Before the thread runtime and thread-scoped store are dropped |
Turn scope — TurnLifecycleContributor #
| Callback | When called |
|---|---|
on_turn_start | After turn-scoped stores are created, before the task runs |
on_turn_stop | Before the completed turn runtime and turn store are dropped |
on_turn_abort | After the host aborts a running turn |
Tool scope — ToolLifecycleContributor #
| Callback | When called |
|---|---|
on_tool_start | Once the host has accepted a tool call for execution |
on_tool_finish | After the tool returns, is blocked, fails, or is cancelled |
ToolLifecycleContributor is observer-only — it cannot inspect or rewrite tool input/output .
Additional contributor traits#
ContextContributor— injects prompt fragments during prompt assemblyConfigContributor— receives before/after snapshots on config changesTokenUsageContributor— called each time token usage is recorded from a model responseToolContributor— exposes native tools owned by a featureApprovalReviewContributor— can claim and respond to approval-review promptsTurnItemContributor— post-processes parsed turn items before emission
Tool Execution Metadata for Extensions#
The ToolLifecycleContributor callbacks receive typed structs defined in tool_lifecycle.rs.
ToolStartInput — supplied when the host starts a tool call:
session_store,thread_store,turn_store— scoped extension dataturn_id,call_id— stable identifiers for correlationtool_name: &ToolNamesource: ToolCallSource
ToolFinishInput — all of the above plus:
outcome: ToolCallOutcome
ToolCallSource distinguishes how the call originated:
Direct— the model invoked the tool directlyCodeMode { cell_id, runtime_tool_call_id }— a code-mode runtime cell issued the nested call
ToolCallOutcome captures the host-observed result:
Completed { success: bool }— normal output;successis the tool's own success markerBlocked— blocked by host policy before the handler ranFailed { handler_executed: bool }— did not produce normal outputAborted— host cancelled before normal completion; a matchingon_tool_startmay not exist
Subagent Lifecycle Events#
When a subagent's status changes, the runtime injects a SubagentNotification into the model context as a hidden user-turn fragment. The struct carries two fields: agent_reference: String (the agent path) and status: AgentStatus.
The notification implements ContextualUserFragment — it is wrapped in <subagent_notification> / </subagent_notification> XML tags and serialized as JSON with agent_path and status keys. This makes subagent state visible to the model's next turn without appearing as a user-visible message.
AgentStatus variants :
| Variant | Meaning |
|---|---|
PendingInit | Waiting for initialization (default) |
Running | Currently executing |
Interrupted | Turn interrupted; may receive more input |
Completed(Option<String>) | Done; optional final assistant message |
Errored(String) | Terminated with an error message |
Shutdown | Shut down |
NotFound | Agent reference could not be resolved |
Goals System#
Goals let an agent track a persistent objective with a token budget across multiple turns, stored in a dedicated database table that survives thread suspension and resumption.
Feature flag#
Feature::Goals is declared Stage::Stable with default_enabled: true . It can still be overridden in ~/.codex/config.toml via [features] goals = false. The Goals extension checks self.enabled(Feature::Goals) before every operation .
Goal states#
A goal's ThreadGoalStatus follows this state machine :
Active → Paused | Blocked | UsageLimited | BudgetLimited | Complete
Runtime event dispatch#
GoalRuntimeEvent drives all state transitions. The session reports events; goals.rs owns the policy:
| Event | Trigger |
|---|---|
TurnStarted | New turn begins; carries current token usage |
ToolCompleted | Any tool finishes; carries tool name |
ToolCompletedGoal | The update_goal tool completes |
TurnFinished | Turn ends; turn_completed: bool flag |
MaybeContinueIfIdle | Check for goal continuation while idle |
TaskAborted | Task abort (optional turn context) |
UsageLimitReached | Global usage limit hit |
ExternalSet / ExternalClear | External goal mutation |
ThreadResumed | Thread reconstructed from history |
GoalRuntimeState manages concurrency with a semaphore-based accounting_lock and a separate continuation_lock.
Extension integration#
GoalExtension implements all six contributor traits, wiring goals into every relevant lifecycle gate:
| Trait | Responsibility |
|---|---|
ThreadLifecycleContributor | Initialize/rehydrate goal accounting on thread start/resume |
ConfigContributor | Update enabled state on config change |
TurnLifecycleContributor | Account goal progress on turn start/stop/abort |
TokenUsageContributor | Record token consumption during active goals |
ToolLifecycleContributor | Account tool execution toward goal completion |
ToolContributor | Expose get_goal, create_goal, update_goal tools |
Goal persistence is delegated to GoalStore in the state layer, which handles creation, status updates, and accounting-mode-gated writes (ActiveStatusOnly, ActiveOnly, ActiveOrComplete, ActiveOrStopped) .