feat: set policy for phase 2 memory (#11449)

Set the policy of the memory phase 2 worker such that it never ask for
approval
This commit is contained in:
jif-oai
2026-02-11 15:39:22 +00:00
committed by GitHub
parent bd3bf6eda1
commit b58afbfd0a
2 changed files with 100 additions and 3 deletions

View File

@@ -92,6 +92,29 @@ impl<T: Send + Sync> Constrained<T> {
}
}
pub fn allow_only(only_value: T) -> Self
where
T: Clone + fmt::Debug + PartialEq + 'static,
{
let allowed_value = only_value.clone();
Self {
value: only_value,
validator: Arc::new(move |candidate| {
if candidate == &allowed_value {
Ok(())
} else {
Err(ConstraintError::InvalidValue {
field_name: "<unknown>",
candidate: format!("{candidate:?}"),
allowed: format!("[{allowed_value:?}]"),
requirement_source: RequirementSource::Unknown,
})
}
}),
normalizer: None,
}
}
/// Allow any value of T, using T's Default as the initial value.
pub fn allow_any_from_default() -> Self
where
@@ -176,6 +199,20 @@ mod tests {
assert_eq!(constrained.value(), 0);
}
#[test]
fn constrained_allow_only_rejects_different_values() {
let mut constrained = Constrained::allow_only(5);
constrained
.set(5)
.expect("allowed value should be accepted");
let err = constrained
.set(6)
.expect_err("different value should be rejected");
assert_eq!(err, invalid_value("6", "[5]"));
assert_eq!(constrained.value(), 5);
}
#[test]
fn constrained_normalizer_applies_on_init_and_set() -> anyhow::Result<()> {
let mut constrained = Constrained::normalized(-1, |value| value.max(0))?;

View File

@@ -1,9 +1,13 @@
use crate::codex::Session;
use crate::config::Config;
use crate::config::Constrained;
use crate::memories::memory_root;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::sync::Arc;
use tracing::debug;
use tracing::info;
@@ -64,6 +68,42 @@ pub(super) async fn run_global_memory_consolidation(
}
};
let root = memory_root(&config.codex_home);
let consolidation_config = {
let mut consolidation_config = config.as_ref().clone();
consolidation_config.cwd = root.clone();
consolidation_config.approval_policy = Constrained::allow_only(AskForApproval::Never);
let mut writable_roots = Vec::new();
match AbsolutePathBuf::from_absolute_path(consolidation_config.codex_home.clone()) {
Ok(codex_home) => writable_roots.push(codex_home),
Err(err) => warn!(
"memory phase-2 consolidation could not add codex_home writable root {}: {err}",
consolidation_config.codex_home.display()
),
}
let consolidation_sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
if let Err(err) = consolidation_config
.sandbox_policy
.set(consolidation_sandbox_policy)
{
warn!("memory phase-2 consolidation sandbox policy was rejected by constraints: {err}");
let _ = state_db
.mark_global_phase2_job_failed(
&ownership_token,
"consolidation sandbox policy was rejected by constraints",
PHASE_TWO_JOB_RETRY_DELAY_SECONDS,
)
.await;
return false;
}
consolidation_config
};
let latest_memories = match state_db
.list_stage1_outputs_for_global(MAX_RAW_MEMORIES_FOR_GLOBAL)
.await
@@ -81,7 +121,6 @@ pub(super) async fn run_global_memory_consolidation(
return false;
}
};
let root = memory_root(&config.codex_home);
let completion_watermark = completion_watermark(claimed_watermark, &latest_memories);
if let Err(err) = sync_rollout_summaries_from_memories(&root, &latest_memories).await {
warn!("failed syncing local memory artifacts for global consolidation: {err}");
@@ -119,8 +158,6 @@ pub(super) async fn run_global_memory_consolidation(
text: prompt,
text_elements: vec![],
}];
let mut consolidation_config = config.as_ref().clone();
consolidation_config.cwd = root.clone();
let source = SessionSource::SubAgent(SubAgentSource::Other(
MEMORY_CONSOLIDATION_SUBAGENT_LABEL.to_string(),
));
@@ -173,7 +210,9 @@ mod tests {
use crate::memories::rollout_summaries_dir;
use chrono::Utc;
use codex_protocol::ThreadId;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_state::Phase2JobClaimOutcome;
use codex_state::Stage1Output;
@@ -336,6 +375,27 @@ mod tests {
let user_input_ops = harness.user_input_ops_count();
assert_eq!(user_input_ops, 1);
let thread_ids = harness.manager.list_thread_ids().await;
assert_eq!(thread_ids.len(), 1);
let subagent = harness
.manager
.get_thread(thread_ids[0])
.await
.expect("get consolidation thread");
let config_snapshot = subagent.config_snapshot().await;
assert_eq!(config_snapshot.approval_policy, AskForApproval::Never);
assert_eq!(config_snapshot.cwd, memory_root(&harness.config.codex_home));
match config_snapshot.sandbox_policy {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert!(
writable_roots
.iter()
.any(|root| root.as_path() == harness.config.codex_home.as_path()),
"consolidation subagent should have codex_home as writable root"
);
}
other => panic!("unexpected sandbox policy: {other:?}"),
}
harness.shutdown_threads().await;
}