README
¶
nt ↔ OpenCode — a memory, rules & knowledge-base system
This bundle turns nt into the memory, rules, and
knowledge-base backend for OpenCode, wired up the way
OpenCode is designed to be extended: an MCP server, a plugin, two commands
(/recall in, /learn out), a skill, and a thin AGENTS.md. The result is a coding agent whose memory survives across
sessions, lives in plain files you can grep/git diff/open in Obsidian, and
costs the right number of tokens for each kind of content.
nt opencode install # complete setup from any installed nt binary (--print to preview)
(From a repo checkout, ./install.sh performs the same steps.)
The model: three layers, matched to three OpenCode surfaces
The core design problem (from researching OpenCode's extension surfaces) is a
token-budget one. OpenCode's rules layer — AGENTS.md + the instructions
config — is static text loaded into context, billed on every request. So
the question for each kind of memory is not "can the agent read it?" but "should
it be in context all the time?" That splits cleanly into three layers:
| Layer | What it is | nt home | OpenCode surface | Token cost |
|---|---|---|---|---|
| Rules | Small, stable directives ("always run gofmt", review process) | rules/ + tag rule |
Injected into the system prompt (plugin) | Paid every turn → keep tiny |
| Core memory | A handful of evolving, always-relevant facts (user prefs, key conventions) | memory/ + tag memory-core |
Injected alongside rules | Paid every turn → keep tiny |
| Knowledge base | Everything else: findings, decisions, reference, task history | ref/, decisions/, … |
nt MCP tools (nt_index → nt_search/nt_get, nt_links) |
Zero until queried |
The discipline that makes this work: the rules + core-memory core stays
small (it's always in context), and the bulk knowledge base stays behind the
MCP tools (retrieved on demand). Promoting a reference note into a standing
rule is a retag (nt_tag … +rule), never a copy.
Learning from past mistakes — the recall loop
The knowledge base is only useful if the agent actually re-reads the right note at the right moment. A recorded mistake that's never resurfaced is wasted. So this setup adds a lesson class and a proactive retrieval step, both at zero standing token cost:
- Record a mistake/footgun/dead-end as a lesson —
nt_notetaggedlesson(CLInt note … --lesson), with the trigger in the description ("when X, do Y — not Z"). - At the start of each task, the agent calls
nt_recallwith a plain-words description of what it's about to do. Unlikent_search(exact substring),nt_recallstems and expands dev-concept synonyms, so a paraphrased task ("adding parallel request handling") still surfaces the lesson worded differently ("goroutine deadlock") — with recorded lessons ranked first, and, whenNT_WORKSTREAMis set, a soft boost for the same project's notes. - And the plugin makes the loop fire even when the agent forgets: a failed bash command triggers a lessons-only recall automatically and pipes the hits into the next request, and lessons survive context compaction (see the plugin section below).
This closes the learn-from-sessions loop: mistakes are captured as a distinct,
recall-able class and re-surfaced before they recur — without bloating the
always-injected block (lessons cost tokens only when nt_recall returns them).
This mirrors the emerging best practice for OpenCode memory (e.g. Letta-style
"memory blocks": small labelled markdown blocks injected into context, plus
dedicated tools for the agent to maintain them) — except the blocks, tools,
search, links, and history are all just nt, which you already use from the CLI,
TUI, web UI, and Obsidian.
The building blocks (what's in this bundle)
1. MCP server — the read/write engine (mcp.nt)
nt mcp exposes 16 typed tools — deliberately few, so tool selection stays unambiguous. OpenCode is a first-class MCP client, so this
is the knowledge-base + memory read/write path — no custom OpenCode tool
needed. Retrieval follows progressive disclosure: nt_index (cheap catalog of
stubs) → nt_search (ranked stubs) → nt_get (one note's body). No bulk dump.
- Read:
nt_index,nt_search,nt_recall,nt_get,nt_status,nt_links,nt_view - Write:
nt_add,nt_note,nt_note_edit,nt_update,nt_tag,nt_mv,nt_archive,nt_relink,nt_rm
Registered (absolute path, idempotent) by:
nt mcp install --client opencode
which writes OpenCode's schema into ~/.config/opencode/opencode.json:
{ "mcp": { "nt": { "type": "local", "command": ["/abs/nt", "mcp"], "enabled": true,
"environment": { "NT_WORKSTREAM": "auto" } } } }
2. Plugin — injection + the automated learning loop (plugins/nt-memory.ts)
Injects the rules + core-memory block into the system prompt, recompiled
live from nt every session via the experimental.chat.system.transform
hook. Edit a note in nt → the next session sees it. No exported file to go stale.
Compiles with nt export and is fully wrapped so a missing/broken nt can never
break a session.
Three modes (set env on the OpenCode process):
NT_INJECT=system(default) — live injection via the system-prompt transform.NT_INJECT=file— instead refresh~/.config/opencode/nt-rules.mdonsession.createdand load it through theinstructionsconfig (use this if your OpenCode build lacks the experimental hook). Add toopencode.json:"instructions": ["nt-rules.md"].NT_INJECT=off— inject nothing; rely onAGENTS.md+ on-demand MCP.
The plugin also closes the learning loop automatically (each on by default, independently switchable):
- Compaction survival (
NT_COMPACT=0to disable) — onexperimental.session.compactingit pushes the open nt tasks and a "re-nt_recallbefore resuming" directive into the compaction context, so summarization doesn't drop the in-flight work or the memory workflow. - Error-triggered recall (
NT_ERROR_RECALL=0to disable) — when a bash tool call exits non-zero, the plugin runsnt recall --lessons-onlyon the command- error tail and injects any matching lessons into the next model request
as an
<nt-lessons>block. Recorded mistakes stop relying on the agent remembering to ask — the failure summons its own antidote. One recall per distinct failing command; the block is injected once, then cleared (a single prompt-cache miss per failure, no standing token cost).
- error tail and injects any matching lessons into the next model request
as an
- Idle capture nudge (
NT_IDLE_NUDGE=0to disable) — if a session used tools but never wrote to nt, a one-time TUI toast suggests running/learn. User-facing only; never injected into the model context.
Optional: NT_MIRROR_TODOS=1 mirrors OpenCode's todo list into nt tasks on
todo.updated (the OpenCode analog of Claude Code's nt hook). Off by default —
the agent already captures tasks via nt_add.
3. /recall command — on-demand memory briefing (commands/recall.md)
The read-side twin of /learn. Run /recall <topic> at the start of a task
(or mid-session on a topic switch, or after compaction) and the agent builds a
compact task-priming brief: recorded lessons opened in full (they're short
and they're the payload), related decisions/notes as stubs with at most 2
opened, and related open tasks — under a ~1–2K-token budget, stub-first by
design. Run /recall bare for a resume brief ("where was I?"): open
tasks by urgency, recent completions, recently-touched notes — then pick a
thread and it primes for that. Deliberate context-loading as one keystroke,
zero standing cost.
4. /learn command — human-gated session harvest (commands/learn.md)
A user-invoked slash command: run /learn (optionally /learn <focus>) at any
point and the agent reviews the session, extracts candidate learnings in five
buckets — lesson, rule, memory-core, note, task — dedups
them against the store (nt_recall/nt_search), and presents a numbered list
for approval before writing anything. Items headed for the always-injected
layer (rule/memory-core) are flagged with their standing token cost, and the
procedure is deliberately stingy there and generous with lessons/notes. The
approval gate is what keeps the injected core small and high-signal — the
opposite failure mode of silent auto-capture. The idle nudge (below) points at
this command.
5. Skill — the workflow (skills/nt/SKILL.md)
Teaches the agent the recall-first / capture-the-why loop and the folder+tag
conventions, loaded on demand via OpenCode's skill tool (its description sits
in context; the body loads only when relevant — progressive disclosure).
6. AGENTS.md — the thin always-on nudge
A tiny file telling the agent it has nt memory, to nt_index/nt_status at
the start, capture as it works, and how to lazy-load @-references (OpenCode does
not auto-expand them). The substance lives in nt, not here.
7. nt export — the compile primitive
nt export [--tag T] [--folder F] [--type note|task|all] [--format md|json] [--out FILE] [--no-provenance] [--no-header] concatenates selected notes (and optionally open
tasks) into one document — what the plugin uses to build the injected block and
what file-mode writes to nt-rules.md. Each note carries a
<!-- nt:<id> <path> --> provenance line (suppressed with --no-provenance) so
the compiled output traces back to its source note by stable nt id.
Install & verify
nt opencode install # from any installed binary (no checkout needed)
nt opencode install --print # preview every step without writing
Or, from a repo checkout (e.g. while iterating on the plugin):
cd integrations/opencode
./install.sh # or: NT_BIN=/abs/path/to/nt ./install.sh
Both are idempotent; re-running nt opencode install after an nt upgrade
refreshes the plugin/skill/commands to the versions that binary ships.
Then restart OpenCode (or reload MCP). Verify:
nt export --tag rule --title Rules # exactly what gets injected as rules
nt mcp install --client opencode --print # the MCP entry, without writing
In an OpenCode session, the agent should be able to call nt_status / nt_search
and you should see a <nt-memory> block influencing its behavior.
Daily use
nt note "Always prefer table-driven tests" --kind rule --description "…" # a rule
nt note "User deploys via 'make ship', not CI" --kind memory --description "…" # core memory
nt note "Auth uses 24h JWTs, 7d refresh" --kind ref --tag auth --description "Token lifetimes" # KB (on-demand)
The agent reads rules+memory every session automatically, and finds the KB note
only when it nt_searches for "jwt".
Bracket a working session with the two commands: /recall <topic> at the
start (or bare /recall to see where things stand) loads the relevant lessons,
notes, and open tasks as a compact brief; /learn at the end proposes the
session's learnings (deduped against the store) and saves only what you
approve.
Choices & trade-offs
- Global vs per-project.
install.shdoes a global setup (~/.config/opencode/) over a single global nt store — personal memory across all projects. For project-scoped memory, setNT_DIR=./.nt(andnt git-init) and placeopencode.json/.opencode/in the repo; tasks can be isolated per worktree withNT_WORKSTREAMwhile notes stay shared. - Live injection vs static file. Default (
system) is always-fresh but uses an experimental OpenCode hook;filemode is fully documented/stable but refreshes once per session. Switch withNT_INJECT. - Agent-driven vs passive capture. Capture quality is highest when the agent
deliberately writes notes (guided by the skill +
AGENTS.md) rather than auto-summarizing. The todo mirror (NT_MIRROR_TODOS) is the one passive option, off by default. - Token budget is a standing cost. Anything tagged
rule/memory-coreis billed every turn. Audit it occasionally withnt export --tag ruleand trim.
Provider compatibility (LiteLLM / BYO models)
This integration is provider-agnostic. Everything here runs in the OpenCode harness before the model call, so it works identically whether OpenCode talks to Claude or any other model through a LiteLLM proxy or a custom provider — it does not depend on OpenCode's hosted ("Zen") models.
nt mcp installandinstall.shonly mergemcp.ntandpermission.skill.nt; yourprovider/model/ endpoint config is left untouched.- Always-in-context layer (rules + core memory via the plugin, plus
AGENTS.md) is injected as plain system-prompt text — no tool-calling required, so it works on every model/route. - On-demand KB layer (the
nt_*MCP tools) requires the routed model to support tool/function calling through LiteLLM. Claude does. On a model with weak tool support that layer degrades gracefully — the injected rules/memory still apply.
Requirements
nton PATH (orNT_BIN).- OpenCode with MCP support (all current versions). Live injection needs the
experimental.chat.system.transformhook (error-triggered recall rides the same hook); compaction survival needsexperimental.session.compacting. Both are experimental OpenCode APIs — if your build lacks them the plugin degrades gracefully (useNT_INJECT=filefor the rules path). The idle nudge and todo mirror use only stable event hooks. nodeis used only byinstall.shto merge one config key; optional.
Documentation
¶
Overview ¶
Package opencode embeds the nt ↔ OpenCode integration bundle — the nt-memory plugin, the nt skill, the /learn and /recall commands, and the starter AGENTS.md — so `nt opencode install` can set up a complete integration from any installed binary, with no repo checkout. install.sh in this directory is the repo-checkout equivalent of the same steps; keep the two file lists in sync.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Assets embed.FS
Assets holds the installable integration files, addressed by their path relative to this directory (e.g. "plugins/nt-memory.ts").
Functions ¶
This section is empty.
Types ¶
This section is empty.