Persistent long-term memory for OpenCode — give your AI coding agent memory across sessions, with automatic hooks plus explicit tools, backed entirely by a local file store. No server, no API key, nothing leaves your machine.
Structure modeled on @vectorize-io/opencode-hindsight (hooks + retain/recall/reflect tools), with the memory backend swapped for a local JSONL store and BM25 lexical retrieval.
- Explicit tools the agent can call:
foresight_retain— store a specific fact, decision, or conventionforesight_recall— ranked search over past memoriesforesight_reflect— assemble a structured digest of related memories
- Automatic hooks:
experimental.chat.system.transform— recalls relevant memories once per session and injects them into the system prompt (so the agent has context before you say anything)session.idle— auto-retains the conversation transcript every N user turnsexperimental.session.compacting— retains before context compaction and injects recalled memories into the compaction context
- Local & private: memories live in
.opencode/foresight/<scope>/memories.jsonlinside your project - Scoped: one store per project (derived from the git worktree root), with optional
::channel::userisolation for multi-user setups - Non-blocking: memory operations never fail the session; errors are logged and skipped
Requires OpenCode 2.x. The plugin implements the V2 plugin API ({ id, setup(ctx) }),
which OpenCode 1.x does not load.
opencode plugin add github:dialectstudio/opencode-foresightOr register it manually in opencode.jsonc (project) or ~/.config/opencode/opencode.jsonc (global):
Restart OpenCode to load the plugin.
Options use the V2 object form — a package key plus an options object:
{
"plugins": [
{
"package": "github:dialectstudio/opencode-foresight",
"options": {
"mission": "TypeScript monorepo. Focus on architecture decisions, test patterns, and deployment procedures.",
"recallBudget": "high",
"retainEveryNTurns": 5
}
}
]
}Session start
│ ▼
│ System prompt
│ + recalled memories (automatic, first system transform)
├──────────────────────────────────────
│ OpenCode Agent
│ ├── foresight_retain (explicit)
│ ├── foresight_recall (explicit)
│ └── foresight_reflect (explicit)
└──────────────────────────────────────
│ ▲
retain recall
│ │
▼ ▼
.opencode/foresight/<scope>/memories.jsonl
- Session starts — first system transform recalls memories (query built from your messages, falling back to "project context and recent work") and folds them into
system[0]. - During the session — the agent calls the three tools explicitly when useful.
- Session idles — after
retainEveryNTurnsuser turns, the transcript is retained (full-sessionupserts one record per session;last-turnappends chunked windows with overlap). - Context compaction — the conversation is retained first, then recalled memories are injected into the compaction context so nothing important is lost.
Retrieval is BM25 lexical scoring over content + tags, with a recency boost; when nothing matches, the most recent memories are surfaced instead of nothing.
Loaded in order (later wins):
- Built-in defaults
~/.foresight/opencode.json(user-wide defaults)- Plugin options in
opencode.json - Environment variables
| Option | Default | What it does |
|---|---|---|
autoRecall |
true |
Inject memories into the system prompt on session start |
autoRetain |
true |
Capture the conversation on session.idle |
recallBudget |
mid |
Result limit: low (4), mid (8), high (16) |
recallMaxResults |
0 |
Explicit result limit overriding the budget |
recallContextTurns |
1 |
Prior turns folded into the auto-recall query |
recallMaxQueryChars |
800 |
Truncation cap for the composed recall query |
recallResultMaxChars |
1200 |
Per-memory truncation when formatting results |
retainMode |
full-session |
full-session upserts; last-turn appends chunks |
retainEveryNTurns |
3 |
Auto-retain cadence in user turns |
retainOverlapTurns |
2 |
Overlap between last-turn chunks |
memoryDir |
<project>/.opencode/foresight |
Memory root directory |
scopeId |
derived | Static scope ID (skips derivation) |
dynamicScope |
true |
Derive scope from the git project (+ channel/user) |
mission |
"" |
Project mission prepended to injected context and reflects |
debug |
false |
Emit debug logs into OpenCode's log stream |
| Variable | Type | Purpose |
|---|---|---|
FORESIGHT_MEMORY_DIR |
string | Memory root directory |
FORESIGHT_SCOPE_ID |
string | Static scope ID |
FORESIGHT_DYNAMIC_SCOPE |
bool | Enable/disable dynamic scope derivation |
FORESIGHT_AUTO_RECALL |
bool | Toggle session-start injection |
FORESIGHT_AUTO_RETAIN |
bool | Toggle idle retain |
FORESIGHT_RECALL_BUDGET |
low|mid|high |
Recall budget |
FORESIGHT_RECALL_MAX_RESULTS |
int | Explicit result limit |
FORESIGHT_RECALL_CONTEXT_TURNS |
int | Context turns for the recall query |
FORESIGHT_RECALL_MAX_QUERY_CHARS |
int | Query truncation cap |
FORESIGHT_RECALL_RESULT_MAX_CHARS |
int | Per-memory formatting cap |
FORESIGHT_RETAIN_MODE |
full-session|last-turn |
Retain mode |
FORESIGHT_RETAIN_EVERY_N_TURNS |
int | Retain cadence |
FORESIGHT_RETAIN_OVERLAP_TURNS |
int | Chunk overlap |
FORESIGHT_MISSION |
string | Project mission |
FORESIGHT_CHANNEL_ID |
string | Channel segment for dynamic scope |
FORESIGHT_USER_ID |
string | User segment for dynamic scope |
- Dynamic (default): scope =
<git-project>[:channel:user], e.g.my-app, ormy-app::team-backend::alicewithFORESIGHT_CHANNEL_ID/FORESIGHT_USER_IDset. Linked git worktrees of the same repo share one store. - Static: set
dynamicScope: falseandscopeId: "my-project"(orFORESIGHT_SCOPE_ID).
Each scope is a separate directory: .opencode/foresight/<scope>/memories.jsonl.
npm install
npm run typecheck # tsc --noEmit
npm test # vitest run
npm run bundle # tsup → dist/Do not rename
bundletobuild. npm's Git-dependency preparation triggers on abuildscript (along withprepare,prepack, and the install lifecycles): it runsnpm install --forceinside the clone before packing, and that step fails under OpenCode's installer withgit dep preparation failed, aborting the plugin install. With no preparation-triggering script present, npm packs the committeddist/unchanged.
To dogfood during development, add a relative plugin path to your OpenCode config:
{ "plugins": ["./path/to/opencode-foresight"] }OpenCode loads a plugin package directory (or a direct .ts/.js file), so point it at the
repository root. dist/ is committed, so a Git install needs no build step at all. Run
npm run bundle after changing src/ and commit the rebuilt dist/.
- Cold start: the first session in a new scope has nothing to recall; memories accumulate from the second session onward (or pre-populate
memories.jsonlmanually). - Lexical search: retrieval matches keywords, not semantics. Tag memories (
tags: ["architecture"]) and prefer specific nouns in recall queries for best results. - Feedback loops:
<foresight_memories>blocks are stripped from transcripts before retention, so injected context is never re-stored as a memory. - Context budget: recalled memories consume context window tokens — use
recallBudget: "low"for long sessions.
MIT
{ "$schema": "https://opencode.ai/config.json", "plugins": ["github:dialectstudio/opencode-foresight"] }