The Code Hands AI desktop app: the window where you pick a repo, describe a task, choose the agent and the model for each phase, watch the work happen live, read the change, and approve or reject it.
It is the main way people use Code Hands AI. Everything a task needs runs on the same machine as the app.
Owner: Tania Contracts: L1 (local DB), C3 (cloud API), C5 (sessions) in codehands-contracts Part of: Code Hands AI. Read the design in that README first. Status: not started. This repo has no code yet. This README describes what the app must do and how it fits, so it can be built against stand-ins from day one.
The app never talks to the runner and never runs a task itself. It writes what the person wants into the local database, and reads back what happened. The daemon does the work.
flowchart LR
subgraph M["Your machine"]
app["Desktop app (this repo)"]
db[("~/.codehands/state.db")]
daemon["Daemon"]
runner["Runner"]
kc[("OS keychain")]
app -- "writes tasks, commands" --> db
db -- "reads sessions, events, provider_status" --> app
db <--> daemon
daemon --> runner
app -- "app token" --> kc
end
subgraph C["Cloud: state only"]
api["State API"]
end
app -- "C3: sign up, log in, log out" --> api
One writer per local table. The app writes tasks and commands, and nothing else. The daemon writes sessions, events and provider_status, including the rows that sync down from another machine.
So every action in the app becomes a row:
| The person | The app writes | The daemon then |
|---|---|---|
| Starts a task | A tasks row: project, prompt, agent, model per phase |
Runs preflight checks, creates the session, calls the runner |
| Stops a running task | A stop command |
Stops the runner. Status becomes stopped |
| Resumes it | A resume command, with an optional new prompt |
Resumes in the same worktree |
| Approves the change | An approve command with a commit message |
Commits on the task's branch. Status becomes approved |
| Rejects it | A reject command |
Deletes the worktree and branch. Status becomes rejected |
The app shows the result by reading sessions and events. It never changes a status directly.
| Area | Behaviour |
|---|---|
| Sign up and log in | Against the cloud state API (C3). The token goes into the OS keychain, never a file. Logging out revokes only this machine's token |
| Signed-in state | On sign-in, the daemon pulls your sessions, memory and preferences, so a second machine looks like the first |
| Projects | Add a repo by picking a folder. A project is identified by a hash of its origin URL, so the same repo on another machine is the same project |
| New task | Prompt, agent (Claude Code or Codex), and a model per phase. Defaults come from your preferences |
| Session list | Every session with its status, agent, cost and time |
| Live view | Events as they arrive: messages, files read and changed, commands and their output, test results, cost |
| Diff and approve | The full change, file by file. Approve with a commit message, or reject |
| Provider problems | Clear messages for each failure state (below), before a task starts and while it runs |
| Restart | Comes back exactly as you left it |
The seven statuses and their legal moves come from @codehands/contracts. Use canTransition() to decide which buttons to show, so the app never offers a move the daemon will refuse.
| Status | Show | Actions |
|---|---|---|
queued |
Waiting to start | |
running |
Live events | Stop |
stopped |
Where it stopped | Resume |
awaiting_approval |
The diff and its summary | Approve, Reject |
approved |
The branch and commit to merge | |
rejected |
That the change was discarded | |
failed |
The reason from task.failed |
The daemon checks these before starting a task and the runner reports them during one, as a provider_status row and a provider.status event. The app turns each into something the person can act on:
| State | The app shows |
|---|---|
rate_limited |
When it can retry, and offers the other agent |
auth_expired |
Sign in to that agent again on this machine |
usage_limit |
When the plan's cap resets |
subscription_inactive |
That the agent is unavailable |
down |
That the provider is down |
- Store an API key, agent login or the app token anywhere but the OS keychain.
- Send repo files, credentials or raw agent output to the cloud.
- Write to
sessions,eventsorprovider_status. - Start the agent CLIs or touch git itself.
| Piece | Choice | Why |
|---|---|---|
| Shell | Tauri 2 | A small native binary, and the Rust side can reach the keychain and the local database |
| UI | React with Vite, TypeScript | Shared with the rest of the team, fast reloads |
| Local data | ~/.codehands/state.db (SQLite), table types from contract L1 |
The daemon owns the schema and migrations |
| Secrets | Tauri's keyring or stronghold plugin | The OS keychain on macOS, Windows and Linux |
| Logs and terminal output | xterm.js | Thousands of lines a second without dropped frames |
Every part the app depends on ships a stand-in, so the app can be built and demoed on its own:
| Depends on | Stand-in | Swap for the real one when |
|---|---|---|
| The daemon writing sessions and events (L1) | A fake writer that fills sessions and events, and codehands task add |
The daemon reads tasks and runs them |
| The cloud API (C3) | A mock server with accounts in memory | The state API is hosted |
| Session data (C5) | Sample rows | Real runs |
Swap one stand-in at a time on a Friday. If something breaks, write it down and keep the stand-in.
You need Node 22, pnpm 10, the Rust toolchain (rustup), and the Tauri prerequisites for your OS.
git clone https://github.com/devweekends/codehands-contracts.git
git clone https://github.com/devweekends/codehands-desktop.git
cd codehands-contracts && pnpm install && pnpm buildThe first commit scaffolds the app with pnpm create tauri-app (React, TypeScript, Vite) and adds the contracts:
After that, update this section with the real commands (pnpm tauri dev, the stand-in switch, how to point at a local mock server), and the layout of src/ and src-tauri/.
| When | Milestone |
|---|---|
| Month 1 | The whole loop from the app: pick a repo, start a task, watch it live, read the diff, approve. Keys in the keychain. Which model did each step |
| Month 2 | Model per phase in the task form, cost on screen |
| Month 3 | Diff and artifact viewers, memory viewer, used daily by the team |
| Month 5 | Devices list with revoke, the same account on a second machine |
| Month 6 | Signed installers for macOS, Windows and Linux, updates in place |
Details are in the plan under SIG Product.
- One branch per Linear issue, the issue id in the PR title.
mainis always green. CI runs build and test on every PR.- Anything the app needs from another part (a new column, a new event field) is a contract change: open it in codehands-contracts.