fix: auto-bootstrapped memory was accidentally putting the MEMORY.md directly in the repo root rather than .coyote/memory/MEMORY.md
This commit is contained in:
@@ -11,19 +11,25 @@ pub(crate) const DEFAULT_SKILL_INSTRUCTIONS: &str = indoc! {"
|
||||
pub(crate) const DEFAULT_MEMORY_INSTRUCTIONS: &str = indoc! {"
|
||||
## Memory
|
||||
A persistent memory file system survives across sessions. The MEMORY.md content shown above is
|
||||
your always-on context; put universal facts (user identity, hard rules, binding feedback) directly
|
||||
in MEMORY.md so they appear on every turn. Drill files hold deeper, on-demand context.
|
||||
your always-on context (universal facts, hard rules, binding feedback). Drill files hold deeper,
|
||||
on-demand context that you fetch with `memory__read`.
|
||||
|
||||
Tools:
|
||||
- `memory__read(name)`: Read a specific drill file's full content.
|
||||
- `memory__write(name, content, scope)`: Create or replace a drill file (scope: 'global' | 'workspace').
|
||||
The MEMORY.md index is appended automatically; do not also update the index by hand.
|
||||
- `memory__edit_index(scope, content)`: Replace the entire MEMORY.md at the given scope.
|
||||
Use this to add always-on facts, reorganize, prune stale entries, or fix descriptions.
|
||||
- `memory__list()`: See all known drill files and their metadata.
|
||||
- `memory__lint()`: Health-check memory for orphans, broken links, oversized files.
|
||||
|
||||
RULES:
|
||||
- Every interaction has two outputs: your answer AND any memory updates the conversation warrants.
|
||||
Don't let learnings evaporate into chat history.
|
||||
- When you create or modify a drill file, also update MEMORY.md so the index stays accurate.
|
||||
- All MEMORY.md edits MUST go through `memory__edit_index`. NEVER use `fs_write`, `fs_patch`,
|
||||
or any other generic file tool on MEMORY.md — Coyote manages its location and a stray
|
||||
MEMORY.md outside the managed path is invisible to memory.
|
||||
- All drill files MUST go through `memory__write`. The index updates itself.
|
||||
- Use [[wikilink]] notation in memory files to reference other memories by their `name:` slug.
|
||||
- NEVER write secrets, credentials, or API keys to memory — memory is plaintext on disk.
|
||||
Use coyote's Vault for secrets.
|
||||
|
||||
@@ -129,6 +129,41 @@ pub fn memory_function_declarations() -> Vec<FunctionDeclaration> {
|
||||
},
|
||||
agent: false,
|
||||
},
|
||||
FunctionDeclaration {
|
||||
name: format!("{MEMORY_FUNCTION_PREFIX}edit_index"),
|
||||
description:
|
||||
"Replace the entire MEMORY.md index at the given scope. Use to add always-on facts, \
|
||||
reorganize, prune stale entries, or fix descriptions. Coyote manages the path; \
|
||||
NEVER use fs_write or any other generic file tool on MEMORY.md."
|
||||
.to_string(),
|
||||
parameters: JsonSchema {
|
||||
type_value: Some("object".to_string()),
|
||||
properties: Some(IndexMap::from([
|
||||
(
|
||||
"scope".to_string(),
|
||||
JsonSchema {
|
||||
type_value: Some("string".to_string()),
|
||||
description: Some(
|
||||
"Where to edit: 'global' (user-level) or 'workspace' (project-level)"
|
||||
.into(),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"content".to_string(),
|
||||
JsonSchema {
|
||||
type_value: Some("string".to_string()),
|
||||
description: Some("Full new contents of MEMORY.md".into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
])),
|
||||
required: Some(vec!["scope".to_string(), "content".to_string()]),
|
||||
..Default::default()
|
||||
},
|
||||
agent: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -212,11 +247,33 @@ pub fn handle_memory_tool(ctx: &mut RequestContext, cmd_name: &str, args: &Value
|
||||
"index_updated": index_updated,
|
||||
}))
|
||||
}
|
||||
"edit_index" => {
|
||||
let scope = arg_str(args, "scope")?;
|
||||
let content = arg_str(args, "content")?;
|
||||
let target_dir = match scope.as_str() {
|
||||
"global" => paths::global_memory_dir(),
|
||||
"workspace" => workspace_write_dir(&store, &cwd)?,
|
||||
other => bail!("unknown scope '{}': use 'global' or 'workspace'", other),
|
||||
};
|
||||
let index_path = write_memory_index(&target_dir, &content)?;
|
||||
|
||||
Ok(json!({
|
||||
"status": "ok",
|
||||
"path": index_path.display().to_string(),
|
||||
}))
|
||||
}
|
||||
"lint" => lint_memory(&store),
|
||||
_ => bail!("unknown memory action: {action}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_memory_index(target_dir: &Path, content: &str) -> Result<PathBuf> {
|
||||
fs::create_dir_all(target_dir)?;
|
||||
let index_path = target_dir.join("MEMORY.md");
|
||||
fs::write(&index_path, content)?;
|
||||
Ok(index_path)
|
||||
}
|
||||
|
||||
fn ensure_index_entry(index_path: &Path, name: &str, description: &str) -> Result<bool> {
|
||||
let existing = fs::read_to_string(index_path).unwrap_or_default();
|
||||
let already_referenced =
|
||||
@@ -533,6 +590,39 @@ mod tests {
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_memory_index_creates_dir_and_writes_content() {
|
||||
let root = temp_root("write_index_create");
|
||||
let target = root.join("nested").join(".coyote").join("memory");
|
||||
|
||||
let path =
|
||||
write_memory_index(&target, "# Workspace Memory Index\n\n- [[foo]]: hello\n").unwrap();
|
||||
|
||||
assert_eq!(path, target.join("MEMORY.md"));
|
||||
assert!(path.exists());
|
||||
assert_eq!(
|
||||
fs::read_to_string(&path).unwrap(),
|
||||
"# Workspace Memory Index\n\n- [[foo]]: hello\n"
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_memory_index_replaces_existing_content() {
|
||||
let root = temp_root("write_index_replace");
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
let index = root.join("MEMORY.md");
|
||||
fs::write(&index, "# Old\n\n- [[stale]]: gone\n").unwrap();
|
||||
|
||||
let path = write_memory_index(&root, "# New\n").unwrap();
|
||||
|
||||
assert_eq!(path, index);
|
||||
assert_eq!(fs::read_to_string(&path).unwrap(), "# New\n");
|
||||
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lint_flags_orphans_broken_links_and_oversized() {
|
||||
let root = temp_root("lint");
|
||||
|
||||
Reference in New Issue
Block a user