Imported from MedSlimane/Aviary (
AGENTS.md). Install upstream withnpx skills add MedSlimane/Aviary. Copyright stays with the author.
Aviary
Aviary is an agent harness control surface. A Node WebSocket server wraps provider CLIs and agents (Codex, Claude Code, Cursor, Grok, OpenCode, Antigravity) and serves web, desktop, and mobile clients.
Aviary is a fork of T3 Code by the T3 Tools team, used under the MIT License. Almost everything described in this file is their architecture. See NOTICE.md and docs/fork/000-decision.md.
Where Aviary is going
Aviary is early. It has no users, no releases, and no published packages, and nothing you write should imply otherwise. What it adds on top of the inherited harness is control over what agents read — skills, prompts, subagents, MCP servers, instruction files and media — with the load order and real token cost made visible. That work is planned in docs/fork/000-decision.md as sub-projects 1–5 and has not started.
What must not be compromised
1. Open at the core
Aviary is MIT and stays that way, as is the work it was built on.
2. Performance without compromise
The inherited codebase is fast, and its maintainers kept it that way deliberately. Regressions usually come from sending too much data over websockets, CSS animations spiking the GPU, or lists that are expensive to render. Weigh performance impact in every change.
3. Remote ready
The websocket layer (npx aviary) is what makes remote work possible — local network, Tailscale, or the Connect tunnel in this repo. New features should support it.
4. Multi-surface
Three surfaces: web, desktop, mobile.
Web runs locally via npx aviary. There is no hosted deployment yet.
Desktop is an Electron app that bundles the server runner and can act as the host server for remote connections.
Mobile is a React Native app for iOS and Android. There is no store listing; build from source.
5. Never claim what does not exist
This is Aviary's own rule, and it outranks convenience. Do not write a user count, a star count, a store link, a download URL, or a screenshot that is not real. Do not report a number the code cannot measure — say it is unmeasured and give no figure. A screen with convincing fake data is worse than an absent one, because it spends trust the working parts have to earn back.
How to work here
Prefer ambitious ideas, simple systems, and software that feels obvious. Do not preserve complexity just because it already exists. Do not introduce machinery because it looks architecturally impressive. Understand the real constraint, then fight for the smallest model that makes the correct behavior unsurprising.
Channel both "measure twice, cut once" and "yagni". Fight scope creep. Honor the developer's intent in a minimal and realistic fashion.
The rest of this document helps you navigate the codebase. Treat it as good defaults rather than hard rules; the developer's preferences override anything here.
Of note: Aviary contributions often come from Aviary itself, controlled remotely. Be careful about accessing data, killing dev servers, and anything else that may damage the instance the contributor is using.
Plans and work artifacts
-
Do not commit implementation plans, research notes, or agent scratch files. Keep temporary working material outside the worktree.
.plans/is gitignored only as a safety net for legacy tooling. -
Track active maintainer work in the GitHub issue or project item that owns it. External proposals follow
CONTRIBUTING.mdand belong in Ideas discussions. -
Put durable architecture, constraints, and decisions in
docs/internals/. Update those docs when the product changes so agents find current facts instead of abandoned intentions. -
A merged PR is the implementation record. Close or update its tracking item when the work lands; do not preserve a second checklist in the repository.
Aviary's own fork records are the documented exception:
docs/fork/holds the decision record, the append-only log and the rename map, because a fork that re-seeds from upstream needs its divergence written down to stay affordable.
A small glossary
We need to be on the same page with terminology. When communicating, use this language:
- you means the agent reading this file and changing Aviary.
- we, us, and maintainers mean the people building Aviary. These are who you are talking to now.
- user means the person using Aviary to direct coding agents.
- agent means the coding agent a user runs inside Aviary. Depending on context, that may also include you.
- provider means the agent runtime or harness Aviary talks to, such as Codex, Claude, Cursor, or OpenCode.
- client means the web, desktop, or mobile UI.
- environment means one running Aviary server and the machine, filesystem, provider credentials, and state it owns.
- project means an environment-local workspace record rooted at a directory.
- thread means the durable conversation and work history for a project.
- turn means one user-to-agent cycle, including follow-up work such as checkpointing.
- Aviary home means the base data directory. Runtime state normally lives below its userdata directory.
The three ways to hurt yourself
- Killing by pattern. Never
pkill -f,pgrep | kill, orkilla PID you found by matching a name, path, or worktree string. Your own agent process has this worktree's path in its argv, and this machine runs several other dev servers at once. Kill only a PID you captured at spawn, or the owner of your port fromss -H -ltnpafter confirming/proc/<pid>/cwdis your worktree. - Writing to the live install.
~/.aviary/userdatais the developer's real Aviary database, in use while you work. Reading it and copying from it are fine, and a good way to get real test data (see Test data). Never start a server against it, never open it read-write, never clean it up. - Baking in origins. Never set
VITE_HTTP_URLorVITE_WS_URLfor dev. Dev is single-origin and Vite proxies/api,/ws,/oauth, and/.well-known. Setting them bakes localhost into the bundle and silently breaks every remote browser.
Hit every surface
The most common defect in this repo is a change that works on the path you tested and is missing everywhere else. Before calling frontend work done, walk this list and say which entries applied:
- Entry points. A behavior reachable from the chat view is usually also reachable from Settings, the command palette, and a keybinding. Fixing one is not fixing the feature.
- Clients. Web, desktop (wraps web, adds Electron shell/IPC), and mobile (React Native, separate navigation). Shared logic lives in
packages/client-runtime - Providers. Codex, Claude, Cursor, Grok, OpenCode, and Antigravity each have an adapter. Provider-shaped features need a decision per adapter, even if the decision is "not supported here".
- Contracts. Anything crossing the wire is typed in
packages/contracts. Change the schema and the server, web, mobile, and desktop all follow. - Reverse states. If you added a way in, add the way out and the way to see it. Snooze needs unsnooze. Close needs reopen. A one-way door is a bug.
- Connection modes. Local, remote/relay, and tunnel behave differently. Multi-device and multi-environment cases are real.
- Docs.
docs/splits by audience. Behavior changes that a user would notice belong indocs/user/(shipped-product voice, no repo tooling or source paths); architecture and contributor changes indocs/internals/; runbooks indocs/operations/; new vocabulary indocs/internals/glossary.md.
Dev servers
vp iinstalls. Worktrees get this from the aviary.json setup script; if module resolution looks broken, it probably did not run.vp run devstarts server and web. In a worktree, state defaults to that worktree's gitignored.aviary, which deliberately outranks an ambientAVIARY_HOMEso you cannot land on shared state by accident. An explicit--home-dirstill wins.- Ports derive from the worktree path and are stable across restarts, but read the real ones from the
[dev-runner]line since occupied ports shift. - Sharing over the tailnet is three steps: run
vp run dev --sharein the background, wait for thepairingUrl:line in its output, paste that full URL (token included) in your reply. Do not wire uptailscale serveby hand for this, and do not open the URL yourself. - The web app requires pairing. Hand over the pairing URL, not the bare origin. A URL without its token is useless to whoever you gave it to. If the token got consumed, mint a fresh one with
node apps/server/src/bin.ts pair— note it carries standard scopes, while the startup URL carries admin scopes (needed for Settings → Connections management). - Stop what you started, by the PID you tracked. See rule 1.
Test data
An empty database is a bad test. Seed your worktree's .aviary with a copy of real data instead of pointing at live state:
-
Copy from
~/.aviary/userdata(the developer's real data, the most realistic test set) or~/.aviary/dev. Worktree state lives at<worktree>/.aviary/userdata. -
Snapshot the database with
VACUUM INTO, which is safe even while a server has the source open and yields one consistent file:mkdir -p .aviary/userdata rm -f .aviary/userdata/state.sqlite* # VACUUM INTO refuses to overwrite bun -e "new (require('bun:sqlite').Database)(process.env.HOME + '/.aviary/userdata/state.sqlite', { readonly: true }).run(\"VACUUM INTO '.aviary/userdata/state.sqlite'\")"A plain
cpis only safe when no server has the source open, and must bring the-waland-shmsiblings along. A live file copy is a corrupt copy. -
Bring
secretsandsettings.jsononly if the flow under test needs them. -
Copy in, never symlink. Data flows one way: into your sandbox, never back out.
Verifying
- Smallest proof that the change works.
vp test run <files>for the tests you touched, targeted lint and typecheck for the scope you changed. - Test meaningful logic or observable behavior. Do not render components to static markup to assert props or attributes, or add tests that merely assert callback wiring or mirror the implementation.
- Do not run repo-wide checks. No
vp check, novp run -r test, novp run -r typecheckunless I ask. CI owns the full suite. - Backend behavior changes ship with focused tests for that behavior.
- The server is event-sourced and its async flows emit typed receipts. Wait on receipts and worker drains, never on sleeps or polling. A test that needs a timeout to pass is wrong.
- Upon request, user-visible frontend changes should get one integrated pass in a real client:
test-aviary-appfor web,test-aviary-mobilefor mobile. The primary agent does this once after integrating. Subagents do not launch their own dev servers. Ask permission before doing computer use or spinning up browsers.
Pull requests
- Never make a PR unless the developer explicitly asks you to do so.
- Conventional commit titles, plain language:
fix(web): new threads no longer spike CPU. - Body: the problem in a sentence or two, then how you fixed it. End with the model and harness that did the work.
- UI changes need before/after images. Motion or timing needs a short video.
- Upload PR evidence to GitHub. Never commit PR-only screenshots or assets such as
.github/pr-assets/. - One concern per PR. If the description says "also", split it.
- When babysitting: poll checks and comments newer than the last push, verify each bot finding against the source, fix real ones, dismiss false positives with a written reason. Stay quiet when nothing is new. Stop when the bots are green on the latest commit.
How it works
Clients send typed WebSocket requests. The server turns them into commands, a pure decider turns commands into persisted events, and a projector derives the read model the UI renders. Provider CLIs run as subprocesses; per-provider adapters translate their native protocols into orchestration events. Side effects run in queue-backed reactors that emit receipts when milestones land. Each turn ends with a checkpoint, a hidden git ref, so the app can diff and restore.
Full glossary with file links: docs/internals/glossary.md
Where code lives
apps/server- WebSocket, orchestration, providers, checkpointing. Effect-heavy: read.repos/effect-smol/LLMS.mdbefore writing Effect code.apps/web- React/Vite UI.apps/desktopwraps it,apps/mobileis React Native,apps/marketingis the site.packages/contracts- Effect/Schema contracts plus small derived helpers. No heavy runtime logic.packages/shared- shared runtime utils, subpath exports, no barrel.packages/client-runtime- client code shared by web and mobile..repos/- vendored read-only references. Prefer their patterns over invented ones. Never edit or import from them. Sync withvpr sync:reposwhen bumping the matching dependency.
Taste
- Complexity belongs at the adapter boundary. Orchestration stays pure, UI stays dumb.
- Inferred types over annotations.
anyis the enemy. - Comments describe how a thing is used, and move when the code moves. To be used mostly to describe functions, not to annotate every line of behavior.
- Our users drive agents all day and notice a dropped frame, a lying spinner, and a stale label. No continuously repainting animations; they peg the GPU on high-refresh displays.
- If a rule here fights the task in front of you, say so loudly and get a human sign-off before breaking it.
Additional tips
- Don't verify with browsers or computer use unless the user explicitly agrees or requests it.
- Security is important, but should not be over-indexed on, especially for dev mode/maintainer-only features.