Setup sessions and config change sets
This page describes setup in the browser as built for #127 and #153, from the design approved in #132. Where the build differs from that design, the page says so.
open-conduit init --with claude (or --with codex) runs the setup playbook as an interactive Claude Code session in a terminal (operator setup). There the agent edits the config file itself with open-conduit config set. A setup session runs the same playbook from the web view:
- The agent’s output arrives as typed events the page renders: a message, a probe and its result, a question with choices, a proposed change, checks, and an applied generation. There is no terminal text to parse.
- The agent never writes the config. It proposes changes, the operator approves or rejects each one, and only the operator applies the approved set.
Setup is an installed extension package, setup, built on protocol/sdk like any third party’s. It lives in its own repository, open-conduit/extension-setup, and speaks only the extension protocol (#243; until then it was an in-process extension under extensions/setup). Nothing in this repository imports it, and the boundary checker fails if anything does (see where it lives and how it ships). A setup scope is ordinary config:
{ "id": "setup", "name": "Setup", "authorization": {"connections": [], "read_scopes": [], "operations": [ "read_context", "wait", "declare_outcome", "setup.say", "setup.ask", "setup.probe", "setup.read", "setup.propose", "setup.withdraw", "setup.validate", "setup.trace" ]}, "extensions": [{"name": "setup", "type": "setup", "secret_names": [], "config": {}}], "routes": [{"name": "setup", "handler": "setup", "binding": "agent", "active_policy": "inject", "unsupported_fallback": "queue", "error_fallback": "queue", "config": {}}], "bindings": [{"name": "agent", "executor": "claude-code", "requires": {"toolbelt": "mcp"}, "operations": ["read_context", "wait", "declare_outcome", "setup.say", "setup.ask", "setup.probe", "setup.read", "setup.propose", "setup.withdraw", "setup.validate", "setup.trace"], "secret_names": [], "config": {"playbook": "setup", "args": "session", "builtin_tools": [], "strict_mcp": true, "models": ["opus", "sonnet"]}}]}open-conduit init setup writes this scope, and a daemon with no config file and no stored generation runs on it.
- Work. A session is Work of kind
setup.sessionin the setup scope, on the existing Claude Code executor. Work gives the session everything it needs as it is: detached segments that survive a daemon restart,--resumecontinuity, waits that park it until the operator answers, an authorised toolbelt over MCP, and run history. The attempt pins its generation and binding like any other (execution scheduling). - Route. The
setuproute handler matches only Events the extension’s own actions produce (setup.*, sourcecore.action) on a session key of its own scope (setup,session/<scope>/<id>). Asetup.startwith an unowned key creates the session’s Work, with the chosen model as the Work’s context. Routes set a new Work’s context throughRouteDecision.Context, a new field, because a route had no way to before. Every later operator Event carries the session’s key. During a running turn the route injects and falls back to queue. While the session waits on a question, only the answer wakes it, and a message is queued until the turn the answer starts. The agent’s own Events carry no key, so they are recorded on the session and never route back to it. - No shell. The binding turns Claude Code’s built-in tools off. That is a new executor option,
builtin_tools: a list passed as--tools, where an empty list means none. The extension’s config check refuses a setup route whose bound agent is not onclaude-code, does not setbuiltin_toolsto[], or is granted operations beyondread_context,wait,declare_outcomeandsetup.*(binding_refused, at the route’sbinding). A route handler’s type can now check the binding each of its routes binds (inproc.ConfigType.Binding, orroutes[].bindingin a package’s manifest); before, a handler saw only its own route config. The scopeinit setupwrites also setsstrict_mcp, so the operator’s own MCP servers and claude.ai connectors (Slack, Drive and the like) don’t load into a session either;builtin_toolsalone turns off only Claude Code’s own tools. The check doesn’t requirestrict_mcp, so a setup scope written before it still validates. Everything the agent learns comes through a probe, and everything it says goes through an operation. - More, for one session. What a session may have beyond talking is the setup binding’s grants, which the binding check doesn’t limit: the config file is their ceiling. The scope
init setupwrites offersshell(builtin_toolswithBash,Read,Edit,Write,GlobandGrep) andconnectors(strict_mcp: false). An operator who wants a session to reach GitHub adds a grant with the GitHub operations (and a GitHub entry and connection in the setup scope). The Setup page’s start form shows each grant as a checkbox, all off. Thestartaction’s input is the model, then+namefor each grant to turn on (opus +shell); the start Event and the session’s context carry them asgrants, the route gives them to the session’s Work, and the agent is told what is on when it starts. The session panel shows them under Access. - Model. The operator picks the model when starting a session. The
startaction offers the binding’smodelsas choices, and the route puts the choice in the Work’s context. That is a second new executor option: a binding withmodelstakes the Work context’smodelif it is one of them, and otherwise uses the binding’smodel. The model is never written to the config. - One session at a time. The
startaction is listed only while the scope has no open session. - Playbook. The setup playbook is the package’s (
playbooks/setup.md, declared as the manifest’splaybooks), because it covers Slack, Linear and Jira as well as GitHub. The daemon puts it in the turn plugin like any package playbook, andinit --with claudetakes it from the first-run package (see the playbook). - The agent’s events.
setup.say,setup.ask, the proposals, decisions, checks and probes are recorded withevent.acceptin the same commit as the change set they touch, derived from the turn’s trigger, so a session’s events count against the causal depth and per-minute limits like any package’s.setup.appliedis derived from the trigger of the session’s latest attempt, and a task checks for it every second rather than after each configure. They carry no execution ID, and their IDs are the host’s (ext/…), notsetup/…as in-process. A package can’t read Waits, so whether the answer action is offered comes from the record: the session waits, its latest question is unanswered and no attempt has started since. The probessetup.fs.listandsetup.toolsrun inside the package’s process.
Where it lives and how it ships
Section titled “Where it lives and how it ships”- Repository. open-conduit/extension-setup is its own Go module: the extension,
cmd/setup-extensiononprotocol/sdk, the playbook and a certification fixture. Its CI runsmake checkandmake certifyagainst the latest Open Conduit release and againstmain. - Pin. This repository pins a version of it with a
tooldirective ingo.mod. Nothing imports it:make packagesandscripts/bundle-packages.shbuild it from the module cache. Go allows the module cycle (setup requires open-conduit for the SDK); a build here uses this repository’s own packages. - Release. GoReleaser runs
scripts/bundle-packages.shand putsextensions/setup/(the binary, its manifest, certification and playbooks) in every archive besideopen-conduitandos, so install.sh,open-conduit upgradeand the Homebrew cask all carry it. The daemon finds it there as a bundled package. install.sh andupgradereplace that directory with the release’s, so the bundled copy always matches the binary. - An operator’s own copy.
open-conduit extension installof a setup release into<config dir>/extensions/setupreplaces the bundled one. A copy that needs a newer host is refused withneeds_newer_hostrather than swapped for the bundled one;extension remove setupbrings the bundled one back. - State. The change sets keep their KV namespace,
setup.changesets, so a session’s change set carries over from the in-process extension to the package. - Tests. The package’s own repository tests the session and the change set (
TestDaemonSetupSessionandTestDaemonSetupChangeSetonextensiontest.Daemon), its store onsdktest, and its playbook withopen-conduit extension check. Core keeps what is its own behaviour:TestDaemonDefaultConfigurationand the offline checks of the playbook’s example config build the pinned package and lay it out beside the test daemon binary as the archive does, with no network.
Why not a reserved scope
Section titled “Why not a reserved scope”An earlier draft put the session in a core.setup scope that no config file contains, with its binding fixed in code. That broke more written rules than it was worth:
- B05 requires at least one scope and no implicit one, constrains scope IDs, and keeps a stored generation self-contained.
- Execution scheduling pins every attempt to a binding in its generation.
- B04 needs a route before an Event can reach Work.
- B01 keeps domain behaviour out of management.
The extension needs none of those exceptions. Everything the agent may do sits in a file the operator can read. The one thing the reserved scope bought was that config couldn’t widen the agent. Here the extension’s config check enforces the same limits, and only the operator can apply a config that would change them.
A fresh install
Section titled “A fresh install”open-conduit web stays a client: it writes no config and starts no daemon. The daemon gets a default configuration instead:
- No config file and no stored generation.
servebuilds the default document: the first-run setup’sextension.Setupresult with no flags (the scope above), merged into an empty config by the same host thatinituses. The first-run setup is the one markedFirstRun(setup.first_runin a package’s manifest), so core names no extension; today it is the bundledsetuppackage’s. With no such package,serverefuses and names the remedy. It validates that document and publishes it as generation 1. The file is not written. Generation 1 is stored and self-contained like any other, so the validator still requires at least one scope, and no generation 0 is needed. The first apply writes the file.open-conduit init setupwrites the same scope to a file for an operator who wants it there first. - What still refuses to start. A config file that exists but doesn’t validate, with no stored generation, still stops
servewith its diagnostics. Falling back to the default would hide the operator’s mistake. - What the views show.
doctor’sconfigcheck reportsdefault_config(info) while the daemon runs on it. The web view shows “running, nothing configured but setup” and offers to start a session. With no daemon, the web view says so and gives the commands to start one (open-conduit serve, oropen-conduit service install). It runs neither.
Typed events
Section titled “Typed events”Every event is an operation or an action. The extension accepts each one as an Event of its own family on the session’s Work, through AcceptInTx. The page renders the Work’s Events in acceptance order. Claude Code’s transcript stays in the run’s artifacts, as for any turn.
| Event | From | Data |
|---|---|---|
setup.start |
operator start action (scope level) |
model |
setup.message |
setup.say (agent), operator message action |
role, text |
setup.probe |
setup.probe, setup.read, setup.trace |
probe, args, command (for display), why, result |
setup.question |
setup.ask |
question_id, text, context, choices |
setup.answer |
operator answer action |
question_id, answer |
setup.proposal |
setup.propose |
change, op, pointer, before, after, why, diagnostics |
setup.decision |
operator approve_*, reject_*, approve_all_* actions; setup.withdraw |
changes, decision, reason |
setup.checks |
setup.validate, operator validate action |
source, candidate_hash, valid, approved, diagnostics |
setup.applied |
the extension, on the generation that applies the set | generation, hash, changes |
- Questions work like
human.ask.setup.askis anOperation.Wait: it records the question, parks the session, and the operator’sansweraction (one button per choice, or a text box) wakes it. Every ask has a 24-hour wait timeout. When it expires, the agent sums up where it got to and finishes. - Messages from the operator are the
messageaction on the session’s Work. The route injects a message into a running turn. Inject success means accepted for delivery, not read. The agent has read the message only once Claude Code echoes it back (injection). A segment adopted after a daemon restart takes no injected input, so its messages are presented at the next turn or wake instead. Either way, none is lost. - Ending. A session ends as any Work does. The agent’s structured status
finished(ordeclare_outcome) completes it, once the config is applied and checked or the operator says they’re done. The operator’s End session is the operator’s cancellation of the session’s Work (POST /v1/work/{id}/cancel), as for any Work, after which late Events are checkpointedterminalas B04 says. The extension offers no end action, because an action’sInvokemay not write state. The page shows that the Work ended, from its state. Nosetup.endedEvent has to race the cancellation. An open change set is abandoned when its session ends. - Reloading. The page holds no state. It reads the Work’s panels, actions and candidate on every load and redraws them as live regions. A running turn’s text comes from
GET /v1/runs/{id}/live. A reload, a closed tab, or a restart ofopen-conduit webloses only an unsent draft. - The page. The Setup page is the web view’s generic extension page: the extension declares it (
Pages: entry and routesetup, Work kindsetup.session, startstart, composeanswerthenmessage), andinternal/webnames nothing of it. The session draws itself as two panels:conversation, whose messages are its Events in acceptance order (the assistant’s messages and questions, the operator’s messages and answers, and a note for eachsetup.probe,setup.proposal,setup.decision,setup.checksandsetup.applied, the latest 200 kept), andmodel and access, what the operator chose at the start. Core reviews the change set fromConfigCandidateand owns Apply.
What the agent may read and run
Section titled “What the agent may read and run”The setup playbook’s ground rules say the agent probes and never handles a secret. Here the toolbelt enforces them. An execution token reaches only /v1/toolbelt/call (B05), so every probe is an operation of the setup extension. The extension runs each probe under the session’s own authority, never as a proxy for an operator read.
The candidate, and nothing else of the config. The agent can read three things, all redacted the way config get is (secret references become their names with [redacted]):
setup.readreturns the outline of the candidate: connection IDs and types, scope IDs and names, entry types, binding names. This is the same wiringGET /configshows, with no opaque config and no references.setup.readalso returns, in full, the scopes and connections the session’s change set touches, and the settings.- All of it is read from the candidate, the base plus the approved changes, with every change and its state.
To work on an existing scope, the agent proposes a change to it; the outline names every scope. Naming scopes when the session starts is not built: start takes only the model.
Named probes, owned by what they’re about. setup.probe takes a probe name and arguments, never a command line. The probes come from extension.SetupProbes, a new optional interface beside extension.Setup, so each one lives with the code that knows its domain. Each probe declares its arguments, runs without a shell, has a short timeout and an output cap, and returns safe output plus a display command. Adding one is a code change with a test that its output carries no secret.
| Owner | Probe | Returns |
|---|---|---|
setup |
fs.list |
a directory’s entries under the operator’s home, names and kinds only, two levels at most. Hidden entries are skipped, and the result says how many. A directory that is a git checkout lists its remotes’ URLs with any user-info stripped (an https://token@… remote shows the host only) |
setup |
tools |
whether each of gh, git, ngrok and cloudflared is on the daemon’s PATH, with its path and whether it’s a symlink or a script |
open-conduit (the host) |
init |
init TYPE --dry-run for every setup but the first-run one, registered (github, slack, linear, jira) or installed. It calls the type’s extension.Setup with a non-interactive host: Prompt returns ErrSetupNoInput, StoreSecret stores nothing and returns the reference it would use, and Run and OpenURL refuse. Flags take references as init does, and refuse literals |
open-conduit (the host) |
doctor |
the daemon checks that need no secret (config, webhooks, service, the executors’). This is a new narrow host function over the same checks as GET /v1/doctor, without check_secrets |
claude-code |
status |
whether claude is on the daemon’s PATH, its version, and whether it is signed in and how (subscription login or API key), with no account detail or key. The claude-code doctor check doesn’t report it yet |
github |
accounts |
gh auth status --json hosts: each account’s host, login, active flag, token source and state |
github |
repos |
gh repo list OWNER --json nameWithOwner |
The playbook starts with claude-code.status, so its first words to the operator are that Claude Code is installed and signed in, so it can do the work, or what to fix first.
No probe takes a reference or a command. There is nothing that reads an arbitrary file, prints the environment, runs a cmd: reference, or resolves a secret. The earlier draft’s gh.login, which resolved gh auth token --user LOGIN with a login the agent chose, is dropped: B05 lets no request supply a command or reference to resolve. Proving a credential works is doctor with check_secrets, which stays an operator action. Once a change set is applied, the Setup page shows doctor’s checks (GET /v1/doctor). Running doctor with secrets checked, and recording the result for the agent as setup.checks, is not built yet: the playbook asks the operator to run open-conduit doctor --check-secrets and tell it the outcome.
Tracing the candidate. setup.trace runs the route trace from #126 with one of a scope’s named samples, against the session’s candidate only. It never traces other scopes, and it writes nothing, like the trace itself. This is the last step of setup: it proves that a synthetic event, such as “a new issue is opened”, would be picked up, without the operator labelling a real issue. The runtime’s TraceCandidate reads the candidate’s scopes in place of the stored ones, through the host’s config service; it is an internal function, not a new route, and a trace’s generation reads candidate.
Secrets and references
Section titled “Secrets and references”- No values. No probe reads or resolves a secret (above).
setup.proposeappliesconfig set’s rule: a value undersecret_refs, or in any declared secret slot, must be a reference, and anything else is refusednot_a_reference. - No reference text in Events. A reference is sensitive in views, errors and telemetry (B05). An Event for a pointer inside
secret_refsstoresbeforeandafteras the names with[redacted]. So does everything the agent reads back. The reference text the agent proposed is kept only in the change set, in the extension’s KV namespace. - The extension never holds the config’s references. It works through the host’s config host, as an installed package would: the host keeps the config text behind handles and answers only copies with references redacted, and builds each candidate from the edits the extension proposes against a handle.
- The operator sees what they approve. The change set’s review shows the full reference.
GET /v1/work/{id}/config-candidateis an operator-class read, so the reference reaches the operator and not the execution. This is the one view that shows reference text, and it shows only references proposed in this change set. - A last check. Before accepting any Event, the extension scans its strings for the token shapes Open Conduit knows (GitHub, Slack, Linear, Anthropic, private-key blocks). A match in probe output replaces the output with
redacted: true. A match in text the agent wrote refuses the call withlooks_like_secret. This is a backstop for mistakes, not the guard: the structural rules above are the guard.
Credentials still go in from the operator’s own terminal. When a connection needs one, the agent asks the operator to run open-conduit secret set NAME, asks whether it’s done, and proposes the reference.
Change sets
Section titled “Change sets”A change set is the session’s list of JSON-pointer edits to the config file, each with a reason, waiting for the operator. It lives in the setup extension’s KV namespace (setup.changesets), keyed by session. There is no core table.
-
Base. A change set is based on the committed generation and the config file’s text when the session starts, which it names by the host’s handle; it keeps only the redacted text. A change edits the file text with
config set’s editor, which addresses array elements byidorname;addinserts into an array before a numeric index or appends with-, never overwriting an element. A pointer into a scope from an included folder is refusedin_folder, because that text lives in another file. Running on the default configuration with no file, the base text is the default document. -
Proposing.
setup.propose {op, pointer, value?, why}adds aproposedchange. The extension fills inbefore, checks the pointer resolves forreplaceandremove, and validates the base plus the approved changes plus this one, attaching the diagnostics. A proposal that doesn’t validate on its own is still recorded, since a scope and its binding may come in two proposals, and the review shows its diagnostics.setup.withdrawtakes back an undecided change. -
Deciding. The session’s candidate lists each change, naming
approve_<change>andreject_<change>on each open one, and the web view’s review draws them as buttons. Reject prompts for an optional reason. With more than one open, the candidate also offers Approve all, labelled “Approve all N”. Its ID carries no list, only the revision of the open proposals it was listed for (approve_all_<revision>), so it approves exactly the proposals the operator saw and refuseschange_set_movedif any changed. One click is one action and onesetup.decision, whosechangesnames every change it approved. Decisions are Events, since an action may not write state: a change’s state is its operator’s latest decision after its proposal, folded from the session’ssetup.decisionEvents. A rejected change stays in the list, so the agent doesn’t propose it again. -
Validating.
setup.validate(agent) and the operator’svalidateaction build the candidate (the base text plus the approved changes, in proposal order, which the host builds and holds) and validate it. The host runs the same side-effect-free validation asPOST /v1/config/validate. The result and itscandidate_hashare recorded assetup.checks. -
Applying. Apply is not an action and not an operation. The web view’s Apply button is an operator client doing what
config applydoes:- It reads the candidate from a new operator-class route,
GET /v1/work/{id}/config-candidate, served by an optionalextension.ConfigCandidateinterface on the Work’s extension. The extension lists its changes and, once the approved changes validate, names its candidate’s handle; the host serves the text behind it, withbase_generationandcandidate_hash. - It calls the existing
POST /v1/config/applywith thatexpected_generationandcandidate_hash.
The extension’s
Configurerreceives every applied generation while the host holds the config service, so it only notes the generation; itsappliedtask, run by the host loop straight after, compares each open session’s candidate with the active hash. On a match it marks those changesapplied, moves the change set’s base to the new generation, and acceptssetup.appliedon the session’s key, which reaches the agent as input. Whatever reads a change set first after an apply moves it onto the new generation, and the page reloads its review straight away, usually before the task has looked. So every read makes the same comparison before it moves the change set: on a match it marks the changesappliedand leavessetup.appliedpending, and the task sends it (#261). - It reads the candidate from a new operator-class route,
-
Key order.
POST /config/applysavesconfigin compact form (B05). To keep the operator’s layout, apply takesconfig_text, the file text as a JSON string, in place ofconfig. The daemon validates the parsed text and saves the text byte for byte.open-conduit config apply FILEsends it too. -
When the config moved. If the generation changed after the page read the candidate, apply fails with
generation_conflictand the page asks the operator to reload and review. There is norebaseaction: the next time the agent proposes, reads, validates or traces, or the operator reads the candidate, the change set moves onto the configuration now. Each change’sbeforeis recomputed, approvals whosebeforeis unchanged stand, and the rest return toproposed.
Only the operator applies
Section titled “Only the operator applies”The agent can propose, withdraw and validate. It cannot apply:
- No operation applies config, and the setup scope’s config check refuses a binding that grants anything beyond
setup.*and the builtins. POST /v1/config/applyandGET /v1/work/{id}/config-candidateneed the operator credential. The agent’s execution token reaches only/v1/toolbelt/call(B05).- Actions are invoked through the management API with the operator credential, so the agent can’t approve its own proposals either.
- The agent has no shell, so it can’t run
open-conduit config applyor read the operator token file, as the terminal setup could.
The acceptance test runs a session’s execution token against all of them: every setup.* operation with apply-shaped input, POST /v1/config/apply, the candidate route, and the action routes. Each must be refused, and the generation must be unchanged afterwards.
The setup playbook under a session
Section titled “The setup playbook under a session”There is one playbook, rendered from a template as {{bin}} already is. Session mode is on when the args are session. The introduction, the ground rules, the questions to settle and the config examples stay shared. What changes:
| In the terminal | In a session |
|---|---|
checks command -v claude along the way |
starts with claude-code.status, and tells the operator first whether Claude Code can do the work |
reads with ls, git, gh, config show, doctor |
setup.probe and setup.read |
| talks and asks in the chat | setup.say, and setup.ask with the likely answers as choices |
config set, init TYPE |
the init probe to build a scope, then setup.propose for each change, with its reason |
config validate and config apply |
setup.validate, then asks the operator to review and apply in the page; setup.applied wakes it |
asks the owner to run secret set |
the same, then setup.ask whether it’s done |
| suggests labelling a real issue to see it work | setup.trace with a sample, then asks the operator to run doctor with secrets checked |
| ends when the owner closes the terminal | ends with the structured status finished and a summary |
Session mode tells the agent it has no shell, and that a proposal is a request, not an edit: nothing has changed until setup.applied arrives. It proposes in small, explained steps (a connection, then a scope, then its binding), so each can be judged on its own.
Rules this changes
Section titled “Rules this changes”The build amends each of these pages in the same change:
- B05 configuration and toolbelt and management: “The first start activates the config file as generation 1” gains the default configuration when there is no file and no stored generation.
POST /config/applygainsconfig_text, saved as given. New:GET /v1/work/{id}/config-candidate. The change set’s review is the one view that shows reference text, and only to the operator. - Claude Code executor: new
builtin_toolsandmodelsoptions, and theclaude-code.statusprobe. - Extensions: new optional
extension.SetupProbesandextension.ConfigCandidate, and in-process host functions for validation, narrow doctor checks and setup registrations. - Operator setup:
init setup, the playbook’s new home and its session mode, thedefault_configdoctor code, and theclaude-codecheck’s sign-in state. - Observability: the web view’s Setup page, since #242 a generic extension page.
- #126 route trace: tracing against a candidate snapshot.
How it was built
Section titled “How it was built”Each step landed with its tests and the page amendments above:
-
The
setupextension with its scope,setup.start,setup.sayandsetup.ask, messages, the route, and the executor’sbuiltin_toolsandmodels(#136, #142). Then the config check on the setup route’s binding (#143), the default configuration at first start withinit setup(#144;doctoralso reportsdisk_missingwhen a generation that is not the default runs without its file), and the playbook’s move toextensions/setup, which adds it to the turn plugin. -
Change sets: propose, withdraw, decide (including Approve all), validate, the candidate route,
config_text, and the applied hook, with the operator-only test (TestDaemonSetupChangeSet, now in the package’s repository: the session’s own execution token is refused by apply, the candidate route, validate and the action routes, apply-shaped operation input is refused, and the generation is unchanged). -
Probes:
extension.SetupProbes, the setup,claude-codeandgithubprobes,setup.read, the secret scanner, andsetup.traceagainst the candidate. -
The playbook’s session mode.
-
The web view’s Setup page, built from the session’s Events and panels (observability). #242 made it a generic extension page the extension declares, with the conversation drawn as the extension’s panel and the change set’s review and Apply core’s. The conversation and the session panel redraw as live regions of their own, with the message box outside them, so typing never holds back a reply. The page stays at the newest message while the operator is at the bottom and leaves them where they are once they scroll up. The message and answer boxes are textareas that grow, sending on Enter, with Shift+Enter for a new line, and an operator’s line breaks are kept in the conversation.
-
The move to an installed package in its own repository (#243): core gained
entry_gated, route binding checks,setup.first_run, package playbooks and bundled packages first (#248); then the extension moved to open-conduit/extension-setup onprotocol/sdk, and the release archive bundles it.
Not built: naming scopes when a session starts, the operator’s doctor action with secrets checked recorded as setup.checks, and the sign-in state on the claude-code doctor check.
Decided in review
Section titled “Decided in review”- Daemon-owned session, typed events as toolbelt calls, no shell, named probes, operator-only apply: approved on #127.
- Model: chosen when the session starts and stored on its Work, not in config.
- Fresh install: the daemon starts without a file, and
open-conduit webstays a client. - Approve all: one action per click, recorded as one
setup.decisionnaming every change it approved. - Extension over reserved scope: chosen after review, for the reasons above.