Repository navigation
feat(setup): add Alma target - #175
Merged
Merged
Conversation
Alma is an Electron desktop AI client whose hook system differs from every other target here: hook stdout is buffered and only the last parseable JSON line is read, and only `decision`, `reason` and `updatedInput` from it have any effect. A reminder-style hook would be a silent no-op, so this integration is shaped around what actually survives. The host injects `~/.config/alma/MEMORY.md` into the system prompt on every request, and Alma's own prompt tells the model to write to that same file. The integration therefore owns exactly one region of it, delimited by `<!-- mnemon:begin -->` / `<!-- mnemon:end -->`, and never touches a byte outside those markers. - `mem` shim: single entry point for recall and writes. Every read passes `--readonly` (default recall appends to the oplog; 10 concurrent recalls produced 4 SQLITE_BUSY failures in testing). Direct sqlite access uses `file:...?immutable=1`, because `-readonly` and `?mode=ro` both fail with "unable to open database file (14)" on a WAL database. Id prefixes are resolved here, since `mnemon show` requires a full UUID. `--cat` is required and `general` is rejected, so unclassified writes cannot turn into noise. - `prime.sh`: one `app.ready` hook, with no SQL of its own. It calls `mem sync`, so the hook and a manual sync cannot drift. The sync is idempotent and leaves the file's mtime alone when the block is unchanged, which keeps Alma's prompt prefix cache intact across restarts. - `SKILL.md`: recall triggers, the 0-3 writes per conversation rule, and the marker ownership contract. - `--eject` removes the block, the skill, the shim and the hook, and keeps any backups left in the bridge directory. Alma has no project-local hook configuration, so `--target alma` requires `--global`.
The skill alone did not trigger reliably: Alma runs an LLM-based skill selector, and mnemon lost that vote on most ordinary turns. It also has a deterministic injection path that this target was not using. - mark the skill `always-inject: true`, so its description is listed on every turn instead of depending on the selector - move the recall policy into the generated MEMORY.md block: a short statement of when to recall, when not to, that a recalled memory is evidence rather than an order, and how to write. MEMORY.md is injected in full on every request, so this survives the selector entirely. - cap the block at 8 entries x 100 chars and keep it under 3000 bytes; it is charged against every request, so the previous 4.3 KB was too expensive - generate the static prose with a heredoc: the previous echo chain leaked escaped quotes into the output Adds a test that asserts the block carries the policy, contains no escaped quotes, and stays within the byte budget.
mtroym
force-pushed
the
feat/alma-hooks
branch
from
October 31, 2026 11:57
f6fb855 to
861e31a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an
almatarget tomnemon setup, on par with the existing 16 targets: installable, ejectable, detectable, and test-covered.Alma is an Electron desktop AI client on macOS.
Why this target is shaped differently
Alma's hook system does not behave like the lifecycle-hook targets. Hook stdout is buffered and parsed backwards line by line; only the last parseable JSON line is used, and only
decision,reasonandupdatedInputfrom it have any effect. Everything else printed is discarded.So porting
user_prompt.sh/stop.sh/nudge.shwould produce three silent no-ops. This integration is built around what actually survives.Design
Alma injects
~/.config/alma/MEMORY.mdinto the system prompt on every request, and Alma's own prompt tells the model to write to that same file. Two writers on one file is the bug, so mnemon owns exactly one region:Every byte outside those markers is preserved.
memshim — the single entry point, installed by setup:--readonly: default recall appends to the oplog, and 10 concurrent recalls produced 4SQLITE_BUSYfailures in testing; with--readonly, 10/10 succeededfile:...?immutable=1, because-readonlyand?mode=roboth fail withunable to open database file (14)on this WAL databasemnemon showrequires a full UUID (Error: sql: no rows in result setotherwise)--catis required andgeneralis rejected — it is the unclassified default that turns into noisemnemon recall --catis accepted but does not filter in 0.2.13 (asking forpreferencereturneddecisionrows in the same score band), so the shim filters client-siderelatedhandles the bare-array response shape, not{"related": [...]}prime.sh— oneapp.readyhook, containing no SQL of its own. It callsmem sync, so the hook and a manual sync cannot drift apart. The sync is idempotent: when the block would be unchanged it does not write, leaving the mtime alone so Alma's prompt-prefix cache survives a restart.SKILL.md— recall triggers (not every turn), the 0–3 writes per conversation rule, marker ownership, and the category/importance guidance.Eject — removes the block, the skill, the shim and the hook, and keeps backups in the bridge directory.
--target almarequires--global: Alma has no project-local hook configuration.Verification
make testpasses.alma_test.go, including: fresh install produces a working skill+shim+hook; re-install is idempotent; unrelated hooks inhooks.jsonsurvive; eject removes only mnemon artifacts and preserves user data; the legacy whole-fileMEMORY.mdformat is migrated rather than duplicated; a half-written marker block is refused and logged instead of mangled; the hook contains no SQL and delegates to the shim.HOME: install →mem save→mem find→mem sync→ eject leaves no residue.GET /api/hookspicks up the config without a restart, and invoking the hook the way Alma does (sh -c+ stdin JSON +ALMA_HOOK=true) returns exit 0 with zero bytes on stdout.Notes
Two behaviours of the shipped app are worth recording for future work on this target:
chat.system.transformis listed in Alma's hook UI and handled inapplyResult, but nothing ever triggers it, so there is no system-prompt injection channel; andapp.willQuitis wrapped in a hard 3000 ms outer race, so no slow work can live there. Neither is used here.