How it works
The short version
Section titled “The short version”Claude Code starts every session with no memory. For solo work, that’s fine — you re-brief Claude at the start. For a team it breaks down: two people’s Claudes give conflicting advice because neither knows what the other worked on.
Wyren’s fix is a shared file in your git repo: .wyren/memory.md. It holds what matters — decisions made, approaches that didn’t work, temporary hacks still in the code. Claude reads it silently at every startup. No briefing needed, no special prompts, no change to how you work.
Here’s the data flow:
Your conversation ↓ (background, every ~5 turns) distiller.mjs → .wyren/memory.md → git push ↓ (at every new session start) teammate's Claude CodeThe file is plain markdown — you can open it, read it, and edit it directly. Everything else in Wyren is engineering to keep that file accurate and in sync.
The rest of this page walks through the full sequence step by step.
T=0 — Install once
Section titled “T=0 — Install once”Alice and Bob each run the one-liner on their machines (see Install guide). The installer clones Wyren to ~/.claude/wyren/, patches ~/.claude/settings.json with the three hooks, and registers wyren on PATH. No further setup per session.
T=1 — Initialize the repo
Section titled “T=1 — Initialize the repo”Alice runs wyren init inside the repo:
$ wyren init✔ Created .wyren/memory.md (empty stub)✔ Created .wyren/broadcast/✔ Appended .wyren/state/ and .wyren/log to .gitignore✔ Verified git remote is configured (origin)
Next: git add .wyren/ && git commit -m "Add Wyren" && git pushShe commits and pushes. Bob pulls. They’re ready.
T=2 — Alice opens Claude Code
Section titled “T=2 — Alice opens Claude Code”Claude Code fires the SessionStart hook. session-start.mjs:
- Calls
WyrenSync.pull()→git fetch, thengit checkout FETCH_HEAD -- .wyren/(path-scoped, 1.5s cap). - Reads
.wyren/memory.mdand any files under.wyren/broadcast/. - Prints a JSON envelope to stdout with
hookSpecificOutput.additionalContextcontaining the merged memory + broadcast content.
Claude Code ingests that text as hidden system context. Alice’s Claude now knows the project state before she types a word.
T=3 — Alice works
Section titled “T=3 — Alice works”Alice chats with Claude. Over 10 turns she:
- Decides to use SQLite, not Postgres (too heavy for scope).
- Tries a WebSocket approach — browser proxy drops the long-lived connection. Abandons it. Switches to SSE.
- Installs a
user_id=1hardcoding in/dashboardfor fast iteration.
Every assistant turn fires the Stop hook. stop.mjs:
- Appends the turn’s UUID to
.wyren/state/watermark.json(per-machine, not git-tracked). - After 5 new turns (or 2 min idle), spawns
distiller.mjsdetached. The turn itself is never blocked.
T=3b — Live sync on every user prompt
Section titled “T=3b — Live sync on every user prompt”While Alice works, the UserPromptSubmit hook fires on each prompt she sends — before Claude sees it:
git fetchthengit checkout FETCH_HEAD -- .wyren/memory.md(1.5s cap, 3s hook budget).- Computes a section-aware diff against the memory version injected at
SessionStart. - If there’s new content (a teammate pushed a distilled update mid-session), injects only the delta as
additionalContext.
If Bob is working concurrently and his distiller pushes an update, Alice’s next prompt picks it up automatically — no restart required. Sessions stay warm without polling.
T=4 — Distiller runs in background
Section titled “T=4 — Distiller runs in background”distiller.mjs:
- Reads the transcript JSONL from the
transcript_pathhook input. - Slices from the last-processed UUID to the end.
- Runs Tier 0 weighted signal filter — scores the transcript across decision, rejection, hack, scope-change, and structural signals (session length, edit count). Skips the API call if the score falls below threshold. Kills ~70% of triggers for free.
- For slices with signal: shells out to
claude -p(headless Claude Code) with the distiller prompt + current memory + transcript slice. - Gets back a full new
memory.md— hygiene-respecting (replaces superseded entries, removes resolved workarounds, never blindly appends). - Atomic write (
.tmp+ rename). WyrenSync.push()→ commit.wyren/memory.mdvia temp index, push. On non-fast-forward, fetches remote, fast-forwards HEAD if safe, and retries with a fresh wyren commit (up to 3 attempts).- Updates watermark.
Alice’s .wyren/memory.md now contains:
# Wyren Memory
## Decisions- SQLite over Postgres — lightweight, no external DB needed [session 7a2e, turn 12]
## Rejected paths- WebSocket for live sync — browser proxy drops long-lived conn; switched to SSE
## Live workarounds- `/dashboard` hardcodes `user_id=1` — remove before demo
## Scope changes- Dropped: CSV export (stretch, unblocked for v2)T=5 — Bob opens Claude Code
Section titled “T=5 — Bob opens Claude Code”Bob opens Claude Code in his clone. SessionStart fires:
git fetch, thengit checkout FETCH_HEAD -- .wyren/— picks up Alice’s.wyren/memory.md.- Reads the file, emits
additionalContext. - Claude Code injects it as hidden system context.
Bob’s first prompt: “what’s the state?”
Claude’s first message:
I see Alice picked SQLite over Postgres and tried WebSocket unsuccessfully (switched to SSE because the browser proxy dropped the connection). There’s a hardcoded
user_id=1workaround in/dashboardthat needs removing before demo. CSV export was cut but is unblocked for v2. Where do you want to start?
Zero context re-establishment. Bob picks up where Alice stopped.
That’s the loop
Section titled “That’s the loop”Everything else in the implementation plan is engineering to make that loop reliable across:
- Flaky networks — timeboxed hooks, best-effort sync, log-and-continue on failure.
- Concurrent distillation — advisory lock + safe HEAD recovery on push conflict.
- Long transcripts — watermark-based slicing, never re-read what was already processed.
- Cost pressure — Tier 0 weighted signal filter kills free triggers; Haiku for routine; Sonnet for cleanup.
See the architecture for the system diagram, or jump to the roadmap to see what ships when.