Add agents.interrupt_message for interruption markers (#19351)

## Why

Agent interruptions currently always persist a model-visible
interrupted-turn marker before emitting `TurnAborted`. That marker is
useful by default because it gives the next model turn context about a
deliberately interrupted task, but some deployments need to suppress
that history injection entirely while still keeping the client-visible
interruption event.

## What changed

- Add `[agents] interrupt_message = false` to disable the model-visible
interrupted-turn marker.
- Resolve the setting into `Config::agent_interrupt_message_enabled`,
defaulting to `true` so existing behavior is unchanged.
- Apply the setting to both live interrupted turns and interrupted fork
snapshots.
- Keep emitting `TurnAborted` even when the history marker is disabled.
- Regenerate `core/config.schema.json` for the new
`agents.interrupt_message` field.

## Testing

- `cargo test -p codex-core load_config_resolves_agent_interrupt_message
-- --nocapture`
- `cargo test -p codex-core
disabled_interrupted_fork_snapshot_appends_only_interrupt_event --
--nocapture`
- `cargo test -p codex-core
multi_agent_v2_interrupted_marker_uses_developer_input_message --
--nocapture`
- `cargo test -p codex-core
multi_agent_v2_followup_task_can_disable_interrupted_marker --
--nocapture`
- `cargo test -p codex-core
multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message
-- --nocapture`
- `cargo check -p codex-core`
This commit is contained in:
jif-oai authored and GitHub committed 2026-04-24 16:02:45 +02:00
1 parent deb4509302
commit 28742866c7
8 files changed
+287 -57

No files matched your search

+32
View File
@@ -3941,6 +3941,7 @@ async fn load_config_rejects_missing_agent_role_config_file() -> std::io::Result
max_threads: None,
max_depth: None,
job_max_runtime_seconds: None,
interrupt_message: None,
roles: BTreeMap::from([(
"researcher".to_string(),
AgentRoleToml {
@@ -4856,6 +4857,29 @@ model = "gpt-5-mini"
Ok(())
}
#[tokio::test]
async fn load_config_resolves_agent_interrupt_message() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cfg = ConfigToml {
agents: Some(AgentsToml {
interrupt_message: Some(false),
..Default::default()
}),
..Default::default()
};
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides::default(),
codex_home.abs(),
)
.await?;
assert!(!config.agent_interrupt_message_enabled);
Ok(())
}
#[tokio::test]
async fn load_config_normalizes_agent_role_nickname_candidates() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
@@ -4864,6 +4888,7 @@ async fn load_config_normalizes_agent_role_nickname_candidates() -> std::io::Res
max_threads: None,
max_depth: None,
job_max_runtime_seconds: None,
interrupt_message: None,
roles: BTreeMap::from([(
"researcher".to_string(),
AgentRoleToml {
@@ -4906,6 +4931,7 @@ async fn load_config_rejects_empty_agent_role_nickname_candidates() -> std::io::
max_threads: None,
max_depth: None,
job_max_runtime_seconds: None,
interrupt_message: None,
roles: BTreeMap::from([(
"researcher".to_string(),
AgentRoleToml {
@@ -4942,6 +4968,7 @@ async fn load_config_rejects_duplicate_agent_role_nickname_candidates() -> std::
max_threads: None,
max_depth: None,
job_max_runtime_seconds: None,
interrupt_message: None,
roles: BTreeMap::from([(
"researcher".to_string(),
AgentRoleToml {
@@ -4978,6 +5005,7 @@ async fn load_config_rejects_unsafe_agent_role_nickname_candidates() -> std::io:
max_threads: None,
max_depth: None,
job_max_runtime_seconds: None,
interrupt_message: None,
roles: BTreeMap::from([(
"researcher".to_string(),
AgentRoleToml {
@@ -5234,6 +5262,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
agent_roles: BTreeMap::new(),
memories: MemoriesConfig::default(),
agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS,
agent_interrupt_message_enabled: true,
codex_home: fixture.codex_home(),
sqlite_home: fixture.codex_home().to_path_buf(),
log_dir: fixture.codex_home().join("log").to_path_buf(),
@@ -5431,6 +5460,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
agent_roles: BTreeMap::new(),
memories: MemoriesConfig::default(),
agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS,
agent_interrupt_message_enabled: true,
codex_home: fixture.codex_home(),
sqlite_home: fixture.codex_home().to_path_buf(),
log_dir: fixture.codex_home().join("log").to_path_buf(),
@@ -5582,6 +5612,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
agent_roles: BTreeMap::new(),
memories: MemoriesConfig::default(),
agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS,
agent_interrupt_message_enabled: true,
codex_home: fixture.codex_home(),
sqlite_home: fixture.codex_home().to_path_buf(),
log_dir: fixture.codex_home().join("log").to_path_buf(),
@@ -5718,6 +5749,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
agent_roles: BTreeMap::new(),
memories: MemoriesConfig::default(),
agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS,
agent_interrupt_message_enabled: true,
codex_home: fixture.codex_home(),
sqlite_home: fixture.codex_home().to_path_buf(),
log_dir: fixture.codex_home().join("log").to_path_buf(),
+9
View File
@@ -432,6 +432,9 @@ pub struct Config {
/// Maximum runtime in seconds for agent job workers before they are failed.
pub agent_job_max_runtime_seconds: Option<u64>,
/// Whether to record a model-visible message when an agent turn is interrupted.
pub agent_interrupt_message_enabled: bool,
/// Maximum nesting depth allowed for spawned agent threads.
pub agent_max_depth: i32,
@@ -2002,6 +2005,11 @@ impl Config {
"agents.job_max_runtime_seconds must fit within a 64-bit signed integer",
));
}
let agent_interrupt_message_enabled = cfg
.agents
.as_ref()
.and_then(|agents| agents.interrupt_message)
.unwrap_or(true);
let background_terminal_max_timeout = cfg
.background_terminal_max_timeout
.unwrap_or(DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS)
@@ -2366,6 +2374,7 @@ impl Config {
agent_roles,
memories: cfg.memories.unwrap_or_default().into(),
agent_job_max_runtime_seconds,
agent_interrupt_message_enabled,
codex_home,
sqlite_home,
log_dir,