Skip to content
Alex Clarke edited this page Jun 11, 2026 · 2 revisions

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 (which handle within-conversation state) and Skills (which are static knowledge packs).

Quick Start

The fastest path is the bootstrap command:

# 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 <config_dir>/memory/MEMORY.md

Then edit the file with anything you want the LLM to remember:

# 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 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): <workspace>/COYOTE.md (lite) or <workspace>/.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 <git_root>/.coyote/memory/MEMORY.md with an empty index, appends .coyote/memory/ to <git_root>/.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:

<workspace>/.coyote/memory/
├── MEMORY.md            # index (always injected)
├── project_compliance.md
├── feedback_terse.md
└── reference_dashboards.md

Each drill file has YAML frontmatter:

---
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 <workspace>/.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.

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 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 Skills RAG 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 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: per-conversation state
  • Skills: modular knowledge packs
  • Roles: model behavior customization (supports memory: false)
  • RAG: document retrieval (complementary, not redundant)
  • Vault: encrypted secret storage

Clone this wiki locally