Deferred scope note: plugin-level automatic cloud enrollment/login/upgrade orchestration is not part of this rollout yet. Current cloud flows are CLI-driven (
engram cloud ...).Validation boundary (current): plugin scripts are validated for memory/session workflows, not as cloud bootstrap orchestrators. Use CLI for cloud config/auth/enrollment/upgrade.
For OpenCode users, a thin TypeScript plugin adds enhanced session management on top of the MCP tools:
# Install via engram (recommended — works from Homebrew or binary install)
engram setup opencode
# Or manually: cp plugin/opencode/engram.ts ~/.config/opencode/plugins/The plugin auto-starts the HTTP server if it's not already running — no manual engram serve needed.
Local model compatibility: The plugin works with all models, including local ones served via llama.cpp, Ollama, or similar. The Memory Protocol is concatenated into the existing system prompt (not added as a separate system message), so models with strict Jinja templates (Qwen, Mistral/Ministral) work correctly.
The plugin:
- Auto-starts the engram server if not running
- Auto-imports git-synced memories from
.engram/manifest.jsonif present in the project - Creates sessions on-demand via
ensureSession()(resilient to restarts/reconnects) - Injects the Memory Protocol into the agent's system prompt via
chat.system.transform— strict rules for when to save, when to search, and a mandatory session close protocol. The protocol is concatenated into the existing system message (not pushed as a separate one), ensuring compatibility with models that only accept a single system block (Qwen, Mistral/Ministral via llama.cpp, etc.) - Injects previous session context into the compaction prompt
- Instructs the compressor to tell the new agent to persist the compacted summary via
mem_session_summary - Strips
<private>tags before sending data - Enables
opencode-subagent-statuslineintui.jsonortui.jsoncduringengram setup opencode, adding a live sub-agent monitor to OpenCode's sidebar/home footer. To disable it later, remove"opencode-subagent-statusline"from the"plugin"array in your TUI config and restart OpenCode.
No raw tool call recording — the agent handles all memory via mem_save and mem_session_summary.
The plugin injects a strict protocol into every agent message:
- WHEN TO SAVE: Mandatory after bugfixes, decisions, discoveries, config changes, patterns, preferences
- WHEN TO SEARCH: Reactive (user says "remember"/"recordar") + proactive (starting work that might overlap past sessions)
- SESSION CLOSE: Mandatory
mem_session_summarybefore ending — "This is NOT optional. If you skip this, the next session starts blind." - AFTER COMPACTION: Immediately call
mem_contextto recover state
The OpenCode plugin uses a defense-in-depth strategy to ensure memories survive compaction:
| Layer | Mechanism | Survives Compaction? |
|---|---|---|
| System Prompt | MEMORY_INSTRUCTIONS concatenated into existing system prompt via chat.system.transform |
Always present |
| Compaction Hook | Auto-saves checkpoint + injects context + reminds compressor | Fires during compaction |
| Agent Config | "After compaction, call mem_context" in agent prompt |
Always present |
For Claude Code users, a plugin adds enhanced session management using Claude's native hook and skill system:
# Install via Claude Code marketplace (recommended)
claude plugin marketplace add Gentleman-Programming/engram
claude plugin install engram
# Or via engram binary (works from Homebrew or binary install)
engram setup claude-code
# Or for local development/testing from the repo
claude --plugin-dir ./plugin/claude-code| Feature | Bare MCP | Plugin |
|---|---|---|
| MCP tools available | 17 default (engram mcp) |
13 agent-profile tools (engram mcp --tools=agent) |
| Session tracking (auto-start) | ✗ | ✓ |
| Auto-import git-synced memories | ✗ | ✓ |
| Compaction recovery | ✗ | ✓ |
| Memory Protocol skill | ✗ | ✓ |
| Previous session context injection | ✗ | ✓ |
plugin/claude-code/
├── .claude-plugin/plugin.json # Plugin manifest
├── .mcp.json # Registers engram MCP server
├── hooks/hooks.json # SessionStart + SubagentStop + Stop lifecycle hooks
├── scripts/
│ ├── session-start.sh # Ensures server, creates session, imports chunks, injects context
│ ├── post-compaction.sh # Injects previous context + recovery instructions
│ ├── subagent-stop.sh # Passive capture trigger on subagent completion
│ └── session-stop.sh # Logs end-of-session event
└── skills/memory/SKILL.md # Memory Protocol (when to save, search, close, recover)
On session start (startup):
- Ensures the engram HTTP server is running
- Creates a new session via the API
- Auto-imports git-synced chunks from
.engram/manifest.json(if present) - Injects previous session context into Claude's initial context
On compaction (compact):
- Injects the previous session context + compacted summary
- Tells the agent: "FIRST ACTION REQUIRED — call
mem_session_summarywith this content before doing anything else" - This ensures no work is lost when context is compressed
Memory Protocol skill (always available):
- Strict rules for when to save (mandatory after bugfixes, decisions, discoveries)
- When to search memory (reactive + proactive)
- Session close protocol — mandatory
mem_session_summarybefore ending - After compaction — 3-step recovery: persist summary → load context → continue
mem_judge is available in the agent profile (engram mcp --tools=agent). It is NOT exposed in the admin profile.
Records a verdict on a pending memory conflict surfaced by mem_save. When mem_save returns judgment_required: true, the agent iterates candidates[] and calls mem_judge once per entry.
| Parameter | Required | Type | Description |
|---|---|---|---|
judgment_id |
yes | string | From candidates[].judgment_id in the mem_save response. Format: rel-<hex>. |
relation |
yes | string | One of: related, compatible, scoped, conflicts_with, supersedes, not_conflict |
reason |
no | string | Free-text explanation of the verdict |
evidence |
no | string | Supporting evidence (JSON or free text; e.g., user's exact words) |
confidence |
no | float | 0.0..1.0 — default 1.0; clamped to range |
session_id |
no | string | Session ID for provenance (auto-detected if omitted) |
On success, mem_judge:
- Flips
judgment_statusfrompendingtojudgedon the matchingmemory_relationsrow - Persists
relation,reason,evidence,confidence, actor provenance (actor="agent",marked_by_kind="agent"), andsession_id - Returns the updated relation row as JSON
On error (unknown judgment_id or invalid relation), returns IsError: true. The relation row is NOT mutated on error.
Re-judging an already-judged judgment_id overwrites the verdict (deliberate revision is allowed).
After a verdict is recorded, mem_search annotations surface as follows:
| Relation verdict | Annotation in mem_search results |
|---|---|
supersedes |
supersedes: #<id> on the source observation; superseded_by: #<id> on the target |
pending (not yet judged) |
conflict: contested by #<id> (pending) on both observations |
conflicts_with |
No annotation line. Judgment is stored in memory_relations but not surfaced in search results in Phase 1. |
compatible, related, scoped, not_conflict |
No annotation line. Judgment is stored but not surfaced in Phase 1. |
Known gap:
conflicts_withjudgments are persisted and queryable inmemory_relationsbut do not produce a visible annotation inmem_searchoutput. Onlysupersedesandpendingrelations produce annotation lines. This is by design for Phase 1 — aconflicts_withannotation format is not yet defined.
When mem_save detects candidates, the JSON response includes:
| Field | Type | Description |
|---|---|---|
judgment_required |
bool | true when candidates were found; false otherwise |
judgment_status |
string | "pending" (only present when judgment_required: true) |
judgment_id |
string | Convenience: the first candidate's judgment_id (use candidates[].judgment_id for multi-candidate loops) |
candidates |
array | Each entry has id, sync_id, title, type, score, judgment_id, and optionally topic_key |
id |
int | Internal ID of the just-saved observation |
sync_id |
string | Stable sync ID of the just-saved observation |
Old clients that read only the result string continue to work — these fields are additive.
Wrap sensitive content in <private> tags — it gets stripped at TWO levels:
Set up API with <private>sk-abc123</private> key
→ Set up API with [REDACTED] key
- Plugin layer — stripped before data leaves the process
- Store layer —
stripPrivateTags()in Go before any DB write