Repository navigation
Conversation
The Go AI SDK needs Go 1.26 and go-server-sdk v7.19.1. This change moves the module to Go 1.26 and go-server-sdk from v7.13.4 to v7.19.1. golangci-lint v1.63.4 cannot read the type data of Go 1.26, so the pre-commit hook moves to v2.11.4. The new .golangci.yml keeps the v1 rules: staticcheck runs only the SA and S1 checks, and unchecked Close and print calls are not errors. The hook now pins Go 1.26.0. Without the pin, pre-commit downloads the newest Go, and the linter cannot read the type data of a newer release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The setup agent can now get its instructions and model from the ldcli-setup-agent AI Config. The CLI evaluates the config locally with the Go AI SDK. The detected facts are the context attributes, so targeting can give each kind of project its own instructions and model. The relay does not exist yet, so the source is opt-in for development. If LDCLI_SETUP_AGENT_SDK_KEY is set, the CLI uses the AI Config. If not, it uses the built-in configuration. If the SDK cannot start, or the config is off or missing, the CLI also uses the built-in configuration. The CLI sets every connection option of the SDK. The SDK fills an empty option from an environment variable such as LD_BASE_URI, and ldcli uses that variable for its own API address. The SDK is pinned to commit 6152dc9, because it has no release yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AI SDK now reads the setup-agent AI Config through the relay in gonfalon, so no SDK key is on the user's machine. This replaces the LDCLI_SETUP_AGENT_* variables. The SDK gets the placeholder key ldcli-placeholder. A loopback proxy in the CLI adds the user's access token in X-LD-CLI-Token and forwards each request to /internal/ldcli-setup-agent/sdk under the user's base URI. The proxy accepts requests only under a random path, so OpenCode cannot use it. The proxy is also needed because the SDK cannot add a header to its trace exports. The CLI makes one polling request before the SDK starts. The Go server SDK retries a refused stream until its start timeout ends, so without the check a refusal would delay setup by 5 seconds. The CLI creates the SDK's LaunchDarkly client itself, with logging off, because the SDK logs to the terminal and breaks the wizard's view. The CLI also sets LD_ENVIRONMENT_ID for its own process, so a value in the user's shell does not reach the SDK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI no longer hardcodes the client-side ID of the environment that holds the setup-agent AI Config. The relay sends it in the X-LD-Setup-Agent-Environment-ID response header, and the CLI reads it from the check that runs before the SDK starts. The relay holds that environment's SDK key, so it knows the ID. When the environment changes, only the gonfalon configuration changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gonfalon removes client request headers that start with X-LD-, because its private auth headers use that prefix. So the relay never received X-LD-CLI-Token, and it refused every request with ldcli_token_invalid. The token header is now LD-CLI-Token, and the environment ID header is now LD-Setup-Agent-Environment-ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenCode can ask for several tool calls at once. The wizard kept only one pending request, so each new request replaced the one before it. The replaced requests never got an answer, and OpenCode waited forever. The wizard now keeps a queue. It shows the oldest request and how many more are waiting, and each y or n answers one request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK client now polls through the relay. gonfalon holds back the relay's streamed response, so the flag stream never started, the SDK start timed out after 5 seconds, and setup used the built-in instructions without saying so. Setup reads the config once per run, so one polling request is all it needs. Polling also does not depend on a long-lived connection through the relay and the load balancer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wait-for-app screen printed the SDK key, the client-side ID, and the mobile key, although each SDK reads only one of them. It now shows only the credential that the chosen SDK uses. The prompt also names only that environment variable, so the agent does not have to choose. The wizard now says "the agent" instead of "OpenCode" in its progress text and permission questions. Messages that tell the user to install or upgrade OpenCode keep the name, because the user must act on them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent can ask the user to choose with OpenCode's question tool, for example where the SDK key lives. The wizard did not show these questions, so the run waited for an answer that never came. The wizard now shows each question with numbered options. A number picks an option, and s skips the request, so the agent goes on without answers. `ldcli setup agent` asks on the terminal the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If the instructions of the setup-agent AI Config use a {{variable}},
they describe the task themselves. The CLI fills the variables with the
detected facts and the earlier setup choices, and it sends only a short
start message. So AgentControl controls the whole job without a CLI
release.
Instructions without variables keep the CLI's own task message, so a
config can add guidance without taking over the task.
The variables are sdkId, language, framework, packageManager,
entryPoint, sdkInstalled, flagKey, and credentialEnvVar. The CLI fills
them with aiserver.ParseTemplate, the same function the AI SDK uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit e84a0e1.
When the setup-agent AI Config loads, its instructions are the agent's system prompt, and the first user message gives only the facts from setup: the SDK, the language, the entry point, the flag, and the credential variable. The CLI no longer adds its own task, so a task in the instructions is not overruled by the CLI's flag-only task. If the config cannot load or has no instructions, the built-in instructions and the CLI's own task message apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent now runs shell commands in the project without asking. It asks the user only with the question tool, when it needs information such as where to store a key. Approving every command made a run slow, and most commands only read the project. Some limits stay. Rules deny git commit, git push, git reset, sudo, and rm -rf on absolute paths. Files outside the project, web access, and sub-agents stay denied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de handler
The setup agent is now an AI SDK agent. For a logged-in user, setup
calls aiserver.Config("ldcli-setup-agent").Stream with a new OpenCode
handler. The AI SDK resolves the config through the relay, composes the
prompt from it, and calls the handler. The handler sends that prompt to
the OpenCode session and turns the session's events into the SDK's
stream events. The SDK records the run: an opencode.session span, a
chat span per reply with its tokens, an execute_tool span per tool call,
and the tokens, duration, and success events for the config's
Monitoring tab.
The handler implements the AI SDK's handler contract for agent-mode
configs of any provider, so it can move into go-ai-sdk unchanged. The
wizard still gets every OpenCode event, including questions and changed
files. If the SDK cannot resolve the config before the handler starts,
the run uses the built-in instructions and task.
Closing a run that ended now waits up to 5 seconds for the SDK to record
it. Before, the wizard closed the run at once and cut the tracking off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A closed terminal sends SIGHUP, which ended ldcli at once and left the OpenCode process running. The wizard now quits on SIGHUP the same way that Bubble Tea quits on SIGINT and SIGTERM, so it stops OpenCode. `ldcli setup agent` stops OpenCode on SIGINT, SIGTERM, and SIGHUP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent runs shell commands without approvals, and OpenCode's rules limit only its file tools. In a test run, the agent searched the whole disk with find, read files in another repo, and read the ldcli access token with `ldcli config get`. On macOS, ldcli now starts OpenCode with sandbox-exec and a Seatbelt profile for each run. The sandbox applies to OpenCode and to every command that it runs. It denies the home folder, except the project, OpenCode's own folders, package caches, and toolchains. Other repos, ~/.config/ldcli, and ~/.ssh stay unreadable. System folders and the network stay open. The profile uses resolved paths, because Seatbelt matches the real path behind a symlink. OpenCode's rules also deny `ldcli` and `npx skills` now. The CLI does the LaunchDarkly work, and skills must not be installed into the repo. Linux and Windows run without a sandbox for now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This draft adds the Go AI SDK (
github.com/launchdarkly/go-ai-sdk) to the CLI. The setup agent reads its instructions and model from theldcli-setup-agentAI Config, through the SDK relay in gonfalon (launchdarkly/gonfalon#75196 to #75199). No SDK key is on the user's machine.This PR is stacked on #876, and it also contains the commit of #877, because the SDK needs Go 1.26. Review the last two commits. After #877 merges and #876 picks up main, the upgrade commit leaves this diff.
How it works
127.0.0.1with a random path. The random path is the per-run secret, so OpenCode or another process cannot use the proxy.ldcli-placeholder.LD-CLI-Tokenand forwards each request to<base-uri>/internal/ldcli-setup-agent/sdk/{polling,stream,events,otel}/.... The relay checks the token, adds the real catfood key, and forwards to catfood.aiserver.InspectConfig. The CLI turns the variation into the OpenCode model and instructions.The proxy also solves a gap in the SDK: its trace exporter cannot add a custom header, so without the proxy the trace requests would reach the relay without the token.
If the user is not logged in, if the relay refuses or cannot be reached, or if the config is off or missing, the CLI uses the built-in instructions from #876. Setup does not fail because the config is unavailable.
The config decides the task
When the config loads, its instructions are the agent's system prompt. The first user message gives only the facts from setup (
BuildFacts): the SDK, the language, the framework, the package manager, the entry point, whether the SDK is installed, the flag key, and the credential variable. The CLI adds no task of its own, so the instructions decide what the agent does. If the config cannot load or has no instructions, the built-in instructions and the CLI's own task message (BuildPrompt) apply.Details
LD_BASE_URI,LD_SDK_KEY, orOTEL_EXPORTER_OTLP_ENDPOINT, and the CLI usesLD_BASE_URIfor its own API address.LD_ENVIRONMENT_IDfor its own process to the client-side ID that the relay sends in theLD-Setup-Agent-Environment-IDresponse header. If the relay sends no ID, the CLI clears the variable, so a value in the user's shell does not reach the SDK. OpenCode does not get it, because the CLI removesLD_variables from OpenCode's environment. The relay does not send this header yet. It needs aclientSideIdsetting in[ldcliSetupAgent]. Until then, the config loads, but runs do not link to the config's Monitoring tab.ldcli_setup_agent_disabled, and it reads the environment ID header.InitWithClient. The SDK's own client logs to the terminal, which breaks the wizard's full-screen view.ldcli-setup, a random key, and these attributes:sdkId,language,framework,packageManager,hasEntryPoint, andsdkInstalled. It has no file paths and no project names.go-ai-sdkis pinned to commit 6152dc9 (v0.0.0-20261016194641-6152dc96d702), because the SDK has no release yet.Test locally against a local gonfalon
sdkKeyset under[ldcliSetupAgent]ingonfalon.override.conf.enable-ldcli-setup-agent-servicein the local flag server:ldcli dev-server add-override --project default --flag enable-ldcli-setup-agent-service --data true.ldcli login --base-uri <local gonfalon URL>, so the CLI has a member token.ldcli setuporldcli setup agentwith that base URI.Cost
Not in this PR
go-ai-sdk.Testing
TestAIConfigSource_RealSDKThroughTheProxystarts the real AI SDK through the proxy against a fake relay. The SDK sent onlyGET .../sdk/stream/allandPOST .../sdk/events/bulk, both with the placeholder key and the token, and it read the config.LD_ENVIRONMENT_ID.go test ./...andpre-commit run --all-filespass.🤖 Generated with Claude Code