Research demo

September 8, 2026 · View on GitHub

The flagship B4.run example: a deep-research assistant that plans sub-questions, researches a bundled local corpus with a specialist subagent, and writes a cited report. Live research uses a real model and API key; the included tests and evals use deterministic fixtures and run offline.

Run it

pnpm install                 # from the repo root
pnpm build                   # build B4.run packages before commands that use dist
pnpm --filter @b4-example/research-server exec b4 typegen  # write generated types
pnpm --filter @b4-example/research-server check   # validate routes, tools, and config
pnpm --filter @b4-example/research-server test    # harness tests, offline (replay fixtures)
pnpm --filter @b4-example/research-server eval     # quality evals, offline (replay fixtures)
pnpm --filter @b4-example/research-server memory:list

To run against a real model, set OPENAI_API_KEY and add --live (e.g. pnpm --filter @b4-example/research-server eval -- --live). The offline path uses recorded fixtures, so tests and evals are deterministic and need no API key.

Run the live web client

The current Next.js/CopilotKit client streams cited research, renders generic tool calls, handles standard permission interrupts, offers suggestion prompts, and reviews memory candidates. After the root install and build above, run from examples/research:

cp server/.env.example server/.env   # add a real OPENAI_API_KEY
pnpm dev                             # B4.run server on :3002, web client on :3010

Open http://localhost:3010. The key stays on the B4.run server; see ../web/README.md for the architecture and smoke checklist.

To dogfood the Docker sandbox, start Docker and run:

pnpm --filter @b4-example/research-server test:sandbox:docker

The normal test path uses the local workspace/ so the bundled corpus works immediately. The Docker sandbox path creates an isolated per-thread workspace; the sandbox test seeds a corpus document there before running the same tools.

The tour — where each capability lives

CapabilityFileWhat it shows
Agent routesrc/app/research/index.tsthe research coordinator
Tools + typegensrc/tools/shared searchCorpus, readDoc; b4 typegen writes their generated types
Subagentssrc/app/research/subagents/researcher/dispatched via task({ subagent, input })
Planningsrc/app/research/plan.mdseeded checklist becomes the thread's todos
Offloadingb4.config.ts + a large readDocbig results spill to the workspace, stubbed in-context
Memoryworkspace/AGENTS.md, memory.md, memory.tsprompt memory plus typed recall/remember
Skillssrc/app/research/skills/cite-sources, synthesize-findings
HITL permissionsb4.config.ts + workspace/scripts/fetch-source.mjsthe external fetch pauses for approval
Workspaceworkspace/corpus + report output behind a path-jail
Docker sandboxb4.config.ts, test/sandbox-docker.test.tsopt-in isolated workspace via @b4run/sandbox
Persistence(default)threads survive a restart (SQLite)
Teststest/research.test.tscreateAgentHarness + script()
Evalssrc/app/research/evals/defineEval + scorers + a gate

Memory review

This app uses candidate memory writes. When the agent calls remember, the memory is saved for review instead of becoming active immediately.

pnpm --filter @b4-example/research-server memory:list
pnpm --filter @b4-example/research-server memory:approve -- <memory-id>

The tests show both paths: seeding an active memory with seedMemory, and writing a reviewable candidate through the real remember tool.