diff --git a/Home.md b/Home.md index 3141b51..96cd58f 100644 --- a/Home.md +++ b/Home.md @@ -38,6 +38,7 @@ Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration g * [Macros](Macros): Automate repetitive tasks and workflows with Coyote "scripts" (macros). * [RAG](RAG): Retrieval-Augmented Generation for enhanced information retrieval and generation. * [Sessions](Sessions): Manage and persist conversational contexts and settings across multiple interactions. +* [Memory](Memory): Persistent file-based memory that survives across sessions. Bootstrap with `coyote --init-memory [global|workspace]`. * [Roles](Roles): Customize model behavior for specific tasks or domains. * [Skills](Skills): Modular knowledge or capability packs the LLM can load and unload mid-conversation. Multiple skills compose; instructions stack, tools and MCPs union. * [Agents](Agents): Leverage AI agents to perform complex tasks and workflows, including sub-agent spawning, teammate messaging, and user interaction tools. diff --git a/Memory.md b/Memory.md new file mode 100644 index 0000000..ca56e64 --- /dev/null +++ b/Memory.md @@ -0,0 +1,207 @@ +# Memory + +Coyote can maintain a **persistent memory file system** that survives across sessions. Memory captures cross-session +facts about you, your project, and your preferences, complementing [Sessions](Sessions) (which handle +within-conversation state) and [Skills](Skills) (which are static knowledge packs). + +## Quick Start + +The fastest path is the bootstrap command: + +```bash +# Workspace memory (per-project) +cd /path/to/your/project +coyote --init-memory workspace +# → creates ./COYOTE.md with a skeleton + +# Global memory (per-user, applies everywhere) +coyote --init-memory global +# → creates /memory/MEMORY.md +``` + +Then edit the file with anything you want the LLM to remember: + +```markdown +# Project Memory + +This project is a Rust CLI tool. I prefer terse responses and concrete code examples. +``` + +Run coyote in that directory and the memory content is injected into every system prompt automatically. The LLM will see and use the `memory__*` tools to update it over time (when function calling is on). + +That's it for "lite mode." For structured memory, see below. + +> Memory is **opt-in by file presence** for the read path. If no marker exists on disk, no workspace memory is injected and global memory is loaded only if the global marker exists. `--init-memory` is the explicit consent signal. + +> **Write-side exception (git repos only):** if the LLM calls `memory__write(scope=workspace)` and no marker exists, coyote auto-bootstraps a structured layout at the git root and adds `.coyote/memory/` to `.gitignore`. See [Git-Repo Auto-Bootstrap](#git-repo-auto-bootstrap) below. Outside a git repo this fails with a hint to run `coyote --init-memory workspace`. + +> **Re-running is safe.** `--init-memory` refuses to overwrite an existing marker — it prints `Memory marker already exists at '...'` and exits without touching the file. Useful in setup scripts. + +## How It Works + +Memory has two scopes: + +- **Global** (user-level): `~/.config/coyote/memory/` - facts about you that apply everywhere +- **Workspace** (project-level): `/COYOTE.md` (lite) or `/.coyote/memory/` (structured) + +Memory is **opt-in by workspace**: coyote walks up from your current directory looking for a memory marker. If none is +found, no workspace memory is loaded. Global memory is loaded if it exists. + +When both a `.coyote/memory/MEMORY.md` and a `COYOTE.md` exist at the same level, the structured layout wins. + +### Git-Repo Auto-Bootstrap + +If the LLM tries to write workspace memory and no marker exists in any ancestor, coyote checks for a git repository before erroring: + +- **Git root found** (ancestor contains a `.git` directory or file, worktrees and submodules included): coyote creates + `/.coyote/memory/MEMORY.md` with an empty index, appends `.coyote/memory/` to `/.gitignore` + (idempotent; no duplicate if you already have it, including the trailing-slash-less form), and emits a `WARN`-level + log line so you know it happened. The write then proceeds. +- **No git root**: the write fails with an error pointing you at `coyote --init-memory workspace`. + +The rationale: if you're in a git repo, the natural anchor for workspace-wide memory is the repo root, and the +gitignore entry keeps the LLM's drill files out of version control by default. If you're not in a git repo, the +bootstrap location is genuinely ambiguous, so coyote requires explicit consent via `--init-memory`. + +This only affects the write path. The read path remains opt-in: until a marker exists, no workspace memory is injected. + +## Two Halves: Read and Write + +Memory has two independent halves: + +| Half | Requires | Behavior | +|-----------|--------------------------|-----------------------------------------------------------------| +| **Read** | Nothing | Memory content is injected into the system prompt every session | +| **Write** | Function calling support | The LLM uses `memory__*` tools to curate memory autonomously | + +If your model lacks function calling, memory degrades to read-only: you maintain the files manually, the LLM sees them. +In read-only mode coyote injects all known drill files (up to the without-tools cap) so the model has full context +without needing tools. + +## Structured Mode + +For non-trivial memory, use the structured layout: + +``` +/.coyote/memory/ +├── MEMORY.md # index (always injected) +├── project_compliance.md +├── feedback_terse.md +└── reference_dashboards.md +``` + +Each drill file has YAML frontmatter: + +```markdown +--- +name: project_compliance +description: Compliance constraints driving the auth rewrite +type: project +--- + +We must store session tokens server-side per the 2026 audit. ... +``` + +`MEMORY.md` is what gets injected on every prompt. It serves two purposes: +1. **An index** of available drill files (one line per file: name + description) +2. **A home for universal facts** the LLM should always see (user identity, hard rules, binding feedback) + +Drill files are fetched on demand by the LLM via `memory__read`. Keep them focused; for facts that should always be +visible, write them directly in `MEMORY.md` instead. + +## Memory Types + +| Type | Purpose | Example | +|-------------|---------------------------------------------------|------------------------------------------------| +| `user` | Persistent facts about you | "Senior Rust dev, terse responses preferred" | +| `feedback` | Past corrections that should bind future behavior | "Never use `as any`; got burned in incident X" | +| `project` | Workspace-specific facts | "API freeze begins 2026-03-05" | +| `reference` | Pointers to external systems | "Bugs tracked in Linear project INGEST" | + +The `type:` field is informational, meaning coyote does not change behavior based on it. It exists so the LLM can +categorize its own writes and so you can grep by category. + +## Tools + +When function calling is enabled, coyote exposes: + +- `memory__read(name)`: read a specific drill file by its slug +- `memory__write(name, description, content, scope, type)`: create or replace a drill file (`scope`: `global` | + `workspace`) +- `memory__list()`: see all known drill files with metadata +- `memory__lint()`: health-check (orphans, broken `[[wikilinks]]`, oversized files >2K chars) + +The LLM is instructed to update `MEMORY.md` whenever it writes a new file. Two silent promotions can happen on a +`memory__write(scope=workspace)`: + +- **Lite -> structured**: if the workspace is in lite mode (`COYOTE.md` only), the new file goes into + `/.coyote/memory/` and `COYOTE.md` is left untouched. +- **None -> structured (git only)**: if no marker exists anywhere, coyote auto-bootstraps at the git root. See + [Git-Repo Auto-Bootstrap](#git-repo-auto-bootstrap). + +## Toggles + +Memory can be disabled at multiple levels (most specific wins): + +1. **CLI flag**: `coyote --no-memory ...` (per-invocation; sets `false` at the app layer) +2. **Graph agents**: memory is always off +3. **Agent config**: `memory: false` in the agent's `config.yaml` +4. **Session**: `memory: false` in the saved session frontmatter (also settable via the session's `set_memory` API) +5. **Role config**: `memory: false` in role frontmatter +6. **AppConfig**: `memory: false` in global config +7. **Workspace presence** (read side): absence of `COYOTE.md` and `.coyote/memory/`. On the write side, + `memory__write(scope=workspace)` inside a git repo can auto-create `.coyote/memory/` (see + [Git-Repo Auto-Bootstrap](#git-repo-auto-bootstrap) for more information). + +Resolution cascade for the boolean flag: **agent > session > role > app**, with the first explicit value winning. +No value means "no opinion, defer to next layer." If the flag resolves to true but no memory exists on disk anywhere, +memory stays off (no empty injection). + +## Caps + +Two cap constants control how much memory content is injected: + +| Field (in AppConfig) | Default | When | +|----------------------------|--------------|----------------------------------------------------------------------------| +| `memory_cap_with_tools` | 6,000 chars | Function calling on. Only indexes injected, drill bodies fetched on demand | +| `memory_cap_without_tools` | 12,000 chars | Function calling off. Indexes plus drill bodies up to the cap | + +In without-tools mode, drill files are appended alphabetically until the cap is exhausted; any omitted files are +reported via a truncation marker in the injection block. + +If the `MEMORY.md` indexes alone exceed the cap, coyote injects them fully and logs a warning. A partial index would +confuse the LLM more than going over budget by a few hundred chars. + +## When NOT to Use Memory + +- **One-shot prompts**: no recurring context, memory is overhead +- **Privacy-sensitive sessions**: memory persists on disk in plaintext +- **Throwaway scripts using coyote as a library**: the script is the context + +For session-specific suppression without changing config, use `--no-memory` or set `memory: false` on a Session before +saving it. + +## Comparison + +| | Memory | [Sessions](Sessions) | [Skills](Skills) | [RAG](RAG) | [Vault](Vault) | +|-------------|---------------------|----------------------|------------------|--------------------|----------------| +| Scope | Cross-session facts | One conversation | Knowledge packs | Document retrieval | Secrets | +| Lifecycle | Evolves over time | Per-conversation | Static, curated | Indexed, retrieved | Long-lived | +| Encrypted | No (plaintext) | No | No | No | Yes | +| LLM writes? | Yes (with tools) | No | No | No | No | + +## Privacy & Security + +Memory files are **plaintext markdown on disk**. Anything you (or the LLM) writes there is readable to anyone with +access to your home directory. + +**Never store secrets in memory.** Use [Vault](Vault) for credentials and API keys. The default memory instructions +explicitly tell the LLM not to write secrets, but treat that as a defense-in-depth measure, not a guarantee. + +## See Also + +- [Sessions](Sessions): per-conversation state +- [Skills](Skills): modular knowledge packs +- [Roles](Roles): model behavior customization (supports `memory: false`) +- [RAG](RAG): document retrieval (complementary, not redundant) +- [Vault](Vault): encrypted secret storage diff --git a/Roles.md b/Roles.md index eafc824..7f3ac23 100644 --- a/Roles.md +++ b/Roles.md @@ -6,6 +6,9 @@ Think of them kind of like a baby: That baby can grow up to do anything! Be a re The only difference is that with roles, we're explicitly telling the LLM what we want it to be. Also: the LLM is already grown up so we don't have to wait! +> Roles can disable the memory system for their scope by setting `memory: false` in their frontmatter. +> See [Memory](Memory) for the full toggle cascade. + ![Role demo](./images/roles/code.gif) --- diff --git a/Sessions.md b/Sessions.md index b588ca6..b0d2530 100644 --- a/Sessions.md +++ b/Sessions.md @@ -7,6 +7,9 @@ answers and ask follow-up questions and the model will know what you're referrin Sessions can be temporary, or can be saved so you can continue conversations at a later time. +> Sessions persist *one conversation*. For facts that should survive across all sessions (e.g., user preferences, +> project constraints, accumulated feedback), see [Memory](Memory). + Saved sessions are stored in the `sessions` subdirectory of the Coyote configuration directory. The location of the `sessions` directory varies by system, so you can use the following command to find the `sessions` directory if you need it: diff --git a/Skills.md b/Skills.md index 9162aa4..732a4c4 100644 --- a/Skills.md +++ b/Skills.md @@ -5,6 +5,9 @@ the user), a skill is a capability the model layers onto whatever role/agent/ses Multiple skills can be loaded at once. They compose, meaning their instructions stack, their tool whitelists union, and their declared MCP servers get acquired automatically. +> Skills are *static* curated knowledge packs. For *evolving* facts the LLM curates over time across sessions, +> see [Memory](Memory). + Common uses: - **Methodology overlays** ("how to do git surgery", "how to review code"). diff --git a/_Sidebar.md b/_Sidebar.md index ae42e13..fd6d150 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -45,6 +45,11 @@ - [Limitations](Graph-Agents#limitations--gotchas) ## Knowledge & Automation +- [Memory](Memory) + - [Quick Start](Memory#quick-start) + - [Structured Mode](Memory#structured-mode) + - [Tools](Memory#tools) + - [Toggles](Memory#toggles) - [RAG](RAG) - [Macros](Macros) - [Roles](Roles)