mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
chore: move memory prompt builder into extension (#24558)
## Why The memories extension now owns the read-path developer instructions it injects at thread start. Keeping that prompt builder and template in `codex-memories-read` left the extension depending on a helper crate for extension-specific prompt assembly, and kept async template/truncation dependencies in the read crate after the remaining read surface no longer needed them. ## What changed - Moved `prompts.rs`, its tests, and `templates/memories/read_path.md` from `memories/read` into `ext/memories`. - Wired `MemoryExtension` to call the local prompt builder and added the moved templates to `ext/memories/BUILD.bazel` compile data. - Removed the now-unused prompt export and prompt-related dependencies from `codex-memories-read`. ## Testing - Not run locally.
This commit is contained in:
@@ -3,4 +3,7 @@ load("//:defs.bzl", "codex_rust_crate")
|
||||
codex_rust_crate(
|
||||
name = "memories",
|
||||
crate_name = "codex_memories_extension",
|
||||
compile_data = glob([
|
||||
"templates/**",
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -17,10 +17,10 @@ async-trait = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-extension-api = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-memories-read = { workspace = true }
|
||||
codex-tools = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-output-truncation = { workspace = true }
|
||||
codex-utils-template = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -10,10 +10,10 @@ use codex_extension_api::ThreadLifecycleContributor;
|
||||
use codex_extension_api::ThreadStartInput;
|
||||
use codex_extension_api::ToolContributor;
|
||||
use codex_features::Feature;
|
||||
use codex_memories_read::build_memory_tool_developer_instructions;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use crate::local::LocalMemoriesBackend;
|
||||
use crate::prompts::build_memory_tool_developer_instructions;
|
||||
use crate::tools;
|
||||
|
||||
/// Contributes Codex memory read-path prompt context and memory read tools.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod backend;
|
||||
mod extension;
|
||||
mod local;
|
||||
mod prompts;
|
||||
mod schema;
|
||||
mod tools;
|
||||
|
||||
@@ -11,6 +12,7 @@ pub(crate) const MAX_LIST_RESULTS: usize = 2_000;
|
||||
pub(crate) const DEFAULT_SEARCH_MAX_RESULTS: usize = 200;
|
||||
pub(crate) const MAX_SEARCH_RESULTS: usize = 200;
|
||||
pub(crate) const DEFAULT_READ_MAX_TOKENS: usize = 20_000;
|
||||
pub(crate) const MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT: usize = 2_500;
|
||||
|
||||
pub(crate) const MEMORY_TOOLS_NAMESPACE: &str = "memories/";
|
||||
pub(crate) const LIST_TOOL_NAME: &str = "list";
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_output_truncation::TruncationPolicy;
|
||||
use codex_utils_output_truncation::truncate_text;
|
||||
use codex_utils_template::Template;
|
||||
use std::sync::LazyLock;
|
||||
use tokio::fs;
|
||||
|
||||
static MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
|
||||
parse_embedded_template(
|
||||
include_str!("../templates/memories/read_path.md"),
|
||||
"memories/read_path.md",
|
||||
)
|
||||
});
|
||||
|
||||
fn parse_embedded_template(source: &'static str, template_name: &str) -> Template {
|
||||
match Template::parse(source) {
|
||||
Ok(template) => template,
|
||||
Err(err) => panic!("embedded template {template_name} is invalid: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the memory read-path prompt that is added to developer instructions.
|
||||
///
|
||||
/// Large `memory_summary.md` files are truncated at
|
||||
/// [MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT].
|
||||
pub(crate) async fn build_memory_tool_developer_instructions(
|
||||
codex_home: &AbsolutePathBuf,
|
||||
) -> Option<String> {
|
||||
let base_path = codex_home.join("memories");
|
||||
let memory_summary_path = base_path.join("memory_summary.md");
|
||||
let memory_summary = fs::read_to_string(&memory_summary_path)
|
||||
.await
|
||||
.ok()?
|
||||
.trim()
|
||||
.to_string();
|
||||
let memory_summary = truncate_text(
|
||||
&memory_summary,
|
||||
TruncationPolicy::Tokens(MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT),
|
||||
);
|
||||
if memory_summary.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let base_path = base_path.display().to_string();
|
||||
MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_TEMPLATE
|
||||
.render([
|
||||
("base_path", base_path.as_str()),
|
||||
("memory_summary", memory_summary.as_str()),
|
||||
])
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "prompts_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,35 @@
|
||||
use super::*;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
use tokio::fs as tokio_fs;
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_memory_tool_developer_instructions_renders_embedded_template() {
|
||||
let temp = tempdir().unwrap();
|
||||
let codex_home = AbsolutePathBuf::from_absolute_path(temp.path()).unwrap();
|
||||
let memories_dir = codex_home.join("memories");
|
||||
tokio_fs::create_dir_all(&memories_dir).await.unwrap();
|
||||
tokio_fs::write(
|
||||
memories_dir.join("memory_summary.md"),
|
||||
"Short memory summary for tests.",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let instructions = build_memory_tool_developer_instructions(&codex_home)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(instructions.contains(&format!(
|
||||
"- {}/memory_summary.md (already provided below; do NOT open again)",
|
||||
memories_dir.display()
|
||||
)));
|
||||
assert!(instructions.contains("Short memory summary for tests."));
|
||||
assert_eq!(
|
||||
instructions
|
||||
.matches("========= MEMORY_SUMMARY BEGINS =========")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
## Memory
|
||||
|
||||
You have access to a memory folder with guidance from prior runs. It can save
|
||||
time and help you stay consistent. Use it whenever it is likely to help.
|
||||
|
||||
Decision boundary: should you use memory for a new user query?
|
||||
|
||||
- Skip memory ONLY when the request is clearly self-contained and does not need
|
||||
workspace history, conventions, or prior decisions.
|
||||
- Hard skip examples: current time/date, simple translation, simple sentence
|
||||
rewrite, one-line shell command, trivial formatting.
|
||||
- Use memory by default when ANY of these are true:
|
||||
- the query mentions workspace/repo/module/path/files in MEMORY_SUMMARY below,
|
||||
- the user asks for prior context / consistency / previous decisions,
|
||||
- the task is ambiguous and could depend on earlier project choices,
|
||||
- the ask is a non-trivial and related to MEMORY_SUMMARY below.
|
||||
- If unsure, do a quick memory pass.
|
||||
|
||||
Memory layout (general -> specific):
|
||||
|
||||
- {{ base_path }}/memory_summary.md (already provided below; do NOT open again)
|
||||
- {{ base_path }}/MEMORY.md (searchable registry; primary file to query)
|
||||
- {{ base_path }}/skills/<skill-name>/ (skill folder)
|
||||
- SKILL.md (entrypoint instructions)
|
||||
- scripts/ (optional helper scripts)
|
||||
- examples/ (optional example outputs)
|
||||
- templates/ (optional templates)
|
||||
- {{ base_path }}/rollout_summaries/ (per-rollout recaps + evidence snippets)
|
||||
- The paths of these entries can be found in {{ base_path }}/MEMORY.md or {{ base_path }}/rollout_summaries/ as `rollout_path`
|
||||
- These files are append-only `jsonl`: `session_meta.payload.id` identifies the session, `turn_context` marks turn boundaries, `event_msg` is the lightweight status stream, and `response_item` contains actual messages, tool calls, and tool outputs.
|
||||
- For efficient lookup, prefer matching the filename suffix or `session_meta.payload.id`; avoid broad full-content scans unless needed.
|
||||
|
||||
Quick memory pass (when applicable):
|
||||
|
||||
1. Skim the MEMORY_SUMMARY below and extract task-relevant keywords.
|
||||
2. Search {{ base_path }}/MEMORY.md using those keywords.
|
||||
3. Only if MEMORY.md directly points to rollout summaries/skills, open the 1-2
|
||||
most relevant files under {{ base_path }}/rollout_summaries/ or
|
||||
{{ base_path }}/skills/.
|
||||
4. If above are not clear and you need exact commands, error text, or precise evidence, search over `rollout_path` for more evidence.
|
||||
5. If there are no relevant hits, stop memory lookup and continue normally.
|
||||
|
||||
Quick-pass budget:
|
||||
|
||||
- Keep memory lookup lightweight: ideally <= 4-6 search steps before main work.
|
||||
- Avoid broad scans of all rollout summaries.
|
||||
|
||||
During execution: if you hit repeated errors, confusing behavior, or suspect
|
||||
relevant prior context, redo the quick memory pass.
|
||||
|
||||
How to decide whether to verify memory:
|
||||
|
||||
- Consider both risk of drift and verification effort.
|
||||
- If a fact is likely to drift and is cheap to verify, verify it before
|
||||
answering.
|
||||
- If a fact is likely to drift but verification is expensive, slow, or
|
||||
disruptive, it is acceptable to answer from memory in an interactive turn,
|
||||
but you should say that it is memory-derived, note that it may be stale, and
|
||||
consider offering to refresh it live.
|
||||
- If a fact is lower-drift and expensive to verify, it is usually fine to
|
||||
answer from memory directly.
|
||||
|
||||
When answering from memory without current verification:
|
||||
|
||||
- If you rely on memory for a fact that you did not verify in the current turn,
|
||||
say so briefly in the final answer.
|
||||
- If that fact is plausibly drift-prone or comes from an older note, older
|
||||
snapshot, or prior run summary, say that it may be stale or outdated.
|
||||
- If live verification was skipped and a refresh would be useful in the
|
||||
interactive context, consider offering to verify or refresh it live.
|
||||
- Do not present unverified memory-derived facts as confirmed-current.
|
||||
- Prefer a short refresh offer for interactive questions, especially about prior
|
||||
results, commands, timing, or older snapshots.
|
||||
|
||||
Memory citation requirements:
|
||||
|
||||
- If ANY relevant memory files were used: append exactly one
|
||||
`<oai-mem-citation>` block as the VERY LAST content of the final reply.
|
||||
Normal responses should include the answer first, then append the
|
||||
`<oai-mem-citation>` block at the end.
|
||||
- Use this exact structure for programmatic parsing:
|
||||
```
|
||||
<oai-mem-citation>
|
||||
<citation_entries>
|
||||
MEMORY.md:234-236|note=[responsesapi citation extraction code pointer]
|
||||
rollout_summaries/2026-02-17T21-23-02-LN3m-example.md:10-12|note=[weekly report format]
|
||||
</citation_entries>
|
||||
<rollout_ids>
|
||||
019c6e27-e55b-73d1-87d8-4e01f1f75043
|
||||
019c7714-3b77-74d1-9866-e1f484aae2ab
|
||||
</rollout_ids>
|
||||
</oai-mem-citation>
|
||||
```
|
||||
- `citation_entries` is for rendering:
|
||||
- one citation entry per line
|
||||
- format: `<file>:<line_start>-<line_end>|note=[<how memory was used>]`
|
||||
- use file paths relative to the memory base path (for example, `MEMORY.md`,
|
||||
`rollout_summaries/...`, `skills/...`)
|
||||
- only cite files actually used under the memory base path (do not cite
|
||||
workspace files as memory citations)
|
||||
- if you used `MEMORY.md` and then a rollout summary/skill file, cite both
|
||||
- list entries in order of importance (most important first)
|
||||
- `note` should be short, single-line, and use simple characters only (avoid
|
||||
unusual symbols, no newlines)
|
||||
- `rollout_ids` is for us to track what previous rollouts you find useful:
|
||||
- include one rollout id per line
|
||||
- rollout ids should look like UUIDs (for example,
|
||||
`019c6e27-e55b-73d1-87d8-4e01f1f75043`)
|
||||
- include unique ids only; do not repeat ids
|
||||
- an empty `<rollout_ids>` section is allowed if no rollout ids are available
|
||||
- you can find rollout ids in rollout summary files and MEMORY.md
|
||||
- do not include file paths or notes in this section
|
||||
- For every `citation_entries`, try to find and cite the corresponding rollout id if possible
|
||||
- Never include memory citations inside pull-request messages.
|
||||
- Never cite blank lines; double-check ranges.
|
||||
|
||||
Updating memories:
|
||||
|
||||
You can update the memories **only** when explicitly asked by the user. This must always come from a direct request from the user.
|
||||
- Write your update in {{ base_path }}/extensions/ad_hoc/notes/
|
||||
- Each update must be one small file containing what you want to add/delete/update from the memories.
|
||||
- The name of this file must be `<timestamp>-<short slug>.md`
|
||||
- Do not try to edit the memory files yourself, only add one update note in {{ base_path }}/extensions/ad_hoc/notes/
|
||||
|
||||
========= MEMORY_SUMMARY BEGINS =========
|
||||
{{ memory_summary }}
|
||||
========= MEMORY_SUMMARY ENDS =========
|
||||
|
||||
When memory is likely relevant, start with the quick memory pass above before
|
||||
deep repo exploration.
|
||||
Reference in New Issue
Block a user