# Open Walnut — Personal AI > **ACP reference implementation:** https://github.com/agentclientprotocol/claude-agent-acp (public). > **References**: [ARCHITECTURE.md](./ARCHITECTURE.md) | per-directory `AGENTS.md` files are > concise quick-references; the **deep implementation details live in skills** (auto-discovered, > load on demand): `walnut-core-internals` (src/core/), > `walnut-web-frontend` (web/src/), `walnut-testing` (tests/), `walnut-ops` (incidents + src/logging/). > Load the matching skill before non-trivial work in that area. > **Important docs:** browse [`docs/`](./docs/README.md) first; model work starts with > [Claude model configuration](./docs/reference/claude-model-configuration.md). ## Ownership: You Are the CTO **Act as the CTO of this repo — proactive, decisive, and accountable for the outcome.** Don't wait to be told the obvious next step or stop to ask which way to go when the intent is clear. Make good, thoughtful decisions: weigh all the trade-offs (UX, maintainability, performance, blast radius, root-cause vs. band-aid), pick the option you'd defend in a design review, and state the call + the reasoning as you go. When a choice is genuinely the user's to make (irreversible, a real product fork, or it contradicts a stated preference) surface it with a recommendation — otherwise pick the obvious option and proceed. Fix root causes, not symptoms. Verify your own work (build + real-UI E2E) before claiming it's done. Default to finishing the whole job. **Done means live on prod, and you saw it there.** A change is finished only when it is committed, deployed to the live server on :3456, and you opened live prod yourself and watched it work (the real UI for a UI change, a read-only check for the rest). After the review, commit and deploy on your own; don't stop and wait to be asked. The shared tree almost always holds other agents' uncommitted work, so deploy the commit, not the tree: `bash scripts/deploy-committed.sh` builds the committed HEAD in a clean clone and serves it as this checkout: the compiled walnut file for this Mac (plus the Linux files host servers take, from the same build), run directly with no node. Once it serves, the dist of that build is copied into this checkout's `dist/`, because the local `walnut` CLI runs the op registry (descriptions, schemas, outcomes) from there (`scripts/publish-serve-root-dist.mjs`; 2026-10-05: sessions read op text two deploys old). It skips the deploy (exit 0, no restart) when production already serves that commit or a later one containing it from a clean build, because every restart costs open windows seconds of timeouts (2026-10-28: up to 76 a day); `WALNUT_DEVPROD_FORCE=1` deploys anyway. Deploys are queued (`scripts/lib/deploy-queue.sh`, 2026-11-03: 31 restarts in a day): production restarts at most once per 10 minutes (`WALNUT_DEPLOY_MIN_GAP_SECS`), and on a Mac whose load is over 4 per CPU it waits for the load to ease, up to 20 minutes after the last restart (`WALNUT_DEPLOY_BUSY_GAP_SECS`, 2026-11-09: four deploys an hour with an iOS build took the load to 400), a call inside the gap waits and goes out with the newest commit of then, and it exits 0 only once production serves a build containing its commit (it says which); a host's daemon is replaced at most once per 20 minutes (`src/providers/daemon-update-gap.ts`). Never wait for the tree to be clean, and never stash, revert or commit their files to get there. If a deploy guard refuses (type-check, smoke boot, cooldown), fix what it names or report it as the blocker. Never end with "committed, not deployed, say deploy". Scope the commit to your own changes only (never sweep up other agents' uncommitted files), and run the usual pre-commit sensitive-content scan. Push still only happens on request. ## CRITICAL: Open Source Repository **PUBLIC repo. Every commit is visible to the internet.** No company-internal names, personal info, internal URLs, credentials, or internal processes. Generic descriptions only. Internal plugins go in `~/.open-walnut/plugins/` (never committed). **When in doubt, leave it out.** ## Every feature works for anyone who installs it Walnut and its plugins ship to the public, so nothing may work only on the maintainer's machine (2026-11-01: a Focus block's macOS Do Not Disturb, the network relay). Set it up for the user in one click where the platform allows it; where it needs a step only they can take (a permission, an OS setting, a port on their own server), walk them through it in place, showing the exact screen, and check that it worked. Where the maintainer picked one way for himself (his own relay, say), the other reasonable ways stay options for everyone else. Never depend on a file, shortcut or setting that exists on one machine only. **Walnut never sets an engine's own defaults.** The default model and settings of an underlying agent (Claude Code, Codex and the rest) belong to that agent's own settings; Walnut only passes a model someone chose and lists each engine's models, so it stays generic and works the same on every platform. ## Existing users keep working and keep upgrading Every change reaches people who already run an older Walnut. Before it lands, make sure: - **It still works for them.** Data an older version wrote opens and reads the same, and a host, daemon, phone app or companion still on the older version keeps working with it. - **They can upgrade.** The updater their OLD version already has (npm, the install script, Homebrew, the Mac app) takes them to the new one with no manual step. - **They can go back.** The previous release still starts on data the new one wrote, or refuses with a clear message; it never corrupts it. Prove it against the real published release, not only the new code (`scripts/release-rehearsal/`). Keep the way across simple: one obvious path per install kind, not a pile of special cases. ## Multi-Agent Safety - **NEVER** delete/revert other agents' changes or switch branches unless asked - No `git stash`, no `git worktree` ops unless explicitly requested - On "push": `git pull --rebase` OK. On "commit": scope to your changes only - If build fails, retry — another agent may be mid-commit - Bug investigations: read npm dep source + all related code before concluding - Code style: brief comments for tricky logic; files under ~500 LOC ## Production Server Safety **Port 3456 = PRODUCTION. NEVER kill, restart, or interfere with it by hand: the deploy scripts below are the only way to replace it.** ### Never block the web server (each rule = a shipped outage) - **No sync blocking on the event loop** (`execSync`, sync native-addon calls, multi-MB parse): one call freezes EVERY route. Child process + timeout + cached value instead (`setImmediate` doesn't help). Ratchet: `tests/core/event-loop-blocking-ratchet.test.ts`. - **Every route touching daemon/SSH/network/whale files needs a deadline** — answer degraded (204/stale), never hang: one pinned response starves the browser's 6-connection pool → app-wide fake 15s timeouts. ```bash bash scripts/deploy-committed.sh # Deploy the committed HEAD (clean clone) → restart 3456 bun run dev:prod # Build the working tree as it is → restart 3456 bun run dev:ephemeral # Ephemeral server (random port, temp data, auto-cleans) bun run dev:ephemeral -- --file [] # the same, running a compiled file # Verify the deploy script itself WITHOUT deploying: runs every guard, then stops # before build / server kill / launch. WALNUT_DEVPROD_PORT is honoured only here # and in a rehearsal. WALNUT_DEVPROD_DRY_RUN=1 WALNUT_DEVPROD_PORT=35999 TMPDIR=/tmp/dry bash scripts/dev-prod.sh # Rehearse the WHOLE real deploy (build, smoke, kill and launch, LKG, rollback) away # from production: its own port, data dir, daemon dir and launchd label, and a state # dir it makes, /rehearsal-state (lock, stamps, stages, LKG, log, the CLI publish # stand-in), with the build clone beside it in . It writes only into those dirs # (and the daemon's -streams), except that its servers, like every Walnut # server, append their exit line to /tmp/open-walnut-exit.log, and on a Mac with # Walnut.app it adds its server's command to the session host's approvals (the others # kept) and runs it under the app, as the real deploy does (WALNUT_SESSION_HOST=0: not). Refuses :3456, :3457, # a port served by anything it did not start, ~/.open-walnut, /tmp/open-walnut, # ~/.cache/open-walnut, the production log and any WALNUT_DEVPROD_LKG_DIR. # Clean up after: bootout com.open-walnut.dev-prod-rehearsal., then remove the dirs. WALNUT_DEVPROD_REHEARSAL=1 WALNUT_DEVPROD_PORT=35871 WALNUT_DEVPROD_REHEARSAL_DIR=/tmp/rh \ OPEN_WALNUT_HOME=/tmp/rh/home WALNUT_DAEMON_DIR=/tmp/rh/daemon bash scripts/deploy-committed.sh ``` ### Live verification runs on an ephemeral server, never on :3456 Anything that creates tasks, sessions, notes or cron jobs to prove a change works (a real-CLI turn, a Playwright click path, a `curl` or `walnut tools call` against the API) runs on `bun run dev:ephemeral`. Reading :3456 is fine; writing to it for a test is not: every probe task an agent made there stayed on the user's board (a 2026-09-23 audit found dozens). `bun run dev:ephemeral -- --file ` runs a compiled walnut file instead of the source build (`--file` alone builds this tree's file first, about a minute): the same launcher code, so the same isolation below, and the file's runtime tree goes to `$TMPDIR/walnut-ephemeral-runtime`, never into the real data home (`scripts/dev-ephemeral.sh`). **When a check must write on :3456, it runs as a test run** (`src/core/test-run.ts`; 2026-10-27: an Org chart acceptance run made 9 tasks and 6 projects there, which showed on the user's board and Org page, and its cleanup left 3 session records with no task). A request carrying `x-walnut-test-run: