10 KiB
apps/daemon/AGENTS.md
Follow the root AGENTS.md and apps/AGENTS.md first. This file records daemon-specific code organization and editing rules.
Role
apps/daemon is the local Express + SQLite daemon and owns:
/api/*HTTP routes and SSE streams.- The
odCLI entrypoint insrc/cli.ts. - Project persistence, generated files, artifacts, media, skills, design systems, plugins, MCP, connector credentials, automation state, agent spawning, and static serving.
- The daemon sidecar entry under
sidecar/.
The daemon is not a shared library for the web app. Do not import daemon private src/ modules from apps/web; shared web/daemon contracts belong in packages/contracts.
Source Layout
src/server.tsis the composition root: create process-wide services, build dependency objects, install middleware, and register route modules. Keep request/domain logic out ofserver.tsunless the route is genuinely bootstrap-wide.src/cli.tsis the CLI composition root: parse top-level commands, dispatch subcommands, and format process output. Keep substantial command implementation in domain modules or focused*-cli.tshelpers.src/server-context.ts,src/route-context-contract.ts,src/route-registration-guard.ts, and small root constants/startup helpers may stay at the top level because they describe daemon-wide wiring.src/routes/contains domain route registrars that were split out ofserver.ts. New daemon domain endpoints should normally land here.- Legacy route files still at
src/*-routes.tsmay remain until touched. When making meaningful changes in one, prefer moving it tosrc/routes/<domain>.tsif the move is small and mechanically safe. src/http/owns shared HTTP helpers, error/result adapters, origin checks, and route mounting utilities.src/services/owns reusable daemon services that are not tied to Express request/response objects.src/runtimes/owns agent runtime definitions, spawning, parser integration, executable discovery, and runtime environment shaping. Agent argument definitions belong insrc/runtimes/defs/.src/prompts/owns daemon-side prompt construction. Keep mirrored BYOK/API wording inpackages/contracts/src/prompts/when the same text is exposed outside the daemon.src/plugins/,src/connectors/,src/registry/,src/research/,src/media-adapters/,src/live-artifacts/,src/storage/, andsrc/critique/own their named domains. Prefer adding code inside the existing domain folder before creating a new top-level folder.tests/contains daemon tests. Keep test paths roughly parallel tosrc/when useful.
Do not edit generated dist/ output.
Top-Level src/ Hygiene
Do not keep adding unrelated files directly under src/. The top level is currently crowded, so use these rules for new code and for touched legacy files:
- New domain code belongs in a domain folder, not in
src/<feature>.ts, unless it is a daemon-wide primitive. - New route code belongs in
src/routes/or an existing route subfolder such assrc/routes/plugins/. - New runtime or stream-parser code belongs in
src/runtimes/, with runtime definitions insrc/runtimes/defs/. - New provider/integration client code belongs in the existing domain folder when one exists, or under
src/integrations/<provider>.tsfor provider-specific glue. - New persistence/storage abstractions belong in
src/storage/unless they are tightly coupled to the legacy SQLite facade insrc/db.ts. - New prompt construction belongs in
src/prompts/. - New plugin, connector, registry, research, media adapter, live-artifact, critique, metrics, logging, QA, or GenUI code belongs in the matching existing folder.
- New general-purpose helpers should be avoided. If a helper has a real owner, put it with that owner. If it is daemon-wide infrastructure, use a focused folder such as
src/http/,src/services/,src/storage/, orsrc/runtimes/instead of creating another top-level utility file.
When touching a legacy top-level file:
- Prefer a small, safe move into an existing domain folder when imports are straightforward and the change is already about that domain.
- Do not mix a broad mechanical move with behavior changes unless the move is required to make the behavior change understandable.
- If a file is split, keep the public function names stable at call sites where possible and move tests with the behavior they cover.
- Use temporary root-level compatibility exports only when they materially reduce churn; remove them in the same PR if the diff stays small.
Suggested ownership for common legacy top-level families:
project-routes.ts,import-export-routes.ts,mcp-routes.ts->src/routes/. (Route modules already split out, such asroutes/chat.ts,routes/terminal.ts, androutes/social-share.ts, are done; do not list them here.)copilot-stream.ts,acp.ts,agents.ts,run-*,agent-*,*-diagnostics.ts-> usuallysrc/runtimes/or a futuresrc/runs/folder, depending on ownership. (claude-stream.ts,qoder-stream.ts,json-event-stream.ts, andruns.tsalready live undersrc/runtimes/.)design-systems-cli-help.ts,tools-design-systems-cli.ts,claude-design-import.tswhen used only there ->src/design-systems/. (Core design-system modules, design tokens,swift-colors.ts, andfrontmatter.tsare already undersrc/design-systems/.)inline-assets.ts,lint-artifact.ts,pdf-export.ts,document-preview.ts,static-spa.ts->src/artifacts/or the existing artifact owner. (Theartifact-*family already moved undersrc/artifacts/.)memory*.ts,orbit*.ts,automation-*.ts,routines.ts,prompt-*,handoff-*,finalize-design.ts-> keep with their domain; introduce folders when touching multiple related files.
The media-* family has already moved into src/media/; no media modules remain at the top level.
These are migration targets, not permission to do a large cleanup PR. Move only what helps the current change or removes active ambiguity.
Route Structure
Route modules should follow this shape:
import type { Express } from 'express';
import type { RouteDeps } from '../server-context.js';
export interface RegisterExampleRoutesDeps extends RouteDeps<'http' | 'paths'> {
example: ExampleService;
}
export function registerExampleRoutes(app: Express, ctx: RegisterExampleRoutesDeps): void {
// app.get/post/patch/delete(...)
}
Guidelines:
- Keep one exported registrar per domain, except where the existing file already has a small family of closely related registrars.
- Declare a narrow
Register*RoutesDepstype. Pick only theServerContextkeys the route uses, and add explicit service interfaces for domain-specific dependencies. - Add the registrar dependency type to
src/route-context-contract.tswhen it should be covered by the server context assertion. - Register the route from the matching semantic section in
src/server.ts. - Use existing route helpers from
src/http/when they fit. Do not invent another error envelope if a contract already exists. - Keep parsing/validation near the route boundary and push reusable behavior into named helpers or services.
- Do not add new route handlers directly to
server.tsunless they are bootstrap-wide process metadata such as health/version.
Dependency Boundaries
src/server-context.tsis the route dependency map. If a route needs a new cross-route dependency, add it there deliberately and keep its type narrow.- Prefer explicit domain service interfaces in route files over
anyorunknown. - Use types from the implementation module or
packages/contractsinstead of restating response shapes by hand. - Keep
packages/contractspure. Do not move daemon-only Node, SQLite, Express, filesystem, or process types into contracts. - Daemon data paths must follow the root Daemon data directory contract. Route all daemon-owned data through
RUNTIME_DATA_DIRor constants derived from it.
CLI and Surface Parity
User-facing capabilities must be reachable through both:
- Web/API routes in the daemon.
odCLI subcommands insrc/cli.ts.
When adding a user-facing capability, close the loop in one change: contract type, daemon route, web surface if applicable, and CLI command with --json plus --prompt-file <path|-> for long prompts where relevant.
Runtime and Agent Changes
- Parser changes belong beside the matching runtime stream helper and should include focused parser tests.
- Runtime definition changes belong in
src/runtimes/defs/. - For agent-stream/parser changes, replay a mock CLI trace from
mocks/when practical instead of burning provider budget. - Preserve Claude stream-json bookkeeping in
src/runtimes/claude-stream.tsandsrc/server.ts; do not close stdin ontool_usestop reasons.
Tests
- Tests belong under
apps/daemon/tests/, not undersrc/. - Use the cheapest layer that can observe the behavior: pure helper test, route-level Vitest with
startServer, then broader integration only when necessary. - For bug fixes, prefer a red spec that fails before the fix.
- If a test depends on native modules such as
better-sqlite3, make sure local dependencies were built for the active Node version before blaming the code.
Commands
Common daemon checks:
pnpm --filter @open-design/daemon typecheck
pnpm --filter @open-design/daemon test
pnpm --filter @open-design/daemon build
Focused tests from apps/daemon:
pnpm exec vitest run -c vitest.config.ts tests/<file>.test.ts
For local runtime validation, start through the repo control plane, not daemon package lifecycle aliases:
pnpm tools-dev run web --daemon-port <port> --web-port <port>
Review Checklist
Before handing off daemon changes, check:
- Route logic is in a route module, not newly embedded in
server.ts. - New route deps are explicit and covered by
route-context-contract.tswhere appropriate. - Shared DTOs or error shapes live in
packages/contractswhen the web or CLI consumes them. - CLI parity is handled or explicitly not applicable.
- Daemon data paths derive from the resolved daemon data root.
- Tests are under
tests/and relevant checks were run.