Skip to content

feat(examples): flagship research demo — server (slice 1)#311

Merged
blove merged 4 commits into
mainfrom
blove/zealous-goldberg-ab9dfc
Jul 13, 2026
Merged

feat(examples): flagship research demo — server (slice 1)#311
blove merged 4 commits into
mainfrom
blove/zealous-goldberg-ab9dfc

Conversation

@blove

@blove blove commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Slice 1 of the polished flagship research demo: a new standalone in-repo example examples/research/server (@dawn-example/research-server), created by promoting the default scaffold template packages/devkit/templates/app-research into a concrete example that mirrors examples/chat/server. The default scaffold template is untouched.

This is the first of four planned slices (see design + plan docs in this PR). Later slices add the web UI + no-key demo mode, slim/align the default scaffold, and extract a docs recipe.

What it dogfoods (the demo's point): a research coordinator + researcher subagent, custom corpus tools (searchCorpus/readDoc) with tool-output offloading, semantic memory with candidate → CLI-approve → recall, planning/todos, skills, HITL permissions (interrupt + resume), an optional Docker execution sandbox, workspace path-jail, evals with an LLM judge, and a deterministic test suite.

Determinism: the capability suite and evals run entirely on aimock fixtures — no API key required. Live/model paths are gated behind OPENAI_API_KEY + --live. The Docker sandbox is explicit but env-gated (DAWN_DEMO_DOCKER_SANDBOX=1) and skips cleanly without Docker.

CI wiring: the package is discovered automatically by the examples/*/* workspace glob (turbo build/typecheck) plus one line registering its test project in vitest.workspace.ts — the same treatment examples/chat/server gets.

Design: docs/superpowers/specs/2026-07-06-research-demo-design.md
Plan: docs/superpowers/plans/2026-07-06-research-demo-slice1-server.md

Test Plan

  • pnpm --filter @dawn-example/research-server test → 7 passed | 1 skipped (offline, no key)
  • pnpm --filter @dawn-example/research-server eval → PASS, mean 1.00 across scorers (offline)
  • pnpm --filter @dawn-example/research-server test:sandbox:docker → 1 passed (Docker present)
  • pnpm --filter @dawn-example/research-server typecheck / build → green
  • pnpm --filter @dawn-example/research-server check generates .dawn/ types (gitignored)
  • Runs under the root vitest workspace (vitest.workspace.ts) → 7 passed
  • Full pnpm ci:validate on CI (standard gate for a new workspace package)

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dawnai Ready Ready Preview, Comment Jul 13, 2026 3:17am

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 3m 17s —— View job


Code Review

  • Gathering context
  • Reading repo guidelines
  • Reviewing changed files
  • Analyzing for correctness, security, type-safety, test coverage
  • Posting findings

Overall this is a well-structured PR that mirrors the examples/chat/server pattern cleanly. The HITL, offloading, and memory demos are coherent, and the deterministic fixture approach is solid. A few issues worth addressing before merge:


Medium — harness/DB ordering hazard

examples/research/server/test/research.test.ts:37-38

beforeAll(cleanMemoryDb)
const h = await createAgentHarness({ appRoot, route: "/research#agent" })

createAgentHarness runs at module evaluation time — before Vitest fires any beforeAll hook. If the harness eagerly opens the SQLite connection (memory.sqlite), cleanMemoryDb will then delete the underlying files from a live connection. On Linux/macOS this appears to work coincidentally (the inode stays open until the connection closes), but the harness will be pointing at a deleted-then-recreated file after cleanup, which can produce ghost data or WAL corruption across test runs.

Fix: either move the harness creation inside beforeAll, or call cleanMemoryDb before createAgentHarness:

cleanMemoryDb()
const h = await createAgentHarness({ appRoot, route: "/research#agent" })
afterAll(async () => { await h.close(); cleanMemoryDb() })

Fix this →


Low — path jail has no negative-path test

examples/research/server/src/tools/readDoc.ts:3-7

function assertCorpusPath(path: string): void {
  if (!path.startsWith("corpus/") || path.includes("..") || path.startsWith("/")) {
    throw new Error(`readDoc only accepts workspace corpus paths, got "${path}"`)
  }
}

The guard is correct (.. traversal and absolute paths are caught). But there are zero tests that verify it throws on bad input. Since this is the demo's path jail, at least one negative test (e.g. "corpus/../secret", "/etc/passwd", "other/file.md") would make the guarantee observable and catch any future refactor that weakens the check.

Fix this →


Low — redundant eval scorers

examples/research/server/src/app/research/evals/research-quality.eval.ts:36-43

contains("[corpus/", { threshold: 1 }),
custom((run) => (run.finalMessage.includes("corpus/") ? 1 : 0), {
  name: "cites-source",
  threshold: 1,
}),

Any message that passes contains("[corpus/") trivially passes cites-source (includes("corpus/")). The reverse isn't true — a message could include corpus/ without a bracket and score 1 on cites-source while scoring 0 on contains. With gate.perScorer(), the weaker scorer can mask a citation-format failure: if the model writes "see corpus/foo.md" (no bracket), contains fails but cites-source passes, which may or may not be the intended behavior. Consider either removing the custom scorer (it's fully dominated) or tightening it to match the same bracket pattern.


Nit — passWithNoTests: true silently passes on empty test directory

examples/research/server/vitest.config.ts:10

This flag is present in examples/chat/server too, so it's a convention. Just noting that if the test/ directory is accidentally excluded from a future move/rename, CI stays green with 0 tests.


No TypeScript type-safety issues found; the custom tools use typed inputs correctly, and the Zod schemas for memory and state look sound.

blove and others added 4 commits July 12, 2026 20:14
Design for a standalone examples/research app (server + web UI monorepo,
like examples/chat) that dogfoods research workflow, memory candidates,
Docker sandbox, subagents, HITL, evals, and deterministic tests. Scaffold
slimming and docs-recipe extraction are later slices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…emplate)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@blove blove force-pushed the blove/zealous-goldberg-ab9dfc branch from 9fb5ac0 to 031d274 Compare July 13, 2026 03:16
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove blove enabled auto-merge (squash) July 13, 2026 03:16
@blove blove merged commit 7b210d8 into main Jul 13, 2026
12 checks passed
@blove blove deleted the blove/zealous-goldberg-ab9dfc branch July 13, 2026 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant