An app is a capability provider (hachej/boring-app). Every capability folder is
optional; boring.json declares what the hub may use. Add a definition; add
it to boring.json only if the hub must be able to call it. An undeclared
definition is internal: it works inside the app and is absent from the manifest.
Which capability
| The need | Create | Declare in boring.json exposes |
|---|---|---|
| One model call with a contract | agents/<name>/agent.md (+ rules.md, tool.json) |
agents.<name>: "<one line>" if the hub calls it |
| Several agents with a lifecycle (a result produced from parts) | agents/<name>/JOB.md + index.mjs (plan, collect) |
jobs.<name> |
| A chat with history over one agent | agents/<name>/CHAT.md (+ optional index.mjs context) |
chats.<name> |
| A pure function an agent or the hub calls | tools/<name>/tool.json (runtime: sandbox) + tools/<name>/index.js |
tools.<name>: { runtime, description } |
| A function that needs the database, the network or a secret | tools/<name>/tool.json (runtime: server) + server/src/tools/<name>.ts |
tools.<name> |
| A component bound to data that the hub can frame | web/src/views/<name>/index.tsx (+ register it in web/src/main.tsx) |
views.<name>: { shows, description } |
| The app's own screen | web/src/pages/<name>/index.tsx |
pages.<name> |
An agent alone suffices for most features. Add a job only when the feature
coordinates several agents with a lifecycle, a chat only when people talk to
the agent over turns. Names are unique across agents/.
An agent
agents/<name>/
agent.md front matter + system prompt (the body)
rules.md the owner's rules, read-only in Settings; they change by pull request
tool.json only when output: tool: { name, description, input: JSON schema } (the output schema)
index.mjs optional: buildMessage(input), validate(output), asText(output); then the agent needs server/
agent.md front matter: name (the folder), title, description, model
(openai-codex/<model>), effort, max_tokens, output (tool or
markdown), helper_tools: [<tool names>] (only declared tools), inputs:
(name: { type, description, required }).
- Declarative (no
index.mjs): the message is the rules and the inputs; a markdown output must be non-empty, a tool output must carrytool.json's required fields. The hub can run it from the files alone. - With code:
validate(output)is the contract the runtime applies before anything is stored (APP-B4); refuse withfail("…")fromagents/_shared/validate.mjs, a message addressed to the model. - Fake models (
server/src/fake-models.ts) answer markdown agents; add a scripted reply for a tool agent, or its tests cannot run. - A Cloudflare app (
project.mdruntime: cloudflare) does not run the agent runtime: its agents stay declarative and the hub runs them. Noindex.mjson an agent or chat, no job;verify checkrefuses them.
A tool
- Sandbox:
tools/<name>/index.jsdefinesfunction run(input, ops), plain JavaScript with no import, no Node or network API, and only theops.<name>itstool.jsonlists underoperations.verify checkrefuses anything else. - Server:
server/src/tools/<name>.tsexportshandler(input, { db }); validate the input with zod, andawaitevery query (the same code runs on SQLite and on D1). On a Cloudflare app it uses no Node API (theworker-portablerule). tool.json:description,runtime,inputandoutputJSON schemas.
Data, routes, the web
- A table:
server/src/db/schema.ts, thennpm run db:generate, commitserver/drizzle/(neverdrizzle-kit push). A write that edits names the revision it read (APP-B3,server/src/routes/memories.tsis the example). - A route:
server/src/routes/<name>.ts(Hono +zValidator), mounted inserver/src/app.ts. - The web reaches the server only through
web/src/lib/api.ts(web/src/lib/model.tsholds the calls).
Tests and verification
- Unit (
npm test): the route and the tool through the app (test/unit/app.test.mjs), an agent's contract when it has code. - End-to-end (
test/e2e/journey.spec.mjs) when a person sees something new. node scripts/verify.mjs check, then drive it with the verify-app skill.- The feature map: the area file in
docs/verify/for what a person can do.
Never
- Expose something by accident: declare it, or keep it internal.
- Call a model outside the runtime, or give a sandbox tool an import.
- Edit
vendor/.