This page is for people and coding agents changing Seed Agents. It names where things are, how to run and validate them, and the conventions that keep the runtime coherent. Read the root AGENTS.md, then agents/AGENTS.md for the service (Bun only, never pnpm; bun check && bun test before every commit) and frontend/AGENTS.md for UI work. General repo setup is in contributing.
Commands
The whole stack in one mprocs TUI (Docker web backends, desktop, web, and the agents server, one pane per process; q stops everything):
./dev upThe agents server alone:
direnv exec . bash -lc 'cd agents && bun src/main.ts' # plain
direnv exec . bash -lc 'cd agents && bun run dev' # hot reload, dev web backends, subscription auth onValidate the service:
direnv exec . bash -lc 'cd agents && bun check && bun test' # typecheck + formatter, then the suite
direnv exec . bash -lc 'cd agents && bun run test:build' # the compiled binary boots
direnv exec . bash -lc 'cd agents && bun run test:docker' # the image boots
direnv exec . bash -lc 'cd agents && bun run test:trigger' # real daemon, mention trigger fires once
direnv exec . bash -lc 'cd agents && bun run protocol:check' # the protocol surface did not change silentlyValidate the frontend:
direnv exec . bash -lc 'pnpm typecheck'
direnv exec . bash -lc 'pnpm test'
direnv exec . bash -lc 'pnpm format:check'Build the deployment image and run the desktop:
docker build -t seedhypermedia/agents:dev . -f ./agents/Dockerfile
direnv exec . bash -lc './dev run-desktop'pnpm audit fails today on repository dependency advisories unrelated to this feature. Report that. Do not claim it passed.
Local URLs
The dev shell sets SEED_AGENTS_HTTP_PORT=3051 in .env.vars, so the dev server never shares a port with the 3050 default a packaged build uses.
what | dev URL |
|---|---|
server base |
|
health |
|
signed API |
|
WebSocket |
|
latency snapshot |
|
Code map
The service, in agents/:
src/main.ts: the Bun HTTP and WebSocket server, CORS, the webhook route, health and version, live event fan-out.
src/api-service.ts: the heart of the service: action dispatch, persistence operations, the Pi-backed model loop, the verb implementations, the Space index, trigger firing, subscription verification.
src/auth.ts: signed envelope verification, the five-minute timestamp window, capability-based delegation.
src/cbor.ts: DAG-CBOR request and response helpers and the protocol version header.
src/config.ts: every environment variable and CLI flag, with defaults.
src/sqlite.ts and src/sqlite-schema.sql: open, schema gate, migrations; the canonical schema.
src/runs.ts: durable run records and the dispatch queue: leases, fair-share claiming, retries, cancellation cascade, timer wakes.
src/run-events.ts: waiting runs and what wakes them.
src/workflow-host.ts: the QuickJS script engine: lint, realm prelude, journaled effect pump, replay; src/workflow-worker-host.ts runs it in a worker behind a flag.
src/tool-documents.ts: tools as content-addressed tool documents: the lambda ABI, builtin materialization, the MCP projection, authoring validation, contract markdown.
src/mcp.ts: remote MCP servers: connect, discover, proxy, the lazy per-run connection pool.
src/web-tools.ts: self-hosted web_search and the tiered web reader behind read https://….
src/agent-memory.ts: the per-agent memory filesystem and the signed memory actions.
src/code-exec.ts: sandboxed execution in microsandbox microVMs, boot-per-call and the warm pool.
src/activity-monitor.ts, src/activity-triggers.ts, src/schedule-monitor.ts, src/schedule-triggers.ts: the trigger monitors and matching.
src/provider-oauth.ts: the ChatGPT subscription sign-in flow.
src/json-schema.ts: the bounded JSON Schema validator for typed results and authored contracts.
src/perf.ts, src/session-perf.ts: the latency recorder behind /api/perf.
src/protocol-compat.ts, src/protocol-surface.ts: shims for older clients and the surface snapshot the CI gate diffs.
protocol/src/index.ts: the canonical protocol types for actions, responses, session events, and WebSocket events, published as the private package @seed-hypermedia/agents-protocol; protocol/PROTOCOL.md holds the versioning rules and changelog.
protocol/src/tool-registry.ts: the verbs and the callable tools: model-facing descriptions, JSON schemas, render metadata. Every word of a description is prompt.
protocol/src/write-guides.ts: the per-resource guides an agent reads at ~/tools/write/<resource>.
protocol/src/delegation.ts, protocol/src/reasoning.ts, protocol/src/model-capabilities.ts: thoroughness presets, the reasoning-level matrix, image-input support.
e2e/run.ts and e2e/live-gate.ts: the record/replay model gate and the live gate against a real server and model.
The shared UI, in frontend/packages/ui/src/agents/: client.ts signs and sends actions, models.ts holds the React Query hooks and signed subscriptions, platform.ts is the seam each app implements, and the pages and pieces are listed on the desktop and web UI page. The desktop's platform is frontend/apps/desktop/src/agents-platform.ts (its pages/agents.tsx only re-exports the shared list page); the web's in frontend/apps/web/app/web-agents-platform.ts and web-assistant-host.tsx. Routes are in frontend/packages/shared/src/routes.ts.
Shared Hypermedia behaviour the service reuses from @seed-hypermedia/client: resource-read.ts (resolveIdWithClient, shared with the CLI), hm-resolver.ts, blocks-to-markdown.ts and markdown-to-blocks.ts, explore-query.ts, and the blob signing and DAG-CBOR primitives (imported through the @shm/shared/blobs and @shm/shared/cbor re-exports).
Test map
The whole service suite runs from agents/ with bun test:
api-service.test.ts: the big one: actions, ownership, sessions, delegation, obligations, plans.
verbs.test.ts: the five verbs: address dispatch, touch-expand, promotion, user-invoked verbs.
tool-documents.test.ts: CIDs, builtin materialization, lambda authoring validation.
runs.test.ts, run-time.test.ts: queue claiming, leases, sweeps, parks and wakes.
workflow-host.test.ts, workflow-worker.test.ts: the script engine: lint, journal replay, fuel and caps, the worker transport.
activity-triggers.test.ts, trigger-events.test.ts, activity-trigger-race.test.ts, schedule-triggers.test.ts: trigger matching, firing idempotency, the comment/citation sibling race.
agent-memory.test.ts, session-attachments.test.ts, code-exec.test.ts, exec-pool.test.ts, exec-verify.test.ts, web-tools.test.ts, agent-tools-api.test.ts, mcp.test.ts, session-continuation.test.ts, write-link-validation.test.ts.
auth.test.ts, sqlite.test.ts, main.test.ts, config.test.ts, json-schema.test.ts, poll-loop.test.ts, provider-oauth.test.ts, protocol-surface.test.ts, statements.test.ts, perf.test.ts, session-perf.test.ts.
e2e-replay.test.ts shells out to e2e/run.ts. It currently skips: the cassettes predate the verb collapse (e2e/recordings/STALE.md), so a green run is not model-gate coverage. See operations.
Frontend: the shared UI's agents tests live in frontend/packages/ui/src/__tests__/, the desktop's in frontend/apps/desktop/src/__tests__/, and the web app's in frontend/apps/web/app/__tests__/. New page or hook tests belong beside those.
Conventions
Normalize user and network input at API boundaries. Internal APIs expect normalized values.
Never hold a SQLite write transaction around a model, provider, or tool network call.
Never log secrets, signed bodies, or full session or model content.
Protocol types live in agents/protocol/src/index.ts. Do not recreate mirrors in the desktop or the service. A change to the surface must pass bun run protocol:check, and a breaking one bumps the protocol version per protocol/PROTOCOL.md.
Provider responses stay redacted.
Prefer broad tests that exercise real behaviour, and existing files over tiny one-off modules.
Adding an API action
Add the request and response types to agents/protocol/src/index.ts.
Dispatch it in Service.message() with validation and account-ownership checks.
Add idempotency if client retries could duplicate side effects.
Emit service events if live clients need updates.
Add the hook in models.ts and any UI.
Add tests, run protocol:check, and update the signed API.
Adding a WebSocket event
Extend AgentWSEvent in the protocol package.
Emit the service event where the change originates and map it in the fan-out in main.ts.
Handle it in the subscription hook in models.ts.
Update WebSocket subscriptions.
Changing the database
Edit sqlite-schema.sql, the fresh-install baseline.
Prepend the migration to the migrations array in sqlite.ts (the array is reversed, so the newest literal applies last). Never edit or reorder a migration that has shipped.
Keep the two equivalent: baseline plus every migration must produce the schema in sqlite-schema.sql. sqlite.test.ts synthesizes an old baseline, applies the migrations, and asserts the resulting tables and columns; add yours to those assertions. It is not a full schema diff.
Never silently accept an unknown or future schema version.
Update persistence.
Changing the model-facing surface
The verbs (the five working verbs plus status and continue_session) are the whole provider-facing surface. New capability arrives as an address, an option, or a callable, never as another verb without a design discussion.
A new address form for read or write goes in the verb's description in the tool registry and in the address dispatch in api-service.ts. Edit the description as prompt.
A new callable goes in callableToolRegistry with runtimes including agent-service. It is reachable through call and never added to the provider payload directly. The next listing materializes it as a tool document for every agent, and the CID change is the version bump.
Keep touch-expand intact: a wrong or unexpanded call answers with the contract, and never with an error.
Anything promoted into the provider payload must be filtered against the agent's enabled callables, because promotion is derived from durable events and an unfiltered allowlist would let a hallucinated tool name activate a real one.
Grants are publish plus the callable set. Do not add a grant for a verb.
Adding a provider
Add the PROVIDER_SPECS entry in api-service.ts and the matching PROVIDER_METADATA entry in the UI's provider-registry.ts.
If the model needs reasoning control, add its generation to reasoning.ts with a note on how the levels were verified. If it takes images, add it to model-capabilities.ts.
Preserve the session lifecycle and WebSocket partials. Map Pi events into ordered message, tool_call, and tool_result events.
Add mocked network tests for success, streaming, text-before-tool ordering, tools, missing key, and provider errors.
Confirm decrypted secrets stay in memory and never reach Pi auth files.
Update model providers.
Which page to update
Documentation is part of the change, in the same commit.
action or response semantics: signed API
WebSocket or streaming: WebSocket subscriptions, operations
schema or migrations: persistence
provider execution or config: model providers
MCP servers: MCP servers, tools, security
triggers: triggers
UI behaviour: desktop and web UI
prompts: prompt injection map
auth, secrets, logging: security, operations
environment variables, deployment: operations, environments
something shipped or something new to do: the roadmap. Do not add a history page. Git has the history
a new page: link it from Seed Agents or the page it belongs under
Manual acceptance checklist
After a core change: start the server and the app, open Agents, confirm the server is online, configure a provider, create an agent, open a session, send a message, confirm the subscription succeeds and the reply streams and persists across a reload. Then ask it to read a URL, read ~/tools/, and read ~/memory/. Confirm tool rows appear and that a call of an unexpanded tool comes back as the contract. Run a verb from the wrench palette and confirm the You chip and that the agent sees it. Give it a task worth a checklist and a delegation. Confirm the run card shows the plan, the child attaches to the running step, and the parent resumes with the result.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime