recoil v0.3.1

recoil

Local-first memory for coding agents. One SQLite store holds verbatim memories with provenance — who wrote each one, from where, in which scope, and whether it still holds — and hands an agent a bounded block of the ones that matter at the start of every session.

For people, it is the decision log that never goes stale. For agents, it replaces re-reading the history with three calls: wake to start, check before acting against a decision, handoff to close.

Install
$ curl -fsSL https://recoil.sh/install | sh
Or hand it to your agent
Install recoil and set it up in this repo for me.
1. Run: curl -fsSL https://recoil.sh/install | sh
2. Run `recoil setup` at the repo root and keep the hooks it offers.
3. Run `recoil wake --max-chars 1600` and tell me what it found.
From here on, start every session with `recoil wake`, run
`recoil check "<action>"` before changing a remembered decision, and
close with `recoil handoff --agent <you> --next-step "<next>"`.

Overview

recoil stores memories, not summaries. Each is the text an agent or a person wrote, with a role (note, decision, constraint, preference, rule, adr, handoff, or source for indexed docs), a source agent, an optional source path, a scope, and a validity state. Retrieval is SQLite FTS5 — no model in the loop unless you opt into embeddings.

Decisions carry a claim key. A new decision in the same family supersedes the old one, and check audits a proposed action against the family before an agent goes ahead. Project docs are indexed at setup and refreshed by file hash, so wake also surfaces the README and docs/ an agent would otherwise re-read.

Everything is local: one store on the machine, nothing on the network. Hooks exist for Claude Code, OpenCode and Codex; the same surface is available over MCP.

Durable memory is authored by an agent or operator action. Automation may enrich, connect, publish, and review it. Automation may propose new memory, but proposals go to Review unless explicitly accepted.

Install

One line on macOS and Linux. The script picks the archive for your platform, verifies it against the release's checksums.txt, and installs into /usr/local/bin when that is writable, otherwise ~/.local/bin.

$ curl -fsSL https://recoil.sh/install | sh

Pin a release or choose the directory; RECOIL_VERSION and RECOIL_INSTALL_DIR do the same from the environment.

$ curl -fsSL https://recoil.sh/install | sh -s -- --version v0.1.1 --bin-dir ~/bin

FTS5 is compiled into every published binary, so the requirement never shows up as a flag you have to remember.

Windows PowerShell
> irm https://recoil.sh/install.ps1 | iex

Installs to %LOCALAPPDATA%\recoil and adds it to the user PATH. irm https://recoil.sh/uninstall.ps1 | iex removes it and keeps your memories; pass -Purge to delete the database too.

By hand archives and checksums

Every release ships recoil_<tag>_<os>_<arch>.tar.gz for linux/x86_64, linux/arm64, darwin/arm64 and darwin/x86_64, a .zip for windows/x86_64, and one checksums.txt covering all of them.

$ TAG=$(curl -fsSL https://api.github.com/repos/1broseidon/recoil/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4)
$ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
$ ARCH=$(uname -m); [ "$ARCH" = "aarch64" ] && ARCH=arm64
$ curl -fsSLO "https://github.com/1broseidon/recoil/releases/download/${TAG}/recoil_${TAG}_${OS}_${ARCH}.tar.gz"
$ curl -fsSLO "https://github.com/1broseidon/recoil/releases/download/${TAG}/checksums.txt"
$ grep "recoil_${TAG}_${OS}_${ARCH}.tar.gz" checksums.txt | shasum -a 256 -c -
$ tar xzf "recoil_${TAG}_${OS}_${ARCH}.tar.gz" && install -m755 recoil ~/.local/bin/recoil
Go requires CGO for SQLite FTS5
$ CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" go install github.com/1broseidon/recoil@latest

A binary built without FTS5 does not work; there is no degraded fallback.

From source make sets the CGO flags
$ git clone https://github.com/1broseidon/recoil && cd recoil
$ make build      # ./recoil
$ make install    # $GOPATH/bin/recoil
chain the rest of the toolkit

recoil is one of the chain.sh tools. One command installs the set:

$ curl -fsSL https://chain.sh/bootstrap.sh | sh

Quickstart

Run it inside a repository. setup marks the project, indexes its docs, and installs hooks for the agents it finds.

$ recoil setup --agent claude-code
---
project_root: /tmp/orbit
project_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
chunks: 2
hooks: 1
next_command: recoil wake
---
indexed 2 chunks from 2 files
detected agents: claude-code
hook claude-code: .claude/settings.json
next: recoil wake

Remember what an agent or a person just learned. The role is inferred from the text; a plain observation is stored as a note, and the output says why.

$ recoil remember --agent claude "The parser cache is keyed by file content hash, not mtime: editors rewrite mtimes on save without changing content."
---
id: mem_jtijf5wmyvvdoehp23sbjs3szu
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
role: note
claim_key: ""
inference_confidence: low
inference_reason: no decision, constraint, preference, or handoff signal; stored as note
duplicate: false
---
The parser cache is keyed by file content hash, not mtime: editors rewrite mtimes on save without changing content.

Decisions get a claim key so the family can be superseded and audited later. --stance and --subject let check recognise a request that goes against it.

$ recoil decide --claim-key cache.backend --stance prefers --subject "parser cache backend" --agent claude "Keep the parser cache in SQLite with WAL. Redis was rejected: installs must work offline with no daemon."
---
id: mem_7vda2sxdlkgrurmicljaq5fq6k
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
created: 2026-09-21T20:17:17Z
role: decision
validity: active
claim_key: cache.backend
stance: prefers
subject: parser cache backend
predicate_status: unknown
supersedes: ""
superseded_by: ""
auto_superseded: ""
duplicate: false
---
Keep the parser cache in SQLite with WAL. Redis was rejected: installs must work offline with no daemon.

Every session starts with wake: a bounded block in lanes, each result with a why. The two doc chunks came from setup.

$ recoil wake --max-chars 1600
---
query: ""
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
result_count: 4
selected_count: 4
shown_count: 4
refreshed_sources: 0
staled_memories: 0
truncated: false
max_chars: 1600
---
## Current Decisions
### mem_7vda2sxdlkgrurmicljaq5fq6k
score: 0.3500
created: 2026-09-21T20:17:17Z
role: decision
source_kind: direct
validity: active
claim_key: cache.backend
source_agent: claude
why: current decision with claim_key cache.backend

Keep the parser cache in SQLite with WAL. Redis was rejected: installs must work offline with no daemon.

## Project Docs
### mem_okx2nsjxiq4cm6mpr6ziouobru
score: 1.8500
created: 2026-09-21T20:17:17Z
role: source
source_kind: file
validity: unknown
source_agent: recoil
source_path: README.md
source_ref: chunk 1 lines 1-3
why: project document chunk from README.md

# orbit

A parser for satellite two-line element sets with an on-disk cache.

### mem_552rvrcwonfryls5juoabgpvyv
created: 2026-09-21T20:17:17Z
role: source
source_kind: file
validity: unknown
source_agent: recoil
source_path: docs/architecture.md
source_ref: chunk 1 lines 1-9
why: project document chunk from docs/architecture.md

# Architecture

The parser reads TLE files and keeps a cache of parsed elements so repeated
runs skip the slow checksum pass. Cache entries are keyed by file content hash.

## Cache

Entries live in a SQLite file in WAL mode. Redis was considered and rejected
because installs must work offline with no daemon.

## Recent Evidence
### mem_jtijf5wmyvvdoehp23sbjs3szu
score: 0.3500
created: 2026-09-21T20:17:17Z
role: note
source_kind: direct
validity: active
source_agent: claude
why: recent current memory in this scope

The parser cache is keyed by file content hash, not mtime: editors rewrite mtimes on save without changing content.

Before an agent acts against something remembered, it asks. The verdict is review: the request would replace the subject of a decision that prefers it, so the recommendation is to ask the operator rather than proceed.

$ recoil check "replace the parser cache backend with Redis"
---
verdict: review
predicate_status: unknown
recommendation: ask_operator
reason: request_contradicts_decision
predicate_reason: ""
claim_key: cache.backend
decision_stance: prefers
decision_subject: parser cache backend
requested_action: replace_subject
advisory: ""
recheck: ""
---
## Decision Check

claim_key: cache.backend
verdict: review
predicate_status: unknown
recommendation: ask_operator
decision_stance: prefers
decision_subject: parser cache backend
requested_action: replace_subject
matched_id: mem_7vda2sxdlkgrurmicljaq5fq6k
current_id: mem_7vda2sxdlkgrurmicljaq5fq6k

## Current Decision

## mem_7vda2sxdlkgrurmicljaq5fq6k
created: 2026-09-21T20:17:17Z
validity: active
claim_key: cache.backend
role: decision
source_kind: direct
source_agent: claude

Keep the parser cache in SQLite with WAL. Redis was rejected: installs must work offline with no daemon.

Choosing a command

I want to…Use
Start a session with what mattersrecoil wake
Find out whether something was decidedrecoil search "<topic>"
Know if an action is safe against past decisionsrecoil check "<action>"
Store an observationrecoil remember "<text>"
Record a decision that may later changerecoil decide --claim-key <family> "<text>"
Correct a memory that is now wrongrecoil supersede <id> "<replacement>"
Close a session for the next agentrecoil handoff --next-step "<action>"
See the decision families in scoperecoil claims
Pull a family verbatim into a promptrecoil export --claim-key-prefix <prefix>
Retire a memoryrecoil forget <id>
Change validity or links by handrecoil mark <id>
Index docs without a full setuprecoil mine
Give an agent the contractrecoil instruct <agent>
Keep an agent on the contractrecoil hook install <agent>
Serve the same tools over MCPrecoil mcp

Commands

Every command takes -d <path> (or RECOIL_DB) to aim at another store and --json for a structured envelope. Memory commands default to the project you are in; --user selects the persistent user scope, --project <path> another workspace, --session <id> a session scope.

Sessions

setup Bootstrap a project in one step

Marks the project (.recoil/project.json), indexes its docs, and installs agent hooks.

FlagEffect
--agent <name>Hook to install; repeat or comma-separate. Auto-detected by default
--hook-scope projectWhere hooks land: project (default) or user
--no-hooksSkip hook installation
wake Bounded starter context

wake [query] prints up to --max-chars (default 1600) of memory in lanes, current decisions first. --include-decisions appends a claim-keyed decision trail, --explain adds per-result score components, --minimal prints tab-separated rows. The filters below apply to search, list and forget as well.

FilterMatches
--current / --historicalLifecycle: current, or historical, rejected, superseded, stale and tombstoned
--validity <state>One exact validity state
--role <role> / --agent <name>Exact role or source agent
--claim-key <key>One claim family
--since 7d / --before 2026-09-01A date or a duration
--source <substr> / --source-kind <kind>Source path substring; direct, file, session_evidence or extracted_claim
handoff Close a session for the next agent

Writes a structured handoff under the handoff.latest claim key, superseding the previous one. --next-step, --decision, --constraint and --open-question are repeatable; a free-text summary can come as an argument, from --file, or from stdin with --file -.

$ recoil handoff --agent claude --next-step "Land the per-project cache path, then re-run the cold-start benchmark" --open-question "Prune the cache by age or by size?"
---
id: mem_yr2bckupv2ebyrt3hki6g343ch
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
role: handoff
claim_key: handoff.latest
supersedes: ""
auto_superseded: ""
publish_mode: off
publish_published: 0
publish_queued: 0
publish_duplicate: 0
publish_failed: 0
publish_skipped: policy
---
## Next Steps
- Land the per-project cache path, then re-run the cold-start benchmark

## Open Questions
- Prune the cache by age or by size?

Writing memory

remember Store text with an inferred role

The role is inferred from decision, constraint, preference and handoff signals in the text, and the inference is reported with its confidence. Override it with --role, give a family with --claim-key, attach provenance with --source-path and --source-ref, read long content with --file. Links use --supersedes and --superseded-by.

decide A decision with a claim key

decide is remember for decisions. The claim key is required; a new decision automatically supersedes the previous active one in the family (--no-supersede keeps both). --stance prefers|rejects|requires|forbids and --subject describe what the decision is about so check can spot a contradicting request.

$ recoil decide --claim-key cache.backend --stance prefers --subject "parser cache backend" --agent claude "Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon."
---
id: mem_f3epegqcib4qepn47onte7nri2
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
created: 2026-09-21T06:33:07Z
role: decision
validity: active
claim_key: cache.backend
stance: prefers
subject: parser cache backend
predicate_status: unknown
supersedes: mem_7vda2sxdlkgrurmicljaq5fq6k
superseded_by: ""
auto_superseded: mem_7vda2sxdlkgrurmicljaq5fq6k
duplicate: false
publish_mode: off
publish_published: 0
publish_queued: 0
publish_duplicate: 0
publish_failed: 0
publish_skipped: policy
---
Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon.

A decision can carry a predicate for when it applies: --valid-until <date> is deterministic, --holds-while "<condition>" is semantic, --predicate key=value,… adds structured fields, and --recheck "<question>" is what to ask the operator when the predicate needs review. check reports the result as predicate_status.

supersede Replace a memory that is now wrong

Creates the replacement, marks the old memory superseded, and links both. The claim key and role default to the old memory's.

$ recoil supersede mem_jtijf5wmyvvdoehp23sbjs3szu --agent claude "The parser cache is keyed by file content hash plus parser version, so a parser upgrade invalidates stale entries."
---
action: supersede
old_id: mem_jtijf5wmyvvdoehp23sbjs3szu
new_id: mem_w5seoglca7u74isihepwxxag4t
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
claim_key: ""
old_validity: superseded
new_validity: active
duplicate: false
publish_mode: off
publish_published: 0
publish_queued: 0
publish_duplicate: 0
publish_failed: 0
publish_skipped: policy
---
The parser cache is keyed by file content hash plus parser version, so a parser upgrade invalidates stale entries.
mark Change lifecycle metadata by hand

mark <id> sets --validity (active, historical, rejected, superseded, stale, unknown), --claim-key, --stance, --subject, and the --supersedes / --superseded-by links.

forget Tombstone or purge

forget <id> --reason "<why>" tombstones one memory: it stays in the store, leaves every current view, and shows up again with list --include-deleted. --destroy hard-purges. The same filters as wake select memories in bulk; --dry-run previews and --force confirms.

Reading memory

check Audit an action against decisions

check "<action>" finds the decision family the request touches and returns a verdict, a recommendation and the reason. check <memory-id>, --claim-key <key> and --claim-key-prefix <prefix> audit a family directly. After the decision from the quickstart was superseded, the same question points at the replacement:

$ recoil check "replace the parser cache backend with Redis"
---
verdict: use_replacement
predicate_status: unknown
recommendation: use_current_decision
reason: matched_historical_decision
predicate_reason: ""
claim_key: cache.backend
decision_stance: ""
decision_subject: ""
requested_action: ""
advisory: ""
recheck: ""
---
## Decision Check

claim_key: cache.backend
verdict: use_replacement
predicate_status: unknown
recommendation: use_current_decision
matched_id: mem_7vda2sxdlkgrurmicljaq5fq6k
current_id: mem_f3epegqcib4qepn47onte7nri2
replacement_id: mem_f3epegqcib4qepn47onte7nri2

## Current Decision

## mem_f3epegqcib4qepn47onte7nri2
created: 2026-09-21T06:33:07Z
validity: active
claim_key: cache.backend
supersedes: mem_7vda2sxdlkgrurmicljaq5fq6k
role: decision
source_kind: direct
source_agent: claude

Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon.

## Matched Decision

## mem_7vda2sxdlkgrurmicljaq5fq6k
created: 2026-09-21T06:33:07Z
validity: superseded
claim_key: cache.backend
superseded_by: mem_f3epegqcib4qepn47onte7nri2
role: decision
source_kind: direct
source_agent: claude

Keep the parser cache in SQLite with WAL. Redis was rejected: installs must work offline with no daemon.
VerdictMeaning
useThe current decision applies; act on it
use_replacementThe request matched a superseded decision; use the one that replaced it
reviewThe request contradicts the current decision
RecommendationWhen
use_current_decisionuse and use_replacement
ask_operatorreview against a decision that prefers or rejects
block_actionreview against a decision that requires or forbids

The request is read against the decision's subject. use, add, enable, switch to and similar cues adopt it; drop, remove, avoid, disable and similar remove it; replace, switch from and move from replace it; keep the decision affirms it. A request that does not name the subject reports requested_action: unknown and falls back to the family verdict.

claims Claim families in scope

One row per family: key, number of memories, validity of the current one, and its id.

$ recoil claims
---
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
result_count: 1
---
cache.backend	2	active	mem_f3epegqcib4qepn47onte7nri2
show and list One memory or many

show <id> prints a memory with its full frontmatter; the id can be a unique prefix. list takes the wake filters, --limit (default 50) and --include-deleted; --minimal is one tab-separated row per memory.

$ recoil show mem_f3epegqcib4qepn47onte7nri2
---
id: mem_f3epegqcib4qepn47onte7nri2
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
created: 2026-09-21T06:33:07Z
validity: active
claim_key: cache.backend
supersedes: mem_7vda2sxdlkgrurmicljaq5fq6k
superseded_by: ""
role: decision
source_kind: direct
source_agent: claude
source_path: ""
source_ref: ""
---
Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon.
$ recoil list --current --minimal
mem_yr2bckupv2ebyrt3hki6g343ch	2026-09-21T06:33:07Z	claude	## Next Steps - Land the per-project cache path, then re-run the cold-start benchmark ## Open Questions - Prune the cache by age or by size?
mem_w5seoglca7u74isihepwxxag4t	2026-09-21T06:33:07Z	claude	The parser cache is keyed by file content hash plus parser version, so a parser upgrade invalidates stale entries.
mem_toqzgisy5ms6ygdwy5c2upqfb2	2026-09-21T06:33:07Z	recoil	# orbit A parser for satellite two-line element sets with an on-disk cache.
mem_nbiasls4rskoavtnivpm6v6p7a	2026-09-21T06:33:07Z	recoil	# Architecture The parser reads TLE files and keeps a cache of parsed elements so repeated runs skip the slow checksum pass. Cache entries are keyed by file content hash. ## Cac...
mem_f3epegqcib4qepn47onte7nri2	2026-09-21T06:33:07Z	claude	Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon.
export Whole claim families verbatim

For doctrine injection: every current memory in a family or prefix, verbatim, in one block. --historical includes past memories.

$ recoil export --claim-key-prefix cache.
---
kind: export
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
claim_key_prefix: cache.
lifecycle: current
count: 1
---
## cache.backend
id: mem_f3epegqcib4qepn47onte7nri2
created: 2026-09-21T06:33:07Z
validity: active
role: decision
source_kind: direct
source_agent: claude
supersedes: mem_7vda2sxdlkgrurmicljaq5fq6k

Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon.

Project and store

mine Index project files

setup runs it once; mine [path] runs it again. Files are chunked and stored as source memories with their path and line range, chunks already in the store count as duplicates rather than being stored twice, and a .recoilignore at the root lists paths to leave out. --dry-run previews.

$ recoil mine --dry-run docs
---
root: /tmp/orbit/docs
scope: project
scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
dry_run: true
files_scanned: 1
files_skipped: 0
chunks: 1
added: 0
duplicates: 0
sources: 0
staled: 0
---
dry-run	would-add	docs/architecture.md	chunk 1 lines 1-9
status Store health
$ recoil status
---
db_path: /tmp/orbit-home/.config/recoil/recoil.db
existed_before: true
memory_count: 7
tombstone_count: 0
fts5: true
project_scope_id: local:dbb8165a8ec8e32bfb255a15ecd75d69
project_root: /tmp/orbit
project_initialized: true
project_marker: /tmp/orbit/.recoil/project.json
---
ready
config Effective configuration

Settings live in .recoil/config.json at the project root, layered over the defaults. config list shows every key with its source, config explain <key> one key, config set and config unset change a project override, and config path prints the store path in use.

$ recoil config list
---
path: /tmp/orbit/.recoil/config.json
count: 17
---
aging.window-days=30	default	Freshness window in days before note and handoff memories receive read-side aging demotion.
backup.dir=	default	Default destination directory for recoil backup snapshots.
backup.max=3	default	Maximum number of rotating backup snapshots to keep.
mine.exclude_paths=	default	Comma-separated path globs to exclude from mining even when they otherwise look important.
mine.follow_repo_symlinks=true	default	Follow symlinks that resolve inside the mined root for supported text files.
mine.include_hidden_operational=true	default	Include allowlisted hidden operational files such as .github/SECURITY.md without enabling every hidden file.
mine.include_paths=	default	Comma-separated path globs to force into mining, subject to file size and UTF-8 checks.
retrieval.mode=auto	default	Search retrieval mode: auto enables hybrid FTS+embeddings when a usable local embedding index exists; fts forces sparse search; hybrid requires a usable embedding index.
session-evidence.enabled=false	project	Enable mining session evidence captured by installed agent hooks.
session-evidence.min-chars=200	default	Minimum content length for session evidence files before ingestion selects them.
backup, migrate, repair Maintenance

backup writes a consistent snapshot next to the database (or --out) and rotates old ones (--max, default 3). migrate brings the schema up to date and repair rebuilds the FTS index; both take --dry-run, and migrate --age re-runs read-side aging.

embed, eval, profile, tray, version The rest

embed index builds an embedding sidecar for the scope (--provider local with local-hash-v1, or openrouter), and search --hybrid fuses it with FTS5. eval runs the retrieval fixtures under eval/. profile builds a deterministic entity profile from current memories. tray runs the system tray companion. version prints version, commit and build date.

Memory model

Scopes

ScopeSelected byHolds
projectthe working directory, --project <path>, or RECOIL_PROJECTDecisions, notes and docs for one repository; worktrees share it
user--userWhat follows you across projects
session--session <id>Context for one run

All scopes live in one store. The project is marked by .recoil/project.json and its id is derived from the repository, so clones on other machines line up.

Validity

StateMeaning
activeCurrent
supersededReplaced by a linked newer memory
historicalTrue once, kept for the record
rejectedConsidered and turned down
staleIts source file changed since it was indexed
unknownNot asserted; the default for indexed docs

Tombstoned memories (forget) are hidden from every current view. Notes and handoffs older than aging.window-days (default 30) are demoted at read time, never rewritten.

Claim keys and supersession

A claim key names a family: cache.backend, voice.tone, handoff.latest. decide supersedes the previous active decision in its family, supersede links any two memories explicitly, mark edits the links. claims, check and export work on families, and --claim-key-prefix voice. selects a whole tree of them.

Wake lanes

LaneWhat lands there
Current DecisionsActive decisions, newest first
Project DocsIndexed file chunks, root docs first
Recent EvidenceRecent notes, handoffs and session evidence
HistoricalMatches that lost their validity; search keeps them in their own section

Every result carries a why line.

For agents

recoil was built to be called by something that isn't a person.

The contract

instruct <agent> prints the short contract for an agent's instructions file. It names the agent so its memories carry the right source_agent. (instructions is an alias kept for hooks that already call it.)

$ recoil instruct claude-code
# Recoil memory contract for claude-code

Your workspace has a memory store. Agents leave durable intent. Recoil keeps it fresh and cleaned up.

- Start work with `recoil wake --max-chars 1600`.
- Before assuming prior context or making a claim about project history, run `recoil search "<topic>"`.
- Before acting against or changing a remembered decision, run `recoil check "<proposed action>"` and respect its verdict (use / review / use_replacement).
- When durable intent is explicit, write it with `recoil remember --agent claude-code "<memory>"`.
- For durable decisions with a stable subject, prefer `recoil decide --claim-key <family> "<decision>"` so supersession and contradiction checks work.
- When something previously remembered is now wrong, use `recoil supersede <old-id> "<replacement>"` instead of writing a duplicate memory.
- End the session or compacting window with `recoil handoff --agent claude-code --next-step "<next action>"`.

Treat Recoil output as sourced working context with IDs and provenance.

Hooks

hook install <agent> wires the contract into a runtime so it survives a full context; --scope project keeps it in the repository, --dry-run shows the change. hook remind prints the reminder block for any other runtime, with --format text|json|claude-code|codex and bounded wake context included by default.

AgentInstalled as
claude-codeNative SessionStart and SessionEnd hooks in Claude settings
opencodeA managed OpenCode plugin
codexNative SessionStart and Stop hooks in hooks.json
codex-agentsA managed block in AGENTS.md, for runtimes without hooks

MCP

recoil mcp serves the same surface over stdio. Read-only by default: recoil_search, recoil_wake, recoil_check, recoil_list, recoil_claims and recoil_export. --allow-write adds recoil_add, recoil_remember and recoil_handoff. Scope resolves the same way as on the command line, so RECOIL_PROJECT aims a server whose working directory you don't control.

Frontmatter, JSON, or rows

The default output is YAML frontmatter followed by content: metadata an agent can parse, then text it can read. --json wraps the same data in an envelope with a version and a kind; errors use the envelope with kind: "error", and the exit code still tells the story.

$ recoil show mem_f3epegqcib4qepn47onte7nri2 --json
{
  "version": "0.1",
  "kind": "show_result",
  "data": {
    "id": "mem_f3epegqcib4qepn47onte7nri2",
    "hash": "2ec8f21a024079023dbcfb9b327db14695a6b10dac48d9c04009f664b3e688d4",
    "role": "decision",
    "content": "Parser cache moves to a per-project SQLite file under .cache/ so worktrees stop sharing one cache. Still SQLite, still no daemon.",
    "source_kind": "direct",
    "source_agent": "claude",
    "scope_kind": "project",
    "scope_id": "local:dbb8165a8ec8e32bfb255a15ecd75d69",
    "project_id": "local:dbb8165a8ec8e32bfb255a15ecd75d69",
    "metadata_json": "{\"stance\":\"prefers\",\"subject\":\"parser cache backend\"}",
    "validity": "active",
    "claim_key": "cache.backend",
    "supersedes": "mem_7vda2sxdlkgrurmicljaq5fq6k",
    "created_at": "2026-09-21T06:33:07Z"
  }
}
$ recoil show nope --json
{"version":"0.1","kind":"error","error":{"code":"NOT_FOUND","message":"memory not found"}}
→ exit 3

--minimal on wake, search and list prints one tab-separated row per memory: id, created, agent, content.

Exit codes

CodeMeaning
0OK
1Generic failure
2Validation: a bad flag, a missing argument, an invalid value
3Not found
4Upstream: the database, an embedding provider, the network
5Failed precondition, such as an uninitialised project or a disabled feature
6Cancelled

Bootstrap files

Two files on this domain are written for agents rather than people: /llms.txt is the short index and /llms-full.txt is this manual as plain Markdown.

Notes

FTS5 is mandatory

Published binaries have it compiled in. A Go build needs CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" and CGO_ENABLED=1; make sets both.

Where the store lives

One database in your OS config directory: ~/.config/recoil/recoil.db on Linux, ~/Library/Application Support/recoil/recoil.db on macOS, %AppData%\recoil\recoil.db on Windows. -d <path> or RECOIL_DB aims a command at another one, and config path prints the one in use. RECOIL_PROJECT aims the project scope when the working directory can't; RECOIL_VERBOSE turns on diagnostics.

Session evidence is opt-in

Installed hooks can capture compact evidence from agent sessions, but nothing is ingested until config set session-evidence.enabled true. session-evidence discover lists other agents' sessions bound to the repository without writing, backfill ingests them, and forget purges one session's evidence with the memories mined from it.

Automation proposes, agents and people author

Mining and refresh never invent memory. Indexed docs are source memories with validity: unknown, and wake can mark a memory stale when its file changed but never rewrites it.