Commit Graph
17 Commits
Author SHA1 Message Date
Dylan Hurd 8703d32bd0 Treat goal objectives as untrusted context (#60342)
## What changed

- Pass goal objectives as untrusted `goal_objective` context instead of embedding them in continuation instructions. Direct the model to use `get_goal` if the objective is missing or truncated.
- Inject newly created objectives into their originating active turn without dropping unrelated client context. Render them as tool outputs when `UntrustedAsToolOutput` is enabled.
- Queue additional context together with automatic turn input so both reach the first model request.

## Testing

Extend app-server coverage to verify that model-written objectives appear as tool outputs at creation and continuation, without duplicating them in user messages. Update steering and Python SDK tests to check objective preservation and delivery alongside continuation instructions.

GitOrigin-RevId: 6f44f6d6c895bb4dc378c9cf84d6470ccc885c67
2026-11-09 03:58:05 +00:00
Alex Zamoshchin c9e0fffbbc Enable parallel execution for read-only tools (#52245)
## Why

Tools that do not opt into parallel execution acquire an exclusive scheduler lock, serializing independent reads.

## What changed

Allow skill listing and reading, memory listing, reading and searching, conversation history search and reading, plugin listing, agent listing, current time, remaining context, and `get_goal` to run in parallel. Keep goal creation and updates serialized.

## Testing

Add `cloud_skill_reads_run_in_parallel`, which uses a barrier to require two cloud skill reads to overlap and verifies that each returns the expected resource contents.

GitOrigin-RevId: 97b5634f1f84be2ba092be89e6e9c634ca75c07b
2026-10-08 19:29:47 +00:00
jif bc5957eac9 Preserve parent cache affinity for ephemeral forks (#44862)
## Why

ChatGPT derives Responses cache affinity from the `session-id` header. Ephemeral forks need to reuse their parent's cache routing while retaining their own session and thread identities.

## What changed

- Inherit the parent session ID as the `prompt_cache_key` for ephemeral root forks.
- Use the prompt cache key for root-agent Responses `session-id` headers, including WebSocket handshakes. Preserve the actual session identity in turn metadata and leave non-root-agent routing unchanged.
- Keep enabled goal tool definitions visible on ephemeral threads, but reject execution with `Goal tools require a persistent thread.` and disable automatic goal continuation without persistent state.

## Testing

Add regression coverage for inherited cache routing with distinct fork identities, matching parent and fork tool definitions, WebSocket session headers, and rejection of ephemeral goal tool execution.

GitOrigin-RevId: d235e97630068b27f7ebd562dd23ac6266ebc1b9
2026-09-11 16:41:56 +00:00
Eric Traut fa7af3883d Allow user-requested goal pauses through update_goal (#44290)
## Why

`update_goal` only accepted `complete` and `blocked`, preventing the agent from pausing a goal in response to an explicit user request.

## What changed

- Accept `paused` and account for final goal progress when pausing, with budget limits taking precedence.
- Update tool instructions and goal prompts to allow pauses only at the user's explicit request, report the returned status, and stop goal work. A later resume revokes the pause request.

## Testing

Extend coverage for pause accounting, budget-limit precedence, rejection of resume and system-limit statuses, and preservation of a tool-paused goal when resuming a thread.

GitOrigin-RevId: c3c15a51f848ce6eb65854d51b1a1bc75bb456a7
2026-09-09 20:03:38 +00:00
Adam Perry @ OpenAI 81e180044d Scope extension capabilities to invocation lifetimes (#41020)
## What changed

- Add callback lifetimes to extension `ToolCall`, `ToolEnvironment`, turn-input context, and skill-read request types.
- Require extension tool executors to handle calls for any invocation lifetime and tie their returned futures to that lifetime.
- Update built-in extensions, adapters, and tests to use the lifetime-scoped APIs without retaining host-owned environment capabilities.

GitOrigin-RevId: aca9ce3a1b6870df550bc84ec9308cc65533f161
2026-08-27 02:00:31 +00:00
Eric Traut a9dee37f9c Add configurable goal token budget limits (#37878)
## What changed

- Add `goals.max_goal_token_budget` as a positive-integer configuration setting.
- Use the configured maximum as the default budget for new goals and when `tokenBudget` is reset to `null`.
- Reject goal creation and updates whose token budget exceeds the configured maximum, including requests through goal tools and `thread/goal/set`.
- Respect managed configuration precedence and per-thread configuration overrides.

## Testing

- Cover configuration parsing and managed overrides.
- Cover defaulting, resetting, and rejecting oversized budgets through the goal service, goal tools, and app-server API.

GitOrigin-RevId: f8d7e6418cdc237d454c8cf47bb32ba0d44a60cf
2026-08-10 21:00:00 +00:00
Adam Perry @ OpenAI 2704ecea9a [codex] Remove async_trait from ToolExecutor (#27304)
## Why

We're now [discouraging use of
`async_trait`](https://github.com/openai/codex/pull/20242).

Removing use of `async_trait` from `ToolExecutor` yields a `codex_core`
debug test build speedup of ~78% (from 227.5s to 50.3s) on my machine.

Stacked on #27299, this PR applies the trait change after the handler
bodies have been outlined.

## What

Changed `ToolExecutor::handle` to return an explicit boxed
`ToolExecutorFuture` instead of using `async_trait`.

Updated ToolExecutor implementors to return `Box::pin(...)`, reexported
the future alias through `codex-tools` and `codex-extension-api`, and
removed `codex-tools` direct `async-trait` dependency.
2026-06-10 10:26:53 -07:00
marksteinbrick-oai 608b8b1cc6 [codex-analytics] emit goal lifecycle analytics (#27078)
## Why
- Currently, there is no analytics event for `/goal` behavior
- Existing events cannot identify goal execution or its resulting
outcome
- The original update in
[#26182](https://github.com/openai/codex/pull/26182) was implemented
before `/goal` moved into `codex-goal-extension`.

## What Changed
- Adds `codex_goal_event` serialization and enrichment to
`codex-analytics`
- Emits goal events from the canonical `codex-goal-extension` mutation
and accounting paths:
  - `created` when a new logical goal is persisted
  - `usage_accounted` when cumulative goal usage is persisted
  - `status_changed` when the stored goal status changes
  - `cleared` when the goal is deleted
- Preserves causal `turn_id` for turn driven events and uses null
attribution for external or idle lifecycle events
- Changes goal deletion to return the deleted row so `cleared` retains
the stable goal ID

## Event Details

Includes standard analytics metadata along with goal specific fields:
- `goal_id`: Stable ID stored in the local SQLite goal row and shared
across the goal's events
- `event_kind`: Observed operation (see the 4 lifecycle events cited in
the above bullet)
- `goal_status`: Resulting or last stored status: `active`, `paused`,
`blocked`, `usage_limited`, etc.
  - `has_token_budget`: Indicates whether a token budget is configured
  - `turn_id`: Causal turn ID, or null when no causal turn exists
- `cumulative_tokens_accounted`: Cumulative tokens on `usage_accounted`
events; null otherwise
- `cumulative_time_accounted_seconds`: Cumulative active time on
`usage_accounted` events; null otherwise

## Validation
- `just test -p codex-analytics -p codex-state -p codex-goal-extension`
- `just test -p codex-core -E 'test(/goal/)'`
- `just test -p codex-app-server`
- `cargo build -p codex-analytics -p codex-core -p codex-state -p
codex-app-server`
2026-06-09 18:45:54 -07:00
Eric Traut 36bf63a5cf Allow creating a new goal after completion (#26681)
## Why

Users have indicated that they want an agent to be able to create a new
goal for itself after completing the previous goal. Currently, that's
not possible because agents cannot overwrite an existing goal even if
it's complete. This PR removes this limitation and allows `create_goal`
to overwrite an existing goal if it is in the `complete` state.

## What changed

`create_goal` now replaces the existing goal only when its status is
`complete`. The replacement is performed atomically in the goal store,
creates a fresh active goal with reset usage, and continues to reject
creation while any unfinished goal exists. App server clients see a
single `thread/goal/updated` event when the previous goal is replaced
with the new one.

The tool description and error message now reflect these semantics.

## What didn't change

Agents are not allowed to create a new goal (overwrite their existing
goal) if an existing goal is still active, blocked, paused, or in any
other state other than "completed".
2026-06-09 13:23:31 -07:00
jif 7db18c8241 fix: serialize goal progress accounting (#26155)
## Why

Goal progress accounting can be reached from multiple completion paths
for the same thread. Each path takes a progress snapshot, writes the
usage delta, and then marks that snapshot as accounted. When two
tool-completion hooks run at the same time, they can both observe the
same unaccounted delta and charge it twice.

## What changed

- Added a per-thread progress-accounting permit to
`GoalAccountingState`.
- Held that permit across the snapshot/write/mark-accounted critical
section for active-turn, idle, and tool-finish accounting.
- Added regression coverage for parallel tool-finish hooks so a shared
token delta is charged once and only one progress event is emitted.

## Testing

- Not run locally.
- Added `parallel_tool_finish_accounts_active_goal_progress_once`.
2026-06-03 15:10:16 +02:00
jif-oai f27bbbd49c Add goal extension GoalApi (#25096)
## Summary

- add an extension-owned `GoalApi` for thread goal get/set/clear
operations
- register live goal runtimes with the API from the goal extension
backend
- cover the API and runtime-effect paths in goal extension tests

## Stack

Follow-up app-server wiring PR: #25108

## Validation

- `just fmt`
- `just fix -p codex-goal-extension`
- `just test -p codex-goal-extension`
2026-06-01 11:32:13 +02:00
jif-oai 08504e86fb Add goal extension telemetry parity (#24615)
## Why

`core/src/goals.rs` already emits OTEL metrics for goal creation,
resume, terminal transitions, token counts, and duration. As `/goal`
moves into `ext/goal`, the extension needs to preserve that telemetry
contract instead of only emitting app-visible `ThreadGoalUpdated`
events.

This keeps the existing `codex.goal.*` metric surface intact while goal
lifecycle ownership shifts toward the extension.

## What changed

- Added an extension-local `GoalMetrics` helper that records the
existing `codex.goal.*` counters and histograms through `codex-otel`.
- Threaded an optional `MetricsClient` through `install_with_backend`,
`GoalExtension`, `GoalRuntimeHandle`, and `GoalToolExecutor`.
- Emitted created, resumed, and terminal goal metrics from the extension
paths that create goals, restore active goals on thread resume, account
budget limits, complete or block goals, and handle external goal
mutations.
- Updated existing goal extension test setup callsites to pass `None`
for metrics when instrumentation is not under test.

## Verification

Not run locally.
2026-05-26 19:48:32 +02:00
jif-oai 516f134641 Make tool executor specs mandatory (#23870)
## Why

`ToolExecutor` is the runtime contract that keeps a callable tool and
its model-visible spec together. Leaving `spec()` optional lets a
registered runtime silently omit that half of the contract, and it also
overloads a missing spec as an exposure decision for tools that should
stay dispatchable without being shown to the model.

## What

- Make `ToolExecutor::spec()` required and update core, extension, and
test tool executors to return a concrete `ToolSpec`.
- Add `ToolExposure::Hidden` for dispatch-only tools. The legacy
`shell_command` runtime in unified-exec sessions now uses that explicit
exposure instead of hiding itself by omitting a spec.
- Build MCP tool specs when `McpHandler` is constructed so invalid MCP
specs are skipped before the handler is registered.
- Keep tool planning aligned with the new contract for direct, deferred,
hidden, code-mode, dynamic, and namespaced tool paths.

## Testing

- Added tool-plan coverage that invalid MCP tool specs are not
registered.
- Updated shell-family coverage for the hidden legacy `shell_command`
runtime and the affected tool executor test fixtures.
2026-05-21 15:25:56 +02:00
jif-oai d4f842f3b3 feat: account active goal progress in the goal extension (#23696)
## Why

The goal extension can create and surface goals, but the live
turn-accounting path still stopped short of persisting active-goal
progress. That leaves token and wall-clock usage, plus
`ThreadGoalUpdated` events, out of sync with the extension boundary once
work actually advances or a goal transitions out of active state.

## What changed

- Teach `GoalAccountingState` to track the current turn, active goal,
token deltas, and wall-clock progress snapshots against the persisted
goal id.
- Flush active-goal accounting from tool-finish, turn-stop, and
turn-abort lifecycle hooks, and emit `ThreadGoalUpdated` events when
persisted progress changes.
- Route `create_goal` and `update_goal` through the same accounting
state so new goals start from the right baseline, final progress is
flushed before status changes, and `update_goal` can mark a goal
`blocked` as well as `complete`.
- Keep budget-limited goals accruing through the end of the turn while
clearing local active-goal state once a turn or explicit update is
finished.
- Expand backend and lifecycle coverage around store ids, baseline
reset, tool-finish accounting, budget-limited carry-through, and
blocked-goal updates.

## Testing

- Added focused backend coverage in
`codex-rs/ext/goal/tests/goal_extension_backend.rs` for baseline reset,
tool-finish accounting, budget-limited turns, and blocked-goal updates.
- Extended `codex-rs/core/src/session/tests.rs` to assert that lifecycle
inputs expose the expected session, thread, and turn store ids.
2026-05-20 18:36:37 +02:00
jif-oai b555dd5d1d feat: wire goal extension tools to the dedicated goal store (#23685)
## Why

`ext/goal` already had the tool specs and contributor wiring for
`/goal`, but the installed tools still depended on a placeholder backend
that always errored. That meant the extension could not actually own
goal persistence even though the dedicated `thread_goals` store already
exists.

This change wires the extension tools directly to the dedicated goal
store so the extension can create, read, and complete goals against real
state instead of falling back to host-side placeholders.

## What changed

- make `install_with_backend(...)` require
`Arc<codex_state::StateRuntime>` so goal storage is always available
when the extension is installed
- remove the unused no-backend/public backend abstraction from
`ext/goal` and have the tool executors talk directly to `StateRuntime`
- map `thread_goals` rows into the existing protocol response shape for
`get_goal`, `create_goal`, and `update_goal`
- preserve current thread-list behavior by filling an empty thread
preview from the goal objective when a goal is created through the
extension path
- add integration coverage for the installed tool surface, including
successful goal creation and duplicate-create rejection

## Testing

- `cargo test -p codex-goal-extension`
2026-05-20 14:44:17 +02:00
jif-oai 4ca60ef9ff Emit goal update events from goal extension tools (#23306)
## Why

Goal creation and completion are moving through the goal extension, but
the rest of Codex still observes goal state through `ThreadGoalUpdated`
events. Without an event from the extension-owned tool path, a
model-initiated `create_goal` or `update_goal` can mutate the backend
and return a tool result while app-server and TUI listeners miss the
goal state transition.

## What changed

- Added `GoalEventEmitter` as a small wrapper around the host
`ExtensionEventSink` to build `EventMsg::ThreadGoalUpdated` events for
goal updates.
- Threaded the registry event sink into `GoalExtension` and the
`GoalToolExecutor`s created by the extension. The public
`GoalExtension::new` constructor keeps a `NoopExtensionEventSink`
fallback for standalone use.
- Emitted a goal update after successful `create_goal` and `update_goal`
tool calls. Until `ToolCall` exposes the current turn submission id,
these events use the tool call id as the event id and leave `turn_id`
unset.

Relevant code:

-
[`GoalEventEmitter::thread_goal_updated`](https://github.com/openai/codex/blob/1fe2d73890df9a50996f67f705d4da4cc3d4b866/codex-rs/ext/goal/src/events.rs#L19-L32)
- [`GoalToolExecutor` emission
points](https://github.com/openai/codex/blob/1fe2d73890df9a50996f67f705d4da4cc3d4b866/codex-rs/ext/goal/src/tool.rs#L161-L190)

## Testing

- `cargo test -p codex-goal-extension`
2026-05-18 16:14:37 +02:00
jif-oai a80f07ec4a chore: goal ext skeleton (#23288)
Skeleton of `/goal` in extension
Lot's of follow-ups coming
2026-05-18 13:32:21 +02:00