Watch and steer runs
The terminal UI
Section titled “The terminal UI”open-conduit tuiThe TUI lists one row per issue or thread, with its latest turn and how many turns it has had. g lists every turn, and enter shows one with all of its Work’s turns.
| Key | Does |
|---|---|
enter |
show a run with all of its Work’s turns |
g |
switch between one row per issue or thread and one row per turn |
l |
the run’s log |
t |
the agent’s transcript; in a log, the run’s next output |
f |
cycle the state filter (running, parked, failed, …) |
r |
refresh now |
esc |
in a detail or log, go back |
q |
quit from the list; in a detail or log, go back (ctrl+c quits from anywhere) |
c |
the scopes’ actions, such as claiming an issue by hand |
1–9 |
in a Work’s detail, the actions its extensions offer now |
R |
run or retry, after asking, since it starts a new turn |
H |
hold, asking for a reason (enter to skip), or release |
S |
in a Work’s detail, skip the first Event routing is stuck on, after asking |
E |
in a Work’s detail, route the first stuck or failed Event again |
a |
attach to the agent’s session |
i |
interrupt a running turn, after asking; the Work stays, and its next turn starts with the input queued for it |
x |
cancel a live run, after asking |
X |
cancel the whole Work, after asking |
The web view
Section titled “The web view”open-conduit webThis serves the same view in a browser on this machine, at http://open-conduit.localhost:7420. It prints a sign-in URL; open it to start a session in that browser. Browsers resolve every *.localhost name to this machine without any setup. If yours doesn’t, use the second URL it prints, which names 127.0.0.1. Prefer the first: browsers don’t keep cookies apart by port, so a browser signed in on 127.0.0.1 sends its session to every other server on that address, such as an agent’s dev server.
To have the daemon serve the view itself, start it with --web, or install the service that way:
open-conduit serve --webopen-conduit service install --webThe sign-in URL lasts across restarts, so you can bookmark it, and open-conduit web url prints it at any time. Each sign-in starts a session of its own, which lasts across restarts too until the browser has been idle for 12 hours or signed in for 7 days; then open the URL again. Sign out everywhere, at the end of the status line, ends every browser’s session at once and leaves the URL working. To change the URL as well, delete private/web.signin in the state directory and restart the view.
The web view has five pages, with the daemon’s state, the active generation and the agent and job slots in use along the bottom.
- Runs lists one row per Work, grouped by what it needs from you: first what needs a person (a decision an extension offers, such as answering an agent’s question, or a repair such as a turn that ran out of retries), then running, queued, waiting, held and closed. Queued is Work with input waiting for a turn, and says so when a concurrency limit is holding it back. Work that handed its turn back and has nothing new to act on is waiting, for its next event, not queued, so raising
settings.concurrencynever leaves it behind. Each row shows what the Work is doing now in its source’s words (what a Wait is for, such as “checks on PR #137”), its lifetime as a line with a bar for each stretch of active time, and its turns. The state strip, search, filters, grouping and sort are all in the URL. Select rows to hold or release several at once; running Work is skipped and the result says how many. - A Work’s page shows what the Work is and is doing now, any panel an extension shows that offers an action (such as the open question with its options), its turns as a score with the waits between them, and one turn at a time with its transcript read as a conversation: the agent’s and people’s messages as chat, what the agent sent elsewhere with its text, and tool calls as quiet rows you can expand. The Events tab lists every Event with how it was routed. The facts beside it give the Work’s IDs, routing, the turn’s executor and workspace, and the totals.
- Needs you gathers what waits on a person (an action its extension says the Work is waiting for, not one you may take at any time), what stopped and needs a repair, what an operator holds, and what has sat unchanged for a while, with the changes you made in this session.
- Configuration draws each scope’s routing as a flow chart: the sources that bring events in, the routes tried in order with what each does while its Work is busy, and the binding and executor each route hands its Work to. Pick a sample event to trace how it would route now, without sending it. It is read-only; change the configuration with
open-conduit config setorconfig edit, or set up withopen-conduit init --with claude. - Setup runs a setup session in the browser, in a scope with a
setupentry. A daemon started with no config file runs a defaultsetupscope; otherwiseopen-conduit init setupadds one, applied as a new generation withconfig validateandconfig applyonce the daemon has run (see set it up in the browser). Start one (choosing the model, when the binding offers more than one, and ticking any grants the binding offers for that session only: the default scope offers a shell and your own connectors), then read what the assistant says, answer its questions with a button per choice and send it messages. A message sent while it waits on a question reaches it with the answer. The assistant has no shell: it looks around through probes and proposes config changes, each with its reason. The Changes section shows each proposal’s before and after, with Approve and Reject, Approve all and Validate; Apply applies the approved changes as the next generation, with the generation the page read, and shows doctor’s checks afterwards. Nothing in a session changes the configuration until you apply.open-conduit actions run setup start --scope setup --input "MODEL +shell"starts a session from a terminal. The conversation follows the newest message while you are at the bottom, and the message box takes several lines (Shift+Enter). End a session by cancelling it. Without a setup scope, the page points toopen-conduit init --with claudein a terminal.
When a newer release is out than the daemon runs, the status line says so. Its link opens the Update page, which shows the running and latest versions with a link to the release notes, and installs the new release as open-conduit upgrade does, then restarts the background service and reloads until the daemon answers on the new version. Without a running service it says to restart open-conduit serve; for a Homebrew install it gives the brew upgrade command instead. The view checks GitHub for the latest release at most every six hours.
Every change (run again, rebind, send a wake event, hold, release, interrupt or cancel a turn, cancel the Work, and the actions extensions offer) asks first, says what it will do, and sends the Work’s current revision; the confirmation names the call and shows the event the daemon accepted. Every page works without JavaScript. With it, ⌘K jumps to any Work, scope or page, g then r, a or c goes to a page, / searches, j/k move through rows or turns, x selects, e jumps to the first error in a transcript, and ? lists the keys. To attach to an agent’s session, use the terminal.
--addr HOST:PORT(orOPEN_CONDUIT_WEB_ADDR) changes where it listens. Only a loopback address is accepted, so the view is never reachable from another machine on its own. To reach it from elsewhere, put it behind a reverse proxy with your own sign-in (see share it with a team).--host NAME(orOPEN_CONDUIT_WEB_HOST) changes the name it answers to.- With
serve, the same choices are--web-addrand--web-host. Either one also turns the view on. Pass the same values toweb url.
open-conduit web runs the view as its own process, a client of the daemon like the TUI; stop it with Ctrl-C. With serve --web it runs inside the daemon and stops with it, but it is still a client of the management socket. Either way it holds the operator token itself and never gives it to the browser. If the view can’t start (its port is taken, say), the daemon logs why and keeps running without it.
The same controls on the command line
Section titled “The same controls on the command line”open-conduit claim 12 # claim issue #12 by handopen-conduit actions list --work ID # what extensions offer on a Work nowopen-conduit work list --state waitingopen-conduit work retry IDopen-conduit work hold ID --reason "needs a decision"open-conduit work interrupt ID --reason "read my message" # stop the running turn; its queued input starts the nextopen-conduit events emit --scope eng --work-id ID --type operator.reply --delivery-id reply-1 --schema operator.reply.v1 --data '{"text":"try again"}' # send a Work inputopen-conduit work attach ID # hold, attach to the agent's session, release on exitopen-conduit runs listopen-conduit runs log ID --follow # follow a running turnopen-conduit route issue/12 # explain how an issue's Events were routedopen-conduit route --samples --scope eng # sample Events to traceopen-conduit route --trace issue-opened --scope eng # how a new issue would be routed now (records nothing)open-conduit events chain IDopen-conduit events skip ID --reason "bad payload" # route an Event routing is stuck on as skippedopen-conduit events reroute ID # route a failed or skipped Event againwork retry|hold|release|interrupt|cancelact on the Work’s current revision. Pass--revision N(fromwork get ID) to act only if nothing has changed it since.events emit --work-id IDsends the Work input, whether or not a route would take it: Work you made withwork createhas no route to take its follow-up. With no route matching, the Event queues for the Work’s next turn on the binding the Work runs on next (the onework rebindlast moved it to, else its latest attempt’s), and the route record’s reason isaddressed. A route that matches still decides, as for any input. Ready Work starts its next turn from it, which continues the session where the executor can; running Work takes it next; waiting Work keeps it as context and its wait still decides. Finished Work records it asterminal.work interruptstops a running turn without ending the Work: input queued for it (messages the turn couldn’t read during a long tool call, say) starts the next turn, which continues the session where the executor can. It is refused, with nothing stopped, when the turn’s executor can’t be interrupted; cancel the turn instead.- Lists print a table;
--jsonprints the records.--scopefilters by scope everywhere. runs log IDshows a run’s output;--transcriptshows the agent’s transcript.events chain IDshows the chain of Events that led to something.- An Event whose route keeps failing is retried with a growing backoff and, after five failures over at least 15 minutes (or at once when retrying can’t help, such as a route whose extension is gone), routed as
failed. Doctor’sroutingcheck lists Events still unrouted after ten minutes.events skiplets the Work behind one move on;events reroutetries an unrouted one now, or accepts a failed or skipped one again as a copy. doctor --check-secretschecks connections, credentials, webhooks, bindings and the executors’ CLIs.
More than one operator
Section titled “More than one operator”By default there is one operator: you, with the token the daemon writes to private/operator.token in its state directory. Everything you do is recorded as operator:local. To let someone else run controls under their own name, issue them a credential of their own:
open-conduit auth add alice --out /path/to/alice.token # an owner-only token file for alice, role operateopen-conduit auth operators # every operator credential, active or revokedopen-conduit auth revoke TOKEN_ID # revoke one (the IDs are in auth operators)Alice then points the CLI at her file with --token-file or OPEN_CONDUIT_TOKEN_FILE, on a machine that can reach the management socket. Everything she does (an Event she sends, a hold, a cancel, a rebind, an action such as answering an approval) is recorded as operator:alice. The web view’s Events tab shows who sent each Event, and events get and --json lists show it as actor. An Event a person sent from Slack or GitHub shows their identity there, such as github:octocat. By default only you (operator:local) may answer asks and approvals. To let others answer, add operator:alice, or a bare operator for any operator, to the human entry’s responders.
-
A name is lower case letters, digits and dashes, up to 32 characters. A name has one active credential at a time; revoke it before you issue another.
-
auth rotatereplaces the caller’s own credential and keeps its name. -
Each credential has a role, set with
--rolewhen you add it (defaultoperate). To change a role, revoke the credential and add it again.Role May viewread everything: Work, Events, runs and their logs, config, doctor, the operator list; trace a route and validate config, which change nothing operatealso send Events and run every control and action: create, cancel, retry, wake, hold, release, rebind and attach Work, cancel executions, skip and reroute Events, reconcile scopes, answer asks; rotate and revoke their own credential adminalso apply config, add operators and revoke other operators’ credentials You are always
admin. A call the role doesn’t allow is refused withforbidden_rolebefore it does anything. A credential added before roles existed isview. Revoke it and add it again with--roleto restore its controls. -
No one but you can revoke your own credential, not even an
admin.auth recover(with the daemon stopped) revokes every operator credential, named ones included, and writes you a fresh one. -
The web view still signs browsers in with its one sign-in URL and acts as
operator:local. -
To reach the daemon from another machine, see share it with a team.