nodwyn.

Clients: what each front-end does

nodwyn has four ways in, over one core:

  • The console: the browser view, also shown by nodwyn app and nodwyn ui (internal/frontend/web, browser.md, app.md). It always talks to a daemon, through the bridge in internal/frontend/web/server.go.
  • The TUI: nodwyn tui (internal/frontend/tui, tui.md). It always talks to a daemon: the one on the default socket or --socket's, or one it starts in its own process when none answers, as nodwyn app does (ADR 30, amended 2026-10-17). Its operations are calls in remote.go and beside each view (inspect_backend.go, models.go, judges.go, retrieval.go).
  • Line mode: nodwyn or nodwyn chat (internal/frontend/plain), the scriptable client. It talks to the daemon on the default socket, or one it starts in its own process when none answers, as the TUI does (ADR 30, amended 2026-10-17); its operations are calls in daemon.go, and "Line mode and the daemon" below says what became of its per-run flags.
  • The CLI: the other subcommands in cmd/nodwyn/cli.go (sessions, replay, versions, undo, credential, keychain, mcp, trust, devices, startup, run, summary, eval, repl).

This page records what each one can do as of 2026-10-17 (the judge choice, ADR 47, a task's helpers, ADR 27, a task's own limits, ADR 49 amended 2026-10-27, retrieval, ADR 50, the TUI and line mode on the daemon, the console as a phone's app, ADR 52, devices that pair by invitation and sign in with a passkey, ADR 30 amended 2026-10-17 twice, workflows, ADR 53, and several hosts in one window, ADR 54, included), so work on a client starts from it instead of a fresh survey of the code. It does not replace the per-client docs. A change that gives one client something updates its row here in the same commit, and a gap is either not built, saying where that is recorded, or deliberate, saying why.

In the tables, yes means built and tested. — means absent: the notes say whether that is deliberate or simply not built. read means it can see the thing but not change it.

Tasks and sessions

Console TUI Line mode CLI
Start a task in a folder yes, Home's ask bar yes, the chooser's row or --repo yes, --repo —
Start in a git worktree (ADR 34) yes, the default for a git workspace yes, the chooser's first row in a repository's top folder; --worktree not built (ADR 34) nodwyn run: not built
The worktree's branch shown yes, the header and sidebar yes, the title and the chooser — —
Resume a stored session yes, the sidebar yes, the chooser (paged, typing filters it) or --resume yes, --resume —
Switch between open tasks yes, the sidebar; several open at once one session per process; quit and choose again one session —
Tasks needing an answer, across tasks yes, Home (attention.list) not built — —
Several hosts in one window (ADR 54): the tasks of this computer and of the nodwyns it is linked to, grouped by host (a host's heading folds its folders, and a host starts folded), Needs you across them, Runs on for a new task, a linked host's tasks opened, answered and given files yes, this computer's console (/socket?host=, /attach?host=); what a linked host lets its link do, its settings in Settings (#/host/NAME/settings) when the link is the person's and the host was not started with --no-link-settings (General, and Models, where the link adds, changes and removes models among the host's providers, not the providers), and the rest of its setup in its own console not built: this computer's tasks not built: this computer's tasks —
Told when a task needs an answer while looking elsewhere yes, Settings › This browser › Notifications; on a phone through the console's service worker (ADR 52); with no window open, the daemon's desktop notices (Settings › General › While you're away, ADR 53 §11) not built — —
Application shortcut icon yes, the macOS bundle; Linux native window and scripts/install-desktop.py launcher use the console mark — — —
Installed on a phone's Home Screen (ADR 52) yes, over https: the console is a web app deliberate gap: a terminal client — —
Create and manage schedules (ADR 49) yes, Home’s Options › Schedule… and a task’s Schedule… (in its ⋯ menu): set times on chosen weekdays, an interval within hours of the day, monthly dates or weekday, once, a first minute and a last day; the daemon's sentence, next times and zone name; Home’s cards pause and run now; a device signed in with a passkey creates and changes them, and other paired devices can pause or run now not built, and it does not show a session.schedule record or a run's marker either not built not built
A schedule's recent runs: when, how each ended or why it was skipped, cost (history in schedule.read, ADR 49 amended 2026-10-17) yes, Recent runs in a task's Schedule… not built — replay shows the records
Every workflow in one place, with today's runs and spend and a daily spend limit (ADR 53, slices 2 and 6): timetables and the ones that watch a source, pause, resume, check or run now yes, the Workflows page; New timetable opens Home's Schedule dialog read and act: :workflows, :workflows pause\|resume\|check\|run REF (REF is in brackets in the list) — nodwyn workflows, nodwyn workflows pause\|resume\|check\|run REF
A workflow's answer held to be posted or saved as a draft reply (ADR 53, slices 4, 5 and 6): post, edit, discard yes, Home's Needs you (Post review or Save draft, Edit on this computer, Discard); a paired device posts or discards what the run wrote :outbox, :outbox post\|discard REF; deliberate gap: no editing the words, which is the console's — nodwyn outbox, nodwyn outbox post\|discard REF; the same gap
A workflow that watches pull requests, email or a command (ADR 53, slices 3 and 5): build with Test the trigger, change, remove yes, Workflows › Watch for events, on this computer; a paired device sees, pauses and checks deliberate gap: a form of cards and a live test is the console's; the TUI lists, pauses, resumes and checks them (above) — the same gap
Name a task yes (session.rename); a task nobody named shows the title the model wrote from its first message (ADR 39, amended 2026-10-23) not built; lists first messages — nodwyn sessions lists first messages
Archive, delete (ADR 39) yes; delete on this computer, or on a device signed in with a passkey after a touch of it not built — —
Quit while a turn runs the task goes on in the daemon Ctrl+Q detaches and the turn goes on; when the TUI's own process serves the daemon, quitting ends its tasks, as closing nodwyn app does aborts it —
Come back after the connection drops yes, by itself after a wait that grows to 30 s, at once when the page is shown again or the network returns; reloads the open task from its log and sends nothing again; a revoked device stops trying; back to an upgraded daemon (another version in hello), says so and offers Reload, keeping the reply drafts until the reader reloads Ctrl+R reconnects; not built: reconnecting by itself ends, saying it lost the daemon; not built: reconnecting —
Close a task: its log released, a worktree with nothing unlanded removed (session.close) yes, the task's ⋯ menu, Close yes, :close, which then quits quitting closes a task it opened; one another client had open stays open —

The conversation

Console TUI Line mode CLI
Jump from a search match to bounded, read-only turn history (log.context) yes; load earlier/later within that turn, or open the live task deliberate gap: replays whole history replays whole memory recall reads an event and the turn’s card
Open at the latest run; page back through collapsed earlier runs (ADR 49) yes; earlier contents load on expansion deliberate gap: replays the conversation whole replays whole replay reads whole
Send a message, abort a turn yes yes; Ctrl+C aborts, and clears an unsent draft before it quits yes —
Queue a message behind a running turn, or steer with it (stop the turn and send it next) yes; Queue and Steer beside Send while a turn runs, the message shown as queued until it starts yes; Ctrl+O queues the draft, Ctrl+S steers with it, shown as queued> until it starts — (line mode reads no line while a turn runs) —
A turn another client or the schedule started live live shown when line mode next reads a line, with its message —
/compact, /optimize yes yes yes —
Evaluate Scheme (session.eval) deliberate gap: the console never offers it (protocol.md) yes, :eval yes, :eval (recorded as eval.req and eval.result, as in the TUI) nodwyn eval, nodwyn repl
Thinking inline, folded :thinking toggles it --show-thinking --show-thinking replay
Tool calls grouped into one Activity row per stretch between replies one line per call; :details expands them a card per call —
Retrieval calls said in words (index-search and where it searched, recall and its seq, index-update; ADR 50) yes, folded as looks (searched 3 times · recalled 1 event · updated the index) yes yes replay
An index build or refresh (index.update) a line under the call that made it a line under the call a line replay
Usage per reply (tokens, cache share, cost) yes; reported cache reads win, otherwise timing estimates show ≈ yes; the same precedence yes; the same precedence —
Rendered markdown yes — (deliberate: plain text in a terminal) — —
Attach a file (ADR 22) yes, the composer's paperclip (staged upload) yes, :attach PATH not built —
An image's confirm yes, inline yes, as any prompt not built —

Approvals and policy

Console TUI Line mode CLI
Answer a confirm once, for this turn, or for the session yes yes: y, n, t, a yes —
Save a binding (session.binding.create) yes, on this computer yes, b yes nodwyn credential
Masked secret prompts yes yes yes —
Budget checkpoints (an older log's; no limit asks before it stops now) yes yes yes —
Don't ask (session.approve_all) yes, when Settings offers it; a paired device sees it while on and turns it off not built --yolo turns it on at start, when Settings offers it nodwyn run --yolo answers a run's confirms itself
A task's own limits (limits, session.limits.set, ADR 49 amended 2026-10-27) yes: the task panel's Limits, add, change and remove; a paired device adds or lowers only; a CLI run's absent time and turn limits show as "no limit" on Home, with no maximum or expiry in its heading and sidebar read: :budget shows them, "no limit" for each not set, and the conversation each change; not built: setting them yes: --max-tool-calls, --budget-tokens, --budget-actions, --budget-wall, --budget-cost for the task opened, or in place of the resumed task's nodwyn run: the same flags, --turn-wall for each turn's time, besides the run's optional --budget-wall and --max-turns
Edit allow.toml (session.policy.write) yes, the policy editor deliberate gap: :bindings reads only — —

Models (ADR 40, ADR 41)

Console TUI Line mode CLI
See the model and its efforts yes, the picker yes, :model the banner names the model —
Move the conversation to a configured model yes yes, :model NAME [EFFORT] not built —
Choose a model as a task opens yes, New task's picker; the next task starts on the model, effort and judge chosen last, kept by the daemon (newtask.last, ADR 41, amended 2026-10-27) not built: it opens on [control]; use :model before the first message --model NAME: a [models.NAME], or a model on [control]'s server (with --resume, moves the task to it); --url is refused nodwyn run: not built
A model on a server with no [models.NAME] entry (server + model) not offered: the picker lists names not offered — —
A choice while a turn runs is queued yes yes, and the conversation says so — —
Choosing from a paired device yes, signed in with a passkey; refused to one paired by a plain invitation (ADR 30, amended 2026-10-17) — — —
Edit a model's labels (ADR 27, amended 2026-10-09) yes, Settings › Models (on a linked host too: its models are added, changed and removed there, ADR 54); reaches open tasks not built — edit config.toml
Let a task's model hand work to other models (delegates) yes, New task's Helpers fold, on this computer or a device signed in with a passkey; the helpers ticked last are the next task's not built: a task opens with none none nodwyn run: none

Changing a task while a turn runs

A task can be changed while a turn runs in four ways, set once here (the checks that keep a turn, an attachment and a change from overlapping are one function, Session.busyWith, in internal/core/session.go):

Change While a turn runs
Move to another model queued, applied when the turn ends (ADR 40)
Send a message refused, as a plain send always is while a turn runs; a message sent as queue waits and runs when the turn ends, and one sent as steer stops the turn and runs next
Move to another judge, or none refused: "choose its judge when this turn ends"
Attach a file refused until the turn ends
A task's limits set applies from the next turn
Don't ask applies at once

The model queues and the judge refuses on purpose, for now: the queue and its "will apply when the turn ends" state were built for the model (ADR 40) and no client shows one for the judge. Making the judge queue is a change in every client's picker, not in the core's guard.

Queued messages are held by the session, first in first out; a steering message goes ahead of them. Stop discards them with the turn. A model chosen while the turn ran is applied first, so a queued message runs on it. An attachment or a model switch under way, no model chosen, and a task a run drives refuse a queued or steering message as they refuse a plain one, and an evaluation is never queued.

The judge (ADR 46, ADR 47)

Console TUI Line mode CLI
See the task's judge, and the judges set up yes, the picker's Judge select yes, :judge — —
Choose a task's judge as it opens yes, New task's picker not built: [judge]'s; use :judge before the first message [judge]'s nodwyn run: [judge]'s
Move a task to another judge, or none yes, between turns yes, :judge NAME, :judge none not built —
Set a judge up: Jev's key, an OpenAI API key Settings › Judge, in place — — edit config.toml, :eval (credential-register …)
The default judge Settings › Judge (Models links to it) — — [judge] server
Choosing from a paired device yes, signed in with a passkey; refused to one paired by a plain invitation (ADR 30, ADR 47, both amended 2026-10-17) — — —

Memory (ADR 51)

Console TUI Line mode CLI
The model sees what it found under each turn's card, and recalls a turn's message with the card and its events (index-search, recall) yes, as any tool's result yes yes nodwyn run: yes
A person searches this conversation or earlier tasks yes, the sidebar's search box (Search conversations for "…") or a task's Search; task/turn groups, matches in words with the query marked, and links into recorded history :eval (index-search "…" "earlier"), with :earlier on :eval, likewise nodwyn memory search, nodwyn memory recall, whatever the switch

Files and changes

Console TUI Line mode CLI
A worktree's changes since its base (worktree.diff) yes, Review in the centre panel deliberate gap: git diff in the worktree (ADR 34, amended 2026-10-08) — git diff in the worktree
Line comments sent as one message (worktree.review) yes, Review in the centre panel deliberate gap: write the message — —
Kept versions of a file, restore (versions.*, session.restore) yes, Changes not built — nodwyn versions, nodwyn undo
Recorded write approvals, attachments list yes, Changes the conversation only the conversation only replay
Project notes (notes.read, session.notes.write) yes, Workspace not built — —
The repository index: what it holds, and forgetting it (index.status, index.forget; ADR 50) yes, Workspaces (a folder's line and Forget the index) and a task's Workspace tab yes, :index, :index forget — delete projects/<key>/index in the data directory

The model's terminal (ADR 48, experimental and temporary)

The terminal tools are the model's (term-open and five more, offered only with [experimental] terminal = true in config.toml), so every client that runs a session has them; none of the clients shows a terminal. It is temporary: a row here goes with the feature if it is removed.

Console TUI Line mode CLI
Turn it on edit config.toml; Settings does not offer it (deliberate: a switch would suggest a feature) same same same; nodwyn run too
The opening prompt, saying it is experimental yes, as any command's yes yes nodwyn run follows its flags
The session's note saying it is experimental yes, in the transcript yes yes —
The model's calls and the screens it read as tool calls and results, like any tool same same replay
A live view of the terminal for the person not built not built — —

Inspection

The console's Details (Workspace tab) has a view for each TUI inspector but :replay. Both read the same log.

Console TUI Line mode CLI
Log records filtered by kind Details › Log (latest run and inherited setup; earlier runs expand in Conversation) :log [n] [prefix] :log [n] replay
Plan record, step marks Progress tab, Details › Plan record :plan — —
Indexed whole-task totals and turn/run counts (session.totals, ADR 49) yes, Statistics and Changes, including closed tasks deliberate gap: its inspectors read the log — —
Budget, spend, checkpoints Statistics tab, Details › Budget :budget — replay shows the records
Live gauges (spend, time, context, cache hit, 90% warnings) yes, the summary above the tabs; session totals from session.totals the context share in the title — —
Tools offered, with schemas Details › Tools :tools [name] :tools —
Bindings and approvals in effect Details › Policy :bindings, :allow :allow —
docs/friction.md, guarded (nodwyn's own development log: a developer command, not offered to every project) deliberate gap: no Details entry, since only nodwyn's repository has the file :friction — —
A transcript replayed from the log — (the conversation is it) :replay — nodwyn replay

Setup and the computer (ADR 30)

All deliberate gaps outside the console. They are forms over config.toml, the keychain and the config directory, and the CLI covers what a terminal needs. The console's column is this computer's, and that of a device signed in with the person's passkey after a touch of it (ADR 30, amended 2026-10-17); a device paired by a plain invitation has none of it. Another nodwyn linked here with the person's passkey has the first row for a host's limits, models and default model, with no touch (not its approvals, policy, judge or providers, which wait for a touch), unless this computer's console was started with --no-link-settings (ADR 54, amended 2026-10-23), and none of the rest.

Console TUI Line mode CLI
Providers, models, efforts, judges, limits, timeouts Settings: Models, Judge, and General (limits, timeouts and the context window under Advanced); one Save bar for the settings; a linked host's, at #/host/NAME/settings, when it lets the link change them (General and Models: add, change and remove a model among the host's providers, never a provider) — — edit config.toml
MCP servers: add, inspect, approve, pull Connections › Tools — — nodwyn mcp
Credentials: add, replace, allow a host, take a host back, remove; OAuth2 sign-in Connections › Credentials (a ChatGPT plan is connected on its provider, Settings › Models); a sign-in whose browser is on another computer finishes from the address it ended on, pasted into the card (credentials.authorize.finish, ADR 24 amended 2026-10-17) — — nodwyn credential (taking a host back is an edit of bindings.toml)
Keychain: create, unlock, passphrase Settings › Keys & devices, and beside a provider form that needs it unlocks the daemon's at start, over the socket (keychain.unlock); an empty answer or --no-keychain leaves it locked the same; under a script, only with --keychain-tty, which asks the controlling terminal nodwyn keychain
Named workspaces and mounts Workspaces — — edit workspaces.toml
Let the model search a folder's earlier tasks (ADR 50) Workspaces, the folder's editor; a task's Workspace tab says which yes, :earlier, :earlier on, :earlier off — edit workspaces.toml (earlier_tasks = true)
Project trust (ADR 45) Workspaces › Review…: both files whole, Trust this revision and Revoke trust, on a device signed in with a passkey after a touch of it; deliberate gap on this computer's own console and on a device paired by invitation (this computer's sign-in was a cookie every loopback server was sent, and the grant stays the passkey's; ADR 45 amended 2026-10-17 and 2026-10-19) — — nodwyn trust
Pair devices yes: Settings › Keys & devices › Invite a device (a code and a link, good once for ten minutes); over https one from a device signed in with a passkey, after a touch, makes a passkey, which signs other browsers in with no invitation; deliberate gap on this computer's own console, whose invitation lets in a device that only answers (making the person stays the terminal's and the passkey's; ADR 30 amended 2026-10-19) — — nodwyn devices invite, which makes the first passkey
List and revoke devices, remove a passkey yes, on this computer or after a passkey touch, each after a confirm; another nodwyn linked here is said to be one — — nodwyn devices (another nodwyn linked here says so), nodwyn devices revoke NAME; a passkey only in the console
Link to another nodwyn, and unlink (ADR 54: host.link, host.unlink) Hosts: Add a host (its address, this computer's name there, an invitation made there), its state and what it may do there, Settings (that host's), Open its console, Unlink after a confirm; on this computer, or on a device signed in with a passkey after a touch — — not built: edit nothing by hand, since the token is in the keychain
Start the daemon at login (startup.read, startup.write; ADR 53 §11) Settings › General › While you're away, on this computer — — nodwyn startup, nodwyn startup on, nodwyn startup off
The daemon's desktop notices (notify.desktop) Settings › General › While you're away; the daemon shows them whichever client is in use, when no window of this computer watches — — edit config.toml ([notify] desktop = true)
Home says when locked keys hold back a scheduled run (held) yes, the Scheduled section and the card, unlocked from Home's callout not built — —

Runs (ADR 35)

Console TUI Line mode CLI
Start, stop, resume a run shows a run and Stop aborts it; starts none (ADR 35) follows a run and answers its checkpoint — nodwyn run
A run in sandbox mode, on a disposable machine whose every path but nodwyn's own and credential files is its tools' (sandbox, ADR 55) shows it as the unattended run it is; starts none follows it — nodwyn run --sandbox container, and again at resume

Not built, in the order proposed

  1. Attention across tasks in the TUI, with a session switcher, so a task waiting on an approval is not invisible there.
  2. :rename and :archive in the TUI, and titles in its chooser.
  3. Kept versions and restore in the TUI.
  4. A model at open in the TUI (--model NAME), and --worktree for line mode and nodwyn run, with --worktree-base (ADR 34).
  5. A test that every RPC method is either used by the TUI or named here as a deliberate gap, so a new console feature cannot widen the difference unnoticed.

Line mode and the daemon

Every client reaches tasks through a daemon (ADR 30, amended 2026-10-17), so a task open in line mode is open for every client and for the scheduler, which runs its schedule as for a closed task. Until then line mode ran its session in its own process and held the task's log: while it had a task open the daemon could not open it, run its schedule, or even record that the run was skipped.

Its per-run flags each reach the daemon or are refused, with a sentence saying where the setting is now:

Flag On the daemon
--repo, --resume session.open, session.resume; the daemon's task-root refusal now applies. --repo beside --resume must name the task's folder, checked before it is resumed
--model NAME session.open's name for a configured model, else server and model for a model on [control]'s server; with --resume, session.model.set
--url refused: the daemon reaches only the servers config.toml names
--budget-*, --max-tool-calls the task's own limits: session.open's limits for a new task; with --resume, session.limits.set with those named in place of the task's, 0 removing one (ADR 49, amended 2026-10-27)
--no-confirm line mode answers every confirm and checkpoint no without reading a line; a question with set answers (uploads to Google Docs), none of which only declines, stops the turn
--yolo session.approve_all, so it needs [approval] dont_ask, as the console's switch does; security confirms and checkpoints ask, or with --no-confirm are declined. The task is not unattended: nodwyn run --yolo is
--no-keychain, --keychain-tty the daemon's keychain is left locked, or its passphrase asked on the controlling terminal under a script (keychain.unlock, as the TUI does)
--show-thinking line mode's own

Quitting closes a task line mode opened, as it always did, and leaves open one another client had open, turning off a Don't ask its --yolo turned on. Ctrl-C aborts a turn line mode started and otherwise quits; a turn another client or the schedule started is shown when line mode next reads a line, since its input is read in turn with the events. Line mode refuses a daemon that keeps its tasks in another data directory than its own (a script that set XDG_DATA_HOME and not XDG_RUNTIME_DIR), closing the task it opened there before any model is asked.

Found while writing this

  • The daemon's task-root refusal is still the daemon's, not core's (ADR 34, amended 2026-10-08); since 2026-10-17 every client opens tasks through it.
  • The console reads whole-task totals from the daemon's log index; its current-turn gauges still read the loaded conversation. The TUI computes its own in gauge.go and plan_budget.go.

Managed child console (ADR 56)

nodwyn-kids is a separate deployment embedding the core. Its browser has projects, a conversation, Stop, See app and website links. Create, rename and confirmed deletion are validated host operations. It exposes neither the TUI, ordinary RPC, tools/thinking/logs, settings, approvals nor linked hosts. Parent passkey devices can invite/revoke plain browsers after a fresh touch. Operator bootstrap is nodwyn-kids --profile PATH --invite parent; keep that invitation private. Laptop browsers receive only plain child invitations.

The managed console accepts a one-use child invitation in a URL fragment for operator bootstrap of a private profile. It clears the fragment immediately and leaves any existing pairing intact; this never registers a passkey.

Operator recovery can revoke one named managed device with nodwyn-kids --profile PATH --revoke NAME; ordinary browser administration still requires the parent passkey touch.