Development record
Connecting a model service from inside your coding session looks up the details and offers the choices
Press Release
When a person connects a model service from inside your coding session, abcd works out the details with them instead of asking them to type each one. It asks for the service's address and, on a yes, looks up the models the service offers. It suggests the models the person already uses through their other connections, and finds anything else when they type part of a name. It then shows one command, with every file that command will write, for the person to paste into a terminal: nothing is set up from inside the session. Where a service needs the key before it will list its models, that one terminal step asks for the key with the typing hidden, lists the models, lets the person pick one, checks the pick with a real answer, and only then saves anything. A service that lists no models asks for the name to be typed, and says why.
Proposed by the facilitator from decisions 1 to 8; confirmed as written by the product thinker at the planning interview, 2026-10-03 (decision 9).
Previous wording (superseded at the interview):
Connecting a model service from inside your coding session works out the details with the person instead of asking them to type them. It looks up what it can: the service's own list of models from the address given, where the person's key could be kept on this machine, and abcd's settings folder. It offers the choices one question at a time, and then either sets the connection up itself or prints the exact command to paste into a terminal. When a service lists hundreds of models, the person sees three suggestions and can type part of a name to narrow the list; the full searchable list is the plain-Terminal picker's (itd-2610030810370060).
Proposed; to be confirmed at the planning interview.
Why This Matters
The product thinker asked on 2026-10-03, as relayed by the technical facilitator (the routing row in the decomposition-calibration note), that ahoy connect, used from the harness, probe for its flag values (--base-url, --model, --home and the rest) and offer options, so that it either runs the setup itself or tells the person the exact terminal command, because nobody should have to work out exact model names: easy with a handful of models, hard with hundreds (a routing service such as OpenRouter lists several hundred).
Today abcd ahoy connect <provider> needs every value typed: the base URL, --model repeated for each model the provider may serve (the first becomes the verification call's model), --home (none, external, abcd, or keychain) with --env or --file and --field for an external home, and --key, which names the credential rather than carrying its value. It verifies the provider with one call and refuses a key typed at a terminal. The shipped intent itd-2609081951381895 owns the adapter and this verb; its decision 5 hands the external and keychain homes to the credential store, itd-2609221017023290 (shipped), whose standard-input discipline the key's handling rests on.
What the binary could look up without asking, once built (no such call exists today): an OpenAI-compatible service's model list (its /models endpoint, at the base URL the person names), the homes a key can live in on this machine (environment variables and known tool configuration files, named but never read for their value until the person chooses), and abcd's own settings folder. The proposed promise (see the review findings below: it is a discipline, not a property): connect never prints, echoes or records the key, and the guided path never asks for it; the person stores it, and the binary reads it from the chosen home.
Routed out: whether a request is judged against a model's published size before it is sent is the adapter's question, not connect's; it is captured as its own record, a follow-up of the streaming fix iss-2610030931521214.
Typed links: refines itd-2609081951381895 (its connect verb gains a guided path); the arrow-key list the product thinker wants later is routed to itd-2610030810370060 (abcd's interviews in a plain Terminal), with model choice as its first example (the product thinker's routing, 2026-10-03); the questions it asks follow itd-2610030810350727 (one question layout).
Mechanism
We expect a person to connect a service listing hundreds of models without knowing an exact model name because abcd reads the names from the service's own list and the person only types part of one to narrow it; a person who still has to type a full model name for a service that publishes its list shows the claim wrong.
Proposed by the facilitator from decisions 1 to 8; confirmed by the product thinker, 2026-10-03.
Scope Conditions
- Services that publish an OpenAI-compatible model list; a service that publishes none falls back to typing the name (decision 5).
- A person who can open a terminal on the same machine to paste the printed command; the session never finishes the setup itself (decisions 1 and 2).
- Suggestions only for a person with an existing connection; a first-time user sees the search alone (decision 6).
- A key that reaches abcd through its own hidden input in that terminal step; a key saved by hand in the computer's password store is not adopted (decisions 2 and 7).
Proposed by the facilitator from decisions 1 to 8; confirmed by the product thinker, 2026-10-03.
Acceptance Criteria
Proposed by the facilitator from decisions 1 to 8 and the design review's proposed criteria that survive them; each is unconfirmed until walked with its addressee. Review criterion C3 (it let the session run the setup for some homes) is rewritten to decision 1's always-print rule; C5 is rewritten to decision 2's single terminal step, with no listing taken as the test; C8 (a listed model size) is dropped, because decision 8 routes the size question out. How many suggestions a question shows is owed at planning; G2 proposes at most three.
- G1 (technical facilitator; CONFIRMED 2026-10-03) Given a service address, when the guided path offers to look up its models, then one question first shows the scheme and host, and only on a yes does a request go out, carrying no key, following no redirect, and keeping the adapter's short timeout and size cap (example: a stand-in service that answers with a redirect is refused and never sees a key header); a go test asserts each.
- G2 (product thinker; CONFIRMED 2026-10-03) Given a person whose existing connections use two models a service of 300 lists, when the model question is asked, then it suggests those two (at most three) and typing part of a name narrows the full list without a second request, while a first-time user sees no suggestions, only the search (example: typing "coder" narrows 300 names to the few that contain it); a go test asserts both cases.
- G3 (technical facilitator; CONFIRMED 2026-10-03) Given any guided connect run inside a session, when it ends, then it has printed exactly one command and written nothing: abcd's settings folder and every key home are byte-identical before and after (example: a run that reaches the end with a keyless local service still only prints the command); a go test asserts it.
- G4 (product thinker; CONFIRMED 2026-10-03) Given the values worked out with the person, when the command is printed, then the command and every path it will write are shown in full before the person runs it, and no setting skips that (example: the command is followed by the connection file it writes in abcd's settings folder); a go test asserts the output, and a dated receipt from a Claude Code session shows it on screen.
- G5 (technical facilitator; CONFIRMED 2026-10-03) Given a stand-in service that refuses to list its models without the key, when the printed command runs in a terminal, then the key is read from hidden input, the list is fetched with the key held in memory, the person picks a model, a real completion verifies it, and only then are both writes made, while a failed completion writes nothing and no listing counts as the test (example: a stand-in that lists with the key but fails the completion leaves no connection and no stored key); a go test asserts both paths.
- G6 (technical facilitator; CONFIRMED 2026-10-03) Given a known key value, when the guided path and its printed command run end to end, then the value appears in no question, answer, printed command, error or record (example: a canary key string is searched for in every output and file written, and found only in the chosen key home); a go test asserts it.
- G7 (product thinker; CONFIRMED 2026-10-03) Given a service whose model list request fails or returns nothing usable, when the model question is reached, then the person is asked to type the model name and told the service publishes no list (example: a stand-in answering not found on its model list); a go test asserts the fallback and its reason.
- G8 (product thinker; CONFIRMED 2026-10-03) Given a key the person saved by hand in the computer's password store, when the guided path asks where the key lives, then it does not offer that item, and the printed command takes the key again through abcd's hidden input (example: an item named for the service in the password store is not listed as a choice); a go test asserts it.
State of the art (2026-10-03; full report in the local tier, reports/sota-guided-connect.md)
- The standard model list (a request for
/modelsat the address given) has no context-size field; servers add their own, and they differ in meaning: some give the size the server actually runs, others the size the model was trained for (a 128k trained figure can sit under a 4k served one). The figure's source must travel with it. - Several local servers list models with no key; OpenAI, a key-protected vLLM, and (per its documentation) OpenRouter need the key to list. OpenRouter can search and sort its list on its own side (by name, popularity, price, context).
- In the harness, three suggestions plus a search beats paging: three at a time, 300 models would take about 100 questions.
- OpenRouter shortens an oversize prompt for small models unless told not to, which would break "the record shows what answered".
- A request near a model's limit can only be judged exactly by the server (a few have a token-count endpoint); the error text names both numbers.
- abcd's adapter already refuses to follow a redirect, so a look-up built on it cannot carry a key to another address.
Two questions this puts to the product thinker before criteria are written:
- The order for a service that needs the key to list its models: today abcd stores a key only after a test call with a model, so it cannot list first and choose after. Options: accept the list itself as the test where a wrong key is refused; a service-specific check; or have the person store the key and choose the model in one Terminal step, outside the session.
- A key the person has already put into the computer's own password store, with the input hidden, typed into the system's tool and not into abcd: may abcd adopt it by name?
Review findings (design and feasibility, then record discipline, 2026-10-03, reports/review-connect-design.md and reports/review-connect-records.md in the local tier)
Nothing here is settled; each item is put to its addressee at the interview.
- Blocking: "the key never passes through the conversation" is stated as a property but is a discipline: the agent runs as the person and could read a stored key with one command. Proposed promise: connect never prints, echoes or records the key, and the guided path never asks for it; the agent's own reads are governed by the host's permission deny list, with a documented recipe, as a scope condition.
- Blocking, which homes "run it" may use, decidable from the code: it may run for no key, for an environment variable already set in the session, and for a file pointer; for abcd's own credential file and the system password store, whose value arrives on standard input and is refused from a terminal, it prints the command. Adopting a password-store item the person created by hand does not work today (it reads as not held); that needs its own scope and a ruling on "refuses a key typed at a terminal", or the password-store path prints the command this release.
- Blocking, the circular order, ranked by safety and generality: (c) one terminal step outside the session (no model given, the key on standard input, list with the key in memory, the person chooses, a real completion verifies, then both writes land) works for every service; (a) a listing that refuses a bad key as the test is weaker (a key with no credit lists fine) and needs a new seam; (b) a service-specific check contradicts the shipped adapter's decision 2 ("OpenRouter is configuration of this adapter, not code").
- Should-fix: "run it" is one more question showing the exact command and every path it writes, answered yes, print it instead, or decide later (the connect page already says it announces itself).
- Should-fix, long lists: with three models per question, the free-text "Other" row is the search box (so that question carries no side preview, which would remove the row); suggestions come from the service's own order (for OpenRouter, most popular; for a local server, the loaded models), and from the person's routes once one exists; the binary narrows over a cached list without a second call.
- Should-fix, the look-up: http is already refused except on this machine, so a local-network address over http is refused; the look-up is a GET with no key, no redirect followed, a short timeout and the size cap, after one question showing the scheme and host.
- Consider: request size is the adapter's (iss-2610030931521214 and its follow-up), not connect's; connect may show a listed size labelled with its meaning and source, storing none of it.
Applied from the record-discipline review: the press release no longer promises the full list (the plain-Terminal picker's), the request-size question is routed out, the refines link is in the front matter, and the statements about --home, --key, the look-up and the key are corrected. Reversals of the shipped intent's decisions for the product thinker to confirm at the interview: setting up from inside the session (its decision 6); the list itself as the test (decision 7); any service-specific check or service-named parameter (decision 2); a look-up before setup (a network call adr-25's default does not make); a setting that runs without asking (invariant 1); a service-ordered shortlist inside a question (the asking rules put recommendations in prose).
Proposed acceptance criteria (agent-seeded, unconfirmed): C1 keyless listing call rules and the host shown first (facilitator); C2 three suggestions, search, narrowing (product thinker); C3 run only for no key, a session variable or a file pointer, else print the command (facilitator); C4 one confirming question showing the command and every path written (product thinker); C5 the one-terminal-step path for a service that refuses keyless listing (facilitator); C6 no key value in any question, answer, printed command or error (facilitator); C7 a service with no model list falls back to typing, with the reason told (product thinker); C8 a listed size labelled with its meaning and source, nothing written (facilitator). Full text in the review report; each is walked with an example at the interview.
Decisions
- 2026-10-03, the product thinker at the planning interview, asked whether connecting from inside Claude Code may finish the setup itself when no key has to move (gain: one step for a keyless service; cost: two paths, and a reversal of the shipped intent's decision 6): always print the command. abcd works out every value with the person, then prints one command for them to paste into a terminal. The shipped intent's decision 6 (the walkthrough is a verb the person runs) stands, so reversal 5(a) of the record review does not arise.
- 2026-10-03, decided without a question (follows from decision 1, the technical facilitator's): a service that needs the key to list its models is handled inside the printed command, in one Terminal step: the key arrives on hidden input, the models are listed with it in memory, the person picks one, a real completion verifies it, and only then are both writes made. The shipped intent's decision 7 (verify with the first model, then write) stands; no listing is accepted as the test, and no service-specific check is written (its decision 2 stands). The picker in that step is itd-2610030810370060's.
- 2026-10-03, decided without a question (brief invariant 1 settles it): the printed command, and every path it writes, are shown in full before the person runs it; no setting skips that.
- 2026-10-03, decided without a question (adr-25's default settles it): looking up a service's models before setup is a network call, so one question shows the scheme and host first, and the look-up runs only on a yes; it carries no key, follows no redirect, and keeps the adapter's short timeout and size cap.
- 2026-10-03, decided without a question (the only option besides deferral): a service that publishes no model list falls back to typing the model name, and the person is told why.
- 2026-10-03, the product thinker, asked where suggestions for a long model list come from (the person's own connections; the service's popular list in prose; search only): models the person already uses, from their existing connections; anything else is found by typing part of a name. The product thinker accepted the cost shown: a first-time user sees no suggestions, only the search. No service's ranking appears in or beside the question, so reversal 5(f) does not arise.
- 2026-10-03, the product thinker, asked whether abcd may use a key already saved by hand in the computer's password store, by its name: no; the person stores the key again through abcd's own hidden input. The credential store's adoption of a hand-made item stays out of scope.
- 2026-10-03, decided without a question (the record review's finding 2, both reviews agreeing): the request-size question is routed out to its own capture, iss-2610030956156354.
- 2026-10-03, the product thinker: the revised press release confirmed as written.
Open Questions
None open: the interview of 2026-10-03 answered them (decisions 1 to 8). Owed at planning: the press release and the acceptance criteria rewritten to the decisions (the press release no longer says the connection may be set up from the session), for the product thinker to confirm; the Mechanism and Scope Conditions; and how many suggestions a question shows (at most three, under the layout's limits).
Audit Notes
Fidelity review — receipt rcp-8c051b55af1b (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:bf4699f8dfbfc48a908c669c391e233decc4ef5004577abcf85cd93eb4cd5aeb Input attestations: diff:5bf4aa93b^1..5bf4aa93b (PR #791, connect step 1)@sha256:b3a8df593ac475c1276b0e17f12df4e903ccc5065c1ac5d1941e5cc00bb47050; diff:93e7b7caa^1..93e7b7caa (PR #801, connect step 2)@sha256:a27f29cafda4286312a6a776f9c873009cb8be6c98110b5989d0d610b9c15368; diff:81548efe5^1..81548efe5 (PR #807, connect step 3 and the close)@sha256:ffaddf46964672ccd115d76287ecd0d7e79d4e33f1f0f557106bf5d8724f3c89; test-run:go test ./internal/adapter/openaiapi ./internal/core/oracle ./internal/core/credential ./internal/core/question ./internal/term/... ./internal/surface/cli/ask; go test -run 'TestGuide|TestConnect|TestKeyCanary|TestPicked|TestReadKey|TestHiddenKey|TestActAsConnect|TestPickQuestion' ./internal/surface/cli at 81548efe5: all ok@-;
Acceptance rollup: MET 5 · MET_WITH_CONCERNS 3 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: The look-up question shows scheme and host and sends nothing until a yes; the guide lists with a keyless client; the adapter sets Authorization only when a key is held, fails on the pinned redirect, bounds the call by ListTimeout and the body by MaxResponseBytes+1; TestGuideAsksBeforeLookingUp (redirect target sees no request, redirecting service sees an empty Authorization) and TestListModelsSendsNoKeyAndFollowsNoRedirect / TestListModelsBoundsTimeAndSize assert each, and pass at HEAD. evidence: internal/core/oracle/connect_guide.go:447 — "abcd can ask %s://%s for the list of models it offers. The request carries no key, follows no redirect, and gives up after %d seconds." evidence: internal/core/oracle/connect_guide.go:619 — "client, err := openaiapi.New(base, "")" evidence: internal/adapter/openaiapi/models.go:87 — "if c.key != "" { req.Header.Set("Authorization", "Bearer "+c.key) }" evidence: internal/adapter/openaiapi/models.go:190 — "case errors.Is(err, errRedirect): return c.listFail(false, "answered with a redirect, which abcd never follows")" evidence: internal/adapter/openaiapi/models.go:80 — "lctx, cancel := context.WithTimeoutCause(ctx, c.listWait, errListTimeout)" evidence: internal/adapter/openaiapi/models.go:106 — "io.ReadAll(io.LimitReader(resp.Body, MaxResponseBytes+1))" evidence: internal/core/oracle/connect_guide_test.go:148 — "func TestGuideAsksBeforeLookingUp(t *testing.T)" evidence: internal/adapter/openaiapi/models_test.go:56 — "func TestListModelsSendsNoKeyAndFollowsNoRedirect(t *testing.T)" evidence: internal/adapter/openaiapi/models_test.go:112 — "func TestListModelsBoundsTimeAndSize(t *testing.T)"
- ac-2 — MET_WITH_CONCERNS: suggestions() draws from routes then provider allowlists, exact-id matches only, capped at maxSuggestions = 3; TestGuideSuggestsTheModelsAlreadyUsed asserts two of 300 are offered and a first-time user gets the typed part alone; TestGuideNarrowsWithoutASecondRequest asserts 'CODER' narrows to the four matches with one request across every turn. Concern (signed off 2026-10-04): the resume object carries at most MaxCarriedBytes (32 KiB) of ids, so for a list past that budget 'narrows the full list' holds only over the carried prefix and a fragment matching none carried asks for the model's full name instead of narrowing; a 300-model list is carried whole, so the criterion's own example holds. evidence: internal/core/oracle/connect_guide.go:53 — "maxSuggestions = 3" evidence: internal/core/oracle/connect_guide.go:778 — "func (g *guide) suggestions(listed map[string]bool) []string" evidence: internal/core/oracle/connect_guide_test.go:212 — "func TestGuideSuggestsTheModelsAlreadyUsed(t *testing.T)" evidence: internal/core/oracle/connect_guide_test.go:236 — "func TestGuideNarrowsWithoutASecondRequest(t *testing.T)" evidence: internal/core/oracle/connect_guide.go:69 — "const MaxCarriedBytes = 32 << 10" evidence: internal/core/oracle/connect_guide.go:649 — "func carry(ids []string) (kept []string, more int)" evidence: dcn-2610040000000003 — "the guided connect carries at most 32 KiB of listed ids between turns, with the count not carried" evidence: internal/surface/cli/ahoy_connect_guide_test.go:491 — "t.Run("300", func(t *testing.T)"
- ac-3 — MET: Guide never calls Connect (only the write-free checkConnect) and ends in guideCommand; TestGuideWritesNothing snapshots ~/.abcd, the index, the abcd credential file and the fake keychain before and after a run to the end for the none, abcd and keychain homes against a keyless loopback stand-in, asserts byte-identity, zero keychain reaches and exactly one "command" printed; passes at HEAD.
evidence: internal/core/credential/guide_test.go:133 — "func TestGuideWritesNothing(t *testing.T)"
evidence: internal/core/credential/guide_test.go:155 — "if after := snapshot(t, roots.Home, roots.Repo, chain); !reflect.DeepEqual(before, after)"
evidence: internal/core/credential/guide_test.go:148 — "strings.Count(strings.Join(said, "\n"),
"command":) != 1" evidence: internal/core/oracle/connect_guide.go:205 — "if err := checkConnect(&probe, false); err != nil" evidence: internal/core/oracle/connect_guide.go:869 — "func guideCommand(provider, base, model, home, env string, picks bool) (*GuideDone, error)" - ac-4 — MET_WITH_CONCERNS: The go-test half is demonstrated: TestGuideShowsTheCommandAndEveryPath asserts for none/abcd/external that the text form opens with the command, 'When it runs, this command writes:' and each path, and that running the printed command reports exactly those writes; TestGuideKeychainWritesAreTheRunsWrites covers the keychain home; TestGuideCannotRun refuses --guide with --home/--model/--key/--env, so no setting skips it. The receipt half is present at the path spec:439 names, .abcd/.work.local/logs/connect-guide-2026-10-04.md, carrying every element spec:439 lists (date, commit 81548efe5 = the shipped HEAD, Claude Code 2.1.289, each turn, every stand-in request, and the done turn as shown on screen), and its on-screen text, question ids, provider name and write order match the code at HEAD. Concerns, both named in the receipt itself: the answers were the Claude Code session's own, not a person's, so 'the values worked out with the person' is shown by the mechanism rather than by a person's run; and the receipt is a replacement produced after the close by the shipping session, in the gitignored tier, replacing one lost with a removed worktree, so it is not reproducible from the repository and the auditor did not observe the run.
evidence: internal/surface/cli/ahoy_connect_guide_test.go:168 — "func TestGuideShowsTheCommandAndEveryPath(t *testing.T)"
evidence: internal/surface/cli/ahoy_connect_guide_test.go:210 — "want := append([]string{done.Command, "When it runs, this command writes:"}"
evidence: internal/core/credential/guide_test.go:222 — "func TestGuideKeychainWritesAreTheRunsWrites(t *testing.T)"
evidence: internal/surface/cli/ahoy_connect_guide_test.go:145 — "{"--home", "none"}, {"--home", "abcd"}, {"--model", "vendor/coder"}, {"--key", "k"}, {"--env", "X_API_KEY"}"
evidence: internal/core/oracle/connect_guide.go:888 — "writes := append([]string{}, credential.WritesFor(home, req.KeyName)...)"
evidence: .abcd/development/specs/closed/spc-2610031241482088-connecting-a-model-service-from-inside.md:439 — "The dated receipt
.abcd/.work.local/logs/connect-guide-<yyyy-mm-dd>.md" evidence: .abcd/.work.local/logs/connect-guide-2026-10-04.md:3 — "Date: 2026-10-04 / Commit: 81548efe55dd5cedba5b6b4dd22eb2355be47d1b / Claude Code: 2.1.289" evidence: .abcd/.work.local/logs/connect-guide-2026-10-04.md:24 — "abcd ahoy connect local --base-url http://127.0.0.1:48731/v1 --model example/coder-small --home abcd / When it runs, this command writes: / ~/.abcd/credentials.json / ~/.abcd/config.json" evidence: .abcd/.work.local/logs/connect-guide-2026-10-04.md:6 — "The answers are the session's own, not a person's." evidence: internal/surface/cli/ahoy_connect_guide.go:183 — "fmt.Fprintln(w, "When it runs, this command writes:")" evidence: internal/surface/cli/ahoy_connect_guide.go:193 — "Paste it into a terminal on this machine; nothing is set up until it runs there." evidence: internal/core/oracle/connect_guide.go:76 — "GuideQNarrow = "model-narrow"" - ac-5 — MET: readConnectKey prints one stderr line then reads through term.ReadHidden (echo off inside a RawSession); Connect with no model and Pick set lists through pickModel with the held key, hands the ids to Pick, then Walk verifies with a real completion and only on success stores the key and writes the provider block. The stand-in answers a keyless list 401 and lists with the key; TestConnectPicksAfterAKeyedListing asserts the keyed list, the pick, the completion to the picked model and both writes; TestConnectWritesNothingWhenTheCompletionFails asserts no provider block and no stored key; TestConnectReadsTheKeyHiddenAtARealTerminal and TestHiddenKeyRestoresTerminalOnInterrupt hold the hidden read on a real pty. All pass at HEAD. evidence: internal/surface/cli/ahoy_connect.go:308 — "Paste the key for %s and press Enter. It is not shown." evidence: internal/term/hidden.go:34 — "func ReadHidden(in *os.File, out io.Writer) (line string, err error)" evidence: internal/core/oracle/connect.go:134 — "if picking { m, err := pickModel(ctx, req, cfg.denylist, opts)" evidence: internal/core/oracle/connect.go:153 — "walked, err := credential.Walk(ctx, req.Roots.Home, svc, credential.Choice{Home: req.Home, Value: req.Key, Pointer: req.Pointer})" evidence: internal/core/oracle/connect.go:148 — "the verification call failed, so nothing was written" evidence: internal/surface/cli/ahoy_connect_terminal_test.go:100 — "if auth == "" { echo(http.StatusUnauthorized)" evidence: internal/surface/cli/ahoy_connect_terminal_test.go:237 — "func TestConnectPicksAfterAKeyedListing(t *testing.T)" evidence: internal/surface/cli/ahoy_connect_terminal_test.go:273 — "func TestConnectWritesNothingWhenTheCompletionFails(t *testing.T)" evidence: internal/surface/cli/ahoy_connect_pty_test.go:122 — "func TestConnectReadsTheKeyHiddenAtARealTerminal(t *testing.T)" evidence: internal/surface/cli/ahoy_connect_pty_test.go:148 — "func TestHiddenKeyRestoresTerminalOnInterrupt(t *testing.T)"
- ac-6 — MET_WITH_CONCERNS: TestKeyCanaryAppearsOnlyInItsHome runs the guide to its end and then the printed command with a canary key against a stand-in that echoes the Authorization header into error bodies and lists one id carrying the key; it searches every turn's JSON and text, the command's stdout, stderr and error, and every file under the fake HOME, and asserts the canary is found only in ~/.abcd/credentials.json (and nowhere when the completion fails); TestPickedConnectKeepsTheCanaryInItsHome holds the terminal half. Concern: the end-to-end canary covers the abcd home only; the keychain home, fakeable only inside the credential package, has no end-to-end canary search. evidence: internal/surface/cli/ahoy_connect_guide_test.go:289 — "func TestKeyCanaryAppearsOnlyInItsHome(t *testing.T)" evidence: internal/surface/cli/ahoy_connect_guide_test.go:322 — "store := filepath.Join(os.Getenv("HOME"), ".abcd", "credentials.json")" evidence: internal/surface/cli/ahoy_connect_terminal_test.go:354 — "func TestPickedConnectKeepsTheCanaryInItsHome(t *testing.T)" evidence: internal/adapter/openaiapi/models.go:207 — "func (c *Client) listFail(needsKey bool, reason string) error" evidence: internal/adapter/openaiapi/models.go:151 — "if c.carriesKey(strings.Join(l.IDs, ""))"
- ac-7 — MET: A ListError that does not need a key puts the guide on the typed model question whose material opens with '< host> publishes no model list: it < reason>.'; TestGuideFallsBackToTyping asserts the fallback and reason for not found, an empty list and a non-list body, and TestListModelsNamesWhyThereIsNoList holds each reason at the adapter; both pass at HEAD. evidence: internal/core/oracle/connect_guide.go:485 — "why = fmt.Sprintf("%s publishes no model list: it %s.", host, listed.Reason)" evidence: internal/adapter/openaiapi/models.go:101 — "return Listing{}, c.listFail(false, "answered not found")" evidence: internal/core/oracle/connect_guide_test.go:366 — "func TestGuideFallsBackToTyping(t *testing.T)" evidence: internal/adapter/openaiapi/models_test.go:197 — "func TestListModelsNamesWhyThereIsNoList(t *testing.T)"
- ac-8 — MET: The home question offers exactly the three homes with the keychain's meaning 'You paste it hidden in your terminal'; the guide never searches the keychain or index. TestGuideNeverOffersAHandSavedItem seeds an item named for the service in the fake keychain, asserts the options are exactly external/abcd/keychain plus decide later, that no turn carries the item, zero keychain reaches, and that the command run without a key is refused rather than adopting the item; TestConnectReadsTheKeyHidden asserts the keychain home reads the key through the hidden reader. Both pass at HEAD. evidence: internal/core/oracle/connect_guide.go:542 — "{Value: KeyHomeKeychain, Label: "The system keychain", Meaning: "You paste it hidden in your terminal; the system keychain keeps it."}" evidence: internal/core/credential/guide_test.go:164 — "func TestGuideNeverOffersAHandSavedItem(t *testing.T)" evidence: internal/core/credential/guide_test.go:213 — "want it refused, the hand-saved item never adopted" evidence: internal/surface/cli/ahoy_connect_terminal_test.go:177 — "func TestConnectReadsTheKeyHidden(t *testing.T)" evidence: internal/surface/cli/ahoy_connect.go:223 — "if home == oracle.KeyHomeABCD || home == oracle.KeyHomeKeychain { key, err := readConnectKey(cmd, args[0])"
Gap audit:
- honoured:
- Nothing is set up from inside the session: the guide always prints one command and writes nothing evidence: internal/core/credential/guide_test.go:133 — "func TestGuideWritesNothing(t *testing.T)" evidence: commands/ahoy.md:649 — "Never ask for the key, never pass it, and never run the"
- The look-up runs only after one question showing scheme and host, with no key, no redirect, the adapter's bound and cap evidence: internal/core/oracle/connect_guide.go:447 — "abcd can ask %s://%s for the list of models it offers." evidence: internal/adapter/openaiapi/models_test.go:56 — "func TestListModelsSendsNoKeyAndFollowsNoRedirect(t *testing.T)"
- A key-gated service is handled in one terminal step: hidden key, keyed list, pick, real completion, then both writes evidence: internal/core/oracle/connect.go:134 — "if picking { m, err := pickModel(ctx, req, cfg.denylist, opts)" evidence: internal/surface/cli/ahoy_connect_terminal_test.go:237 — "func TestConnectPicksAfterAKeyedListing(t *testing.T)"
- A service that lists no models asks for the name to be typed and says why evidence: internal/core/oracle/connect_guide.go:485 — "publishes no model list: it %s."
- Suggestions come from the person's existing connections; a first-time user sees the search alone evidence: internal/core/oracle/connect_guide_test.go:212 — "func TestGuideSuggestsTheModelsAlreadyUsed(t *testing.T)"
- A hand-saved password-store item is never offered or adopted evidence: internal/core/credential/guide_test.go:164 — "func TestGuideNeverOffersAHandSavedItem(t *testing.T)"
- The command and every path it writes are shown in full, from the same list the run reports evidence: internal/core/credential/walk.go:134 — "func WritesFor(home, name string) []string" evidence: internal/surface/cli/ahoy_connect_guide_test.go:168 — "func TestGuideShowsTheCommandAndEveryPath(t *testing.T)"
- diverged:
- 'Typing part of a name narrows the full list' is delivered over a 32 KiB carried prefix; past that budget a fragment matching none carried asks for the model's full name rather than narrowing (signed off 2026-10-04) evidence: internal/core/oracle/connect_guide.go:69 — "const MaxCarriedBytes = 32 << 10" evidence: dcn-2610040000000003 — "the guided connect carries at most 32 KiB of listed ids between turns"
- The end-to-end canary search (G6) is delivered for the abcd home only, not for the keychain home evidence: internal/surface/cli/ahoy_connect_guide_test.go:322 — "store := filepath.Join(os.Getenv("HOME"), ".abcd", "credentials.json")"
- Suggestions match an existing connection's model only by exact id, so a differently spelled id on this service yields none evidence: internal/core/oracle/connect_guide.go:778 — "kept only where the service lists the id exactly (open question 3, decided (a)), at most three"
- G4's dated Claude Code session receipt is a replacement made after the close by the shipping session with the session's own answers, not a person's, held in the gitignored tier; the original was lost with a removed worktree
evidence: .abcd/.work.local/logs/connect-guide-2026-10-04.md:6 — "The answers are the session's own, not a person's."
evidence: .abcd/development/specs/closed/spc-2610031241482088-connecting-a-model-service-from-inside.md:439 — "The dated receipt
.abcd/.work.local/logs/connect-guide-<yyyy-mm-dd>.md"
- missing: (none)
Scope-condition dispositions:
- cond-2610031241487546 — survived: The look-up decodes the standard data[].id list and nothing else; every other shape or status becomes a ListError whose reason the typed fallback question carries, exactly as the condition assumed. evidence: internal/adapter/openaiapi/models.go:118 — "func (c *Client) decodeListing(raw []byte, keep func(string) bool) (Listing, error)" evidence: internal/core/oracle/connect_guide_test.go:366 — "func TestGuideFallsBackToTyping(t *testing.T)"
- cond-2610031241484784 — survived: The guide never calls Connect, refuses every flag that would run or write, ends in a printed command, and the page tells the agent never to run it; the setup finishes only when the person pastes the command in a terminal where the pick and hidden read need every stream to be a terminal. evidence: internal/core/credential/guide_test.go:133 — "func TestGuideWritesNothing(t *testing.T)" evidence: internal/surface/cli/ahoy_connect_guide_test.go:141 — "func TestGuideCannotRun(t *testing.T)" evidence: internal/surface/cli/ahoy_connect.go:209 — "if len(models) == 0 && !atTerminal" evidence: commands/ahoy.md:591 — "never runs that command, and no flag makes it"
- cond-2610031241488563 — narrowed: Suggestions are drawn only from a person's existing routes and provider allowlists, so a first-time user sees the typed search alone as assumed; but a model is suggested only where this service lists the same id exactly, and at most three are shown, so some people with existing connections also see no suggestion. narrowing: Holds for a person whose existing connections name an id this service lists exactly, at most three of them; an existing connection whose model this service spells differently yields no suggestion, so that person sees the search alone like a first-time user. evidence: internal/core/oracle/connect_guide.go:778 — "func (g *guide) suggestions(listed map[string]bool) []string" evidence: internal/core/oracle/connect_guide.go:53 — "maxSuggestions = 3" evidence: internal/core/oracle/connect_guide_test.go:212 — "func TestGuideSuggestsTheModelsAlreadyUsed(t *testing.T)"
- cond-2610031241482561 — survived: For the abcd and keychain homes the key reaches abcd through readConnectKey's hidden read in the terminal step and is never asked for by the guide; a hand-saved keychain item is neither offered nor adopted, the keyless run being refused. The external home's variable is a separate, named route the condition does not speak to and does not contradict. evidence: internal/surface/cli/ahoy_connect.go:302 — "func readConnectKey(cmd *cobra.Command, provider string) (string, error)" evidence: internal/core/credential/guide_test.go:164 — "func TestGuideNeverOffersAHandSavedItem(t *testing.T)" evidence: internal/surface/cli/ahoy_connect_pty_test.go:122 — "func TestConnectReadsTheKeyHiddenAtARealTerminal(t *testing.T)"
Grounds
- pursued: connecting a service with hundreds of models takes one paste, with no hunting for exact model names (the product thinker, 2026-10-03).
Frontmatter
| id | itd-2610030821294016 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | minor |
.abcd/development/intents/shipped/itd-2610030821294016-connecting-a-model-service-from-inside.md
open on GitHub ↗ · commit history ↗
References1
- itd-2610030810370060abcd's interviews run in a plain Terminal, with the same question layoutbuilds on
Referenced by3
- iss-2610262044407176A shipped connection step uses the picker owned by the plain-Terminal interviews intent, which is still planned and unbuiltrelated
- itd-2610030810370060abcd's interviews run in a plain Terminal, with the same question layoutrelated
- spc-2610031241482088connecting-a-model-service-from-inside-claude-code-works-outimplemented by
mentions11
- adr-25The LLM is host-delegated by default; oracles are opt-in adapters
- dcn-26100400000000032026-10-04 — Decided without a question (the person's ruling that an obvious answer is decided; recorded by abcd-e8): the guided connect carries at most 32 KiB of listed ids between turns, with the count not carried, because a 5,000-id list exceeds Linux's per-argument limit under `sh -c` and costs the session ~80K output tokens a turn; real services list a few hundred. Alternative not taken: one more keyless listing on a miss, which breaks the one-request rule of G2.
- dcn-26100400000000092026-10-04 — Ruling of the product thinker (asked by abcd-e8, one question, answer verbatim: "Reword the title"): the shipped guided-connect intent itd-2610030821294016's title becomes "Connecting a model service from inside your coding session looks up the details and offers the choices". The site features the newest shipped intent whose audit is met, and its banned-token gate keeps the published surface host-agnostic, so a title naming the harness made the site unpublishable once the intent's audit was recorded. The record's body keeps naming the harness. Alternatives not taken: let feature titles name a tool; feature an older intent.
- dcn-26100400000000102026-10-04 — Ruling of the product thinker, correcting the entry above (asked by abcd-e8; the first question wrongly said only the title named the harness; answer verbatim: "Reword both sentences too"): the two press-release sentences of itd-2610030821294016 that read "from inside Claude Code" read "from inside your coding session", like the title, so the site's featured announcement stays host-agnostic. Nothing else in them changes. Alternatives not taken: let a featured announcement name the tool; feature an older intent.
- dcn-26100500000000012026-10-05 — Release v0.13.0 is cut by the coordinating session abcd-d7 while the product thinker is away, and the run's agenda line is: approve the publish step. Under the product thinker's rulings of 2026-10-04 (~10:05Z, verbatim: "Yes, that order", to cut v0.13.0 now with the home rename #815 inside it and rename the folder on their return; "Yes, approve it", for the coordinating session to approve the GitHub publish deployment itself once every check has passed, then verify the downloads and the site; ~13:35, leaving overnight: "keep going autonomously so we can have a new release tomorrow morning"), the session approves the `release` environment's deployment of v0.13.0 only after the merge queue, the verify job and every other gate on the tagged commit report green, and stops with a handover instead if any does not. The cut: v0.13.0, impact breaking (decided by itd-2610030720038073, the home folder renamed to `~/.abcd.noindex` with every abcd entry point stopping until the person renames it; itd-2610030814013772, AGENTS.md the one conventions file; and iss-2610020723185314, only a personal route hands a role to a command-line runner), 55 records since v0.12.0: five shipped intents (two breaking, three additive: itd-201, itd-2610030810350727, itd-2610030821294016) and 50 resolved issues (41 fixes, one additive, one breaking, seven internal and outside the changelog); the release guard and the findings guard passed. The noindex intent's scan receipt (D6) was taken on a `.noindex` probe folder before the rename, with consent, and adr-2610030720195401 accepted with brief invariant 21 (6538ab4a). Brief fidelity: three tier-full reviews and their holds brought 25 brief sentences current before the close (9d57917c, 9e6e0aa0), and the guide's own review found one false sentence, corrected to say the rename is done in a plain Terminal (cde071b9, 405cf34d). Content commit c6441dc0, on top of 3a092b89, which the cross-check's one fix-before-tag finding required; both semantic gates ran at tier full over the first roll 317cd0cc, which differs from the content commit only by that commit and the re-cut CHANGELOG. The docs-currency-reviewer (Fable 5.1) found 2: dc-1 accepted as written (while only `~/.abcd` stands the plugin root holds no binary, so the hook refuses the rename too and the pages describe what the person meets), dc-2 a nitpick deferred to the next release notes. The brief-surface cross-check (45 pinned checkers, Opus 5.5, at most eight alive) found 144 (31 major, 94 minor, 19 nitpick), 143 valid and one invalid (x-036) on an independent classification (Fable 5.1); of the three user-facing findings x-065 (`abcd build --help` said the sub-agent ceiling is not counted, where it binds through `implement step` since ruling DR6) is fixed in 3a092b89, and x-010 and x-042 are minor and deferred; the 140 design-record findings go to the systematic brief pass iss-2609091956001547. Deferred out loud, to be captured after the tag as two records: `launch ship` reads the records at HEAD, so a spec close left uncommitted was silently missing from the cut rather than refused (the close was committed as 44a73e6f before the ingest), and `docs fidelity --apply` flags the drafted replacement while the landed sentence was hand-tidied, so .abcd/work/brief-review-flags.json carries the drafted wording for 10-docs, 15-prepare-this-repo, 22-site, 23-reading and 27-implement. Reason for deferring: neither changes what v0.13.0 ships, and the person's ruling was a release by morning.
- iss-2610030931521214The OpenAI-compatible provider adapter sends non-streaming chat requests with one end-to-end deadline, so a long delegated step routed to a reasoning model fails and wastes the provider's work. internal/adapter/openaiapi/client.go:291 renders "stream": false; DefaultTimeout (client.go:63) bounds the whole call at 120 seconds (connecting, sending and reading the entire answer), and only the connect verification passes its own timeout (internal/core/oracle/connect.go:116), so a dispatched step gets the 120-second default; max_tokens is sent only when a route or --route sets it (client.go:81). Evidence from a user test on 2026-10-03 against a keyless local OpenAI-compatible server on loopback: that server's gateway answers HTTP 504 to a non-streaming request whose generation exceeds about 600 seconds, because no headers are sent until the answer is complete, and the model keeps generating up to its token limit after the 504; reproduced twice with two reasoning models on a 44-line classification prompt, while a non-reasoning model finished in 181 seconds. abcd's own 120-second deadline abandons such a call earlier still, and an abandoned non-streaming request is not cancelled at the server. Any proxy or gateway with a header or idle timeout behaves the same way; intent consistency's bundled corpus (about 29k tokens) is the kind of step that reaches it.
- iss-2610030956156354A request sent through the OpenAI-compatible provider adapter is never judged against the chosen model's size before it is sent. In a user test on 2026-10-03, an intent consistency check sent to a keyless local service carried about 29,300 tokens against a model the service runs at about 29,200, and the service refused it with HTTP 400, about 0.5% over. Some servers publish each model's served size in their model list; the standard list has no such field, and a published figure may be the trained size rather than the served one. Routed out of the guided-connect draft itd-2610030821294016 by its record review; a follow-up of the adapter's streaming fix captured the same day.
- itd-2609081951381895abcd ships an OpenAI-compatible API adapter, and a provider serves only the models it lists
- itd-2609221017023290abcd keeps every external credential the same way
- itd-2610030810350727Every question abcd asks reads the same way, on a narrow window and a wide one
- spc-2610030911534855a-person-can-run-abcd-s-interviews-in-a-plain-terminal