opencode-lessons
v0.2.0
Published
Scoped, evidence-backed lessons in Git-friendly Markdown for OpenCode V2.
Readme
opencode-lessons
Keep the lesson. Skip the transcript.
A small OpenCode V2 plugin that proposes scoped, evidence-backed lessons, searches project Markdown memory, and retrieves lessons on demand during work. Durable memory lives in your repository, not in a client database. Works alongside oh-my-opencode-slim without depending on its internals.
Install
Requires Node.js 22+ and the OpenCode V2 plugin API (tool.transform, command.transform, session.hook and session.get). The packed plugin is tested in a real OpenCode 2.0.22 server and agent loop, using a deterministic local provider fixture. This verifies integration without a provider account; live remote model behavior and the interactive TUI are not covered.
In .opencode/opencode.json:
{
"plugins": [
"oh-my-opencode-slim@latest",
{
"package": "opencode-lessons@latest",
"options": {
"directory": "memory",
"autoRecall": false,
"contextBudget": 4000
}
}
]
}For reproducibility, pin a published npm version, such as [email protected]. Git specifications such as github:vmvarela/opencode-lessons#master also work; pin a full reviewed commit hash when using Git. The plugin does not require Slim; omit its entry if you do not use it. Place Lessons after plugins that rewrite model context. It never changes agents, models, MCP connections or approval settings. Do not load it both explicitly and through auto-discovery.
For local development, configure an absolute path to this checkout directory. Configured local plugins must be directories; the root index.js forwards to the implementation. Relative plugin paths resolve from the configuration file, not the shell's current directory. No build step, SDK download or runtime dependency is required. The package is published on npm as opencode-lessons.
Restart/reload OpenCode and check that the commands below appear. If loading fails, inspect OpenCode's plugin status and logs; do not paste credential-bearing resolved configuration. V1 is intentionally unsupported.
Use
- Work normally. The plugin adds a short reminder to search prior experience and propose only useful, evidenced lessons. It does not observe or archive every tool call.
- Run
/learnafter a task containing a reusable correction or verified workaround. The active agent uses the existing session context to propose at most three lessons; this command requests an ordinary model turn, not a background observer. - Review the diff, evidence, scope and related entries returned by
lessons_propose. - Run
/learn-accept <proposal-id>yourself to save the exact preview. Alternatively use/learn-dismiss <proposal-id>. Proposals belong to the root session tree, so you can accept a Slim specialist's proposal from its parent session. An unrelated root session cannot accept it. Proposals expire after an hour or plugin reload and are never persisted before acceptance. - Review the Git diff and commit through your normal branch/PR workflow. Other machines receive accepted memory only after the appropriate merge and pull.
| Command | Behavior |
| --- | --- |
| /learn | Ask the active agent for useful lessons from this task |
| /learn-search <query> | Ask the agent to search relevant project lessons |
| /learn-review (deprecated) | Ask the agent to inspect lexical duplicates and obsolete entries |
| /learn-promote <lesson-id> (deprecated) | Prepare reviewed diffs for project context, instructions or a skill |
| /learn-accept <proposal-id> | Save the exact displayed preview locally, without a model call |
| /learn-dismiss <proposal-id> | Discard a preview, without a model call |
Read-only tools available to agents: lessons_search, lessons_propose, lessons_review, lessons_promote. OpenCode may expose these through its Code Mode catalog; follow the host's tool catalog to invoke them. There is deliberately no agent tool for accepting a proposal. A direct user command is the save boundary; this is not protection against an untrusted plugin or an agent already authorized to execute arbitrary shell commands. Acceptance and dismissal record a synthetic result with resume: false, so they do not restart the model.
Transition and Reflect
Automatic recall is now off by default, including existing configurations that omit autoRecall. Set autoRecall: true explicitly to retain the experimental behavior. The four core commands are /learn, /learn-search, /learn-accept, and /learn-dismiss.
/learn-review, /learn-promote, lessons_review, and lessons_promote remain functional for this transition release and are planned for removal in the following minor release. They are labeled deprecated and return migration guidance. Existing memory files and states remain compatible; no migration or deletion occurs.
With Slim, ask /reflect to read accepted memory/lessons/ entries as scoped evidence when reviewing recurring workflows. Lessons never invokes Reflect, installs a skill, or changes Slim configuration. Without Slim, ask your normal agent to review memory and propose any context/instruction changes.
Legacy promotion (deprecated)
/learn-promote <lesson-id> asks the active agent to turn an accepted lesson into a reviewable proposal, choosing only the destinations that are justified:
| Destination | Content |
| --- | --- |
| context/ | Verified, stable project facts: architecture, ownership, configuration and constraints |
| AGENTS.md or an existing policy | Reviewed instructions for how to work in this project |
| An existing skill layout | A reusable procedure worth documenting as a skill |
| Keep in memory/ | A useful experience that does not justify changing context or rules |
For example, a lesson about an API change being overwritten by Terraform may justify updating the existing GitHub platform document in context/. The agent must first verify the current Terraform declaration and its actual location, read existing documents, and check for duplicates or contradictions. It then shows a focused diff with the lesson reference and evidence. An isolated workaround is not automatically a stable project fact.
The tool returns the source lesson, destination guidance and review steps. It does not scan context/ itself or generate/apply a diff; the active agent performs that analysis with its normal tools. No destination is forced, and no file, directory or lesson state is changed by the promotion tool. Applying a proposed context, instruction or skill change requires explicit review approval. /learn-accept accepts lesson previews only, not promotion diffs.
context/ remains outside automatic lesson recall. After an approved change is applied, a separate reviewed memory edit can record the adopted document reference while retaining useful historical evidence.
Memory format
Accepted lessons go into memory/lessons/<title>-<id>.md. Each file is readable Markdown with a small frontmatter:
---
opencode_lessons: 1
id: "d564fb84-1905-44dd-a61c-64e6111e0f93"
date: "2026-10-04"
evidence_type: "user-correction"
state: "recorded"
tags: ["terraform"]
---
# Terraform owns branch protection
## Trigger
When changing repository rulesets.
## Action
Edit the declared Terraform source instead of using direct API writes.
## Scope
PrisaMedia workspace; not a policy for personal repositories.
## Evidence
Explicit user correction dated 2026-10-04, with the relevant review reference.
## Limits
Verify the current source of truth before changing it.Required fields: title, trigger, action, scope, evidence, limits and evidenceType (user-correction or verified-observation). An agent-supplied label is not independent verification; review its actual evidence. Assumptions, silence and repeated guesses do not establish correctness.
The plugin also searches existing .md memory under the configured directory, using ##/### section boundaries. It ignores template headings, fenced example headings, README.md, hidden directories and symlinks. Existing files such as devops-lessons.md and known-errors.md are read-only to the plugin and never migrated. JSON frontmatter scalars in managed files must remain valid; do not add Markdown heading delimiters inside their section content.
Managed states: recorded, proposed, incorporated, superseded. Superseded managed entries and legacy entries whose Status: is obsolete/superseded are excluded from recall. State changes and updates to existing lessons are reviewed Markdown edits in this MVP, not automatic rewrites. Duplicate title + scope prevents a new managed entry; similar legacy entries are shown for comparison.
Retrieval and scope
Search uses normalized words, ignores common English/Spanish stop words, and weights titles and explicit scope matches. It requires overlapping meaningful terms and favors matches in titles, triggers or tags. This is lexical retrieval: paraphrases with no shared terms may be missed. It uses no embeddings, remote index or additional model call. Opt-in automatic recall searches the latest identified user message and attaches up to three results as explicitly untrusted, scoped evidence to that message. It never places dynamic memory in the system prompt. Each turn gets a frozen snapshot, replayed unchanged during tool continuations and subsequent turns; memory accepted mid-turn is eligible on the next user turn. Previous turns are never retroactively searched. The active agent can search more precisely with lessons_search.
contextBudget bounds the complete compact JSON search result (1000–12000 characters; default 4000), including metadata and warnings. It is not a token count. Managed results include complete trigger, action, scope, evidence and limits. If these do not fit, a needsRead: true reference points to the file; read it before applying the lesson. omitted counts selected entries or warnings that did not fit. No lesson field is silently truncated. Tool envelopes and the fixed recall label remain outside this search-result budget. Recall snapshots are local to the plugin lifetime and bounded to 32 sessions and 64 visible turns per session. Missing message IDs or exhausted tracking capacity skip new automatic retrieval; manual search still works. Compacted-away turns are discarded. Restarting the plugin rebuilds this in-memory state and may cause a cache miss. The policy reminder is always present; Automatic searches are experimental and require autoRecall: true; the default disables automatic searches while retaining the tools.
Reads are bounded to 500 Markdown files, 256 KiB per file and three nested directory levels. Oversized or malformed managed files produce coverage warnings. No hit is not proof that no lesson exists. Semantic contradictions and obsolescence require human/model review; lexical duplicate hints are not semantic detection.
Sessions must match this plugin instance's exact real project directory, including subagent sessions. A sibling repository or worktree does not silently share memory; use Git to share deliberately. This plugin writes no durable OpenCode storage and creates no parallel Engram memory.
Writes and privacy
- Preview does not create directories or files. Acceptance creates missing memory directories and writes one new file exclusively, using a temporary file and an atomic no-overwrite hard link.
- A short-lived
memory/.opencode-lessons-lockdirectory serializes local acceptance across processes. A concurrent acceptance can ask you to retry. If a process crashes, inspect the workspace and ensure no writer is running before removing a leftover lock. No automatic stale-lock deletion occurs. - No commit, push, PR creation, policy adoption or skill installation occurs. Keep
memory/tracked by Git. You may ignorememory/.opencode-lessons-lock/andmemory/lessons/*.tmpfor interrupted writes. - Common credential patterns are rejected in proposals and redacted in legacy recall. This is a heuristic, not a secret scanner or DLP guarantee; review every diff. Unknown credentials and sensitive prose can still pass. Selected memory is sent to the model already handling your task.
- Filesystem checks reject symlinked paths and existing hardlinked files. They are not a sandbox against a hostile process racing directory replacement.
Development
corepack enable
pnpm install --frozen-lockfile
pnpm run verify
pnpm run check:release
npm packTests use Node's built-in runner and temporary directories. They cover storage round trips, legacy retrieval, duplicate detection, exact-preview acceptance, concurrent writers, linked-path rejection, credential heuristics, context bounds and a V2 host-contract harness.
Run the separate Linux runtime test with an installed OpenCode V2 executable:
OPENCODE_SMOKE_CLI=/absolute/path/to/opencode pnpm run test:runtimeThe runtime test packs and extracts the distribution, starts an isolated OpenCode server with temporary XDG directories, and serves deterministic model responses over localhost. It checks command registration, Code Mode tool execution, read-only preview, exact acceptance without another model request, automatic recall as untrusted evidence, and context promotion without file changes. No real provider key is used. It requires npm and tar; CI pins OpenCode 2.0.22 on Linux. CI also loads the published Slim 3.0.2 server export alongside Lessons through a local entry shim, checks that Deepwork/Reflect remain registered, and repeats the fixture with default manual search and explicit automatic recall. Provider requests verify stable system messages and unchanged prior history across turns. Child-to-parent acceptance is covered by the contract harness. This does not validate a complete Deepwork/Reflect workflow, live-model delegation, or the TUI.
Releases and npm
The CI and release workflows follow opencode-model-aliases: Node 24, pnpm 11, a frozen lockfile, verification and semantic-release 25.0.9. Lessons is plain JavaScript, so verification uses syntax checks and Node tests rather than a TypeScript build. Both workflows verify the packed plugin in real OpenCode with a local provider fixture, with recall off/on and with/without Slim 3.0.2. Development dependencies do not become plugin runtime dependencies.
semantic-release uses Conventional Commits: fix: releases a patch, feat: a minor version and BREAKING CHANGE: a major version. It publishes the npm package, creates a Git tag and writes GitHub release notes. It does not commit version changes back to master or write a changelog into the repository. Git tags and the published package hold the released version.
Publishing runs automatically on pushes to master in vmvarela/opencode-lessons, and can also be dispatched manually. Forks do not publish through this workflow. The release workflow grants only contents: write and id-token: write, disables issue/PR release comments and serializes releases without cancelling them halfway through.
First publication
Version 0.1.2 is published on npm and recorded by the v0.1.2 Git tag. The bootstrap tag points to the source commit used for that package; subsequent versions are managed by semantic-release. Do not manually bump versions, recreate an existing tag or republish an existing npm version.
Automated publication
In npm's package settings, add a GitHub Actions trusted publisher with:
| Field | Value |
| --- | --- |
| Organization or user | vmvarela |
| Repository | opencode-lessons |
| Workflow filename | release.yml |
| Environment | Leave empty |
| Direct publishing | Allow npm publish |
The repository workflow is enabled; no repository variable is required. Push a release-worthy Conventional Commit or run the Release workflow manually from master. No permanent npm token is required for subsequent releases. The workflow pins npm 11.6.2, which supports OIDC, and deliberately omits registry-url from setup-node to avoid authentication conflicts.
After the package is published, OpenCode can use "opencode-lessons@latest" or a pinned npm version instead of the Git specification. Check the npm package and GitHub releases for the actual published version; configuring the workflow alone does not publish it.
References: npm trusted publishing and semantic-release on GitHub Actions.
MVP boundaries
No automatic idle-triggered extraction, numeric confidence, transcript database, global memory, semantic search or automatic promotion. /learn-promote returns a review plan and source lesson; the agent prepares any context, instruction or skill diff for review. Updates to existing entries remain normal reviewed file edits. Scope and review are more useful than automatically accumulating more memory.
References: OpenCode V2 plugins, plugin configuration, ECC Continuous Learning v2. Inspired by ECC's atomic, scoped lessons; no ECC code or runtime dependency is included.
Evidence and cost evaluation
The deterministic fixture returns synthetic usage counters. Passing it proves request structure and integration behavior, not provider cache hits, token savings, or better task outcomes. No provider credentials are needed or used by CI.
For a live comparison, use the same OpenCode/Slim versions, model, repository fixture and accepted lessons; start fresh sessions for each arm (manual versus automatic recall). Repeat tasks involving a known correction, a wrong-scope lesson and an unrelated request. Record whether the prior mistake recurs, whether a retrieved lesson was actually applicable, provider input/output/cache counters, and model turns. Keep transcripts and credentials out of reports. Automatic recall remains opt-in until these results justify a different default.
