@everme/workbuddy
v0.7.10
Published
Native EverMe memory hooks for WorkBuddy.
Readme
@everme/workbuddy
Native EverMe memory hooks for WorkBuddy.
Installed by evercli plugin install workbuddy; you do not add it to WorkBuddy
by hand.
evercli plugin install workbuddy
# then quit WorkBuddy completely (Cmd+Q) and reopen itWhat it does
| Event | Mode | Behaviour |
|---|---|---|
| SessionStart | sync | injects the durable Profile into the new session |
| UserPromptSubmit | sync | injects memories relevant to the prompt |
| Stop | detached | uploads the turn that just finished |
Stop detaches itself so a per-turn upload never sits on a turn's critical
path. Detaching is a stdout protocol, not a settings flag: the host parses
the leading JSON object off the hook's stdout and detaches only on
{"async":true,"asyncTimeout":N}. Its config validator accepts just
type/command/timeout/matcher, so a definition-level async key is silently
ignored — a hook relying on one stays synchronous for the whole upload.
SessionStart and UserPromptSubmit must NOT detach: their stdout is the
context the host injects.
Why the desktop app runs hooks
The app is a shell around the bundled CodeBuddy engine, not an agent runtime of
its own. It resolves cli/bin/codebuddy, speaks the agent-client protocol to
it, and injects CODEBUDDY_CONFIG_DIR=~/.workbuddy. The engine's hook manager
is Claude Code-shaped, so hooks registered in ~/.workbuddy/settings.json
apply to app chat sessions.
~/.workbuddy/settings.json is the file to write — not the
vendor-documented ~/.codebuddy/settings.json, which is the standalone
terminal CLI's home and has its own session store.
Event census
Measured against WORKBUDDY_APP_VERSION 5.5.4 on 2026-09-10, with a probe on
nine events across two sessions:
SessionStart 2 · UserPromptSubmit 2 · PreToolUse 15 · PostToolUse 14
Stop 2 · SubagentStart 0 · SubagentStop 0 · PreCompact 0 · SessionEnd 0Stopfires once per turn (1:1 withUserPromptSubmit), which makes it the capture boundary.SubagentStart/SubagentStopnever fire: the app setsCODEBUDDY_DISABLE_FORK_SUBAGENT=1.PreToolUse/PostToolUseare deliberately not registered. They would spawn a process per tool call to carry data the transcript already holds verbatim, and onePostToolUsepayload reached 52 KB.
Host contract
- stdin carries the hook input as JSON:
session_id,transcript_path,cwd,hook_event_name, pluspromptonUserPromptSubmitandsourceonSessionStart. - stdout is parsed as hook output; the two context events return
hookSpecificOutput.additionalContext. - exit 2 would block the operation and erase the user's prompt. Never used: every failure path exits 0 with one line on stderr.
commandis a shell string, not an argv array, so the installer single-quotes every path. Onlytype: "command"handlers are accepted — the app setsCODEBUDDY_DISABLE_EXTENDED_PLUGIN_HOOKS=1, which makes the engine rejectprompt/agent/httphandlers.- The engine snapshots its hook config at startup, so changes need a full quit and reopen. Closing the window leaves the daemon running.
Transcripts
transcript_path points into
~/.workbuddy/projects/<cwd-slug>/<session-uuid>.jsonl, which the host appends
to during the turn. Capture reads that file rather than reassembling a turn
from event fields.
Session history there is a tree: resending or editing a prompt forks a new
child off the same parent instead of rewriting the old record, so the file holds
abandoned branches next to the live one. src/history.js walks back from the
active leaf, which is the same reconstruction the Go cold-start scanner
performs.
Both implementations are pinned to tests/fixtures/workbuddy-contract.expected.json
— the Go test TestWorkBuddyGoldenMatchesRealtimeContract reads the fixtures in
this directory — so a change to one that is not ported to the other fails on
both sides.
One divergence is deliberate: the Go path runs content through Redact() and
this one does not, matching the shipped @everme/qwenwork hook. Keep
credential-shaped strings out of the shared fixture.
The native id is not a row id
WorkBuddy reuses one native id across sibling rows: an assistant message and
the function_call rows it produced share it, and a single id can cover several
calls. On one real machine that was 61 ids spanning more than one record type
and 10 covering multiple calls.
A checkpoint keyed on the id alone therefore collapses siblings onto one entry.
Since a tool-call message carries no top-level toolCallId, the first version
of this adapter dropped 103 of 194 real tool calls from the upload while
still sending all 193 results, leaving orphaned results the extractor cannot
pair. The identity is [id, type, callId, role] — zero collisions across 557
real records — and adapter.checkpointKey() exposes it so the contract can be
asserted directly.
Array-shaped tool results matter for the same reason: the built-in tools
(present_files, show_widget, read_me, …) return an array of content
blocks, which an object-or-string check silently turns into an empty string.
That was 13 of 193 real results.
Files the installer writes
~/.workbuddy/settings.json hooks.{SessionStart,UserPromptSubmit,Stop}
~/.workbuddy/everme.env credentials (0600)
~/.workbuddy/mcp.json mcpServers.everme-memory (on-demand recall)
~/.workbuddy/everme/ this bundle, stagedThe MCP entry is the on-demand half: mem_search / mem_context as tools the
model can call mid-turn. It is read-only, so it cannot double-write against the
hook channel.
Uninstall
evercli plugin uninstall workbuddyManually: drop the everme-named handlers from hooks in
~/.workbuddy/settings.json (keep every other hook; remove an event key only if
it becomes empty), drop mcpServers["everme-memory"] from
~/.workbuddy/mcp.json (keep every other server), then
rm -rf ~/.workbuddy/everme ~/.workbuddy/everme.env and restart WorkBuddy.
Tests
npm test --workspace @everme/workbuddyLicense
Apache-2.0
