Imported from TarunYadgirkar/Bonsai (
AGENTS.md). Install upstream withnpx skills add TarunYadgirkar/Bonsai. Copyright stays with the author.
AGENTS.md — rules for coding agents in this repo
What this is
Bonsai: tree-structured AI chat. Branch a side question off a parent conversation with a compiled minimal context brief instead of the full history; route each request to a model + effort level automatically, with manual override; cherry-pick insights back into the parent. Read PRODUCT.md for the idea.
The hackathon is over. This is now a real project being taken toward something usable and open source.
The part that matters
The surface (web app / browser extension / CLI plugin) is undecided. The durable value is the layer underneath it:
- the tree data model,
- how context is assembled from a path through that tree,
- what happens when you fork: which ancestors get sent, which get pruned, how two branches merge back.
That is the interesting problem and the thing worth getting right. Surfaces are thin once it is solid. Prefer changes that strengthen that core over changes that only decorate one surface.
Branches
| Branch | Purpose |
|---|---|
main |
Current best state. The only branch Vercel deploys. |
og |
Clean baseline, forked from main. Do not build on it. |
copy-a |
Independent exploration lane. |
copy-b |
Independent exploration lane. |
hackathon-copy |
Frozen archive of the Aug 7 2026 submission, sponsor integrations intact. Never commit here. |
copy-a and copy-b are free lanes — they may go the same direction or diverge completely. Do not coordinate them, and do not merge one into the other. Tarun decides what reaches main.
Stack
- Next.js (App Router) + TypeScript + Tailwind. Vercel deploys
mainonly (vercel.json→git.deploymentEnabled). - The engine is an npm-workspace package:
packages/engine(bonsai-engine) — tree model, path assembly, brief compiler, router, providers. Zero runtime deps; ships as TS source (transpilePackagesin next.config.ts). Unit tests inpackages/engine/test, evals inevals/(npm run eval). - Inference:
packages/engine/src/provider.ts. One ofANTHROPIC_API_KEY/OPENAI_API_KEY/XAI_API_KEYmakes it live; none means the extractive mock inpackages/engine/src/llm.ts. Request bodies come from per-model capability records — never hand-build one. - Store: relational Neon Postgres (conversations/messages/insights/inference_logs — schema in
migrations/) via the working-set API inlib/store.ts; in-memory fallback with fixture seeding whenDATABASE_URLis unset. - The Claude Code plugin lives in
plugin/(skills + tier agents + bundled stdio MCP tree server); repo root carries the marketplace manifest.
There is deliberately no durable-memory layer right now. The hackathon one was a sponsor integration and was removed; whether cross-conversation memory is needed at all, and what should provide it, is an open question. Do not add one back without deciding that first.
Neon — one database branch per git branch
Project bonsai (wild-feather-67393800). Each lane gets its own isolated database so branches cannot clobber each other or production:
| Neon branch | Serves |
|---|---|
main |
Vercel production |
copy-a |
the copy-a lane |
copy-b |
the copy-b lane |
Connection strings come from the Neon console. Put the one for your lane in .env.local as DATABASE_URL. Never point your lane at the main Neon branch — you will overwrite the live demo's tree.
Mock-first rule
Every external dependency sits behind an interface with a mock that activates automatically when its env vars are missing. The engine's mock answers extractively with real token math; the store falls back to in-memory with fixture seeding. The app must fully run with zero keys configured.
Persistence honesty: a wrong DATABASE_URL still degrades reads to memory silently (a
load failure must not take the demo down), but writes no longer lie — commit() reports
failure and mutating routes return 503. Confirm persistence with a restart-survival test all
the same.
Working rules
- Build clean before committing:
npm run build, plusnpm run typecheck && npm run test(andnpm run evalwhen engine semantics changed). Fix every error. - Commit messages: conventional prefix, short imperative subject, nothing else. No
Co-Authored-By:trailers, no generated-with footers, no bulleted change lists. - No refactors outside the current task.
- Keep it boring: fetch + JSON, plain React state. No exotic dependencies without a reason.
- Secrets only via env.
.env.examplelists the names. Never print keys. Agents cannot read or write.env*here — twoPreToolUsehooks block it; hand Tarun the command instead. - Errors from any external service: catch, log one line, degrade. Nothing crashes on a 4xx.
lib/types.tsand the API route signatures are shared contracts. Changing one is a deliberate act, not a side effect.
Known traps
components/TreeSidebar.tsxgeometry: card heights are fixed per variant andcomponents/treeLayout.tsmust agree with them. Change one, change both.- A node's chip shows the last turn's decision, so adding a cheap follow-up turn to a fixture branch overwrites its chip.
fixtures/seed-tree.jsonis generated, never hand-edited. Regenerate withDATABASE_URL= npx next dev -p 3111thennpx tsx scripts/build-seed-tree.ts.plugin/mcp/server.mjsimports the REAL engine ('bonsai-engine', aliased to the TS source in build.mjs) — the old hand-mirrored subset is gone, and with it the dual-maintenance trap. Consequence: server.mjs only runs BUNDLED;node plugin/mcp/dist/server.mjsis the artifact, and the smoke exercises it. After editing server.mjs or the engine, rebuild withnode plugin/mcp/build.mjsand commit the bundle (CI diffs it).- Never send sampling params to 4.6+/5 Claude models, and route effort per BRANCH, not per message — resolved effort is rendered into the prompt, so per-turn changes invalidate the provider prompt cache.
Ongoing
Updated: 2026-08-23T05:50:35Z by claude session — merge-bug fix shipped on copy-a; main fast-forward PENDING Tarun
Done:
e48c176fix: never merge a user turn as the distilled insight. Root cause of the legacy question-insights (hackathon-day mock distiller): pre-5725f5dhad no question filter, and the filter added there only catches?-suffix, so period-terminated imperatives slipped through. Two-layer fix: enginemockDistillconsiders assistant-authored sentences only (sentencesWithRole), and new exportedinsightEchoesUserTurn(compiler.ts) rejects distilled lines that normalize-match a user turn — wired intoapp/api/merge/route.tsbeside the grounding gate (covers real-model echoes too, which the grounding gate passes by construction). 232/232 tests, 15/15 evals, build clean, plugin MCP dist rebuilt + committed. Prod Neon data was already clean (0 insights on main).- Real-data corpus exported to
~/TarunsCode/bonsai-distill-corpus/(conclusions.jsonl, eval.jsonl, README with provenance) for the bonsai-distill experiment. Entire real insight population = one fact cluster; the two question-rows were excluded and are what triggered the fix above. - Mahogany hackathon repo moved
hackathons/→archive/mahogany-mongodb(same loop as Bonsai, collapsed per Tarun).
Blocked:
- Fast-forward of
maintoe48c176+ push: auto-mode classifier blocks agent-run pushes to main; Tarun runscd ~/TarunsCode/bonsai && git merge --ff-only copy-a && git push origin main(then his usualnpx vercel promotefor bonsai-connector prod). - Mahogany's real Atlas insights for the corpus: connection string lives in hook-blocked
.env.
Next:
- After Tarun pushes main: confirm bonsai-lac auto-deploy picked up
e48c176, then promote bonsai-connector prod (Tarun-typed). - Still queued from before:
vercel env add SESSION_SECRET production(+ redeploy),pnpm publish bonsai-engine, MCP Apps tree UI, connector OAuth.
Standing:
- Run the FULL CI sequence locally before pushing (Tarun asked — no more failure emails):
typecheck, extension tsc+build+dist-diff,
npm ci --prefix plugin/mcp+ smoke + build + dist-diff, tests, evals, build, engine tsup smoke.