The API
Every command, and TIDE, reaches wdx through the daemon’s API: HTTP over the Unix socket ~/.wdx/wdx.sock. The API is written first, in api/openapi.yaml, and the server’s interface, its types and the client are generated from it, so the spec and the code cannot drift apart.
One command’s path
Section titled “One command’s path”- The CLI parses the flags, makes paths absolute, as the daemon does not share your working directory, and picks a debug id,
dbg-and 12 hex digits. - The client sends the request with the id in a
Wdx-Debug-Idheader. - The daemon checks the request against the spec before any code of its own runs: a request the spec does not allow is a 400.
- A request that changes anything runs one at a time, and to its end even when the client leaves: no two changes race, and a change is never left half done because a command was interrupted.
- The domain does the work and returns a value, or a typed error that knows nothing of HTTP.
- The daemon turns an error into Problem Details, the standard JSON for HTTP errors, with a stable
codefrom one list. That code decides the status and the CLI’s exit code. - The CLI prints text, or with
--jsonthe body exactly as the daemon sent it, and exits 0, 1 (rejected), 2 (usage) or 3 (system failure).
Resources are plural nouns under /v1. A project in a path is its name, or id: and its id.
Calling it yourself
Section titled “Calling it yourself”Scripts should use the CLI with --json: it prints the same bodies, gives the exit codes, and needs nothing but wdx. The API is there all the same, and curl can call it:
curl --unix-socket ~/.wdx/wdx.sock http://wdx/v1/projects{"projects":[{"created_at":"2026-11-08T00:47:41.306Z","id":"proj-22ca285c","name":"app","origin":null,"state":"closed","updated_at":"2026-11-08T01:31:13.129Z","worktree_count":2}]}A project that is not there is a 404, as Problem Details:
{"code":"not_found","debug_id":"dbg-2d9eacf5e16a","detail":"project not found: \"nope\"","details":null,"hint":null,"status":404,"title":"nothing has this name or id","type":"urn:wdx:error:not_found"}Every code, its status and its exit code is in Errors and exit codes. An HTTP reference generated from the spec is to come; until then the spec is the reference.
One id through both logs
Section titled “One id through both logs”The CLI and the daemon log to the same daily file, and every line of a command carries its debug id. A command that failed prints it:
$ wdx project get --name nopewdx: project not found: "nope" [not_found] hint: see every project with wdx project list --all debug_id: dbg-dc61ff467dbeand its lines in the log tell the whole story, from both processes:
cli INFO command receivedcli INFO command starteddaemon INFO request receiveddaemon DEBUG project looked updaemon ERROR request returnedcli INFO daemon calledcli ERROR command returnedEach received line has what was asked, and each returned line the exact response. The Observability guide shows them in Grafana.
The project stream
Section titled “The project stream”TIDE does not ask again and again what changed. It follows each open project’s stream, GET /v1/projects/<project>/events, as server-sent events. The first event is the project whole: its agents, terminals, docs and plans. After any change under it, whoever made it, the project is sent whole again, never a delta, so a client only ever replaces what it holds:
event: projectdata: {"project":{"agents":[{"aca":"claude_code","activity":null, … ,"name":"reviewer", …With ?worktrees=true each event also carries the worktrees’ changes, which the daemon reads only while such a stream is open, and only when something says they changed: see Worktrees and watchers.
In the repository
Section titled “In the repository”api/openapi.yaml: the API, every path, body and error.internal/api/codes.go: each error code’s status, exit code and description.AGENTS.md, its API, CLI conventions and Logging sections.internal/daemon,internal/client,internal/cli.