Skip to content

feat(claude-code): EverOS memory plugin for Claude Code - #21

Merged
Yangtze-Seventh merged 35 commits into
mainfrom
feat/claude-code-plugin
Sep 16, 2026
Merged

Yangtze-Seventh merged 35 commits into
mainfrom
feat/claude-code-plugin

Conversation

@gloryfromca

@gloryfromca gloryfromca commented Sep 10, 2026

Copy link
Copy Markdown
Member

Adds claude-code/, a sibling of the OpenClaw / Hermes / DSH plugins: same
local-EverOS contract (/api/v2/memory/*), same recall → capture → seal
lifecycle, same fail-open promise. Nothing existed for Claude Code against a
self-hosted EverOS — evermem-claude-code targets the cloud gateway and
EverMe/plugins/claude-code targets the EverMe gateway.

What it does

Four hooks, no tools for the model to call:

Event Behaviour
SessionStart Detect EverOS, start one if it is down and loopback, seal any session a previous run left unsealed
UserPromptSubmit Search the user and agent tracks in parallel, inject a fenced <everos_memory> block
Stop Map the finished turn — text plus the full tool-call trajectory — and POST it
SessionEnd, PreCompact Seal the session buffer

Two skills: /everos:status and /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

  • 169 unit tests across 15 files, node:test, zero dependencies, no skips.
  • scripts/hooks-contract.sh feeds the four hooks synthetic stdin against a
    real 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.sh starts real Claude Code sessions — headless
    and 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 and
    never touches a server you are already running.
  • Every fix in this PR carries a symptom-level reproduction and a mutation
    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:

  • Stop read the transcript as soon as the prompt id appeared, but the closing
    assistant entry lands a fraction of a second later — every assistant reply
    was silently dropped
    .
  • The 3s recall budget lost the opening recall in two of the first three live
    sessions.
  • The turn slice ran to end of file, so a prompt queued mid-turn was captured
    under the wrong turn.
  • Claude Code cancels SessionEnd when the host exits in a hurry, stranding the
    session tail.
  • An agent case injected its entire numbered approach, over 1500 characters on
    every prompt.
  • A quarter of what was stored as "the user's words" was the host's own wrapper
    text, not the user's.
  • The developer profile is fetched by owner id alone, so it came back identical
    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
additionalContext inside its own system-reminder tag; a recalled memory
carrying 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_id also carries host and owner now. The bare repository name is not a
namespace: two api repositories from different owners would otherwise share
one memory partition.

Known limits

Version 0.1.0 in both manifests. Stability is not claimed, and this is what
that 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 200 carrying data: null is reported as
unreachable; the sweep sends a raw session id where flush.js sanitises;
stdin is not paused on the early-return paths; the hook timeout window is not
enforced 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

  • The spawned EverOS is deliberately an orphan. A hook is a two-second process,
    so there is no resident parent to own the server; EverOS's single-instance
    lock makes concurrent spawns from several windows harmless.
  • The full tool trajectory is sent on purpose. everalgo's case extractor rejects
    trajectories without enough tool rounds, so sending less would mean no agent
    memory at all.
  • docs/DESIGN_DOC.md records the decisions, including the ones the live runs
    overturned and the evidence that overturned them.

🤖 Generated with Claude Code

zhanghui and others added 22 commits September 10, 2026 19:34
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>
zhanghui and others added 6 commits September 11, 2026 11:44
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>
zhanghui and others added 7 commits September 15, 2026 11:24
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>
@Yangtze-Seventh
Yangtze-Seventh merged commit a840231 into main Sep 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants