Interface audit
This audit lists what stays, what changes, and what goes on each of Orion's public surfaces before the first release. It's a snapshot of main on 2026-10-18, written for #146. The answers to its four questions are settled and recorded in the decisions. Public surfaces says what each surface promises today.
Each list uses three marks:
- Keep: stays as it is.
- Change: stays, in a different shape.
- Remove: goes, with no alias, as the three-stage decision asks before 0.1.0.
Each change links to the issue that carries it.
Since the snapshot
Most of the changes below have landed. The lists still describe main as it was on 2026-10-18, except Data files and Formats people write, which describe it on 2026-10-20.
- #485 builds the MCP tools from the CLI's command table, named after their commands, which settles #415.
orion_inspectandorion_syncare gone. - #525 refuses HTTP requests from other sites and drops the image routes from the agent guide, which settles #468.
- #486 and #533 store each canvas as a folder of node and link files read through a SQLite index, for #121.
orion.jsonbecomescanvas.json, and the data files section now describes the new layout. - #529 removes
orion sync,--discover, and--pr, which settles #469.--repostays for the repository a card belongs to. - #531 makes
--jsonoutput consistent across commands, which settles #470. - #530 runs
suggestion findthrough a command and refuses version 1 data, which settles #471. - #552 drops
commandfromorion_suggestion_find, and the tool catalog now refuses any tool that takes a program to run.
These calls were settled after the snapshot, each in the decisions. Where the lists below disagree, the decisions win.
createfor new things andaddfor things that already exist, under "CLI commands are a noun, then a verb".- Aliases for renames, in "Aliases and migrations share one lifecycle".
- MCP tools keep
canvasand ignore it, under "MCP tools come from the CLI's commands". - Commands stay on this machine, in "Programs Orion runs are chosen on this machine".
- Theme files build on Vibrant, under "Vibrant and Trading Card are the defaults".
- Nothing people write is promised at 0.1.0, in "What people write for Orion carries no promise until 1.0.0", and no
--jsonshape either, in "No JSON shape is stable before 1.0.0". - Plan operations follow the commands, in "Plans are the CLI written as JSON".
- Text failures go to stderr and JSON errors get a code, in "Failures go to stderr, and JSON errors carry a code".
--untilreads local time unless it gives an offset, in "A time without an offset is local".- A command-line guide page and generated syntax reference, in "The command line gets a guide page and a generated reference".
- One data version in
workspace.jsonfor the whole workspace, in "One data version covers the workspace".
The four questions
Should the MCP tools follow the CLI structure?
Yes. Each CLI command gets one tool, named orion_<noun>_<verb> after it, with arguments named after its options in camelCase. Top-level commands keep one word: orion_next, orion_focus, orion_ingest, orion_apply, and orion_reveal. #415 builds the tool catalog from the CLI's command table and derives the names from it, so they can't drift.
- Agents learn one vocabulary. The MCP reference already says which command each tool runs, so a name that matches makes that mapping obvious.
- Today's names mix three patterns: verb first, such as
orion_add_itemandorion_create_group; noun first, such asorion_log_addandorion_card_timeline; and bare verbs, such asorion_findandorion_tidy. Each new tool makes a later rename larger. - The cost is more tools, about 55. Every tool is small and has its own schema, which is what lets a client check a call before it runs. One tool per noun with a
verbargument, like today'sorion_inspect, would give less precise schemas for a smaller count.
Is the HTTP API internal?
Yes, and the server enforces it. The API that orion server start serves is internal to the app at every stage. Today, though, any web page open in a browser on the same machine can call it:
POSTroutes parse the body as JSON whatever its content type, so a page can send a cross-sitetext/plainrequest that needs no CORS preflight.POST /api/mergemerges a pull request throughghgiven its URL and head commit, which are public for a public repository.POST /api/canvases,POST /api/settings, andPOST /api/operationschange data the same way.- The server answers any
Hostheader, so a DNS-rebinding page can readGET /api/orion. /api/eventsaccepts a WebSocket from any origin.
#468 checks Host and Origin against the server's own address and the app's dev server, which closes all three. A token handed to the app at launch would also stop other local processes, and is worth adding only if the check alone proves too loose.
Which JSON shapes are stable?
None before 1.0.0, as Public surfaces and "No JSON shape is stable before 1.0.0" say. This first proposed promising a short list from 0.1.0: the item shape that card list, node show, node query, and next print, the batch write envelope, canvas check's list, focus, and canvas list. Those are what scripts and agents read most, so they're the likely first promises at 1.0.0, but shapes keep settling after 0.1.0 as commands are renamed and plans follow the CLI. The inconsistencies under JSON output get fixed first, in #470, since each fix breaks the shape it touches.
Data files are never a public interface for writing: tools go through the CLI. They carry a version, and the CLI migrates older versions as it reads them. The charter's data layer promise applies to them from 1.0.
How do breaking changes ship?
In the three stages the decisions already set. Before 0.1.0, a breaking change lands in one pull request that updates the CLI, the MCP tools, the generated reference, the agent guide, and the app together, with no alias. From 0.1.0, each break is deliberate and the release notes list it. From 1.0, aliases come first.
Three deprecated forms predate that decision and still promise a release of overlap: orion sync, --pr, and --repo used with --pr. They go now, with no alias, in #469.
CLI
| Keep | The noun-verb shape and its nouns: card, note, link, node, group, ref, canvas, source, media, suggestion, theme, backup, log, ci, server, and mcp. |
| Keep | Top-level next, focus, ingest, apply, reveal, and help. apply and reveal span every noun, and help comes from the same command table as --help, so it costs nothing to keep. |
| Keep | --where, --dry-run, --if-revision, and --json on batch commands, and selectors wherever a command takes an id. |
| Keep | --canvas, --help, and --version on every command. |
| Keep | ORION_DATA_DIR and ORION_CANVAS, with the option winning over the variable. |
| Change | Every command takes --json (#470). These don't yet: canvas create, rename, archive, restore, use, current, and path, link remove, backup restore, suggestion apply and dismiss, source add and remove, and ci snooze and unsnooze. |
| Change | With --json, a failure prints a JSON error and still exits nonzero (#470). Today errors are text on stderr only. |
| Change | Single-item writes take --dry-run and --if-revision: card add and set, note add, group add, create, and set, link remove, ref remove, media insert, and canvas set (#559). |
| Change | suggestion find --agent claude|codex runs those two tools by name, which is the per-provider code that "MCP stays generic" avoids. It takes a --command '<JSON array>' as source add jira and hop do (#471). |
| Change | One verb for making groups. group create makes an empty group and group add wraps items; fold them into group create <label> [--items …] [--in …]. |
| Remove | orion sync and --discover (#469). orion ingest refreshes pull requests too, and a GitHub source files new ones in Unsorted. |
| Remove | --pr on card add and set, and --repo as its partner (#469). Components may still want --repo for the repository they belong to, or a repository URL through --ref; #469 settles which. |
The variables ORION_BIN, ORION_PERF, ORION_STORYBOOK, and ORION_COMMIT belong to the app's dev server, perf, Storybook, and desktop builds. They stay contributor-only and out of the public docs. ORION_WORKSPACE is already gone.
MCP tools
| Keep | orion mcp start over stdio, each tool running a CLI command in the same process, and the server's instructions drawn from the CLI's orientation text. |
| Keep | Tools only, with no resources or prompts until something needs them. |
| Keep | The -32000 error that asks the client to reconnect after Orion is updated. |
| Keep | readOnlyHint, destructiveHint, and openWorldHint annotations. |
| Keep | Calls that meet a held lock wait their turn, as #398 made them. |
| Change | Names follow the CLI, as in the table below, with #415. |
| Change | orion_inspect splits into one tool per command it runs, with #415. Its view argument switches between five commands, so its schema can't say which arguments go with which. |
| Remove | orion_sync, and the repo and pr arguments, with their CLI forms in #469. |
| Today | With #415 |
|---|---|
orion_canvases | orion_canvas_list |
orion_create_canvas, orion_rename_canvas, orion_update_canvas, orion_archive_canvas, orion_restore_canvas | orion_canvas_create, orion_canvas_rename, orion_canvas_set, orion_canvas_archive, orion_canvas_restore |
orion_select_canvas, orion_check | orion_canvas_use, orion_canvas_check |
orion_inspect with view set to list, ready, groups, matches, or show | orion_card_list, orion_next, orion_group_list, orion_source_matches, and orion_node_show |
orion_find, orion_query, orion_move, orion_remove | orion_node_find, orion_node_query, orion_node_move, orion_node_remove |
orion_add_item, orion_update_item, orion_done, orion_import | orion_card_add, orion_card_set, orion_card_done, orion_card_import |
orion_snooze, orion_unsnooze | orion_card_snooze, orion_card_unsnooze |
orion_snoozed_jobs, orion_snooze_job, orion_unsnooze_job | orion_ci_list, orion_ci_snooze, orion_ci_unsnooze |
orion_add_note | orion_note_add |
orion_link, orion_unlink, orion_insert | orion_link_add, orion_link_remove, orion_link_insert |
orion_group, orion_create_group | orion_group_create, once group add folds into it |
orion_update_group, orion_remove_group, orion_ungroup, orion_tidy | orion_group_set, orion_group_remove, orion_group_ungroup, orion_group_tidy |
orion_remove_ref | orion_ref_remove |
orion_images, orion_add_image | orion_media_list, orion_media_insert |
orion_themes, orion_add_theme, orion_remove_theme | orion_theme_list, orion_theme_add, orion_theme_remove |
orion_sources | orion_source_list |
orion_suggest, orion_suggestions, orion_apply_suggestions, orion_dismiss_suggestions | orion_suggestion_find, orion_suggestion_list, orion_suggestion_apply, orion_suggestion_dismiss |
orion_backups, orion_restore_backup | orion_backup_list, orion_backup_restore |
orion_log_add, orion_log_list, orion_card_timeline, orion_ingest, orion_apply, orion_reveal, orion_focus, orion_server_status | Already match |
orion_sync | Removed in #469 |
HTTP API
The app is the only client. Agents and scripts use the CLI or MCP.
| Route | Methods | What it's for |
|---|---|---|
/api/orion | GET | The canvas document |
/api/operations | POST | Edits to the canvas, applied by the server |
/api/orion/style | PUT | The canvas's cover, background, and header |
/api/orion/check-snoozes | PUT | CI jobs snoozed from the inspector |
/api/focus | POST | The app's open group and selection |
/api/canvases, /api/canvases/active | GET, POST | List, create, and switch canvases |
/api/images, /api/images/<asset> | POST, GET | Upload and serve images |
/api/log | GET | The canvas log |
/api/settings | GET, POST | The app's settings |
/api/themes | GET | Theme files |
/api/sync | GET, POST | Refresh status, and a refresh now |
/api/merge | POST | Merge a pull request through gh |
/api/editor | GET, POST | Whether an editor is available, and open a pull request in it through hop |
/api/links | POST | Link previews |
/api/reveal | POST | Reveal cards in the app for orion reveal |
/api/events | WebSocket | orion:changed, orion:canvas, orion:canvases, orion:log, orion:reveal, and orion:sync |
| Keep | Internal at every stage, with no promise and no aliases when a route changes. |
| Keep | One process owning the data, with the app writing the document through POST /api/operations. |
| Change | Refuse requests whose Host or Origin isn't the server's own or the app's dev server, as the HTTP question explains, and send a content security policy with the static app, since the desktop shell sets none (#468). |
| Change | Edits beyond the document, such as the canvas style and check snoozes, go through POST /api/operations too, so the app has one write path (#560). |
JSON output
| Keep | Lists as arrays, one item as an object, and the batch write envelope. |
| Change | Groups and notes get shapes of their own. group add --json and group set --json print a group with kind: "step", status, and waitsOn, and a note prints status: "done". A group should print what group list does, plus its id. |
| Change | Every write prints the revision it made, so the next command can pass --if-revision. Batch writes do. card add, card set, note add, and group add print only the item. |
| Change | Failures print {"error": {"message": …}} under --json, as in the CLI list. |
| Change | Times use RFC 3339. backup list prints 20261017T164600Z, taken from the backup's file name. |
| Change | An absent value is null or a missing field, not an empty string. suggestion list prints createdAt: "" when nothing has run. |
#470 carries every change in this list.
Data files
This section describes main on 2026-10-20, after #486 and #534 split each canvas into files and #554 moved source commands out of them.
Each canvas's folder holds these. The default canvas's folder is the data folder itself, and every other canvas has one under canvases/<name>/.
| File | What it holds |
|---|---|
canvas.json | The canvas's settings and sources, without their commands. Version 2. |
nodes/<id>.json | One card, note, group, or image, with its order key and when each field last changed |
links/<id>.json | One link |
local.json | What this machine recorded: the revision, cached GitHub results, and suggestions. Kept out of git. |
index.sqlite | An index rebuilt from the files, holding nothing they don't. Kept out of git. |
orion.lock | The write lock |
orion.writing | A write in progress, replayed on the next start if it didn't finish |
backups/ | A copy before every write |
assets/ | Image files |
log.jsonl | The canvas log |
focus.json | The app's last focus report |
The data folder also holds these, shared by every canvas:
| File | What it holds |
|---|---|
canvases.json | The canvas list, labels, the active canvas, and which are archived |
settings.json | App preferences |
commands.json | The programs this machine runs for each canvas's sources, keyed by canvas and source |
themes/ | Theme files |
serve.json | The running server's process, address, and build |
Migrations leave orion.backup.json and workspaces.backup.json behind. The desktop app keeps its server port in server-port under its own app data folder.
| Keep | The CLI owns every file. Nothing else writes them, and the lock and revision rules stay as Data and canvases describes. |
| Keep | The split between files that go in git and files that don't. local.json, index.sqlite, and commands.json never travel with a canvas. |
| Keep | A version in canvas.json, checked on read, and the moves in workspace/orion-cli/src/migrations/, under "Data folders move to a new layout on their own" and "Aliases and migrations share one lifecycle". |
| Change | Node vocabulary matches the public words. Nodes store type: "task" or "cluster", repeat it in data.kind, and keep step, gate, note, and component in data.itemKind. Rename these in version 3 when #122 splits nodes from placements, since that rewrites the shape anyway. |
| Change | serve.json becomes server.json, after the command, as a migration (#561). |
| Change | Only canvas.json carries a version, and node and link files carry none. One version in workspace.json will cover the whole workspace folder, with canvas.json's standing in until the workspace folder lands in #568, as "One data version covers the workspace" says. |
Formats people write
The surfaces above cover what Orion prints and stores. These are what people and agents write for Orion to read: plans, selectors, refs, queries, times, theme files, and the programs a source or suggestions run. It also covers how a failure looks to a script. This section describes main on 2026-10-20.
None of these is promised at 0.1.0. They stay unpromised until 1.0.0, and a rename keeps the old form working through an alias in the meantime, as "What people write for Orion carries no promise until 1.0.0" says. The changes below are settled in the decisions.
Plans
orion apply and orion_apply read a JSON list of operations that lands whole or not at all. An operation can declare a handle, and later ones refer to it as "@handle".
| Keep | One list, applied in order in one transaction, with handles, --dry-run, and --if-revision. |
| Change | Operations follow the CLI. Today they're addCard, addNote, group, setFields, link, unlink, insert, move, ungroup, tidy, remove, and removeGroup: the internal operation names from #93, which POST /api/operations also takes. Their fields differ from the options, too: move takes group where node move takes --to. Name each operation after its command path, such as card create, with fields named after its options, so a plan is the CLI written as JSON and the internal operations can change freely, as "Plans are the CLI written as JSON" says. This follows the verb renames in #566, in #567. |
| Change | Document the operations on a generated reference page. Today only the orion_apply description lists them. |
Selectors and refs
| Keep | An id argument takes an id or a selector: label:"Extract slider", ref:jira:ABC-12, or pr:86. A selector must match exactly one item, and an ambiguous one lists its matches. |
| Keep | --ref and card import take a GitHub or Jira URL, source:key such as jira:ABC-1, or branch:owner/name@branch. |
| Keep | --where compares quoted values with =, !=, and ~, joined by and, or, and not, over the fields kind, repo, status, recordedStatus, group, draft, label, note, pr, ref, and snoozed. It evaluates no code and reads no other data. |
| Change | The --where grammar is written down only in orion help node query and the agent guide. Add a guide page for working from the command line, and generate reference pages for the grammar, selectors, and ref forms from the code that parses them, as "The command line gets a guide page and a generated reference" says. |
Times
| Keep | Output times in RFC 3339 UTC, as #531 made them. |
| Change | card snooze --until reads an ISO 8601 date or date-time in local time unless it gives an offset, and a date means the start of the local day. ci snooze --until reads only a UTC time or a date, which means midnight UTC, and refuses an offset. The same 2026-10-21 ends the two snoozes hours apart. Read both with time/parseUntil, local unless the time gives an offset, as "A time without an offset is local" says. |
Failures and exit codes
| Keep | Exit 0 on success and nonzero on failure, with canvas check exiting 1 when it finds problems. Under --json, a failure prints {"error": {"message": …}} on stdout, where a script already reads. |
| Change | Without --json, a failure prints Error: … on stdout, so orion card list > cards.txt writes the error into the file. Print it on stderr. |
| Change | Every failure exits 1, so a script can't tell an --if-revision conflict from a typo without matching the message. Add an error.code to the JSON error for the failures a script acts on, such as a revision conflict, a missing item, and an ambiguous selector, rather than more exit codes. Every failure still exits 1, as "Failures go to stderr, and JSON errors carry a code" says. |
Theme files
| Keep | The format in Themes: name, author, and light and dark objects keyed by token name, with values checked so a file can't load anything or add rules. Token names are already a contract. |
| Change | Tokens says its defaults are Classic's, and that a theme file builds on them, but #532 deprecated Classic. Once Classic goes, Vibrant's values become the defaults a theme file builds on, as "Vibrant and Trading Card are the defaults" now says. Update the tokens page when that lands. |
Programs Orion runs
A Jira source runs a command that prints Jira's search JSON, such as acli jira workitem search --json. suggestion find --command sends the cards as a prompt on stdin and reads groups from the JSON the program prints.
| Keep | Both stay unpromised. The commands live on this machine and never come from a canvas or an MCP call, as "Programs Orion runs are chosen on this machine" says. |
| Remove | --command on sources, once adapter packages replace it, as "Adapters are Lute packages" plans (#563). |
Desktop shell
The shell starts the bundled orion server start --static on a saved port and opens a web view on it. The orion command links to the binary inside the bundle.
| Keep | One IPC command, process_usage, for the debug bar. Everything else goes through the server. |
| Keep | The opener and window-state plugins, and the Acknowledgements window. |
| Keep | No URL scheme or deep links yet. When #176 adds links to cards, an orion:// scheme should open them through the same path as orion reveal. |
| Change | The capability that lets pages call IPC covers every port on 127.0.0.1. Narrow it to the server's port if Tauri allows it at runtime (#562). |
| Change | orion reveal launches ~/Applications/Orion.app when no app is open, but Orion installs to /Applications (#558). |
Where the docs differ
README.md matches this audit. The agent guide in AGENTS.md and agents/ matches the code today, and these lines change with the follow-ups:
