feat(claude-code): EverOS memory plugin for Claude Code - #21
Merged
Merged
Conversation
Sibling of the OpenClaw / Hermes / DSH plugins: hooks-driven recall, full-trajectory capture and session seal against a local EverOS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
13 TDD tasks from scaffold to end-to-end acceptance, grounded in the verified Claude Code transcript format and the EverOS v2 memory API. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The turn slice is bounded by the next differing promptId rather than end of file: a prompt queued mid-turn is already on disk when Stop fires, and slicing to EOF captured it under the wrong turn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A spawned server that dies immediately is reported as a failure rather than as still starting; health is checked first so a foreign instance winning the OME lock still counts as success. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running it surfaced two defects in the recall block, both fixed here: an agent case injected its whole numbered approach (over 1500 chars per prompt), and joinDash passed Array.map's index into oneLine as the character cap, blanking the first part of every joined line. The acceptance transcript now carries two turns in one session, the second with a failed tool call and a course correction: everalgo rejects linear single-user-message trajectories, so a one-turn fixture could never prove the agent track works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sions Four defects that only real Claude Code sessions could surface: - Stop read the transcript as soon as the prompt id appeared, but the closing assistant entry lands a fraction of a second later, so every reply was silently lost. readTurn now waits for the turn to read as finished, with a 2s budget and a partial-turn fallback. - The 3s recall budget lost the opening recall in two of the first three live sessions, which is the prompt where memory matters most. Raised to 5s and exposed as EVEROS_CC_RECALL_TIMEOUT_MS; a warm search is 0.3-0.8s so the budget is almost never spent. - SessionStart now warms the search path, moving the cold cost off the user's first prompt. - Claude Code cancels SessionEnd when the host exits in a hurry, routine under 'claude -p', stranding the turns after the last topic boundary. A later session seals any session left untouched for ten minutes, under the project id the session actually ran in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Also drops what the over-engineering pass found: two exports nothing imports, four injection seams no test injects, and four unused setters on the test double. The design doc is reconciled with what the implementation turned out to need - the recall budget, the SessionStart warm-up, the abandoned-session sweep and the case rendering are recorded as decisions with the evidence that overturned the planned ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ndings Three fresh subagent reviews (correctness, security, mutation testing) plus live verification of each fix. Critical: the host renders our block inside its own system-reminder tag, so a recalled memory carrying a closing system-reminder tag closed that wrapper, and everything after it read to the model as host-authored instruction. Verified against a real transcript. Every tag in recalled content is now inert, not just our own fence. High: - The abandoned-session sweep could seal a LIVE session. The state file is written only when a turn is captured, so a long agentic turn looked idle. Recall now touches the session on every prompt and the threshold is 30 minutes. - Five sequential flushes at a 10s deadline each could run 50s against a 15s hook timeout. The sweep now shares one 6s budget. - process.exit does not drain a pipe, and pipes are async on macOS, so a large recall block could be cut in half, putting invalid JSON on the ABI. - project_id was the bare repository name, so two repositories with the same name shared one memory partition. It now carries host and owner. Also: state writes go through tmp+rename; a partial batch is not re-sent whole; tool results with no text block carry a typed placeholder instead of an empty string; a list-shaped explicit_info no longer renders as an object stringification; the block has a total size cap; log files are 0600; session_id is sanitised; the recall clamp accounts for the git calls that precede it; a Stop without prompt_id falls back to the last turn on disk. Mutation testing found two untested invariants, both now pinned: that the origin remote beats the git toplevel, which is the property worktree sharing rests on, and that only one profile is injected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
node --test only learned glob arguments in 22 and does not accept a bare directory at all, so CI's Node 20 leg failed with "Could not find" while the local Node 23 was happy. Letting the shell expand means node only ever receives explicit file paths, which every version handles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The whole transcript goes to base_url and EverOS has no authentication of its own, but isLoopback only gated whether to spawn a server, never whether to send. A remote address now announces itself once per session, naming the host. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The plan was scaffolding and the implementation overturned a good deal of it - the recall budget, the project id shape, the skill paths. A 4000-line document that contradicts the code in a dozen places is a trap for the next reader, and docs/DESIGN_DOC.md has been reconciled with what was actually built. Still in history: git show e59d108:docs/superpowers/plans/2026-09-10-everos-claude-code-plugin.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A real session with EverOS down announced it twice in the first two seconds: SessionStart said 'could not be started' and the first recall said 'unreachable'. Both draw on the same one-per-session budget, but only recall was claiming it. The README promises exactly one line. Each hook is tested alone, so nothing in the suite could see this; the new test drives SessionStart and then recall against the same state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A sibling-plugin comparison found three things the earlier passes missed: - DESIGN_DOC promised 'claude plugin validate ... in CI' in two places and CI never ran it. It runs now, on both manifests, with --strict. - D5 still described project_id as OpenClaw's workspaceDir basename, which stopped being true when it gained host and owner. - The mermaid diagram and two prose spots still said a 3s recall deadline after D8 was raised to 5s. The same comparison flagged the id charset as too permissive against openclaw and hermes, which both use ^[a-zA-Z0-9_.-]+$. Checked against EverOS itself instead: memorize.py:41 is ^[a-zA-Z0-9_.@+-]+$ and a live server accepts a.b@c+d-e_f, so ours is right and the siblings are the stale ones. Left alone here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fied Three of the author's answers turned out to rest on unverified premises. Checked each against a live EverOS 1.3.1 instead: - The SessionStart warm-up is removed. It was added alongside the recall budget rise, two changes for one outcome, and nothing attributed the original timeouts to a cold path. Measured on a server that had never served a search: first 2.2s, steady state 0.4-0.9s. A 1.5s saving the 5s budget already absorbs does not pay for a per-session embedding call and up to 5s of SessionStart. - The orphan tool-row filter stays, but its stated reason was wrong. It came from OpenClaw's handoff note and was never checked. EverOS ACCEPTS an orphan with a non-null tool_call_id and extracts it fine; what it rejects is role=tool with NO tool_call_id (_boundary.py:354 raises ValueError). The mapper already cannot emit that, and there is now a test pinning it. Across 3407 real turns the filter drops 26 of 26081 tool rows. - The profile is not partitioned. EverOS keys it by user_id alone, so one profile came back under three unrelated app_id/project_id scopes and reported the scope it was written under. README, README_zh and the design doc now say which kinds are per-project and which are not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tracing "why is SessionEnd cancelled" with tmux and a stalling server overturned the explanation this plugin shipped with. It is not a `claude -p` behaviour. An interactive terminal kills the session-end hook just as fast: measured 4.1s from /exit to process exit, and the hook is gone within a few hundred milliseconds of that, long before any deadline of ours fires. Nothing was being lost, though. The POST leaves first - a stalling server recorded it ~120ms after /exit - and EverOS completes the ~5s extraction with no client attached, verified twice: once by disconnecting a client 0.3s into a flush and finding the episode on disk, once by exiting a real interactive session and finding its episode searchable. What was lost was only the bookkeeping, because the mark was written after an answer that never arrived, so the sweep re-flushed every single session half an hour later for nothing. The mark now goes down before the request and is taken back only on a connection error, which is the one case where the seal truly did not happen. Also splits TIMEOUT from NETWORK_ERROR in the client: a timeout means the socket was open and the server has the body, and only the caller can know whether that distinction matters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ation found A second mutation round, on everything added since the first one: 23 deliberate defects, 19 killed, 4 survivors. Each survivor resolved on its own merits rather than by writing a test around it. - state.js tmp+rename and the flushed reset now have tests; both mutations are killed by a test that names the symptom. - The sweep's `signal.aborted` check was unreachable - the catch already returns on any error, and every later flush shares the aborted signal - so it is deleted rather than tested. - The trailing-heading cleanup in trimToBudget stays untested, and the test says so. 960 generated fixtures never reached it: an episode is one multi-line element of ~1200 chars, so the size cut removes far more than a heading's worth at a time. One line against a cosmetic dangling label is not worth a contorted fixture. The bigger change is the test double. It accepted any body, so contract drift was invisible until the e2e run. It now validates what EverOS validates - path-safe ids, the role literal, millisecond timestamps, the 1..500 message bound, tool rows needing a tool_call_id, search's extra="forbid" and its user/agent XOR - each rule carrying the source location it was copied from. That turns the thirty-odd tests that already go through the double into contract tests. It found a real one immediately: a client test was posting an empty messages list, which the real EverOS rejects with a 422. Reverse-verified by making recall send an unknown search field, which now reddens four tests instead of passing silently. 153 passing, 0 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
scripts/e2e.sh feeds the hooks synthetic stdin. That proves the wire contract and it is what has been called "end to end" here for days, but it never starts Claude Code, so it cannot say the host still calls the hooks. The verification that did use the real host was a string of one-off shell commands that nobody could repeat, including me after a change. This turns that into a suite. Eight cases, each crossing a process boundary because a session that is still open can always answer from its own context: cross-session recall, that another repository cannot see it, that a worktree can, the trajectory a tool-using session sends, fail-open, the abandoned-session sweep, that host noise never becomes memory, and an interactive terminal under tmux. Writing it found four defects in itself, each fixed and reverse-verified: - The watchdog inherited stdout, so a pipeline stayed open for the whole 30-minute cap after the script had exited, which looks exactly like a hung run. - The transcript was located by transforming the repo path into a project slug. The real slug differs in three ways at once, so the lookup found nothing, and "zero warnings, zero errors" passed as a green light for checks that never ran. - Case 8 scraped the pane for readiness, which also matches the trust dialog; answering that blind picks its default, "No, exit". - Recall-dependent cases queried an eventually-consistent index without waiting for it, so which cases failed varied between runs. A killed run cannot reach its trap, and what it leaves in TMPDIR is a copy of real api keys, so preflight now sweeps leftovers and says so. Preflight also proves the LLM works with one real extraction: an exhausted key used to surface as three unrelated case failures. Case 4 asserts what the plugin controls - the trajectory it captured, 18 tool calls in the last run - and reports everalgo's own reason when it declines to make a case of it. Case generation is asserted deterministically in e2e.sh, which was re-run to confirm it still is. Two consecutive full runs: 24 passed, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Asking one question in three sessions gives three episodes that differ only in wording - EverOS makes one per session - and the recall block rendered all three. Measured on that real data: three of five episode slots and 900 of 1587 characters spent restating that line-length is 88, with the atomic facts under them repeating too. Which of those two layers is at fault is arguable, but putting three copies in front of the model is this plugin's choice, so it is fixed here. Items are dropped when their meaningful vocabulary is already covered 80% by something the block has said, counting CJK characters and latin words alike, with one running record shared across all four sections so a fact attached to two episodes is still one fact. A line with fewer than three meaningful tokens is never judged, and containment rather than similarity keeps a longer memory that adds something of its own. Same real data, after: 929 characters, the three restatements collapsed to one, the unrelated memory untouched. Found by actually using it rather than by a test: the e2e suite proved memory works but never asked whether what it injects is worth the context it costs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three clean subagent contexts audited the suite - untested branches, tests that pass for the wrong reason, and the contract double against the real DTOs - and a full e2e run against real Claude Code found the fourth, which none of them could see. Every finding below was re-verified here before being acted on; two of the subagents' claims did not survive that and were dropped. - An unwritable data directory silently killed recall. `touchSession` runs before either search and `writeState` had no guard, so the hook exited 0 with nothing injected and no error anywhere. State is a cache for dedupe and liveness, never the memory itself: it degrades now, and `/everos:status` probes the directory and says so. - "ok", "continue" and slash commands never refreshed the liveness mtime, because `touchSession` sat after the recall test. A live session could be swept and sealed mid-conversation by the next one. - Every repository name outside the id whitelist sanitised to the same run of underscores, so unrelated CJK-named repositories shared one partition - the cross-repository bleed host and owner were added to prevent. A digest is appended whenever sanitising or clipping actually loses a character. The host is also folded to lowercase; one remote typed GitHub.com used to split a repository in two. - The seal was recorded before the flush was sent. The e2e server log showed the `/exit` flush never reached EverOS at all, while the mark made `pendingFlushes` skip that session forever - the sweep exists for exactly that case. The cost it was avoiding is not real: a repeat flush answers "no_extraction" in 3ms against a live 1.3.1. The seal now follows the request. Tests: two false nails rewritten to fail when their guard is deleted (both proven by mutation), the e2e's seal check moved off the plugin's own bookkeeping onto the server log, its on-disk assertions given the same polling the search assertions already had, and the contract double taught the value rules it was blind to - with the three places it is deliberately stricter than EverOS now marked as such. scripts/e2e.sh is renamed hooks-contract.sh: it feeds the hooks synthetic stdin and never starts Claude Code, so it had no business holding the name. The e2e harness had one of its own: teardown killed the sleep its watchdog was blocked in, and the watchdog subshell then fell through to its next statement - the `kill -9` of this very script. Six runs out of six died mid-teardown and exited 137 on a fully green run, leaving the isolated root, which holds copied api keys, on disk. A flag the watchdog checks before firing makes the order it is killed in stop mattering. 165 unit tests, 0 skipped. 24 of 24 real-host e2e checks pass, including the new seal assertion: on `/exit` the host kills SessionEnd before the flush leaves, the server sees no flush, and the session is correctly left for the sweep. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI went green five days ago and no reviewer ever came, so the review loop was closed the other way: three clean-context agents, one angle each (docs vs implementation, credential and injection surface, silent-failure modes). Every finding below was reproduced here before being acted on — that is not a formality, two of the previous round's subagent conclusions did not survive it. - A flush that never left was still recorded as sealed. TIMEOUT was read as "the socket was open, EverOS has it", but a dropped SYN (VPN down, firewall DROP, host asleep) aborts with the same code having sent nothing — and the mark then hides the session from the sweep forever, which is the exact failure the seal ordering was introduced to fix, returning through the error classifier. The inference only holds on loopback, where connect is instantaneous, so that is where it now applies. Reproduced against TEST-NET-1 with a control: refused → left unsealed, hung → was sealed, slow loopback → sealed. - A closing tag carrying attributes or a self-closing slash walked straight through the fence neutraliser. The earlier fix here caught the bare form only, so `</system-reminder/>` and `</system-reminder x>` still closed the host's own wrapper, after which recalled memory reads as a host instruction. The broad rule is scoped to closing tags on purpose: applied to opening tags it would eat `a < b and c > d`. - A quarter of what was posted as "the user said this" was the host talking. `promptSource` is not "the user typed it" — the host sets it on task notifications and IDE file events too. Measured across twelve real transcripts: 412 of 1636 such entries were pure wrapper, the largest 40 KB, every one of them POSTed as a user message, while the README promises they are not captured. Host wrappers are now stripped before capture; entries left empty are dropped, and a wrapper sitting beside real typing keeps the typing. - A partial capture told the user it had saved the whole turn. It now reports what actually landed. Docs: D14's 1.5 s dispatch deadline had not reached the constant table or the failure-strategy section, which still said 10 s; `prompt_id` is recorded once one batch succeeds, not after all of them; the sweep also requires a session to have captured at least one turn; and the orphan-tool rule cited "EverOS rejects orphans" as its reason when a live 1.3.1 accepts them — the real reason is everalgo receiving a result whose request it never saw. The harness had two of its own. The watchdog flag added this morning is named `everos-cc-e2e.$$.running`, which the preflight sweep for leftovers matched and deleted — disarming the hard lifetime cap that same sweep exists to make unnecessary. And the LLM key reached python through argv, where `ps -axww` shows it to every user on the machine; it goes through the environment now. 168 unit tests, 0 skipped; each new nail mutation-verified. 24 of 24 real-host e2e checks pass, exit 0, nothing left behind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects the self-review found and this round verified, plus the wording the docs had drifted from. - Half a search failing read as a clean success. Only both tracks failing counted as a failure, so a dead user track printed `🧠 EverOS: 1 case` while the episodes and the profile had silently gone — the one line the user reads, saying everything is fine. It now names which half is missing, every turn it happens rather than once per session: the warning budget is for "EverOS is down", and this is a different, recurring condition. - The acceptance script reported a check it had never run. `search_hits` collapsed every transport failure into an empty answer, and the one assertion that reads absence as success — case 2, "nothing leaked into the other repository" — passed without asking. A dead port, a timeout, or a quote in the query all produced the same silence. Failures are now a sentinel the caller must handle, and the request body is built by python, so a quote can no longer break the JSON. The harness needed two more things to stop failing for reasons that were not the plugin's. Its readiness probe omitted `include_profile`, so "indexed" could go true while the path recall actually uses was still cold. And the default 5 s recall budget is tuned for a person typing against a warm local EverOS, while this script fires the next session the instant extraction finishes and every hybrid search embeds its query through a remote provider — two tracks, two round trips. Two runs lost case 1 and case 3 to `deadline exceeded` with the fact demonstrably stored and searchable, which reads as "memory broke" when it was the clock; the script now asks for 7000 ms, the ceiling the plugin clamps to. The cost is real and worth naming: the e2e no longer exercises the default budget. The failure log it copies out is chmod 600 now — the script greps its own copy for `api_key`, so it expects one to be there. `/everos:status` prints the recall budget. The README tells people to raise it when recall times out, and until now there was nowhere to confirm the change took. Docs caught up with the code in five places, in both languages: the status command prints a static checklist rather than probing for the first missing step; the debug tail is the last five lines, not the last five errors; the source layer is shown for the four values that resolve through layers, not for every value; VERBOSE has a third message (the SessionStart version line); and the server this plugin starts forces `EVEROS_MEMORIZE__MODE=agent` and the port from `base_url` over whatever `everos.toml` says — which matters, because that server then serves every host on the machine. 169 unit tests, 0 skipped. 25 of 25 real-host e2e checks pass, exit 0, nothing left behind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sweep walked the abandoned sessions serially, each with its own dispatch deadline, under a whole-sweep budget it checked between sessions. With five sessions to seal that took 7.5 s and the budget cut it off after one or two, so the rest stayed unsealed until some later session happened to have fewer neighbours. Dispatching all of them at once costs one deadline for the whole sweep: five sessions now seal in 1.58 s. The budget constant goes with it - there is nothing left to spend it on, and scripts.test.js now bounds SessionStart by a single dispatch instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… every turn EverOS fetches the profile by owner id alone - manager.py:_fetch_profile never sees req.query - so it comes back identical whatever the user asked. Sending include_profile on every recall meant the same paragraph was injected into every turn of a session, crowding out the episodes that actually answered the question, and a hand acceptance found all three answers dominated by one irrelevant profile line. It still has to reappear periodically: a compaction takes it out of the window along with everything else. Every tenth turn keeps it available without making it the loudest thing in the context. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ectory run-hook.js passed HOME through to the hook process, so any test that forgot EVEROS_CC_DATA_DIR got the default - ~/.everos/.claude-code, the developer's own. One did, and wrote a session state file into a real installation. The helper now seeds a fresh temp directory that an explicit env still overrides, which makes the leak impossible rather than merely unlikely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on with memory down Three things a person does every day had never been driven against a real host, here or by hand: clearing a session, having one compacted under them, and working through a memory outage rather than hitting one on a single turn. Case 9 clears an interactive session and asks again. /clear is the sharpest assertion available: it removes the history entirely, so a correct answer afterwards cannot have come from the window - only from a fresh recall. It then compacts, which runs PreCompact -> flush and SEALS the session, and checks that the following turn reopens it. A session left sealed has flushed=true, which is exactly what makes the sweep skip it, so everything said after a compaction would be dropped silently. Case 10 replaces a vacuous check. Case 5 asserts "exactly one warning" inside a single-turn `claude -p` session, where one is the only number it could have been - the promise is that the notice appears once and the session then stays quiet while the user keeps working, and one turn cannot tell those apart. Three turns can. Both bite. Dropping the dedupe in claimWarning takes case 10 to 4 warnings; keeping flushed set across markStored takes case 9's last check to "sealed" while its other four stay green. The shared tmux driving is now three helpers instead of a copy per case, and `grep -c ... || echo 0` - which prints TWO zeros when the count is zero, and would break the arithmetic that consumes it - is gone with them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Adds
claude-code/, a sibling of the OpenClaw / Hermes / DSH plugins: samelocal-EverOS contract (
/api/v2/memory/*), same recall → capture → seallifecycle, same fail-open promise. Nothing existed for Claude Code against a
self-hosted EverOS —
evermem-claude-codetargets the cloud gateway andEverMe/plugins/claude-codetargets the EverMe gateway.What it does
Four hooks, no tools for the model to call:
SessionStartUserPromptSubmit<everos_memory>blockStopSessionEnd,PreCompactTwo skills:
/everos:statusand/everos:search.What this change is, and is not
It is an integration: that recall leaves, that what came back reaches the
prompt, that a finished turn is stored with its trajectory, that the session is
sealed, and that none of it can break the editor. Every case below asserts on
backend receipt — markdown on disk, a real search, the context read back out of
the transcript — never on whether a reply sounded like it remembered.
It is not an evaluation of EverOS's retrieval or extraction quality. Whether
the memories that come back are the right ones is EverOS's question, not this
plugin's, and nothing here measures it.
Verification
node:test, zero dependencies, no skips.scripts/hooks-contract.shfeeds the four hooks synthetic stdin against areal EverOS 1.3.1: the wire contract and what an algorithm decides
deterministically, including that a trajectory with a detour produces an agent
case.
scripts/e2e-claude-code.shstarts real Claude Code sessions — headlessand in a real terminal under tmux — and judges by backend receipt.
All ten cases pass against a live EverOS 1.3.1: 32 checks, 0 failures,
at
89cd6f2. It starts its own EverOS on its own port under its own root andnever touches a server you are already running.
check: the defect is reintroduced and the test has to go red, or the test is
not holding anything.
The ten cases: cross-session recall · another repository cannot see it · a
worktree can · the trajectory a tool-using session sends · fail-open · the sweep
· host noise never becomes memory · an interactive terminal · a session that is
cleared and compacted under itself · a whole conversation with memory down.
Running it for real is what found the defects that mattered. These could not
have been caught by unit tests:
Stopread the transcript as soon as the prompt id appeared, but the closingassistant entry lands a fraction of a second later — every assistant reply
was silently dropped.
sessions.
under the wrong turn.
SessionEndwhen the host exits in a hurry, stranding thesession tail.
every prompt.
text, not the user's.
whatever the question was — and it was being asked for on every single turn.
Security
A dedicated review found one critical issue, since fixed. The host renders hook
additionalContextinside its ownsystem-remindertag; a recalled memorycarrying a closing one escaped that wrapper, and everything after it read to the
model as host-authored instruction. Recalled memory is untrusted by construction
— an earlier session's LLM wrote it from whatever that session contained — so
every tag in it is now inert, not just our own fence.
project_idalso carries host and owner now. The bare repository name is not anamespace: two
apirepositories from different owners would otherwise shareone memory partition.
Known limits
Version
0.1.0in both manifests. Stability is not claimed, and this is whatthat means concretely.
Six known silent degradations, none of them data loss or disclosure, all with
narrow triggers: one boolean covers three different warning kinds, so a user who
saw "could not start" is not told about a later half-failure; corrupt stdin is
logged only to the debug log; a
200carryingdata: nullis reported asunreachable; the sweep sends a raw session id where
flush.jssanitises;stdinis not paused on the early-return paths; the hook timeout window is notenforced end to end.
The verification surface is narrow: one macOS machine, one EverOS (1.3.1), and
Haiku driving the end-to-end sessions. Node 20 and 22 are covered by unit tests
only. The end-to-end suite runs at a 7 s recall budget, so it no longer
exercises the 5 s default.
Notes for review
so there is no resident parent to own the server; EverOS's single-instance
lock makes concurrent spawns from several windows harmless.
trajectories without enough tool rounds, so sending less would mean no agent
memory at all.
docs/DESIGN_DOC.mdrecords the decisions, including the ones the live runsoverturned and the evidence that overturned them.
🤖 Generated with Claude Code