From e57dfcd6133fa4b71f54ab3f46d960a186103581 Mon Sep 17 00:00:00 2001 From: Alexis Sanabria Lopez Date: Thu, 27 Aug 2026 00:39:42 +0200 Subject: [PATCH 1/5] feat: add leteo plugin to the marketplace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leteo is local-first persistent memory for coding agents: one Rust binary over one SQLite database, no service to sign up to. Sessions, prompts and decisions survive a compaction and a restart. The bundle registers the three lifecycle events this client fires — two on SessionStart (startup|clear and compact) and one on UserPromptSubmit — plus the MCP server on the agent tool profile and a skill carrying the memory protocol. Three rather than five: SubagentStop and SessionEnd are not among the seven events supported, so the hooks named for those have nowhere to land, and session-stop is deliberately not moved onto Stop, which fires at the end of every reply rather than at the end of a session. The plugin carries configuration, not the binary; leteo is installed separately and every hook here invokes it from PATH. --- .claude-plugin/marketplace.json | 6 + plugins/leteo/.claude-plugin/plugin.json | 17 ++ plugins/leteo/.mcp.json | 9 + plugins/leteo/README.md | 70 ++++++ plugins/leteo/hooks/hooks.json | 40 ++++ plugins/leteo/skills/memory/SKILL.md | 292 +++++++++++++++++++++++ 6 files changed, 434 insertions(+) create mode 100644 plugins/leteo/.claude-plugin/plugin.json create mode 100644 plugins/leteo/.mcp.json create mode 100644 plugins/leteo/README.md create mode 100644 plugins/leteo/hooks/hooks.json create mode 100644 plugins/leteo/skills/memory/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 059d952..4f9387e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -19,6 +19,12 @@ "source": "./plugins/glm-plan-bug", "category": "development", "description": "Submit case feedback and bug reports for GLM Coding Plan service." + }, + { + "name": "leteo", + "source": "./plugins/leteo", + "category": "development", + "description": "Persistent memory for coding agents: local-first, one binary over one SQLite database. Sessions, prompts and decisions survive compaction and restart." } ] } \ No newline at end of file diff --git a/plugins/leteo/.claude-plugin/plugin.json b/plugins/leteo/.claude-plugin/plugin.json new file mode 100644 index 0000000..f25a359 --- /dev/null +++ b/plugins/leteo/.claude-plugin/plugin.json @@ -0,0 +1,17 @@ +{ + "name": "leteo", + "description": "Persistent memory for AI coding agents. Survives across sessions and compactions.", + "version": "0.1.2", + "author": { + "name": "Alexis Sanabria Lopez" + }, + "homepage": "https://github.com/asanabrial/leteo", + "repository": "https://github.com/asanabrial/leteo", + "license": "MIT", + "keywords": [ + "memory", + "persistence", + "sessions", + "context" + ] +} diff --git a/plugins/leteo/.mcp.json b/plugins/leteo/.mcp.json new file mode 100644 index 0000000..a44d110 --- /dev/null +++ b/plugins/leteo/.mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "leteo": { + "type": "stdio", + "command": "leteo", + "args": ["mcp", "--tools=agent"] + } + } +} diff --git a/plugins/leteo/README.md b/plugins/leteo/README.md new file mode 100644 index 0000000..74b0093 --- /dev/null +++ b/plugins/leteo/README.md @@ -0,0 +1,70 @@ +# Leteo + +Persistent memory for coding agents: one Rust binary over one SQLite database, +local-first, no service to sign up to. + +Each session opens holding what the project already knows, prompts are kept, and +a compaction is recovered from rather than survived. + +## Install + +```text +zcode plugins marketplace add zai-org/zai-coding-plugins +zcode plugins install leteo@zai-coding-plugins +``` + +**The plugin carries configuration, not the binary.** Install `leteo` first, or +the MCP entry and every hook here are commands that are not on `PATH`: + +```sh +curl -fsSL https://raw.githubusercontent.com/asanabrial/leteo/main/scripts/install.sh | sh +``` + +`cargo install leteo` and a Windows script are in the +[project README](https://github.com/asanabrial/leteo#install). + +## What it registers + +| Event | Command | Effect | +| --- | --- | --- | +| `SessionStart` (`startup`, `clear`) | `leteo hook session-start` | Opens the session and hands back the project's recent work, prompts and most relevant memories | +| `SessionStart` (`compact`) | `leteo hook post-compaction` | Puts back what the compaction took, and clears what had been marked as already shown | +| `UserPromptSubmit` | `leteo hook user-prompt-submit` | Keeps the prompt, and names a memory worth having in front of you — never the same one twice in a session | + +Three, not five. This client fires seven lifecycle events — `SessionStart`, +`UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, +`PostToolUseFailure`, `Stop` — and neither `SubagentStop` nor `SessionEnd` is +among them, so the hooks named for those have nowhere to land. + +`session-stop` is deliberately **not** moved onto `Stop` to fill the gap. `Stop` +fires when the agent finishes a reply, at the end of every turn rather than at +the end of the conversation; registered there once, it ended the session on every +single prompt, which deleted the save reminder's debounce and made the reminder +appear on every prompt instead of every fifteen minutes. The closing summary +comes from the agent calling `mem_session_summary` itself, which the skill tells +it to do. + +Alongside them, [`.mcp.json`](.mcp.json) registers the MCP server with the +`agent` tool profile, so the everyday tools are there from the first message, and +[`skills/memory`](skills/memory/SKILL.md) carries the protocol that tells the +agent when to reach for them. Tools without that protocol is the shape that +measured 0 saves out of 8 tasks. + +## Alternative to the plugin + +`leteo setup zcode --hooks` writes the same three registrations into +`~/.zcode/cli/config.json` directly. **Pick one, not both** — registered twice, +every lifecycle event runs twice, which stored each prompt twice on the machine +where it was found, 23 identical pairs before anything looked wrong. `leteo +setup` looks for an installed bundle and refuses rather than adding a second +copy. + +The plugin is the more robust of the two: configuration-file hooks run only +while `hooks.enabled` is true, and that switch starts off and belongs to the +person, while enabling a plugin is what enables the plugin's hooks. + +## Upstream + +Canonical source, issues and the full documentation: +. MIT licensed. Leteo is a +reimplementation of Engram; that attribution is kept in the project's `NOTICE`. diff --git a/plugins/leteo/hooks/hooks.json b/plugins/leteo/hooks/hooks.json new file mode 100644 index 0000000..28eb9a7 --- /dev/null +++ b/plugins/leteo/hooks/hooks.json @@ -0,0 +1,40 @@ +{ + "description": "Leteo persistent memory hooks — session tracking, compaction recovery, and prompt capture", + "hooks": { + "SessionStart": [ + { + "matcher": "startup|clear", + "hooks": [ + { + "type": "command", + "command": "leteo hook session-start", + "timeout": 10, + "statusMessage": "Loading Leteo memory..." + } + ] + }, + { + "matcher": "compact", + "hooks": [ + { + "type": "command", + "command": "leteo hook post-compaction", + "timeout": 10, + "statusMessage": "Recovering Leteo context after compaction..." + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "leteo hook user-prompt-submit", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/plugins/leteo/skills/memory/SKILL.md b/plugins/leteo/skills/memory/SKILL.md new file mode 100644 index 0000000..434f1fd --- /dev/null +++ b/plugins/leteo/skills/memory/SKILL.md @@ -0,0 +1,292 @@ +--- +name: leteo-memory +description: "ALWAYS ACTIVE — Persistent memory protocol. You MUST save decisions, conventions, bugs, and discoveries to Leteo proactively. Do NOT wait for the user to ask." +--- + +# Leteo Persistent Memory — Protocol + +You have access to Leteo, a persistent memory system that survives across +sessions and compactions. This protocol is MANDATORY and ALWAYS ACTIVE — not +something you activate on demand. + +## AVAILABLE TOOLS + +The plugin registers Leteo with the `agent` tool profile, so the everyday tools +are present from the first message — no `ToolSearch` needed. + +- `mem_save`, `mem_search`, `mem_context`, `mem_session_summary` +- `mem_get_observation`, `mem_timeline`, `mem_suggest_topic_key`, `mem_update` +- `mem_session_start`, `mem_session_end`, `mem_save_prompt` +- `mem_current_project`, `mem_pin`, `mem_unpin`, `mem_review` +- `mem_judge`, `mem_compare`, `mem_capture_passive`, `mem_doctor` + +Only the three that change or count the whole store are deferred; reach for +`ToolSearch` when you need one: `mem_stats`, `mem_delete`, `mem_merge_projects`. + +**If the tools are missing**, run `leteo setup zcode` and restart the +client. Verify the store itself with `leteo doctor`. + +## PROACTIVE SAVE TRIGGERS (mandatory — do NOT wait to be asked) + +Call `mem_save` IMMEDIATELY after any of these: + +### After decisions or conventions +- An architecture or design decision was made +- A team convention was established +- A tool or library was chosen, with its tradeoffs + +### After completing work +- A bug was fixed — record the root cause, not just the symptom +- A feature landed with a non-obvious approach +- A configuration or environment change was made + +### After discoveries +- Something non-obvious about the codebase +- A gotcha, edge case, or surprising behaviour +- A pattern worth repeating + +### After the user confirms or rejects something +- The user accepts a recommendation ("go with that", "perfect", or the + equivalent in their language) +- The user rejects an option ("no, better X") +- The user states a preference or constraint + +### Self-check after EVERY task +> "Did we just decide something, fix a bug, learn something non-obvious, or +> establish a convention? If yes, call `mem_save` now." + +## HOW TO WRITE A MEMORY + +- **title**: verb + object, short and searchable — "Fixed N+1 query in UserList" +- **type**: `bugfix` | `decision` | `policy` | `architecture` | `discovery` | + `pattern` | `config` | `preference` +- **scope**: `project` (default), `personal`, or `global` +- **topic_key** (recommended for topics that evolve): a stable key such as + `architecture/auth-model`, so later saves supersede rather than duplicate +- **content**: + - **What**: one sentence + - **Why**: what motivated it + - **Where**: files or paths affected + - **Learned**: gotchas and surprises (omit when there are none) + +Write memories someone else could act on months later. Convert relative dates +("yesterday", "last sprint") into absolute ones. + +### Dense, not short + +The goal is facts per token, not fewer tokens. A vague memory is cheap and +worthless; a precise one costs a little more and is worth reading. Written +loosely and written tightly, the same bug fix came out at 115 and 137 tokens — +and only the longer one named the error, the files, the numbers, the root cause +and how to reproduce it. Optimising for length would have kept the useless one. + +So spend nothing on these: + +- **Filler and hedging.** "It's worth noting that", "basically", "essentially", + "it turns out that", "some kind of", "apparently". They carry nothing. +- **Throat-clearing.** Lead with the fact: "`delete_project` failed with + `FOREIGN KEY constraint failed`", not "we ran into an issue where...". +- **Repetition.** The title already says what happened; **What** should not + restate it, and **Why** should not restate **What**. +- **What the repository already records.** Diffs, file listings and commit + history are not memories. What is not in the code is why, and what surprised + you. + +And spend freely on these, which are what make the memory worth keeping: + +- **Names and paths.** `src/store.rs`, `DeleteProjectResult.sessions_kept`. +- **Numbers.** "27 prompts", "3404 of 3407", "20ms". A number replaces a + paragraph of qualification and costs a token. +- **Error strings, identifiers and commands, verbatim.** These are what a + future search matches on. Never paraphrase or abbreviate them. +- **The reproduction.** "reproducible from `leteo delete project --hard`" turns + a claim into something checkable. + +Write full sentences with their articles. A memory is read by a person in the +TUI as well as by an agent, and clipped grammar saves a token or two while +costing the reader a second guess about what was meant. + +## THE PROJECT A MEMORY BELONGS TO + +Leteo decides the project itself, from the session, the process override, or +the working directory. **Do not invent a project name.** If a write comes back +with `ambiguous_project`, it carries a short-lived recovery token: ask the user +which project they meant and replay the call with their answer. A write naming +a project Leteo has never seen is refused on purpose. + +## WHEN A WRITE COMES BACK `store_busy` + +Nothing happened and nothing was half-written: another Leteo process — a +lifecycle hook, another agent, the background sync — was writing at that +moment. Make the same call again. It is the one failure that is worth +retrying, and it needs no changes to what you sent. + +## CONFLICTS — when a pair is waiting on a verdict + +A save may report that the new memory looks related to existing ones. Judge each +candidate with `mem_judge`, once per candidate, using that candidate's own +`judgment_id`. + +**Settle every verdict yourself** — `related`, `compatible`, `scoped`, +`conflicts_with`, `supersedes`, `not_conflict`. Never put one to the user and +never mention it in your reply. This is Leteo's bookkeeping, not work they asked +for, and a question about two memories they do not remember writing is an +interruption charged against the time memory is supposed to be saving them. A +verdict is not final either: `mem_judge` replaces one wholesale, reason +included, so getting one wrong is a correction rather than damage. + +Judge in the same turn the candidate is reported. A pair you leave is not +deferred to later, it is dropped: `mem_judge` takes a `judgment_id` that only +`mem_save` and a session opening ever hand out, so nothing raises it again in +between. + +The session opening is the second route. When it lists pairs under **Waiting on +a verdict**, those are proposals earlier turns left behind, oldest first, with +the `judgment_id`, the category and the topic key for each. Two memories under +one topic key are revisions of each other; two under different keys rarely +conflict. That is usually enough to rule — read one with `mem_get_observation` +when it is not. A side marked `(deleted since this pair was proposed)` is a +memory removed since — nothing is left to contradict, so `not_conflict` closes +that pair in one call. A trailing line counting pairs `mem_judge` cannot settle +is not work and not yours: leave it and say nothing. + +## WHEN A MEMORY COMES ROUND FOR A REREAD + +A decision, a policy or a preference is saved with a date to look at it again. +When the session's opening block says memories are due, call `mem_review` with +`action: "list"`, read the ones that still matter, and mark each with +`action: "mark_reviewed"` once you have — that is what winds the clock on. A +memory whose answer has changed is a `mem_update` or a new save under the same +`topic_key`, not a note to yourself. + +## AT THE END OF A SESSION + +Call `mem_session_summary` before you say you are done. The stop hook records +the session, but the summary is what makes the next one useful. + +## HOW TO REPORT MEMORY WORK + +Leteo is the store; **Sardi** is the cat who tends it. When you tell the user +what became of their memories, say who did it: + +| What happened | The register | +| --- | --- | +| A memory was saved | 🐈 Sardi kept that one. | +| A search found prior work | 🐈 Sardi remembers three notes about this. | +| Duplicates were folded together | 🐾 Sardi merged it with the earlier decision. | +| Noise was dropped | 🗑️ Sardi discarded 42 redundant notes. | +| Work is under way | 🐈 Sardi is reading your notes... | + +Three rules make this a personality rather than an annoyance: + +1. **Once per reply, at most**, and only when there is something the user would + want to know. A mascot that narrates every tool call is noise. +2. **Your own wording.** The table is the register, not a script — a fixed + phrase repeated verbatim stops reading like a voice within a few turns. +3. **Never in an error.** A failure has to stay precise and actionable, and + there is nothing charming about a cat standing between someone and the thing + they need to fix. + +The data is never touched by any of this: tool arguments, titles, and memory +content stay plain. The voice belongs in what you say, not in what you store. + +## SAVING IS NOT REPLYING + +Memory is bookkeeping for your future self. The user never sees a tool call or +the text you store, so a memory is never an answer. + +- If the answer exists only inside a `mem_save`, the user did not receive it. +- End every turn with the complete user-facing reply, with no tool calls after + it. Save before composing that reply, not after. +- If a save runs late, still write the full answer — do not collapse it into + "saved" or "done". +- If `mem_save`, `mem_judge` or `mem_session_summary` fails or hangs, deliver + the answer anyway and mention the failure in a sentence. A slow memory + operation never blocks, truncates, or replaces what you owed the user. + +## WHEN TO SEARCH + +On any reference to past work — "remember", "what did we do", "how did we solve +it", or the same in whatever language the user writes in: + +1. `mem_context` first. It is the cheapest and covers recent sessions. +2. `mem_search` if that came up short — with the words the memory would carry, + which are not always the words of the question. A memory is written in the + language the conversation was held in, or in the one `leteo setup` pinned — + the session context says which — and the question does not always arrive in + that language. `cobertura` against a store that says `coverage` returns + nothing at all, and an empty result never says why. Identifiers, paths, + numbers and error strings cross languages unchanged, so lead with those, + and try the same idea in the language the store holds before concluding a + thing was never saved. +3. `mem_get_observation` for the whole text once something looks right. The + context is an index, not the memories themselves: the pinned ones and the + newest few open with the first three hundred characters and everything + under `Also remembered` is a title alone. Every entry carries its `#id`, and + that is what to fetch by. + + Assume the answer is past the cut. A memory here runs to about two thousand + characters, and the names, paths, numbers and error strings — the part + written to be worth keeping — are almost all beyond the first three hundred. + A trailing `...` marks a body that was cut. Fetch before answering from a + preview; guessing from one is how a version number turns into the wrong + version number. + +Search proactively too: before starting something that may have been done +before, when the user names a topic you have no context on, and on their first +message of a session if it refers to the project, a feature, or a problem. + +## SUBAGENTS + +When you hand work to a subagent, ask it to end with this section: + +``` +## Key Learnings: +1. [Something worth remembering, in one sentence of at least four words] +``` + +A hook reads the subagent's final message when it stops and keeps those lines +as memories on their own, filed under the subagent's name. Nothing else in its +output is kept, so this is the only way work done in a subagent reaches the +store — without it the subagent finishes, its context is discarded, and what it +found is gone. + +Ask only when there is something to learn. A subagent that read three files to +answer one question has no learnings, and an empty section is better than an +invented one: these are saved without review. + +## AFTER COMPACTION + +When you see a compaction notice: + +1. Call `mem_session_summary` with the compacted summary **first**. Without it, + everything from before the compaction is lost to memory as well as context. +2. Call `mem_context` to recover what earlier sessions knew. +3. Only then carry on with the work. + +## SESSION SUMMARY SHAPE + +`mem_session_summary` takes prose. The first line that is not a heading becomes +the memory's title, so make it say what the session was for — every summary +used to be called "Session summary: " and none of them could be found +again. This shape is what makes it useful to read months later: + +``` +## Goal +[What this session was for] + +## Instructions +[Preferences or constraints the user stated — omit if none] + +## Discoveries +- [Findings, gotchas, things that were not obvious] + +## Accomplished +- [What was completed, with the details that matter] + +## Next Steps +- [What is left, for whoever picks it up] + +## Relevant Files +- path/to/file — [what it does, or what changed] +``` From 1c3682e2dfd0c814d3565ef9105d581f45dfad35 Mon Sep 17 00:00:00 2001 From: Alexis Sanabria Lopez Date: Thu, 27 Aug 2026 01:47:56 +0200 Subject: [PATCH 2/5] docs: correct the install instruction for a command the CLI does not have `zcode plugins` lists and enables installed plugins; it does not add marketplaces. Checked against zcode 0.16.5. Adding one is Settings -> Plugins -> Create -> Add marketplace. --- plugins/leteo/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/leteo/README.md b/plugins/leteo/README.md index 74b0093..2ba9266 100644 --- a/plugins/leteo/README.md +++ b/plugins/leteo/README.md @@ -8,10 +8,12 @@ a compaction is recovered from rather than survived. ## Install -```text -zcode plugins marketplace add zai-org/zai-coding-plugins -zcode plugins install leteo@zai-coding-plugins -``` +Adding a marketplace is a desktop action rather than a command: **Settings -> +Plugins -> Create -> Add marketplace**, pointing at `zai-org/zai-coding-plugins`. +Then install `leteo` from the list it shows. + +The CLI does not add marketplaces -- `zcode plugins` lists and enables what is +already installed. Checked against `zcode 0.16.5`. **The plugin carries configuration, not the binary.** Install `leteo` first, or the MCP entry and every hook here are commands that are not on `PATH`: From 73d7e1cdf412bd1c1b44153cf4144d162f51a0da Mon Sep 17 00:00:00 2001 From: Alexis Sanabria Lopez Date: Thu, 27 Aug 2026 01:50:52 +0200 Subject: [PATCH 3/5] fix: ship the Claude Code bundle, not the ZCode one This marketplace is for Claude Code -- its prerequisites say so, its install commands are `claude plugin marketplace add`, and its manifest carries the Claude Code marketplace schema. The bundle submitted was the ZCode variant, which registers three lifecycle events rather than five. Claude Code fires all five. Installed from here, the ZCode bundle would have left SubagentStop and SessionEnd unregistered and said nothing about it: no sub-agent findings captured before their context is discarded, and no session ever closed. The bundle is now byte-identical to the Claude Code one upstream, and the README carries this client's install commands. --- plugins/leteo/.mcp.json | 1 - plugins/leteo/README.md | 71 +++++++++++++++------------- plugins/leteo/hooks/hooks.json | 26 +++++++++- plugins/leteo/skills/memory/SKILL.md | 2 +- 4 files changed, 64 insertions(+), 36 deletions(-) diff --git a/plugins/leteo/.mcp.json b/plugins/leteo/.mcp.json index a44d110..39d1fc7 100644 --- a/plugins/leteo/.mcp.json +++ b/plugins/leteo/.mcp.json @@ -1,7 +1,6 @@ { "mcpServers": { "leteo": { - "type": "stdio", "command": "leteo", "args": ["mcp", "--tools=agent"] } diff --git a/plugins/leteo/README.md b/plugins/leteo/README.md index 2ba9266..cd116b8 100644 --- a/plugins/leteo/README.md +++ b/plugins/leteo/README.md @@ -3,22 +3,21 @@ Persistent memory for coding agents: one Rust binary over one SQLite database, local-first, no service to sign up to. -Each session opens holding what the project already knows, prompts are kept, and -a compaction is recovered from rather than survived. +Each session opens holding what the project already knows, prompts are kept, +sub-agent findings are captured before their context is discarded, and a +compaction is recovered from rather than survived. ## Install -Adding a marketplace is a desktop action rather than a command: **Settings -> -Plugins -> Create -> Add marketplace**, pointing at `zai-org/zai-coding-plugins`. -Then install `leteo` from the list it shows. - -The CLI does not add marketplaces -- `zcode plugins` lists and enables what is -already installed. Checked against `zcode 0.16.5`. +```shell +claude plugin marketplace add zai-org/zai-coding-plugins +claude plugin install leteo@zai-coding-plugins +``` **The plugin carries configuration, not the binary.** Install `leteo` first, or the MCP entry and every hook here are commands that are not on `PATH`: -```sh +```shell curl -fsSL https://raw.githubusercontent.com/asanabrial/leteo/main/scripts/install.sh | sh ``` @@ -32,41 +31,47 @@ curl -fsSL https://raw.githubusercontent.com/asanabrial/leteo/main/scripts/insta | `SessionStart` (`startup`, `clear`) | `leteo hook session-start` | Opens the session and hands back the project's recent work, prompts and most relevant memories | | `SessionStart` (`compact`) | `leteo hook post-compaction` | Puts back what the compaction took, and clears what had been marked as already shown | | `UserPromptSubmit` | `leteo hook user-prompt-submit` | Keeps the prompt, and names a memory worth having in front of you — never the same one twice in a session | - -Three, not five. This client fires seven lifecycle events — `SessionStart`, -`UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, -`PostToolUseFailure`, `Stop` — and neither `SubagentStop` nor `SessionEnd` is -among them, so the hooks named for those have nowhere to land. - -`session-stop` is deliberately **not** moved onto `Stop` to fill the gap. `Stop` -fires when the agent finishes a reply, at the end of every turn rather than at -the end of the conversation; registered there once, it ended the session on every -single prompt, which deleted the save reminder's debounce and made the reminder -appear on every prompt instead of every fifteen minutes. The closing summary -comes from the agent calling `mem_session_summary` itself, which the skill tells -it to do. +| `SubagentStop` | `leteo hook subagent-stop` | Captures a sub-agent's Key Learnings, in any of the twelve languages Leteo speaks | +| `SessionEnd` | `leteo hook session-stop` | Closes the session | Alongside them, [`.mcp.json`](.mcp.json) registers the MCP server with the -`agent` tool profile, so the everyday tools are there from the first message, and -[`skills/memory`](skills/memory/SKILL.md) carries the protocol that tells the -agent when to reach for them. Tools without that protocol is the shape that +`agent` tool profile, so the everyday tools are there from the first message, +and [`skills/memory`](skills/memory/SKILL.md) carries the protocol that tells +the agent when to reach for them. Tools without that protocol is the shape that measured 0 saves out of 8 tasks. +Every hook is best-effort and bounded: Leteo gives up before Claude Code does, +and a hook that could not do its work says so rather than failing silently. +Memory is an assistant, not a gate. + ## Alternative to the plugin -`leteo setup zcode --hooks` writes the same three registrations into -`~/.zcode/cli/config.json` directly. **Pick one, not both** — registered twice, +`leteo setup claude-code --hooks` writes the same five registrations into +Claude Code's own settings directly. **Pick one, not both** — registered twice, every lifecycle event runs twice, which stored each prompt twice on the machine -where it was found, 23 identical pairs before anything looked wrong. `leteo -setup` looks for an installed bundle and refuses rather than adding a second -copy. +where it was found, 23 identical pairs before anything looked wrong from the +outside. `leteo setup` looks for an installed bundle and refuses rather than +adding a second copy. + +## Design -The plugin is the more robust of the two: configuration-file hooks run only -while `hooks.enabled` is true, and that switch starts off and belongs to the -person, while enabling a plugin is what enables the plugin's hooks. +The bundle is deliberately thin. Project detection, the stable manual session, +`` redaction, deduplication, the save reminder and its debounce, and +the `.leteo/` import all live in the Leteo binary, shared with the CLI, the MCP +server and every other agent's hooks. These files only say which event maps to +which command, so there is one implementation to keep correct instead of two. + +Which events belong here is not written out twice either: upstream holds one +list per agent and a guard reads each bundle against it, matchers and deadlines +included. Written out here and nowhere held, they drift. ## Upstream Canonical source, issues and the full documentation: . MIT licensed. Leteo is a reimplementation of Engram; that attribution is kept in the project's `NOTICE`. + +Leteo also ships a ZCode-targeted bundle upstream, which registers three events +rather than five because that client fires neither `SubagentStop` nor +`SessionEnd`. This entry is the Claude Code one, which is what this marketplace +is for. diff --git a/plugins/leteo/hooks/hooks.json b/plugins/leteo/hooks/hooks.json index 28eb9a7..98d8a2c 100644 --- a/plugins/leteo/hooks/hooks.json +++ b/plugins/leteo/hooks/hooks.json @@ -1,5 +1,5 @@ { - "description": "Leteo persistent memory hooks — session tracking, compaction recovery, and prompt capture", + "description": "Leteo persistent memory hooks — session tracking, compaction recovery, prompt capture, and passive capture", "hooks": { "SessionStart": [ { @@ -35,6 +35,30 @@ } ] } + ], + "SubagentStop": [ + { + "hooks": [ + { + "type": "command", + "command": "leteo hook subagent-stop", + "timeout": 10, + "async": true + } + ] + } + ], + "SessionEnd": [ + { + "hooks": [ + { + "type": "command", + "command": "leteo hook session-stop", + "timeout": 3, + "async": true + } + ] + } ] } } diff --git a/plugins/leteo/skills/memory/SKILL.md b/plugins/leteo/skills/memory/SKILL.md index 434f1fd..17fc485 100644 --- a/plugins/leteo/skills/memory/SKILL.md +++ b/plugins/leteo/skills/memory/SKILL.md @@ -23,7 +23,7 @@ are present from the first message — no `ToolSearch` needed. Only the three that change or count the whole store are deferred; reach for `ToolSearch` when you need one: `mem_stats`, `mem_delete`, `mem_merge_projects`. -**If the tools are missing**, run `leteo setup zcode` and restart the +**If the tools are missing**, run `leteo setup claude-code` and restart the client. Verify the store itself with `leteo doctor`. ## PROACTIVE SAVE TRIGGERS (mandatory — do NOT wait to be asked) From 220378a7c94ab54443da2c6f9951c50f050ac167 Mon Sep 17 00:00:00 2001 From: Alexis Date: Sat, 17 Oct 2026 00:20:07 +0200 Subject: [PATCH 4/5] chore: sync the leteo plugin to the 0.3.0 release The vendored bundle was the 0.1.2 one. It lacked mem_consolidate, which 0.3.0 serves to agents, and its manifest named a version two releases old. The four bundle files are now byte-identical to plugin/claude-code at the v0.3.0 tag, which is what the released binary is written against; the README's language count follows the release from twelve to thirteen. --- plugins/leteo/.claude-plugin/plugin.json | 10 ++-------- plugins/leteo/README.md | 2 +- plugins/leteo/skills/memory/SKILL.md | 11 ++++++++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/leteo/.claude-plugin/plugin.json b/plugins/leteo/.claude-plugin/plugin.json index f25a359..e0d53bc 100644 --- a/plugins/leteo/.claude-plugin/plugin.json +++ b/plugins/leteo/.claude-plugin/plugin.json @@ -1,17 +1,11 @@ { "name": "leteo", "description": "Persistent memory for AI coding agents. Survives across sessions and compactions.", - "version": "0.1.2", + "version": "0.3.0", "author": { "name": "Alexis Sanabria Lopez" }, "homepage": "https://github.com/asanabrial/leteo", "repository": "https://github.com/asanabrial/leteo", - "license": "MIT", - "keywords": [ - "memory", - "persistence", - "sessions", - "context" - ] + "license": "MIT" } diff --git a/plugins/leteo/README.md b/plugins/leteo/README.md index cd116b8..14691e2 100644 --- a/plugins/leteo/README.md +++ b/plugins/leteo/README.md @@ -31,7 +31,7 @@ curl -fsSL https://raw.githubusercontent.com/asanabrial/leteo/main/scripts/insta | `SessionStart` (`startup`, `clear`) | `leteo hook session-start` | Opens the session and hands back the project's recent work, prompts and most relevant memories | | `SessionStart` (`compact`) | `leteo hook post-compaction` | Puts back what the compaction took, and clears what had been marked as already shown | | `UserPromptSubmit` | `leteo hook user-prompt-submit` | Keeps the prompt, and names a memory worth having in front of you — never the same one twice in a session | -| `SubagentStop` | `leteo hook subagent-stop` | Captures a sub-agent's Key Learnings, in any of the twelve languages Leteo speaks | +| `SubagentStop` | `leteo hook subagent-stop` | Captures a sub-agent's Key Learnings, in any of the thirteen languages Leteo speaks | | `SessionEnd` | `leteo hook session-stop` | Closes the session | Alongside them, [`.mcp.json`](.mcp.json) registers the MCP server with the diff --git a/plugins/leteo/skills/memory/SKILL.md b/plugins/leteo/skills/memory/SKILL.md index 17fc485..80a8fa4 100644 --- a/plugins/leteo/skills/memory/SKILL.md +++ b/plugins/leteo/skills/memory/SKILL.md @@ -18,7 +18,7 @@ are present from the first message — no `ToolSearch` needed. - `mem_get_observation`, `mem_timeline`, `mem_suggest_topic_key`, `mem_update` - `mem_session_start`, `mem_session_end`, `mem_save_prompt` - `mem_current_project`, `mem_pin`, `mem_unpin`, `mem_review` -- `mem_judge`, `mem_compare`, `mem_capture_passive`, `mem_doctor` +- `mem_judge`, `mem_consolidate`, `mem_compare`, `mem_capture_passive`, `mem_doctor` Only the three that change or count the whole store are deferred; reach for `ToolSearch` when you need one: `mem_stats`, `mem_delete`, `mem_merge_projects`. @@ -150,6 +150,15 @@ memory removed since — nothing is left to contradict, so `not_conflict` closes that pair in one call. A trailing line counting pairs `mem_judge` cannot settle is not work and not yours: leave it and say nothing. +## WHEN SEVERAL MEMORIES ARE ONE + +When the same fact was saved more than once and no single memory is the one to +keep, `mem_consolidate` writes one replacement and records a judged `supersedes` +relation from it to every source in a single transaction. The sources stop +appearing in search and context but stay readable by id with +`mem_get_observation`. For a single better revision, `mem_update` or a new save +under the same `topic_key` is the smaller move. + ## WHEN A MEMORY COMES ROUND FOR A REREAD A decision, a policy or a preference is saved with a date to look at it again. From 6ed50d67197ff09f80c504527f631d2014870270 Mon Sep 17 00:00:00 2001 From: Alexis Date: Sat, 24 Oct 2026 23:50:40 +0200 Subject: [PATCH 5/5] chore: sync the leteo plugin to the 0.4.2 release plugin.json named 0.3.0, and the skill still described the 0.3.0 opening block (pinned memories with a preview) and lacked mem_update's find/replace edit, which the 0.4.x server offers. Both files are now byte-identical to plugin/claude-code at the v0.4.2 tag; .mcp.json and hooks/hooks.json were already identical. --- plugins/leteo/.claude-plugin/plugin.json | 2 +- plugins/leteo/skills/memory/SKILL.md | 27 +++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/plugins/leteo/.claude-plugin/plugin.json b/plugins/leteo/.claude-plugin/plugin.json index e0d53bc..41251ca 100644 --- a/plugins/leteo/.claude-plugin/plugin.json +++ b/plugins/leteo/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "leteo", "description": "Persistent memory for AI coding agents. Survives across sessions and compactions.", - "version": "0.3.0", + "version": "0.4.2", "author": { "name": "Alexis Sanabria Lopez" }, diff --git a/plugins/leteo/skills/memory/SKILL.md b/plugins/leteo/skills/memory/SKILL.md index 80a8fa4..9baa81d 100644 --- a/plugins/leteo/skills/memory/SKILL.md +++ b/plugins/leteo/skills/memory/SKILL.md @@ -157,7 +157,10 @@ keep, `mem_consolidate` writes one replacement and records a judged `supersedes` relation from it to every source in a single transaction. The sources stop appearing in search and context but stay readable by id with `mem_get_observation`. For a single better revision, `mem_update` or a new save -under the same `topic_key` is the smaller move. +under the same `topic_key` is the smaller move; to correct one span of a body +without rewriting the rest, `mem_update` takes `find` and `replace`, and a +`find` that is absent or matches more than once is refused with +`edit_not_found` or `edit_ambiguous`. ## WHEN A MEMORY COMES ROUND FOR A REREAD @@ -229,17 +232,17 @@ it", or the same in whatever language the user writes in: and try the same idea in the language the store holds before concluding a thing was never saved. 3. `mem_get_observation` for the whole text once something looks right. The - context is an index, not the memories themselves: the pinned ones and the - newest few open with the first three hundred characters and everything - under `Also remembered` is a title alone. Every entry carries its `#id`, and - that is what to fetch by. - - Assume the answer is past the cut. A memory here runs to about two thousand - characters, and the names, paths, numbers and error strings — the part - written to be worth keeping — are almost all beyond the first three hundred. - A trailing `...` marks a body that was cut. Fetch before answering from a - preview; guessing from one is how a version number turns into the wrong - version number. + block a session opens with is an index, not the memories themselves: every + entry in it, the pinned ones included, is a title alone. `mem_context` gives + its newest few a short preview, marked `content_truncated` when it was cut. + Every entry carries its `#id`, and that is what to fetch by. + + Assume the answer is not in what you were handed. A memory here runs to + about two thousand characters, and the names, paths, numbers and error + strings — the part written to be worth keeping — are in the body, never in + a title and rarely in a preview. Fetch before answering from either; + guessing from one is how a version number turns into the wrong version + number. Search proactively too: before starting something that may have been done before, when the user names a topic you have no context on, and on their first