diff --git a/codex-rs/core/src/memories/stage_one.rs b/codex-rs/core/src/memories/stage_one.rs index 31f752e1d..378a5569d 100644 --- a/codex-rs/core/src/memories/stage_one.rs +++ b/codex-rs/core/src/memories/stage_one.rs @@ -32,7 +32,7 @@ pub(super) fn stage_one_output_schema() -> Value { "rollout_slug": { "type": "string" }, "raw_memory": { "type": "string" } }, - "required": ["rollout_summary", "raw_memory"], + "required": ["rollout_summary", "rollout_slug", "raw_memory"], "additionalProperties": false }) } diff --git a/codex-rs/core/src/memories/tests.rs b/codex-rs/core/src/memories/tests.rs index 1474d05f0..40e019c73 100644 --- a/codex-rs/core/src/memories/tests.rs +++ b/codex-rs/core/src/memories/tests.rs @@ -17,6 +17,7 @@ use codex_protocol::protocol::CompactedItem; use codex_protocol::protocol::RolloutItem; use codex_state::Stage1Output; use pretty_assertions::assert_eq; +use serde_json::Value; use tempfile::tempdir; #[test] @@ -57,6 +58,30 @@ fn parse_stage_one_output_accepts_optional_rollout_slug() { assert_eq!(parsed._rollout_slug, Some("my-slug".to_string())); } +#[test] +fn stage_one_output_schema_requires_all_declared_properties() { + let schema = super::stage_one::stage_one_output_schema(); + let properties = schema + .get("properties") + .and_then(Value::as_object) + .expect("properties object"); + let required = schema + .get("required") + .and_then(Value::as_array) + .expect("required array"); + + let mut property_keys = properties.keys().map(String::as_str).collect::>(); + property_keys.sort_unstable(); + + let mut required_keys = required + .iter() + .map(|key| key.as_str().expect("required key string")) + .collect::>(); + required_keys.sort_unstable(); + + assert_eq!(required_keys, property_keys); +} + #[test] fn serialize_filtered_rollout_response_items_keeps_response_and_compacted() { let input = vec![ diff --git a/codex-rs/core/templates/memories/stage_one_input.md b/codex-rs/core/templates/memories/stage_one_input.md index 59c85146a..7c1b981f6 100644 --- a/codex-rs/core/templates/memories/stage_one_input.md +++ b/codex-rs/core/templates/memories/stage_one_input.md @@ -1,4 +1,4 @@ -Analyze this rollout and produce JSON with `raw_memory`, `rollout_summary`, and optional `rollout_slug`. +Analyze this rollout and produce JSON with `raw_memory`, `rollout_summary`, and `rollout_slug` (use empty string when unknown). rollout_context: - rollout_path: {{ rollout_path }} diff --git a/codex-rs/core/templates/memories/stage_one_system.md b/codex-rs/core/templates/memories/stage_one_system.md index ee9062313..fb82ec437 100644 --- a/codex-rs/core/templates/memories/stage_one_system.md +++ b/codex-rs/core/templates/memories/stage_one_system.md @@ -4,7 +4,7 @@ You are in Phase 1 of the memory pipeline. Your job is to convert one rollout into: - `raw_memory` (detailed, structured markdown for later consolidation) - `rollout_summary` (compact retrieval summary for routing/indexing) -- `rollout_slug` (optional; accepted by the caller but currently not used downstream) +- `rollout_slug` (required string; use `""` when unknown; currently not used downstream) The rollout payload is already embedded in the user message. Do not ask to open files or use tools. @@ -76,8 +76,7 @@ Output contract (strict): - Return exactly one JSON object. - Required keys: - `rollout_summary` (string) + - `rollout_slug` (string; use `""` when unknown; currently unused) - `raw_memory` (string) -- Optional key: - - `rollout_slug` (string; accepted but currently unused) - Empty-field no-op must use empty strings. - No additional commentary outside the JSON object.