Imported from silurt/aios (
AGENTS.md). Install upstream withnpx skills add silurt/aios. Copyright stays with the author.
Agent Instructions
This project uses bd (beads) for issue tracking. Run bd prime for full workflow context.
Architecture in one line: Issues live in a local Dolt database (
.beads/dolt/); cross-machine sync usesbd dolt push/pull(a git-compatible protocol), stored underrefs/dolt/dataon your git remote — separate fromrefs/heads/*where your code lives..beads/issues.jsonlis a passive export, not the wire protocol.See SYNC_CONCEPTS.md for the one-screen overview and anti-patterns (don't treat JSONL as the source of truth; don't
bd importduring normal operation; don't reach for third-party Dolt hosting before trying the default).
Quick Reference
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work atomically
bd close <id> # Complete work
bd dolt push # Push beads data to remote
Non-Interactive Shell Commands
ALWAYS use non-interactive flags with file operations to avoid hanging on confirmation prompts.
Shell commands like cp, mv, and rm may be aliased to include -i (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input.
Use these forms instead:
# Force overwrite without prompting
cp -f source dest # NOT: cp source dest
mv -f source dest # NOT: mv source dest
rm -f file # NOT: rm file
# For recursive operations
rm -rf directory # NOT: rm -r directory
cp -rf source dest # NOT: cp -r source dest
Other commands that may prompt:
scp- use-o BatchMode=yesfor non-interactivessh- use-o BatchMode=yesto fail instead of promptingapt-get- use-yflagbrew- useHOMEBREW_NO_AUTO_UPDATE=1env var
Beads Issue Tracker
This project uses bd (beads) for issue tracking. Run bd prime to see full workflow context and commands.
Quick Reference
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
Rules
- Use
bdfor ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists - Run
bd primefor detailed command reference and session close protocol - Use
bd rememberfor persistent knowledge — do NOT use MEMORY.md files
Architecture in one line: issues live in a local Dolt DB; sync uses refs/dolt/data on your git remote; .beads/issues.jsonl is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
Agent Context Profiles
The managed Beads block is task-tracking guidance, not permission to override repository, user, or orchestrator instructions.
- Conservative (default): Use
bdfor task tracking. Do not run git commits, git pushes, or Dolt remote sync unless explicitly asked. At handoff, report changed files, validation, and suggested next commands. - Minimal: Keep tool instruction files as pointers to
bd prime; use the same conservative git policy unless active instructions say otherwise. - Team-maintainer: Only when the repository explicitly opts in, agents may close beads, run quality gates, commit, and push as part of session close. A current "do not commit" or "do not push" instruction still wins.
Session Completion
This protocol applies when ending a Beads implementation workflow. It is subordinate to explicit user, repository, and orchestrator instructions.
- File issues for remaining work - Create beads for anything that needs follow-up
- Run quality gates (if code changed) - Tests, linters, builds
- Update issue status - Close finished work, update in-progress items
- Handle git/sync by active profile:
# Conservative/minimal/default: report status and proposed commands; wait for approval. git status # Team-maintainer opt-in only, unless current instructions forbid it: git pull --rebase git push git status - Hand off - Summarize changes, validation, issue status, and any blocked sync/commit/push step
Critical rules:
- Explicit user or orchestrator instructions override this Beads block.
- Do not commit or push without clear authority from the active profile or the current user request.
- If a required sync or push is blocked, stop and report the exact command and error.
Beads Issue Tracker
Use Beads (bd) for durable task tracking in repositories that include it. Use the beads skill at .agents/skills/beads/SKILL.md (project install) or ~/.agents/skills/beads/SKILL.md (global install) for Beads workflow guidance, then use the bd CLI for issue operations.
Quick Reference
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
bd prime # Refresh Beads context
Rules
- Use
bdfor all task tracking; do not create markdown TODO lists. - Run
bd primewhen Beads context is missing or stale. Codex 0.129.0+ can load Beads context automatically through native hooks; use/hooksto inspect or toggle them. - Keep persistent project memory in Beads via
bd remember; do not create ad hoc memory files.
Architecture in one line: issues live in a local Dolt DB; sync uses refs/dolt/data on your git remote; .beads/issues.jsonl is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
Architecture
Read docs/plan.md before making structural decisions. It carries the locked
decisions (§0) and the reasoning behind them. Do not re-litigate a numbered
decision without saying so explicitly.
AIOS is a Rust daemon that runs coding harnesses (Claude Code, Codex) against a registry of projects, unifying issue tracking (beads), knowledge (Obsidian vault), and VCS behind one capability layer projected onto REST, MCP, CLI, and a generated OpenAPI spec.
This is a polyglot monorepo: crates/ (Rust core + the aios binary),
clients/apple/ (SwiftUI macOS + iOS over a shared AIOSKit package),
clients/ts/ (generated TypeScript client).
Work order — the tier rule
Priority is always, and per feature:
Tier 0 the aios binary -> Tier 1 local desktop client -> Tier 2 mobile client
core + CLI + API + MCP (macOS now) (iOS now)
- A capability lands in the binary first, usable from the CLI and exposed over REST and MCP. Only then does the desktop client render it, and only then does mobile take the subset that makes sense away from a desk. Never the reverse.
- The test: can you do it with
aiosin a terminal? If not, the core is not done and no client work should start. - Clients contain presentation only. No domain logic in view models. If a client needs to know a rule, the API is missing an endpoint.
- macOS and iOS are the current implementations of two client roles, not the roles themselves. Nothing in the core may assume Apple.
Conventions
- Nothing but
aios daemon *subcommands may touchaios-coredirectly; every other surface is an API client. - The
IssueTrackerport goes through thebdCLI. Never read.beads/directly. openapi.jsonis generated and committed; it is the contract for all clients.
Types and API compatibility
Every type that crosses a boundary is defined once, in crates/aios-types, and
nowhere else. OpenAPI, Swift and TypeScript models are all derived from it. See
docs/plan.md §15.
- Derive
Serialize, Deserialize, ToSchemaon every wire type.#[utoipa::path]will not compile withoutToSchema, so a type cannot reach the API without entering the derivation chain. #[serde(rename_all = "camelCase")]everywhere.- Enums are internally tagged:
#[serde(tag = "type", rename_all = "camelCase")]. Untagged and externally-tagged enums generate poor or wrong Swift. - Newtype ids (
ProjectId,RunId), never bareString. Noserde_json::Valuein wire types. - After changing any wire type run
just openapi. The committedopenapi.jsonbeing stale is a CI and pre-commit failure. - Changing the spec requires bumping
apiVersion; a breaking change also raisesminClientApi. CI classifies which viaoasdiffand fails if the bump is missing.
Capabilities
A capability is registered once in crates/aios-caps/src/caps/ and is thereby
callable from the CLI, MCP, and REST. Adding one means:
- Put its input/output types in
aios-types(never inline in the handler). - Register it in the relevant
caps/*.rsregister()function with agroup.operationname, a summary, and the correctEffect. Effect::Writeis not decoration: it drives MCP annotations, read-only agent profiles, and (from phase 3) whether a call requires an approval. A misclassification is a security bug.
Handlers must go through a port trait (IssueTracker, Knowledge, Vcs), never
call a tool directly. The composition root that binds ports to concrete adapters
is crates/aios-cli/src/app.rs -- the only place that names beads, Obsidian, or
git.
CLI commands call Capabilities::call(...) by name rather than the ports, so the
CLI exercises the same path MCP and REST will take.
MCP
aios mcp serve exposes every capability as an aios_* tool. The server
defines no tools of its own -- it enumerates the capability registry -- so
adding a capability adds a tool for Claude and Codex simultaneously.
aios mcp install [project]writes.mcp.jsonand a fenced managed region into CLAUDE.md / AGENTS.md. It merges rather than overwrites, and rewrites only what is between its markers.- Never
println!in a code path reachable frommcp serve: stdout is the protocol transport. Diagnostics go to stderr. - Capability handlers are blocking (they shell out). Anything calling them from
async must use
spawn_blocking.
Storage
No SQL engine. Two primitives in aios-core::store, both plain JSON:
DocStore-- one JSON file per document, for small config-shaped data read often and written rarely (the project registry). Writes are temp file -> fsync -> rename, so a reader never sees a half-written file.AppendLog-- newline-delimited JSON with monotonic sequences andsincereplay, for high-volume append-only streams (run events).
Rules:
- Store the wire type directly. Never hand-write a mapping layer between stored
and wire representations -- that is the drift
docs/plan.mdsection 16 exists to prevent. - Document ids become filenames and come from user and agent input; they are
constrained to
[A-Za-z0-9._-]by the store. Do not bypass that check. - Wrap check-then-write sequences in
DocStore::with_lock. - Never delete a user's data to tidy up. Report it (see
aios doctorand the legacystate.dbnote) and let them decide.
Runs and approvals
aios run start "<task>" spawns a harness, normalizes its output into
RunEvent, writes each event to ~/.aios/runs/<id>/events.jsonl, and persists
a Run document. aios run events <id> --since N replays from a cursor.
Approvals (see docs/plan.md section 7.1):
- Policy is ordered, first-match-wins, with an explicit default, in
~/.aios/policy.json(hand-editable). Unconditional allows become the harness's tool allowlist, so those calls never become questions. - Anything else raises an
Approvaland blocks the harness at a PreToolUse hook (aios approval gate) -- Claude Code has no --permission-prompt-tool, so MCP cannot serve this role.aios mcp installwrites the hook. - Expiry parks the run rather than killing it, and an expired approval can still be decided afterwards. Never make an unanswered approval fatal.
- Approvals settled by policy are still recorded. A decision that leaves no trace cannot be audited.
Rules: never print to stdout from a code path reachable by approval gate or
mcp serve -- both are protocol channels. Never treat RunStatus::Parked as
terminal.
The daemon
aios serve runs the daemon: one axum router over a Unix socket at
~/.aios/aiosd.sock (mode 0600). aios daemon install|start|stop|status|logs
manages it as a launchd LaunchAgent.
The API-only rule is in force. Only aios serve and aios daemon * touch
aios-core directly; every other command is an HTTP client over the socket, and
crate::client::Client::connect() autostarts the daemon if it is not running --
so the rule is not hostile. If a command needs something the API cannot express,
that is a missing endpoint, not a reason to reach into core.
The one exception is aios approval gate, which runs inside a harness process
and must work regardless of daemon state.
Notes:
- Run events stream over SSE with the sequence number as the event id, so
Last-Event-IDresumes exactly where a dropped connection stopped. - The stream finds new events by polling the run's JSONL log, not an in-process channel: a run may have been started by a different process, and a channel would never see it.
POST /api/runsreturns 202 with the run id immediately. Runs take minutes; holding the request open would make every disconnect look like a failure.- Capability calls run on
spawn_blocking-- handlers shell out to bd and git, and one slow call on a reactor thread stalls every event stream. - Unix socket paths are capped near 104 bytes by the OS.
servechecks and says so, rather than failing with "path must be shorter than SUN_LEN".
AIOS
This project is registered with AIOS, which serves its tools over MCP as
aios_*. Prefer them over ad-hoc shell commands for these tasks:
- Issues —
aios_issues_ready(unblocked work),aios_issues_list,aios_issues_create,aios_issues_close. Do not callbddirectly. - Knowledge —
aios_kb_searchbefore assuming something is undocumented;aios_kb_captureto record a decision worth keeping. - Projects —
aios_projects_listto see the other registered repos.
Every tool takes an optional project argument (slug, id, or path) and defaults
to the working directory, so it is normally omitted.