diff --git a/codex-rs/core/src/agent/api.rs b/codex-rs/core/src/agent/api.rs new file mode 100644 index 0000000000..4868fee6ab --- /dev/null +++ b/codex-rs/core/src/agent/api.rs @@ -0,0 +1,247 @@ +//! Coordination of one agent tree, independent of where its threads run. +//! +//! The trait and its requests use shared agent types and captured settings. Live threads +//! and turn contexts stay in the runtime. Implementations own membership, loading, +//! delivery and shared resources. These Rust contracts do not define a wire protocol. + +use crate::agent::types::AgentExecutionGuard; +use crate::agent::types::AgentMessage; +use crate::agent::types::AgentMetadata; +use crate::agent::types::LiveAgent; +use crate::agent::types::MessageDeliveryMode; +use crate::agent::types::SpawnAgentOptions; +use crate::codex_thread::GuardianRootSnapshot; +use crate::codex_thread::ThreadConfigSnapshot; +use crate::config::Config; +use crate::rollout_budget::RolloutBudgetReminder; +use codex_protocol::AgentPath; +use codex_protocol::SessionId; +use codex_protocol::ThreadId; +use codex_protocol::error::Result; +use codex_protocol::protocol::AgentStatus; +use codex_protocol::protocol::MultiAgentVersion; +use codex_protocol::protocol::SessionSource; +use codex_protocol::protocol::TokenUsage; +use codex_protocol::turn_input::TurnStartOptions; +use codex_protocol::user_input::UserInput; +use futures::future::BoxFuture; +use futures::stream::BoxStream; +use std::num::NonZeroU32; + +/// Initial observation followed by changes, with no gap between the two. Observation failures +/// are errors, not terminal agent states. Backends own reconnection and reconciliation. +pub type StatusSubscription = BoxStream<'static, Result>; + +// Keep dynamic dispatch a compile-time property of the contract. +const _: Option<&dyn AgentControl> = None; + +/// Coordinates one agent tree through a local or host backend. +/// +/// Every operation validates tree membership and caller authority, including calls made +/// through stale controller handles. Unknown IDs return `ThreadNotFound`; invalid or +/// unresolved references return `UnsupportedOperation`, preserving existing resolution +/// errors. Backend failures must not be reported as missing targets or successful operations. +/// Mutation success acknowledges acceptance, not completion of the requested agent work. +/// +/// Implementations preserve MAv2 wake modes and keep loading, capacity checks and delivery +/// behind complete operations. Hosts own transport, durable acceptance, retry identities +/// and recovery. Those mechanisms and channel operations are separate implementation work. +/// Boxed Send futures allow callers to use `Arc`. +pub trait AgentControl: Send + Sync { + fn identity(&self) -> ControlIdentity; + + /// Allocate, register and start a child, then accept its initial input. Return effective + /// settings so callers do not need a second configuration lookup. + fn spawn(&self, request: SpawnRequest) -> BoxFuture<'_, Result>; + + /// Explicitly reopen an agent under the caller's authority and captured settings. + /// Reloading required for message delivery belongs inside `send`. + fn resume( + &self, + caller: ThreadId, + target: AgentTarget, + config: Config, + ) -> BoxFuture<'_, Result>; + + /// Resolve, authorize, reload if needed and accept input with its original attribution + /// and wake mode. Queue-only messages cannot start an idle agent; follow-ups cannot + /// target the root. Acceptance does not mean the model has read the input. + fn send(&self, request: SendRequest) -> BoxFuture<'_, Result>; + + /// Stop current work without closing the agent. Return its pre-interrupt snapshot. + fn interrupt(&self, caller: ThreadId, target: AgentTarget) -> BoxFuture<'_, Result>; + + /// Close the agent and its descendants. Return its pre-close snapshot. + fn close(&self, caller: ThreadId, target: AgentTarget) -> BoxFuture<'_, Result>; + + /// Read identity, runtime state and effective settings without loading a dormant agent. + fn inspect(&self, caller: ThreadId, target: AgentTarget) -> BoxFuture<'_, Result>; + + /// Return a bounded membership page. Callers own model and UI formatting. + fn list(&self, caller: ThreadId, query: AgentQuery) -> BoxFuture<'_, Result>; + + /// Subscribe without loading a dormant agent. Unavailability must remain visible. + fn watch( + &self, + caller: ThreadId, + target: AgentTarget, + ) -> BoxFuture<'_, Result>; + + /// Atomically reserve tree-wide execution capacity. Unlimited turns return no guard. + /// Cancellation during acquisition must release any partial reservation. This contract + /// does not require changing the local policy from rejecting at capacity to waiting. + fn admit_turn( + &self, + thread_id: ThreadId, + turn_id: String, + version: MultiAgentVersion, + source: SessionSource, + ) -> BoxFuture<'_, Result>>; + + /// Account for an inference response, including compaction. Retried reports for the + /// same thread, turn and response must not charge the budget twice. As in the existing + /// rollout-budget path, `SessionBudgetExceeded` means the usage was recorded and the + /// budget is exhausted; retries must preserve that result without charging again. + fn record_usage( + &self, + thread_id: ThreadId, + turn_id: String, + response_id: String, + usage: TokenUsage, + ) -> BoxFuture<'_, Result<()>>; + + /// Accept the terminal result and own completion delivery to the parent and task + /// initiator. Retrying the same thread/turn outcome must not duplicate delivery. + fn turn_finished(&self, outcome: AgentTurnOutcome) -> BoxFuture<'_, Result<()>>; + + /// Publish a root-owned update for live agents and later starts/resumes. Only the root + /// may change shared settings; runtimes read their own effective configuration. + fn propagate_config_update( + &self, + caller: ThreadId, + update: AgentConfigUpdate, + ) -> BoxFuture<'_, Result<()>>; + + /// Read authoritative root evidence, including source, completeness and revision. + /// Fetch failures are errors. Approval consumers must revalidate the evidence revision + /// before accepting an approval; this read alone does not grant authorization. + fn get_guardian_package(&self, agent: ThreadId) -> BoxFuture<'_, Result>; + + fn pending_budget_reminder<'a>( + &'a self, + agent: ThreadId, + window: &'a str, + ) -> BoxFuture<'a, Result>>; + + /// Acknowledge only after inserting the reminder into the agent's history. + fn mark_budget_reminder_delivered<'a>( + &'a self, + agent: ThreadId, + window: &'a str, + reminder: RolloutBudgetReminder, + ) -> BoxFuture<'a, Result<()>>; +} + +/// Persistent tree identity and its current owner generation. Clones and reconnects keep +/// the generation; a replacement owner changes it and fences the previous owner. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ControlIdentity { + pub session_id: SessionId, + pub generation: uuid::Uuid, +} + +/// References resolve relative to the caller. IDs still require membership checks. +#[derive(Clone, Debug)] +pub enum AgentTarget { + Id(ThreadId), + Reference(String), +} + +/// Observes existing registry metadata and runtime snapshots without loading an agent. +/// A known identity survives unloading; unloaded does not mean completed. Missing agents +/// are operation errors, not loaded snapshots with `AgentStatus::NotFound`. +#[derive(Clone, Debug)] +pub enum AgentInfo { + Loaded { + agent: LiveAgent, + config: Box, + }, + /// Membership is known, but no runtime is loaded. Metadata identifies the known agent. + Unloaded(AgentMetadata), +} + +/// User input starts or steers a turn; agent messages retain their sender and wake mode. +pub enum AgentInput { + UserInput(Vec), + Message { + message: AgentMessage, + mode: MessageDeliveryMode, + }, +} + +pub struct SpawnRequest { + pub caller: ThreadId, + pub config: Config, + /// Spawning starts work; message input must use `TriggerTurn`. + pub input: AgentInput, + pub source: SessionSource, + pub options: SpawnAgentOptions, +} + +pub struct SendRequest { + pub caller: ThreadId, + pub target: AgentTarget, + /// Captured caller settings used if the recipient must be restored. + pub resume_config: Config, + pub input: AgentInput, + pub start_options: TurnStartOptions, +} + +pub struct DeliveryReceipt { + pub thread_id: ThreadId, + /// Acceptance identifier, not evidence that the recipient processed the input. + pub submission_id: String, +} + +#[derive(Clone, Debug)] +pub enum AgentScope { + Tree, + Children(AgentTarget), + /// Includes the target itself. + Subtree(AgentTarget), +} + +#[derive(Clone, Copy, Debug)] +pub enum AgentVisibility { + Known, + Loaded, +} + +pub struct AgentQuery { + pub scope: AgentScope, + pub visibility: AgentVisibility, + /// Opaque backend cursor. Pages are observations, not a locked tree snapshot. + pub cursor: Option, + /// Requested maximum; backends must also enforce their own hard page-size cap. + pub limit: NonZeroU32, +} + +pub struct AgentPage { + pub agents: Vec, + pub next_cursor: Option, +} + +pub struct AgentTurnOutcome { + pub thread_id: ThreadId, + pub turn_id: String, + pub source: SessionSource, + pub parent_turn_id: Option, + pub initiating_agent_path: Option, + pub status: AgentStatus, +} + +/// Settings shared by the tree. A service tier of `None` restores the default tier. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AgentConfigUpdate { + ServiceTier(Option), +} diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 9d7e57aae3..fe2d660dc9 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -2,11 +2,11 @@ use crate::TurnInputRequest; use crate::TurnInputSubmission; use crate::TurnStartOptions; use crate::agent::AgentStatus; -use crate::agent::registry::AgentMetadata; use crate::agent::registry::AgentRegistry; use crate::agent::role::DEFAULT_ROLE_NAME; use crate::agent::role::resolve_role_config; use crate::agent::status::is_final; +use crate::agent::types::AgentMetadata; use crate::agent_communication::AgentCommunicationContext; use crate::agent_communication::AgentCommunicationKind; use crate::codex_thread::ThreadConfigSnapshot; @@ -16,7 +16,6 @@ use crate::context::SubagentNotification; use crate::environment_selection::TurnEnvironmentSnapshot; use crate::rollout_budget::RolloutBudget; use crate::session::emit_subagent_session_started; -use crate::session::multi_agents::ResolvedMultiAgentV2UsageHints; use crate::session_prefix::format_inter_agent_completion_message; use crate::session_prefix::format_subagent_context_line; use crate::thread_manager::ResumeThreadWithHistoryOptions; @@ -52,8 +51,6 @@ use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::ThreadSource; -use codex_protocol::protocol::TurnEnvironmentSelection; -use codex_protocol::turn_input::CyberAccessProgram; use codex_protocol::user_input::UserInput; use codex_thread_store::LoadThreadHistoryParams; use codex_thread_store::ReadThreadParams; @@ -67,10 +64,7 @@ use tokio::sync::watch; use tracing::warn; use uuid::Uuid; -pub(crate) use self::delivery::AgentMessage; pub(crate) use self::delivery::MessageDeliveryError; -pub(crate) use self::delivery::MessageDeliveryMode; -pub(crate) use self::execution::AgentExecutionGuard; use self::execution::AgentExecutionLimiter; pub(crate) use self::interrupt::AgentInterruptError; pub(crate) use self::interrupt::AgentInterruptOutcome; @@ -89,33 +83,6 @@ mod user_authorization; const MAX_ENVIRONMENT_SUBAGENTS: usize = 8; const MAX_ENVIRONMENT_SUBAGENT_BYTES: usize = 1_024; -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum SpawnAgentForkMode { - FullHistory, - LastNTurns(usize), -} - -#[derive(Clone, Debug, Default)] -pub(crate) struct SpawnAgentOptions { - pub(crate) fork_parent_spawn_call_id: Option, - pub(crate) fork_mode: Option, - pub(crate) parent_thread_id: Option, - pub(crate) parent_turn_id: Option, - /// Attribute delegated usage to the turn that initiated it. - pub(crate) turn_trigger: Option, - pub(crate) root_turn_id: Option, - pub(crate) environments: Option>, - pub(crate) multi_agent_v2_usage_hints: Option, - pub(crate) cyber_access_program: Option, -} - -#[derive(Clone, Debug)] -pub(crate) struct LiveAgent { - pub(crate) thread_id: ThreadId, - pub(crate) metadata: AgentMetadata, - pub(crate) status: AgentStatus, -} - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] pub(crate) struct ListedAgent { pub(crate) agent_name: String, diff --git a/codex-rs/core/src/agent/control/delivery.rs b/codex-rs/core/src/agent/control/delivery.rs index faddcc908b..fa6a8ac2b3 100644 --- a/codex-rs/core/src/agent/control/delivery.rs +++ b/codex-rs/core/src/agent/control/delivery.rs @@ -5,6 +5,8 @@ use super::LocalAgentControl; use crate::TurnStartOptions; use crate::agent::child_config::build_agent_resume_config; +use crate::agent::types::AgentMessage; +use crate::agent::types::MessageDeliveryMode; use crate::agent_communication::AgentCommunicationContext; use crate::agent_communication::AgentCommunicationKind; use crate::context::ContextualUserFragment; @@ -16,18 +18,6 @@ use codex_protocol::ThreadId; use codex_protocol::error::CodexErr; use codex_protocol::protocol::InterAgentCommunication; -#[derive(Clone, Copy, PartialEq, Eq)] -pub(crate) enum MessageDeliveryMode { - QueueOnly, - TriggerTurn, -} - -/// Keeps model-provided encrypted content distinct from text that needs a context wrapper. -pub(crate) enum AgentMessage { - Plaintext(String), - Encrypted(String), -} - impl AgentMessage { pub(crate) fn into_communication( self, diff --git a/codex-rs/core/src/agent/control/execution.rs b/codex-rs/core/src/agent/control/execution.rs index dc9a10aaa2..500905c3b8 100644 --- a/codex-rs/core/src/agent/control/execution.rs +++ b/codex-rs/core/src/agent/control/execution.rs @@ -1,4 +1,8 @@ +//! Tracks local running capacity and releases reservations through the shared guard. +//! The local permit owns the running count; root and MAv1 turns remain unrestricted. + use super::LocalAgentControl; +use crate::agent::types::AgentExecutionGuard; use crate::codex_thread::CodexThread; use codex_protocol::error::CodexErr; use codex_protocol::error::CodexErrorDetails; @@ -16,11 +20,11 @@ pub(super) struct AgentExecutionLimiter { max_threads: OnceLock, } -pub(crate) struct AgentExecutionGuard { +struct LocalExecutionPermit { limiter: Arc, } -impl Drop for AgentExecutionGuard { +impl Drop for LocalExecutionPermit { fn drop(&mut self) { self.limiter.active.fetch_sub(1, Ordering::AcqRel); } @@ -84,7 +88,7 @@ impl AgentExecutionLimiter { fn guard(self: Arc) -> AgentExecutionGuard { self.active.fetch_add(1, Ordering::AcqRel); - AgentExecutionGuard { limiter: self } + AgentExecutionGuard::new(LocalExecutionPermit { limiter: self }) } } diff --git a/codex-rs/core/src/agent/control/spawn.rs b/codex-rs/core/src/agent/control/spawn.rs index a08267aa29..11793c7ceb 100644 --- a/codex-rs/core/src/agent/control/spawn.rs +++ b/codex-rs/core/src/agent/control/spawn.rs @@ -2,6 +2,10 @@ use super::residency::is_v2_resident_session_source; use super::*; use crate::agent::child_config::build_agent_resume_config; use crate::agent::role::apply_role_to_config; +use crate::agent::types::AgentMetadata; +use crate::agent::types::LiveAgent; +use crate::agent::types::SpawnAgentForkMode; +use crate::agent::types::SpawnAgentOptions; use crate::codex_thread::CodexThread; use crate::config::PermissionProfileSnapshot; use crate::context::ContextualUserFragment; diff --git a/codex-rs/core/src/agent/control/user_authorization.rs b/codex-rs/core/src/agent/control/user_authorization.rs index 3dd0ed2064..0b3e2fc7c1 100644 --- a/codex-rs/core/src/agent/control/user_authorization.rs +++ b/codex-rs/core/src/agent/control/user_authorization.rs @@ -223,6 +223,7 @@ impl LocalAgentControl { .map(|turn_id| root_evidence.trusted_skill_paths(turn_id)) .unwrap_or_default(); Some(GuardianRootSnapshot { + root_thread_id, authorization_version, messages, trusted_skill_paths, diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 36c3e89405..c36c58c5b4 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -4,6 +4,10 @@ use crate::StateDbHandle; use crate::ThreadManager; use crate::agent::agent_status_from_event; use crate::agent::next_thread_spawn_depth; +use crate::agent::types::LiveAgent; +use crate::agent::types::ResolvedMultiAgentV2UsageHints; +use crate::agent::types::SpawnAgentForkMode; +use crate::agent::types::SpawnAgentOptions; use crate::agent_communication::AgentCommunicationContext; use crate::agent_communication::AgentCommunicationKind; use crate::config::AgentRoleConfig; diff --git a/codex-rs/core/src/agent/mod.rs b/codex-rs/core/src/agent/mod.rs index 009d2f69f5..c9a1547990 100644 --- a/codex-rs/core/src/agent/mod.rs +++ b/codex-rs/core/src/agent/mod.rs @@ -1,9 +1,11 @@ pub(crate) mod agent_resolver; +pub(crate) mod api; pub(crate) mod child_config; pub(crate) mod control; mod registry; pub(crate) mod role; pub(crate) mod status; +pub(crate) mod types; pub(crate) use codex_protocol::protocol::AgentStatus; pub(crate) use control::LocalAgentControl; diff --git a/codex-rs/core/src/agent/registry.rs b/codex-rs/core/src/agent/registry.rs index 242542c5c3..d7fae371d5 100644 --- a/codex-rs/core/src/agent/registry.rs +++ b/codex-rs/core/src/agent/registry.rs @@ -1,3 +1,4 @@ +use crate::agent::types::AgentMetadata; use codex_protocol::AgentPath; use codex_protocol::ThreadId; use codex_protocol::error::CodexErr; @@ -49,14 +50,6 @@ impl RegisteredAgent { } } -#[derive(Clone, Debug, Default)] -pub(crate) struct AgentMetadata { - pub(crate) agent_id: Option, - pub(crate) agent_path: Option, - pub(crate) agent_nickname: Option, - pub(crate) agent_role: Option, -} - fn format_agent_nickname(name: &str, nickname_reset_count: usize) -> String { match nickname_reset_count { 0 => name.to_string(), diff --git a/codex-rs/core/src/agent/types.rs b/codex-rs/core/src/agent/types.rs new file mode 100644 index 0000000000..81dcd5354e --- /dev/null +++ b/codex-rs/core/src/agent/types.rs @@ -0,0 +1,88 @@ +//! Agent data and execution reservations shared by controllers and their callers. +//! Backend operations and local admission policy live in the implementations. + +use crate::context::MultiAgentRoleInstructions; +use codex_protocol::AgentPath; +use codex_protocol::ThreadId; +use codex_protocol::protocol::AgentStatus; +use codex_protocol::protocol::TurnEnvironmentSelection; +use codex_protocol::turn_input::CyberAccessProgram; + +/// Registry identity shared by loaded and unloaded agents. +/// Registered agents have an `agent_id`; a reserved spawn can still be awaiting its ID. +#[derive(Clone, Debug, Default)] +pub struct AgentMetadata { + pub agent_id: Option, + pub agent_path: Option, + pub agent_nickname: Option, + pub agent_role: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SpawnAgentForkMode { + FullHistory, + LastNTurns(usize), +} + +#[derive(Clone, Debug, Default)] +pub struct SpawnAgentOptions { + pub fork_parent_spawn_call_id: Option, + pub fork_mode: Option, + pub parent_thread_id: Option, + pub parent_turn_id: Option, + /// Attribute delegated usage to the turn that initiated it. + pub turn_trigger: Option, + pub root_turn_id: Option, + pub environments: Option>, + pub multi_agent_v2_usage_hints: Option, + pub cyber_access_program: Option, +} + +/// Identity and status observed from a loaded agent, without a handle to its runtime. +#[derive(Clone, Debug)] +pub struct LiveAgent { + pub thread_id: ThreadId, + pub metadata: AgentMetadata, + pub status: AgentStatus, +} + +#[derive(Clone, Debug, Default)] +pub struct ResolvedMultiAgentV2UsageHints { + pub root: Option, + pub subagent: Option, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum MessageDeliveryMode { + /// Deliver to the mailbox without starting an idle agent. + QueueOnly, + /// Deliver to the active turn or start work if the agent is idle. + TriggerTurn, +} + +/// Keeps model-provided encrypted content distinct from text that needs a context wrapper. +pub enum AgentMessage { + Plaintext(String), + Encrypted(String), +} + +/// Holds a backend-owned reservation until the turn ends or is cancelled. +/// +/// The permit's destructor releases capacity or arranges backend cleanup. Remote backends +/// must also recover reservations after worker loss, when no Rust destructor can run. +#[must_use = "hold the execution guard for the lifetime of the admitted turn"] +pub struct AgentExecutionGuard { + _permit: Box, +} + +impl AgentExecutionGuard { + pub fn new(permit: impl Send + Sync + 'static) -> Self { + Self { + _permit: Box::new(permit), + } + } +} + +#[cfg(test)] +#[path = "types_tests.rs"] +mod tests; diff --git a/codex-rs/core/src/agent/types_tests.rs b/codex-rs/core/src/agent/types_tests.rs new file mode 100644 index 0000000000..6385ce06c2 --- /dev/null +++ b/codex-rs/core/src/agent/types_tests.rs @@ -0,0 +1,34 @@ +//! Checks shared execution-guard ownership through completion and cancellation. + +use super::AgentExecutionGuard; +use pretty_assertions::assert_eq; +use std::sync::Arc; +use tokio::sync::Semaphore; +use tokio::sync::oneshot; + +#[tokio::test] +async fn dropping_guard_releases_backend_reservation() { + let capacity = Arc::new(Semaphore::new(/*permits*/ 1)); + let permit = Arc::clone(&capacity).acquire_owned().await.unwrap(); + let guard = AgentExecutionGuard::new(permit); + assert_eq!(capacity.available_permits(), 0); + drop(guard); + assert_eq!(capacity.available_permits(), 1); +} + +#[tokio::test] +async fn cancelling_turn_releases_backend_reservation() { + let capacity = Arc::new(Semaphore::new(/*permits*/ 1)); + let permit = Arc::clone(&capacity).acquire_owned().await.unwrap(); + let (started, running) = oneshot::channel(); + let turn = tokio::spawn(async move { + let _guard = AgentExecutionGuard::new(permit); + started.send(()).unwrap(); + std::future::pending::<()>().await; + }); + running.await.unwrap(); + assert_eq!(capacity.available_permits(), 0); + turn.abort(); + assert!(turn.await.unwrap_err().is_cancelled()); + assert_eq!(capacity.available_permits(), 1); +} diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index 0c54ced5cb..696822f845 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -173,6 +173,8 @@ pub struct GuardianAuthorizationVersion { /// Bounded root conversation and authorization state from one history snapshot. #[derive(Debug, Eq, PartialEq)] pub struct GuardianRootSnapshot { + /// Authoritative root from which this evidence was captured. + pub root_thread_id: ThreadId, pub authorization_version: GuardianAuthorizationVersion, pub messages: Vec, pub trusted_skill_paths: Vec, diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index c30987fef3..38b87f4dcc 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -53,6 +53,30 @@ pub use codex_thread::ThreadConfigSnapshot; pub use session::turn_context::TurnContext; pub use thread_startup_metadata::ThreadStartupMetadata; mod agent; +pub use agent::api::AgentConfigUpdate; +pub use agent::api::AgentControl; +pub use agent::api::AgentInfo; +pub use agent::api::AgentInput; +pub use agent::api::AgentPage; +pub use agent::api::AgentQuery; +pub use agent::api::AgentScope; +pub use agent::api::AgentTarget; +pub use agent::api::AgentTurnOutcome; +pub use agent::api::AgentVisibility; +pub use agent::api::ControlIdentity; +pub use agent::api::DeliveryReceipt; +pub use agent::api::SendRequest; +pub use agent::api::SpawnRequest; +pub use agent::api::StatusSubscription; +pub use agent::types::AgentExecutionGuard; +pub use agent::types::AgentMessage; +pub use agent::types::AgentMetadata; +pub use agent::types::LiveAgent; +pub use agent::types::MessageDeliveryMode; +pub use agent::types::ResolvedMultiAgentV2UsageHints; +pub use agent::types::SpawnAgentForkMode; +pub use agent::types::SpawnAgentOptions; +pub use rollout_budget::RolloutBudgetReminder; mod agent_communication; mod attestation; mod codex_delegate; diff --git a/codex-rs/core/src/rollout_budget.rs b/codex-rs/core/src/rollout_budget.rs index d66b778fa8..1b010110a8 100644 --- a/codex-rs/core/src/rollout_budget.rs +++ b/codex-rs/core/src/rollout_budget.rs @@ -8,9 +8,11 @@ use std::sync::Mutex; use std::sync::MutexGuard; use std::sync::OnceLock; -pub(crate) struct RolloutBudgetReminder { - pub(crate) remaining_tokens: i64, - reminder_index: i64, +/// Budget reminder returned by a controller and acknowledged after history insertion. +pub struct RolloutBudgetReminder { + pub remaining_tokens: i64, + /// Backend-defined reminder position, returned unchanged on acknowledgement. + pub reminder_index: i64, } /// Shared accounting and reminder state for one root-thread session tree. diff --git a/codex-rs/core/src/session/multi_agents.rs b/codex-rs/core/src/session/multi_agents.rs index 51e43a4dc6..9cec8ae35a 100644 --- a/codex-rs/core/src/session/multi_agents.rs +++ b/codex-rs/core/src/session/multi_agents.rs @@ -1,3 +1,4 @@ +use crate::agent::types::ResolvedMultiAgentV2UsageHints; use crate::config::MultiAgentV2Config; use crate::context::MultiAgentRoleInstructions; use crate::session::step_context::StepContext; @@ -10,12 +11,6 @@ use codex_protocol::protocol::MultiAgentVersion; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; -#[derive(Clone, Debug, Default)] -pub(crate) struct ResolvedMultiAgentV2UsageHints { - pub(crate) root: Option, - pub(crate) subagent: Option, -} - pub(super) fn usage_hint_text(step_context: &StepContext) -> Option { let turn_context = step_context.turn.as_ref(); if turn_context.multi_agent_version != MultiAgentVersion::V2 { diff --git a/codex-rs/core/src/state/turn.rs b/codex-rs/core/src/state/turn.rs index 01b8756e1f..8f45fc5026 100644 --- a/codex-rs/core/src/state/turn.rs +++ b/codex-rs/core/src/state/turn.rs @@ -18,7 +18,7 @@ use rmcp::model::RequestId; use tokio::sync::oneshot; use super::TurnTokenUsage; -use crate::agent::control::AgentExecutionGuard; +use crate::agent::types::AgentExecutionGuard; use crate::session::TurnInputQueue; use crate::session::step_context::StepContext; use crate::session::turn_context::TurnContext; diff --git a/codex-rs/core/src/thread_manager_tests.rs b/codex-rs/core/src/thread_manager_tests.rs index 4a14de1024..e97854b85f 100644 --- a/codex-rs/core/src/thread_manager_tests.rs +++ b/codex-rs/core/src/thread_manager_tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::agent::control::SpawnAgentOptions; +use crate::agent::types::SpawnAgentOptions; use crate::config::test_config; use crate::init_state_db; use crate::installation_id::INSTALLATION_ID_FILENAME; diff --git a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs index d5fccaa57a..b8cded3455 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs @@ -2,12 +2,12 @@ use super::*; use crate::agent::child_config::SpawnConfigOptions; use crate::agent::child_config::SpawnConfigVersion; use crate::agent::child_config::prepare_agent_spawn_config; -use crate::agent::control::SpawnAgentForkMode; -use crate::agent::control::SpawnAgentOptions; use crate::agent::control::render_input_preview; use crate::agent::exceeds_thread_spawn_depth_limit; use crate::agent::next_thread_spawn_depth; use crate::agent::role::DEFAULT_ROLE_NAME; +use crate::agent::types::SpawnAgentForkMode; +use crate::agent::types::SpawnAgentOptions; use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions; use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v1; use codex_tools::ToolSpec; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 86d80ee720..8e8ad7d76d 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -1232,7 +1232,7 @@ async fn multi_agent_v2_send_message_accepts_root_target_from_child() { agent_nickname: None, agent_role: None, })), - crate::agent::control::SpawnAgentOptions::default(), + crate::agent::types::SpawnAgentOptions::default(), ) .await .expect("worker spawn should succeed") @@ -1308,7 +1308,7 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() { agent_nickname: None, agent_role: None, })), - crate::agent::control::SpawnAgentOptions::default(), + crate::agent::types::SpawnAgentOptions::default(), ) .await .expect("worker spawn should succeed") @@ -1473,7 +1473,7 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() { agent_nickname: None, agent_role: None, })), - crate::agent::control::SpawnAgentOptions::default(), + crate::agent::types::SpawnAgentOptions::default(), ) .await .expect("researcher agent should spawn"); @@ -1493,7 +1493,7 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() { agent_nickname: None, agent_role: None, })), - crate::agent::control::SpawnAgentOptions::default(), + crate::agent::types::SpawnAgentOptions::default(), ) .await .expect("worker agent should spawn"); @@ -4023,7 +4023,7 @@ async fn multi_agent_v2_interrupt_agent_rejects_self_target_by_id() { agent_nickname: None, agent_role: None, })), - crate::agent::control::SpawnAgentOptions::default(), + crate::agent::types::SpawnAgentOptions::default(), ) .await .expect("worker spawn should succeed") @@ -4090,7 +4090,7 @@ async fn multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name() { agent_nickname: None, agent_role: None, })), - crate::agent::control::SpawnAgentOptions::default(), + crate::agent::types::SpawnAgentOptions::default(), ) .await .expect("worker spawn should succeed") diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2.rs index 40aecee0a9..ea44c0a427 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2.rs @@ -2,7 +2,7 @@ use crate::agent::AgentStatus; use crate::agent::agent_resolver::resolve_agent_target; -use crate::agent::control::AgentMessage; +use crate::agent::types::AgentMessage; use crate::function_tool::FunctionCallError; use crate::tools::context::ToolInvocation; use crate::tools::context::ToolOutput; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs index 161e644d7e..fa129704a0 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs @@ -2,7 +2,7 @@ use super::analytics::ToolCallAnalytics; use super::message_tool::FollowupTaskArgs; use super::message_tool::handle_message_string_tool; use super::*; -use crate::agent::control::MessageDeliveryMode; +use crate::agent::types::MessageDeliveryMode; use crate::tools::handlers::multi_agents_spec::create_followup_task_tool; use codex_tools::ToolSpec; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs index e319a20183..826b3f1835 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs @@ -6,7 +6,7 @@ use super::analytics::ToolCallAnalytics; use super::*; use crate::agent::control::MessageDeliveryError; -use crate::agent::control::MessageDeliveryMode; +use crate::agent::types::MessageDeliveryMode; use crate::tools::context::FunctionToolOutput; #[derive(Debug, Deserialize)] diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs index f1987170eb..7713f01103 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs @@ -2,7 +2,7 @@ use super::analytics::ToolCallAnalytics; use super::message_tool::SendMessageArgs; use super::message_tool::handle_message_string_tool; use super::*; -use crate::agent::control::MessageDeliveryMode; +use crate::agent::types::MessageDeliveryMode; use crate::tools::handlers::multi_agents_spec::create_send_message_tool; use codex_tools::ToolSpec; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index 22589681d1..3de9822882 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -2,11 +2,11 @@ use super::*; use crate::agent::child_config::SpawnConfigOptions; use crate::agent::child_config::SpawnConfigVersion; use crate::agent::child_config::prepare_agent_spawn_config; -use crate::agent::control::MessageDeliveryMode; -use crate::agent::control::SpawnAgentForkMode; -use crate::agent::control::SpawnAgentOptions; use crate::agent::next_thread_spawn_depth; use crate::agent::role::DEFAULT_ROLE_NAME; +use crate::agent::types::MessageDeliveryMode; +use crate::agent::types::SpawnAgentForkMode; +use crate::agent::types::SpawnAgentOptions; use crate::agent_communication::AgentCommunicationContext; use crate::agent_communication::AgentCommunicationKind; use crate::codex_thread::ThreadConfigSnapshot; diff --git a/codex-rs/core/tests/suite/guardian_subagent_authorization.rs b/codex-rs/core/tests/suite/guardian_subagent_authorization.rs index eb0a8d040b..7a2cfd8790 100644 --- a/codex-rs/core/tests/suite/guardian_subagent_authorization.rs +++ b/codex-rs/core/tests/suite/guardian_subagent_authorization.rs @@ -548,10 +548,11 @@ async fn guardian_subagent_review_preserves_late_root_user_authorization( .expect("worker root snapshot"); assert_eq!( ( + snapshot.root_thread_id, snapshot.messages, snapshot.authorization_version.retained_context_complete ), - (expected_messages.clone(), evidence_complete), + (root_thread_id, expected_messages.clone(), evidence_complete), ); let worker_request = worker_review_request.single_request();