diff --git a/crates/manifest/src/config.rs b/crates/manifest/src/config.rs index 9239fb7c5..692c7ef04 100644 --- a/crates/manifest/src/config.rs +++ b/crates/manifest/src/config.rs @@ -213,6 +213,8 @@ pub struct MemoryExtractionProfileConfigPartial { #[serde(default)] pub model: Option, #[serde(default)] + pub reasoning: Option, + #[serde(default)] pub threshold: Option, #[serde(default)] pub worker_max_turns: Option, @@ -262,6 +264,7 @@ impl MemoryExtractionProfileConfigPartial { Self { enabled: other.enabled.or(self.enabled), model: other.model.or(self.model), + reasoning: other.reasoning.or(self.reasoning), threshold: other.threshold.or(self.threshold), worker_max_turns: other.worker_max_turns.or(self.worker_max_turns), } @@ -407,6 +410,7 @@ impl From for ResolvedMemoryFeatureConfig { extraction: MemoryExtractionProfileConfig { enabled: extraction.enabled.unwrap_or(true), model: extraction.model, + reasoning: extraction.reasoning, threshold: extraction.threshold.or(Some(50_000)), worker_max_turns: extraction .worker_max_turns @@ -432,6 +436,7 @@ impl From for MemoryFeatureConfigPartial { extraction: Some(MemoryExtractionProfileConfigPartial { enabled: Some(value.profile.extraction.enabled), model: value.profile.extraction.model, + reasoning: value.profile.extraction.reasoning, threshold: value.profile.extraction.threshold, worker_max_turns: value.profile.extraction.worker_max_turns, }), @@ -1920,9 +1925,13 @@ inject_summary = false [feature.memory.extraction] enabled = true +reasoning = "medium" threshold = 42000 worker_max_turns = 2 +[feature.memory.extraction.model] +ref = "codex-oauth/gpt-5.6-luna" + [feature.memory.consolidation] request_enabled = false "#, @@ -1935,6 +1944,14 @@ request_enabled = false assert_eq!(memory.consolidation.unwrap().request_enabled, Some(false)); let extraction = memory.extraction.unwrap(); assert_eq!(extraction.enabled, Some(true)); + assert_eq!( + extraction.reasoning, + Some(ReasoningControl::Effort(ReasoningEffort::Medium)) + ); + assert_eq!( + extraction.model.and_then(|model| model.ref_), + Some("codex-oauth/gpt-5.6-luna".to_string()) + ); assert_eq!(extraction.threshold, Some(42_000)); assert_eq!(extraction.worker_max_turns, Some(2)); } diff --git a/crates/manifest/src/lib.rs b/crates/manifest/src/lib.rs index 20d052ff7..63af43df6 100644 --- a/crates/manifest/src/lib.rs +++ b/crates/manifest/src/lib.rs @@ -267,6 +267,7 @@ impl Default for MemoryResidentProfileConfig { pub struct MemoryExtractionProfileConfig { pub enabled: bool, pub model: Option, + pub reasoning: Option, pub threshold: Option, pub worker_max_turns: Option, } @@ -276,6 +277,7 @@ impl Default for MemoryExtractionProfileConfig { Self { enabled: true, model: None, + reasoning: None, threshold: Some(50_000), worker_max_turns: defaults::MEMORY_EXTRACT_WORKER_MAX_TURNS, } diff --git a/crates/manifest/src/profile.rs b/crates/manifest/src/profile.rs index 42270f544..e5572b0bb 100644 --- a/crates/manifest/src/profile.rs +++ b/crates/manifest/src/profile.rs @@ -1230,6 +1230,66 @@ mod tests { } } + #[test] + fn builtin_profiles_pin_role_models_and_reasoning() { + use crate::model::{ReasoningControl, ReasoningEffort}; + + let tmp = TempDir::new().unwrap(); + let resolve = |name: &str| { + ProfileResolver::new() + .with_workspace_base(tmp.path()) + .resolve( + &ProfileSelector::source_named(ProfileRegistrySource::Builtin, name), + ProfileResolveOptions::with_worker_name(format!("{name}-worker")), + ) + .unwrap() + }; + + let companion = resolve("companion"); + assert_eq!( + companion.manifest.model.ref_.as_deref(), + Some("codex-oauth/gpt-6-astra") + ); + assert_eq!( + companion.manifest.engine.reasoning, + Some(ReasoningControl::Effort(ReasoningEffort::High)) + ); + + for name in ["coder", "reviewer"] { + let role = resolve(name); + assert_eq!( + role.manifest.model.ref_.as_deref(), + Some("codex-oauth/gpt-5.6-sol") + ); + assert_eq!( + role.manifest.engine.reasoning, + Some(ReasoningControl::Effort(ReasoningEffort::High)) + ); + let extraction = &role.manifest.feature.memory.profile.extraction; + assert_eq!( + extraction + .model + .as_ref() + .and_then(|model| model.ref_.as_deref()), + Some("codex-oauth/gpt-5.6-luna") + ); + assert_eq!( + extraction.reasoning, + Some(ReasoningControl::Effort(ReasoningEffort::Medium)) + ); + } + + let consolidation = resolve("memory-consolidation"); + assert_eq!( + consolidation.manifest.model.ref_.as_deref(), + Some("codex-oauth/gpt-5.6-luna") + ); + assert_eq!( + consolidation.manifest.engine.reasoning, + Some(ReasoningControl::Effort(ReasoningEffort::Medium)) + ); + } + #[test] fn profile_rejects_workspace_memory_snapshot_authority_fields() { let tmp = TempDir::new().unwrap(); diff --git a/crates/worker/src/feature/builtin/memory_lifecycle.rs b/crates/worker/src/feature/builtin/memory_lifecycle.rs index 13d501ea0..a1772f8e8 100644 --- a/crates/worker/src/feature/builtin/memory_lifecycle.rs +++ b/crates/worker/src/feature/builtin/memory_lifecycle.rs @@ -163,6 +163,17 @@ fn memory_lifecycle_task_spec() -> BackgroundTaskSpec { } impl MemoryLifecycleTask { + fn extraction_manifest(&self) -> WorkerManifest { + let mut manifest = self.manifest.clone(); + if let Some(model) = self.config.profile.extraction.model.clone() { + manifest.model = model; + } + if let Some(reasoning) = self.config.profile.extraction.reasoning.clone() { + manifest.engine.reasoning = Some(reasoning); + } + manifest + } + async fn run_extraction( &self, context: BackgroundTaskContext, @@ -365,10 +376,7 @@ impl MemoryLifecycleTask { return Ok(()); } }; - let mut manifest = self.manifest.clone(); - if let Some(model) = self.config.profile.extraction.model.clone() { - manifest.model = model; - } + let manifest = self.extraction_manifest(); let cancel_observer = move |sender: tokio::sync::mpsc::Sender<()>| { tokio::spawn(async move { @@ -1114,6 +1122,40 @@ permission = "write" registry.shutdown().await.unwrap(); } + #[test] + fn extraction_manifest_applies_memory_model_and_reasoning_overrides() { + use agen::llm_client::capability::{ReasoningControl, ReasoningEffort}; + + let client = ScriptClient::new(Vec::new()); + let extension_writes = Arc::new(Mutex::new(Vec::new())); + let (event_tx, _) = broadcast::channel(16); + let workspace_client: Arc = + Arc::new(RecordingWorkspaceClient::default()); + let mut task = test_task( + capture(2, 250), + Box::new(client), + extension_writes, + event_tx, + workspace_client, + ); + task.config.profile.extraction.model = Some(manifest::ModelManifest { + ref_: Some("codex-oauth/gpt-5.6-luna".to_string()), + ..Default::default() + }); + task.config.profile.extraction.reasoning = + Some(ReasoningControl::Effort(ReasoningEffort::Medium)); + + let manifest = task.extraction_manifest(); + assert_eq!( + manifest.model.ref_.as_deref(), + Some("codex-oauth/gpt-5.6-luna") + ); + assert_eq!( + manifest.engine.reasoning, + Some(ReasoningControl::Effort(ReasoningEffort::Medium)) + ); + } + #[tokio::test] async fn run_committed_background_task_finishes_empty_extraction_and_commits_pointer() { let client = ScriptClient::new(vec![finish_empty_events("finish-1"), completed_events()]); diff --git a/docs/manifest.toml b/docs/manifest.toml index e7f72fd35..5ec595511 100644 --- a/docs/manifest.toml +++ b/docs/manifest.toml @@ -239,12 +239,13 @@ permission = "write" # # [feature.memory.profile.extraction] # enabled = true +# reasoning = "medium" # threshold = 30000 # worker_max_turns = 8 # -# # 任意。省略時はmain modelをcloneする。 +# # modelは任意。省略時はmain modelをcloneする。 # # [feature.memory.profile.extraction.model] -# # ref = "anthropic/claude-haiku-4-5" +# # ref = "codex-oauth/gpt-5.6-luna" # # [feature.memory.profile.consolidation] # request_enabled = true diff --git a/resources/models/builtin.toml b/resources/models/builtin.toml index d88a23bca..05e088d39 100644 --- a/resources/models/builtin.toml +++ b/resources/models/builtin.toml @@ -50,6 +50,13 @@ context_window = 1050000 max_context_window = 272000 capability = { tool_calling = "parallel", structured_output = "json_schema", reasoning = "effort", vision = true, prompt_caching = { kind = "auto" } } +[[model]] +id = "gpt-5.6-luna" +provider = "codex-oauth" +context_window = 1050000 +max_context_window = 272000 +capability = { tool_calling = "parallel", structured_output = "json_schema", reasoning = "effort", vision = true, prompt_caching = { kind = "auto" } } + [[model]] id = "gpt-5.5" provider = "codex-oauth" diff --git a/resources/profiles/base.dcdl b/resources/profiles/base.dcdl index c6cdc32c4..80a6121bd 100644 --- a/resources/profiles/base.dcdl +++ b/resources/profiles/base.dcdl @@ -28,6 +28,8 @@ feature = { resident = { inject_summary = true; }; extraction = { enabled = true; + model = { ref = "codex-oauth/gpt-5.6-luna"; }; + reasoning = "medium"; threshold = 50000; }; consolidation = { request_enabled = true; }; diff --git a/resources/profiles/coder.dcdl b/resources/profiles/coder.dcdl index 4c4dfbfe2..5067ddca8 100644 --- a/resources/profiles/coder.dcdl +++ b/resources/profiles/coder.dcdl @@ -2,7 +2,11 @@ import "./base.dcdl" // { slug = "coder"; description = "Ticket implementation coder profile."; scope = "workspace_write"; - engine = { instruction = "role.coder"; }; + model = { ref = "codex-oauth/gpt-5.6-sol"; }; + engine = { + instruction = "role.coder"; + reasoning = "high"; + }; feature = { task = { enabled = true; }; diff --git a/resources/profiles/companion.dcdl b/resources/profiles/companion.dcdl index 85f4fbee9..43366d48d 100644 --- a/resources/profiles/companion.dcdl +++ b/resources/profiles/companion.dcdl @@ -2,6 +2,7 @@ import "./base.dcdl" // { slug = "companion"; description = "Workspace companion profile."; scope = "workspace_write"; + model = { ref = "codex-oauth/gpt-6-astra"; }; feature = { task = { enabled = true; }; diff --git a/resources/profiles/memory-consolidation.dcdl b/resources/profiles/memory-consolidation.dcdl index d1a0474e8..86eb50670 100644 --- a/resources/profiles/memory-consolidation.dcdl +++ b/resources/profiles/memory-consolidation.dcdl @@ -2,6 +2,8 @@ import "./base.dcdl" // { slug = "memory-consolidation"; description = "Memory staging consolidation profile."; scope = "workspace_read"; + model = { ref = "codex-oauth/gpt-5.6-luna"; }; + engine = { reasoning = "medium"; }; feature = { task = { enabled = false; }; diff --git a/resources/profiles/reviewer.dcdl b/resources/profiles/reviewer.dcdl index b41f9627f..29ee93768 100644 --- a/resources/profiles/reviewer.dcdl +++ b/resources/profiles/reviewer.dcdl @@ -2,7 +2,11 @@ import "./base.dcdl" // { slug = "reviewer"; description = "Ticket review profile."; scope = "workspace_read"; - engine = { instruction = "role.reviewer"; }; + model = { ref = "codex-oauth/gpt-5.6-sol"; }; + engine = { + instruction = "role.reviewer"; + reasoning = "high"; + }; feature = { task = { enabled = true; };