mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
[codex] add memory extensions (#16276)
# External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request.
This commit is contained in:
1 parent
12f0e0b0eb
commit
76de99ff25
4 files changed
+128
-10
No files matched your search
@@ -25,6 +25,7 @@ pub(crate) use control::clear_memory_root_contents;
|
|||||||
pub(crate) use start::start_memories_startup_task;
|
pub(crate) use start::start_memories_startup_task;
|
||||||
|
|
||||||
mod artifacts {
|
mod artifacts {
|
||||||
|
pub(super) const EXTENSIONS_SUBDIR: &str = "memories_extensions";
|
||||||
pub(super) const ROLLOUT_SUMMARIES_SUBDIR: &str = "rollout_summaries";
|
pub(super) const ROLLOUT_SUMMARIES_SUBDIR: &str = "rollout_summaries";
|
||||||
pub(super) const RAW_MEMORIES_FILENAME: &str = "raw_memories.md";
|
pub(super) const RAW_MEMORIES_FILENAME: &str = "raw_memories.md";
|
||||||
}
|
}
|
||||||
@@ -106,6 +107,10 @@ fn rollout_summaries_dir(root: &Path) -> PathBuf {
|
|||||||
root.join(artifacts::ROLLOUT_SUMMARIES_SUBDIR)
|
root.join(artifacts::ROLLOUT_SUMMARIES_SUBDIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn memory_extensions_root(root: &Path) -> PathBuf {
|
||||||
|
root.with_file_name(artifacts::EXTENSIONS_SUBDIR)
|
||||||
|
}
|
||||||
|
|
||||||
fn raw_memories_file(root: &Path) -> PathBuf {
|
fn raw_memories_file(root: &Path) -> PathBuf {
|
||||||
root.join(artifacts::RAW_MEMORIES_FILENAME)
|
root.join(artifacts::RAW_MEMORIES_FILENAME)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::memories::memory_extensions_root;
|
||||||
use crate::memories::memory_root;
|
use crate::memories::memory_root;
|
||||||
use crate::memories::phase_one;
|
use crate::memories::phase_one;
|
||||||
use crate::memories::storage::rollout_summary_file_stem_from_parts;
|
use crate::memories::storage::rollout_summary_file_stem_from_parts;
|
||||||
@@ -31,6 +32,18 @@ static MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_TEMPLATE: LazyLock<Template> = LazyLoc
|
|||||||
"memories/read_path.md",
|
"memories/read_path.md",
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
static MEMORY_EXTENSIONS_FOLDER_STRUCTURE_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
|
||||||
|
parse_embedded_template(
|
||||||
|
MEMORY_EXTENSIONS_FOLDER_STRUCTURE,
|
||||||
|
"memories/extensions_folder_structure.md",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
static MEMORY_EXTENSIONS_PRIMARY_INPUTS_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
|
||||||
|
parse_embedded_template(
|
||||||
|
MEMORY_EXTENSIONS_PRIMARY_INPUTS,
|
||||||
|
"memories/extensions_primary_inputs.md",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
fn parse_embedded_template(source: &'static str, template_name: &str) -> Template {
|
fn parse_embedded_template(source: &'static str, template_name: &str) -> Template {
|
||||||
match Template::parse(source) {
|
match Template::parse(source) {
|
||||||
@@ -39,24 +52,82 @@ fn parse_embedded_template(source: &'static str, template_name: &str) -> Templat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MEMORY_EXTENSIONS_FOLDER_STRUCTURE: &str = r#"
|
||||||
|
Memory extensions (under {{ memory_extensions_root }}/):
|
||||||
|
|
||||||
|
- <extension_name>/instructions.md
|
||||||
|
- Source-specific guidance for interpreting additional memory signals. If an
|
||||||
|
extension folder exists, you must read its instructions.md to determine how to use this memory
|
||||||
|
source.
|
||||||
|
|
||||||
|
If the user has any memory extensions, you MUST read the instructions for each extension to
|
||||||
|
determine how to use the memory source. If it has no extension folders, continue with the standard
|
||||||
|
memory inputs only.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const MEMORY_EXTENSIONS_PRIMARY_INPUTS: &str = r#"
|
||||||
|
Optional source-specific inputs:
|
||||||
|
Under `{{ memory_extensions_root }}/`:
|
||||||
|
|
||||||
|
- `<extension_name>/instructions.md`
|
||||||
|
- If extension folders exist, read each instructions.md first and follow it when interpreting
|
||||||
|
that extension's memory source.
|
||||||
|
"#;
|
||||||
|
|
||||||
/// Builds the consolidation subagent prompt for a specific memory root.
|
/// Builds the consolidation subagent prompt for a specific memory root.
|
||||||
pub(super) fn build_consolidation_prompt(
|
pub(super) fn build_consolidation_prompt(
|
||||||
memory_root: &Path,
|
memory_root: &Path,
|
||||||
selection: &Phase2InputSelection,
|
selection: &Phase2InputSelection,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
let memory_extensions_root = memory_extensions_root(memory_root);
|
||||||
|
let memory_extensions_exist = memory_extensions_root.is_dir();
|
||||||
let memory_root = memory_root.display().to_string();
|
let memory_root = memory_root.display().to_string();
|
||||||
|
let memory_extensions_root = memory_extensions_root.display().to_string();
|
||||||
|
let memory_extensions_folder_structure = if memory_extensions_exist {
|
||||||
|
render_memory_extensions_block(
|
||||||
|
&MEMORY_EXTENSIONS_FOLDER_STRUCTURE_TEMPLATE,
|
||||||
|
&memory_extensions_root,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
let memory_extensions_primary_inputs = if memory_extensions_exist {
|
||||||
|
render_memory_extensions_block(
|
||||||
|
&MEMORY_EXTENSIONS_PRIMARY_INPUTS_TEMPLATE,
|
||||||
|
&memory_extensions_root,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
let phase2_input_selection = render_phase2_input_selection(selection);
|
let phase2_input_selection = render_phase2_input_selection(selection);
|
||||||
CONSOLIDATION_PROMPT_TEMPLATE
|
CONSOLIDATION_PROMPT_TEMPLATE
|
||||||
.render([
|
.render([
|
||||||
("memory_root", memory_root.as_str()),
|
("memory_root", memory_root.as_str()),
|
||||||
|
(
|
||||||
|
"memory_extensions_folder_structure",
|
||||||
|
memory_extensions_folder_structure.as_str(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"memory_extensions_primary_inputs",
|
||||||
|
memory_extensions_primary_inputs.as_str(),
|
||||||
|
),
|
||||||
("phase2_input_selection", phase2_input_selection.as_str()),
|
("phase2_input_selection", phase2_input_selection.as_str()),
|
||||||
])
|
])
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
warn!("failed to render memories consolidation prompt template: {err}");
|
warn!("failed to render memories consolidation prompt template: {err}");
|
||||||
format!(
|
format!(
|
||||||
"## Memory Phase 2 (Consolidation)\nConsolidate Codex memories in: {memory_root}\n\n{phase2_input_selection}"
|
"## Memory Phase 2 (Consolidation)\nConsolidate Codex memories in: {memory_root}\n\n{phase2_input_selection}"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_memory_extensions_block(template: &Template, memory_extensions_root: &str) -> String {
|
||||||
|
template
|
||||||
|
.render([("memory_extensions_root", memory_extensions_root)])
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
warn!("failed to render memories extension prompt block: {err}");
|
||||||
|
String::new()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_phase2_input_selection(selection: &Phase2InputSelection) -> String {
|
fn render_phase2_input_selection(selection: &Phase2InputSelection) -> String {
|
||||||
|
|||||||
@@ -55,14 +55,56 @@ fn build_stage_one_input_message_uses_default_limit_when_model_context_window_mi
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_consolidation_prompt_renders_embedded_template() {
|
fn build_consolidation_prompt_renders_embedded_template() {
|
||||||
let prompt =
|
let temp = tempdir().unwrap();
|
||||||
build_consolidation_prompt(Path::new("/tmp/memories"), &Phase2InputSelection::default());
|
let memories_dir = temp.path().join("memories");
|
||||||
|
|
||||||
assert!(prompt.contains("Folder structure (under /tmp/memories/):"));
|
let prompt = build_consolidation_prompt(&memories_dir, &Phase2InputSelection::default());
|
||||||
|
|
||||||
|
assert!(prompt.contains(&format!(
|
||||||
|
"Folder structure (under {}/):",
|
||||||
|
memories_dir.display()
|
||||||
|
)));
|
||||||
|
assert!(!prompt.contains("Memory extensions (under"));
|
||||||
|
assert!(!prompt.contains("<extension_name>/instructions.md"));
|
||||||
assert!(prompt.contains("**Diff since last consolidation:**"));
|
assert!(prompt.contains("**Diff since last consolidation:**"));
|
||||||
assert!(prompt.contains("- selected inputs this run: 0"));
|
assert!(prompt.contains("- selected inputs this run: 0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn build_consolidation_prompt_points_to_extensions_without_inlining_them() {
|
||||||
|
let temp = tempdir().unwrap();
|
||||||
|
let memories_dir = temp.path().join("memories");
|
||||||
|
let extension_dir = temp.path().join("memories_extensions/tape_recorder");
|
||||||
|
tokio_fs::create_dir_all(extension_dir.join("resources"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio_fs::write(
|
||||||
|
extension_dir.join("instructions.md"),
|
||||||
|
"source-specific instructions\n",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio_fs::write(
|
||||||
|
extension_dir.join("resources/notes.md"),
|
||||||
|
"source-specific resource\n",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prompt = build_consolidation_prompt(&memories_dir, &Phase2InputSelection::default());
|
||||||
|
let memory_extensions_dir = temp.path().join("memories_extensions");
|
||||||
|
|
||||||
|
assert!(prompt.contains(&format!(
|
||||||
|
"Memory extensions (under {}/)",
|
||||||
|
memory_extensions_dir.display()
|
||||||
|
)));
|
||||||
|
assert!(prompt.contains(&format!("Under `{}/`:", memory_extensions_dir.display())));
|
||||||
|
assert!(prompt.contains("<extension_name>/instructions.md"));
|
||||||
|
assert!(prompt.contains("Optional source-specific inputs:"));
|
||||||
|
assert!(!prompt.contains("source-specific instructions"));
|
||||||
|
assert!(!prompt.contains("source-specific resource"));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn build_memory_tool_developer_instructions_renders_embedded_template() {
|
async fn build_memory_tool_developer_instructions_renders_embedded_template() {
|
||||||
let temp = tempdir().unwrap();
|
let temp = tempdir().unwrap();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Folder structure (under {{ memory_root }}/):
|
|||||||
- Recap of the rollout, including lessons learned, reusable knowledge,
|
- Recap of the rollout, including lessons learned, reusable knowledge,
|
||||||
pointers/references, and pruned raw evidence snippets. Distilled version of
|
pointers/references, and pruned raw evidence snippets. Distilled version of
|
||||||
everything valuable from the raw rollout.
|
everything valuable from the raw rollout.
|
||||||
|
{{ memory_extensions_folder_structure }}
|
||||||
============================================================
|
============================================================
|
||||||
GLOBAL SAFETY, HYGIENE, AND NO-FILLER RULES (STRICT)
|
GLOBAL SAFETY, HYGIENE, AND NO-FILLER RULES (STRICT)
|
||||||
============================================================
|
============================================================
|
||||||
@@ -135,7 +135,7 @@ Under `{{ memory_root }}/`:
|
|||||||
- read the existing summary so updates stay consistent
|
- read the existing summary so updates stay consistent
|
||||||
- `skills/*`
|
- `skills/*`
|
||||||
- read existing skills so updates are incremental and non-duplicative
|
- read existing skills so updates are incremental and non-duplicative
|
||||||
|
{{ memory_extensions_primary_inputs }}
|
||||||
Mode selection:
|
Mode selection:
|
||||||
|
|
||||||
- INIT phase: existing artifacts are missing/empty (especially `memory_summary.md`
|
- INIT phase: existing artifacts are missing/empty (especially `memory_summary.md`
|
||||||
|
|||||||
Reference in new issue
Block a user