021 · Service
The per-user service and its API (spec_service_021)
| Status | Implemented. Reverse-engineered from 53f8c53 (2026-09-24) and kept with the code since; its paths and names checked 2026-09-27 (//tools/specs). |
| Source | proto/blitz/v1/{session,workspace,turn,worker}.proto, buf.yaml, buf.gen.yaml; apps/service/main.go (blitzd), apps/service/internal/{daemon,server}/*.go, apps/service/servicetest; pkg/socket; proto (generated, never edited); apps/cli/service.go |
| Tests | apps/service/*_test.go, apps/service/internal/{daemon,server}/*_test.go, apps/cli/attach_test.go, service_test.go |
| Depends on | spec_workspace_018 |
| Used by | spec_client_022, spec_workers_023, spec_desktop_024 |
blitzd, its own program (apps/service), runs one engine process per user that holds every workspace a client opens, runs scheduled workers, and exposes api.Backend over Connect (gRPC, gRPC-Web or plain JSON) on a Unix socket. The CLI and the desktop app share one copy of each workspace through it. Handlers only translate between protos and pkg/engine; they hold no logic.
- SVC-00
blitzd [--socket PATH] [--config FILE]: no arguments (a usage error, exit 2, as a bad flag or a service already answering on the socket);--version;--license[=full|third-party]prints the NOTICE, the Apache License or the third-party notices and exits without starting. It takes no model, agent or workspace flags: each workspace’s configuration decides.blitz serve, from beforeblitzdexisted, is kept as a hidden command that runsblitzd(found as in SVC-50) with the same arguments, so login items installed by older versions keep working.
- SVC-00a Since 2026-10-04 (the thin CLI, spec_cli_020 CLI-14).
--idle-exit DURATIONstops the service cleanly after that long with nothing to do: no request in flight, no turn running in any workspace, no background run going (Server.IdleFor; the CLI starts the shared service with 15m when there’s no login item). Hidden flags for a private service a client starts for a run of its own:--exit-with-stdin(stop when stdin, the client’s pipe, closes; such a service never runs the workers’ schedules, which would run them twice and open every workspace with an enabled worker, keeping it from the shared service), and the run’s settings over every workspace’s configuration:--model,--agent,--agency,--plugin-dir,--add-dir(assandbox.allowed_paths),--session-dir,--trust-project,--append-system-prompt-file.blitzd doctor [--config] [--dir] [--model] [--agent] [--agency] [--plugin-dir] [--add-dir] [--online]runs the setup checks (spec_cli_020 §6), exit 1 when one fails.WorkspaceService.ForgetProjectTrustforgets the decision about a workspace’s project settings and reopens it without them (unless a turn runs);ConfigService.ListModelstakesprovidersand returns each model with Blitz’s price. - SVC-01 Socket:
$BLITZ_SOCKETor~/.blitz/run/blitz.sock(blitzd --socketoverrides it). The service is never on a network port: it runs shell commands. (On Windows the tray serves the desktop page on a loopback port, behind a cookie only its own link gives: spec_windows_036 WIN-11–13.) - SVC-02
Listen: create the directory 0700 and chmod it (no window where others could reach the socket), refuse if a service already answers (ErrRunning→ exit 2), replace a stale socket, chmod the socket 0600. The socket is removed on exit. - SVC-03 HTTP/1.1 and unencrypted HTTP/2 (for gRPC clients); read-header timeout 10 s. Clients use base URL
http://blitzwith a dialer to the socket. - SVC-04 Shutdown on SIGINT/SIGTERM: stop accepting, wait up to 10 s for calls in progress, then close (turns still running are cut off); the scheduler stops its runs; all workspaces close.
- SVC-06
GetServiceInforeports the service’s version (the release, ordev), the program it runs from (os.Executableat start; the file may have been replaced or removed since), when it started, its process ID, andreplaced: whether the program file is no longer the one it started from (another file in its place, as a package install or a new build leaves, or none;os.SameFileagainst the file at start), so clients can tell a stale service (desktop DSK-51a). Services older than it answerUnimplemented. - SVC-06a
ListLogDaysandReadLogread the service’s own diagnostic log (spec_observability_003 LOG-06), in itslog.dir(WithLogDir): the days there are and the directory; a day’s records by level and text, newest first, with the file and how many matched.DeleteLogDaydeletes a past day’s log (DeleteLogDay, LOG-06): today’s is being written (LOG_IN_USE), and a day with no log, or logging off, isLOG_NOT_FOUND. Withlog.level = "off"there’s no directory: no days, no records. A bad day or level isinvalid_argument. - SVC-05 Clients name their workspaces in each request. Request messages are limited to 32 MiB (an added image is the largest).
- SVC-10 Every request names its workspace by absolute directory. The server keys workspaces by canonical path (symlinks resolved), opens one on first use with a fresh configuration per workspace (a workspace owns and mutates its config), streaming on, warnings to the log, and the shared worker store.
- SVC-11 Opening is asynchronous and de-duplicated: callers for the same workspace share one open; other workspaces don’t wait. A caller whose context ends gets
Canceled. After close, requests getSHUTTING_DOWN. - SVC-12 The workspace lock (spec_workspace_018 WS-03) means a workspace held by the shared service can’t also be opened by a private one (
blitz --local,WORKSPACE_BUSY) until the shared one closes it, which it does when asked and no turn runs there. - SVC-14
CloseWorkspacerefuses while a turn runs in any of the workspace’s sessions (TURN_RUNNING, FailedPrecondition): closing would cut off the turn (maybe another client’s), and a turn waiting for an approval would hold the close forever. Closing an idle workspace is safe for workers: the scheduler reopens workspaces when a run is due. - SVC-13 Images: each workspace keeps the 16 most recently used prepared images by ID for later turns (
LoadImage/AddImagereturn IDs;Turn.image_idsrefer to them; unknown →UNKNOWN_IMAGE). PDFs are images here too, with theirpages(spec_images_011 IMG-50). Every attachment carries itskindand, for audio and video,seconds; one the model can’t take isUNSUPPORTED_MEDIA. - SVC-13a Added content:
Turn.context(ContextBlock: kindweb_page,web_selectionorconsole, URL, title, HTML or text), what the user added from the desktop app’s web preview (spec_electron_038 EL-104). The engine (withAdded) gives it to the agent after the prompt’s@mentions, each block’s HTML made into text (textutil.HTMLToText, the web fetch tool’s), cut to 64 KB a block and 256 KB in all with a note, inside<user-added-content>with the note that it’s data written by the page’s authors, not instructions; a block can’t close its own fence. The transcript records the prompt with[added: <titles>], as it does[images: …].
| Service | RPCs |
|---|---|
SessionService |
ListSessions, GetActiveSession, NewSession, OpenSession, LoadSession, ListSessionTools, SaveSnapshot, RenameSession, DeleteSession (SES-23; SESSION_OPEN, SESSION_NOT_FOUND), RunTurn (server stream), Steer, Approve, Answer, GetUsage, Compact, SearchSession |
WorkspaceService |
GetServiceInfo, ListLogDays, ReadLog, DeleteLogDay (SVC-06, SVC-06a), ListWorkspaces, CloseWorkspace, GetSandbox, ListAgents, SetAgent, GetModel, SetModel, PinModel, UnpinModel, GetModelSettings, UpdateModelSettings (every [model_settings] key, including reasoning_effort and thinking_budget), GetSettings (includes permission_mode, effort, the speech model with why it can’t be used, speech_model and speech_error, and what the active agent’s model takes as attachments, accepted_media: spec_images_011 IMG-63), SetSetting, SetPermissionMode, ListCommands, ListPermissionRules (with problem: why rules were left out at the last reload), AddPermissionRule, RemovePermissionRule, ListSkills, GetSkill, ListEnvs, RemoveEnv, PruneEnvs, ListMCPServers, ListTools, ReloadMemory, ReloadAgentFiles (/reload, spec_memory_012 MEM-25), AddMemory, ListLocales, SetLocale, ListCheckpoints, Undo, GetDiff (with git, every change since the last commit and new files: WS-61), GetGitStatus (the branch and changed files, and whether git is installed, for the desktop’s status bar and Changes; a repository with no commit yet is one), InitGitRepo (git init in a workspace that isn’t in a repository; NO_GIT, GIT_FAILED), DraftCommit (the staged files, everything staged first with stage_all, and a commit message the suggestions model, else the reviewer’s, else the main one, drafts from the staged diff, cut at 24,000 characters with secrets redacted, following the style of the last ten subjects; nothing asked with nothing staged; a failing model gives problem, its redacted summary, instead of an error; NOT_A_REPOSITORY, GIT_FAILED), Commit (what’s staged, git commit -F -, the repository’s hooks run, two minutes at most; the new commit’s short hash and subject; NOT_A_REPOSITORY, NOTHING_STAGED, EMPTY_MESSAGE, GIT_FAILED), FetchRemote, Pull, Push, ResolveConflict, CompleteMerge, AbortMerge (the workspace’s remote, SVC-36), ListBranches, SwitchBranch, CreateBranch, MergeBranch (its branches, SVC-37), ListApprovals, RevokeApprovals, ListProcesses, GetProcessOutput, KillProcess (a background process records the session whose turn started it; each call names the client’s sessions and sees only their processes, UNKNOWN_PROCESS otherwise), AuditShell, MoveSession (on SessionService, for /cd, SES-18; SessionInfo.origin says what started a session, SES-24), ListTasks, GetTask, StopTask, WatchTasks (a stream: active tasks and waiting requests, ready, then changes), ListTaskRequests (spec_background_agents_032; Approve and Answer also answer tasks’ requests), GetProjectSettings, TrustProject (records the decision and reopens the workspace unless a turn runs in it; PROJECT_CHANGED for content changed since it was shown, spec_project_config_031), LoadImage, AddImage, GetSearchProvider, SearchWeb |
FileService |
ListDir, ReadFile, ReadPreview (images, PDFs and sound), WriteFile, WriteBinaryFile (bytes, as WriteFile writes text: an exported PDF, spec_desktop_024 DSK-93), CreateFolder, RenameFile, DeleteFile, FindFiles, StatFiles (spec_files_029 §3; reasons FILE_CHANGED with current_version, BAD_PATH, FILE_NOT_FOUND, FILE_EXISTS) |
ConfigService |
DescribeConfig, GetSignIn, SignIn, SignOut, SetApiKey, SecureApiKey, RemoveApiKey, SetProvider, SetConfigValue, DescribePermissions, AddPermission, RemovePermission, CheckPermission, SetReadOnlyDefaults, GetConfigFile, SaveConfigFile, CheckConfigFile, GetSettingsReference (SVC-35), ListModels (SVC-55) |
WorkerService |
ListWorkers (one workspace or every registered one), CreateWorker, EnableWorker, DisableWorker, DeleteWorker (WK-01c), RunWorker, ListWorkerRuns, GetWorkerRun, WatchWorkerRun (server stream), GetWorkerSpec, UpdateWorker, UndoWorkerRun, ListRunningWorkerRuns, StopWorkerRun (WK-53a), RewriteWorker (WK-53b) |
- SVC-35
ConfigServiceedits the settings of a scope —workspacean absolute directory,""the global settings — through pkg/config (spec_config_002 §6), in the service’s settings directory (--config). It never returns a key, only where each comes from. A change reloads the open workspaces it touches (every one for the global scope): each reloads its configuration and rebuilds its model and its agents’ pinned models (Workspace.ReloadProviders; only the providers and the default model change; other settings apply when a workspace next opens; a pin that can’t be built keeps its model), and the response carries the scope’s workspace’s model error, if any. A workspace whose model couldn’t be built tries again (Workspace.RetryModel) whenGetModelasks and before each turn or worker run, so a sign-in made outside Blitz (gcloud’s Application Default Credentials,ant auth login) is noticed without a settings change. Invalid input (a relative directory, an unknown provider or setting, a file that doesn’t decode) isInvalidArgument. - SVC-36 Since v0.6.3. The workspace’s remote, for the desktop’s Changes view (spec_desktop_024 DSK-90b;
pkg/engine/gitremote.go).GetGitStatusalso reportsremote, the branch’supstream,aheadandbehind(as of the last fetch), a merge or rebase in progress (operation) and the files inconflicts.FetchRemotefetches the upstream’s remote (else every remote) with--prune.Pullrunsgit pull --autostash, adding--no-rebaseonly when none ofpull.rebase,branch.<name>.rebaseandpull.ffis set (git refuses divergent branches unasked); its outcome isUP_TO_DATE,FAST_FORWARD,MERGED,REBASEDorCONFLICTS(not an error: the merge or rebase waits), with the commits it brought in.Pushpushes to the upstream, or publishes the branch (--set-upstreamto origin, or the only remote) and returnsremote/branch.ResolveConflictsettles a conflicted file:MINE,THEIRS(swapped into git’s terms during a rebase, whose “ours” is the upstream; a side that deleted the file deletes it) orRESOLVED(git add, refused while conflict markers remain).CompleteMergecommits the merge (git commit --no-edit) or continues the rebase, which may stop on the next commit’s conflicts;AbortMergerunsgit merge --abortorgit rebase --abort. Git never prompts or opens an editor (GIT_TERMINAL_PROMPT=0,GIT_EDITOR=true,GIT_MERGE_AUTOEDIT=no) and has two minutes. Settings that run commands —core.sshCommand,core.gitProxy,core.askPass, credential helpers, filter drivers, merge drivers,remote.*.uploadpack/receivepack,protocol.*.allow,gpg.*.program— are taken from the user’s global and system config only: the agent can write the repository’s.git/config, so when it (or what it includes, or the worktree’s config) sets one, these actions refuse withREPO_CONFIG_COMMANDnaming the key. Hooks run, asCommit’s do. Errors:NOT_A_REPOSITORY,NO_REMOTE,NO_UPSTREAM,GIT_BUSY(a merge or rebase waits),PUSH_REJECTED(the remote has commits the branch hasn’t),REMOTE_AUTH(credentials wanted),NO_MERGE,CONFLICTS_REMAIN,MARKERS_REMAIN,NOT_CONFLICTED,BAD_PATH,INVALID_CHOICE,GIT_FAILED. - SVC-37 2026-10-16 (the owner’s request). The workspace’s branches, for the desktop’s Changes view (spec_desktop_024 DSK-90c;
pkg/engine/gitbranches.go).ListBrancheslists the local branches, then the remotes’ branches no local branch tracks (a remote’sHEADleft out), each newest commit first, withcurrent, a local branch’supstream, and its last commit’ssubjectandtime.SwitchBranchrunsgit switchto a local branch (--no-guess), or to a remote’s (origin/feature) with--track, which makes a local branch that tracks it; uncommitted changes come along unless they clash (LOCAL_CHANGES, nothing changed).CreateBranchrunsgit switch --createat the current commit, keeping uncommitted changes.MergeBranchrunsgit merge --no-edit --autostashwith a local or remote branch: its outcome isUP_TO_DATE,FAST_FORWARD,MERGEDorCONFLICTS(as a pull’s: the merge waits forResolveConflict,CompleteMergeandAbortMerge, where “mine” is the current branch and “theirs” the merged one), with the commits it brought in.GetGitStatusalso reportsmerging, the branch a merge in progress brings in (git name-revofMERGE_HEADover the local and remote branches; empty when it isn’t a branch’s tip), so the Changes view names it. A name must passgit check-ref-format --branchand can’t start with-. These check out files and merge them, which runs filters, merge drivers and hooks, so they take SVC-36’s care: they refuse while a merge or rebase waits (GIT_BUSY), the repository’s own config may not set a command (REPO_CONFIG_COMMAND), and git never prompts or opens an editor. Errors:NOT_A_REPOSITORY,NO_BRANCH,BRANCH_EXISTS,BAD_BRANCH_NAME,LOCAL_CHANGES,MERGE_SELF(merging the current branch),GIT_BUSY,REPO_CONFIG_COMMAND,GIT_FAILED.
- SVC-20
RunTurn(workspace, session_id, Turn)streamsTurnEvent{author, oneof: accepted | text | tool_call | tool_result | approval_request | question | tasks | finished | notice}. Events are sent under a lock (the agent and approval requests can produce them concurrently). The last event is alwaysfinished{output, before, after, leftover, error}; a hook refusal finishes with reasonPROMPT_BLOCKED. Cancelling the call interrupts the turn. Anotice(text, error, transient) is Blitz’s own message about the turn, not the agent’s: a transcript message that couldn’t be saved (WS-28). Since v0.6.3 atransientnotice is a passing status, the model’s connection failing and being retried (spec_models_015 MDL-51b): the desktop shows it in a snackbar and doesn’t keep it in the chat; the terminal prints it as a line. - SVC-21
acceptedmarks the prompt recorded; from then on the client maySteer. - SVC-22 Approval broker: the workspace’s approver and question prompter become
approval_request{request_id, tool, kind, detail, diff, scope_label}/question{request_id, question, options}events on the running turn’s stream, and the agent waits untilApprove(request_id, decision)orAnswer(request_id, text)arrives, or the turn ends. Request IDs are random 96-bit hex. Answering an unknown or expired request →NotFound UNKNOWN_REQUEST. Decisions:DENY,ONCE,SESSION(until the service restarts),ALWAYS(saved); unspecified →INVALID_DECISION. - SVC-23 An approval or question outside a turn with a client (e.g. from a worker) is refused (
no client is attached to answer).
- SVC-30 Errors are Connect errors with an
ErrorInfo{reason, metadata, message}detail, mapped frompkg/enginetyped errors. Reasons:UNKNOWN_COMMAND,BAD_RULE,UNKNOWN_MODE,BYPASS_NEEDS_SANDBOX,MAX_TURNS,COST_LIMIT,TIME_LIMIT(a turn stopped at a limit;Turn.max_cost_usdandTurn.timeoutcarry the limits),INVALID_WORKSPACE,OPEN_FAILED,WORKSPACE_BUSY,SHUTTING_DOWN,NO_ACTIVE_SESSION,SESSION_NOT_FOUND,RESUME_FAILED,SNAPSHOT_NAME_TAKEN,NO_NOTE,NO_GOAL,PROMPT_BLOCKED,INVALID_TURN,UNKNOWN_REQUEST,INVALID_DECISION,UNKNOWN_AGENT,BAD_MODEL_REF,INVALID_SETTING,UNKNOWN_SETTING,INVALID_AGENCY,UNKNOWN_LOCALE,NOTHING_TO_COMPACT,OUTPUT_LIMIT(the turn’s last model reply stopped atmax_tokens, cut off: a tool call it was writing is dropped),UNDO_CONFLICT,NOTHING_TO_UNDO,GIT_FAILED,NO_GIT,IMAGES_DISABLED,UNKNOWN_IMAGE,NO_FETCH,NO_SEARCH,SCRIPTS_DISABLED,SKILL_NOT_FOUND,UNKNOWN_WORKER,WORKER_INVALID,WORKER_EXISTS,WORKER_DISABLED,WORKERS_DISABLED,HASH_MISMATCH,RUN_IN_PROGRESS,RUN_NOT_RUNNING,UNKNOWN_RUN,TOO_MANY_RUNS,NO_SCHEDULER,INTERNAL. - SVC-31 Errors reported inside a response (e.g. a failed save, a turn failure) use the same
ErrorInfo.
- SVC-40 Protos live in
proto/blitz/v1(packageblitz.v1), linted with bufSTANDARD, breaking-change checked withFILE. Their Go code is generated by Bazel and never committed: one package,github.com/retail-cortex/blitz/proto/blitz/v1(blitzv1), holds the messages and Connect’s clients and handlers (protoc-gen-connect-gowithpackage_suffix=;//proto/blitz/v1:blitzv1_go_proto). So is the desktop page’s TypeScript (//apps/desktop/web:api_ts,protoc-gen-espinned by the page’s lockfile). CI checks the protos’ formatting and breaking changes with buf (spec_release_025 REL-04). - SVC-41 A deliberate breaking change is declared with a
Breaking-API: <why>line in the commit message, which CI’sbuf breakingstep honours.
- SVC-50 The login item lives in
pkg/loginitem, shared by the CLI and the desktop app (which installs, restarts and stops the service itself, DSK-51a). On Linux, installing restarts a running unit, so a reinstall runs the new program.install: findsblitzdbeside the real (symlinks resolved)blitzexecutable, else onPATH(neither: usage error), and registers it; macOS writes~/Library/LaunchAgents/dev.blitz.service.plist(RunAtLoad, KeepAlive on unsuccessful exit, stdout/stderr to~/.blitz/logs/service.log) andlaunchctl bootout/bootstrap gui/<uid>; Linux writes~/.config/systemd/user/blitz.service(Restart=on-failure,WantedBy=default.target),daemon-reload,enable --now. Other OSes: usage error. Prints that it must be re-run after upgrading, and warns about API keys set only in the shell environment (a login item doesn’t see them) by reloading the config without them. - SVC-51
uninstallstops and removes the item;statusreports whether the item is installed and whether the service answers. - SVC-52
loginitem.StartandRestartstart or restart the login item’s service (systemctl --user start|restart blitz.service; launchd:bootstrapif unloaded, thenkickstart, with-kto restart); without the item they areErrNotInstalled. - SVC-53 The tray (
apps/tray,blitz-tray): an icon in the system tray, a white bolt on a transparent background (apps/tray/icons/*.svg; a template icon on macOS, which tints it to suit the menu bar), filled while the service answers on its socket and outlined when it doesn’t, with a tooltip and a first, disabled menu line saying so (“Blitz service: running (v1.4.0)”, “stopped” for a login item that isn’t running, “not running”); then Start the service (while it’s stopped), Stop the service and Restart the service (while it runs), Open Blitz, Open the logs, since v0.7.1 MCP servers and Workers (submenus, shown while the service runs and answers: spec_mcp_009 MCP-48, spec_workers_023 WK-57), Quit the tray (the service keeps running). It asks every 2 s (Probe: the socket, andGetServiceInfofor the version and process). Start, Stop and Restart go through the login item when there is one (SVC-52), else Start runsblitzdfrom beside the tray orPATHon its own, and Stop asks the service to stop (Shutdown), then sends the process that answered SIGTERM; each waits for the service to be (or not be) running, up to 15 s, and says “Couldn’t: …” in the first line for 5 s when it fails. Open Blitz runs the bundle the tray is in (macOS: the nearest.appabove it,Blitz.app/Contents/Resources/app/binin the Electron app) orblitz-desktop(Linux: the Electron app’s, three folders up fromresources/app/bin, then beside the tray, then onPATH) (on Windows it serves and opens the page: spec_windows_036); Open the logs opens the running service’s log folder in the file manager (whatListLogDayssays, thelog.dirit started with), elselog.dirof the global settings. The menu is in the desktop app’s language (its Interface language, from~/.blitz/desktop.json; System follows the system’s languages: macOS’s list, thenLANGUAGE,LC_ALL,LC_MESSAGES,LANG), from the same catalogs (tray.*keys). One tray at a time: it holds~/.blitz/run/tray.lock(flock;LockFileExon Windows) while it runs, and a second one exits. On macOS the tray is an accessory app (NSApplicationActivationPolicyAccessory,apps/tray/dock_darwin.go), set before its menu bar item: run fromBlitz.app/Contents/MacOSit would otherwise take the bundle’sInfo.plistand show a second Blitz icon in the Dock and the app switcher. - SVC-53a Since v0.7.0 (the Electron trial, spec_windows_036 WIN-14).
blitz-tray --serve [--page <dir>]serves the desktop page for a window that shows it, with no icon and without the tray’s lock (a tray can show its icon beside it): the page in<dir>(it must haveindex.html), else the one built in (Windows builds; elsewhere it’s an error without--page), the API through the service’s socket, and/host/(pkg/pagehost). It writes a one-time link to the page as a JSON line ({"url": …}) on standard output, a new one for eachlinkline it reads, and stops serving when standard input closes, so it never outlives the window that started it (tray.Serve). - SVC-54 The tray starts at login from its own entry:
blitz-tray --installwrites it and starts the tray (Linux: an XDG autostart entry,$XDG_CONFIG_HOME/autostart/blitz-tray.desktop, and the tray started now in a graphical session; macOS: a launchd agentdev.blitz.tray,LimitLoadToSessionTypeAqua, which starts it);--uninstallremoves it (on macOS, stopping it). On Windows it’s a value in the registry’s Run key, which the tray’s Start Blitz at login also toggles (spec_windows_036 WIN-05). The desktop app’s Show in the system tray (Settings › Service, DSK-60) does the same through its host (pkg/pagehostSetTray): it writes the entry and, on Linux and Windows, starts the tray now (Host.StartTray), or removes it. Since v0.7.1 the host also repairs entries an older install wrote when it starts (Host.Repair,loginitem.RepairTray/RepairService): an entry whose program is gone (the desktop app’s moved in v0.7.0, from/usr/lib/blitz-desktop/blitz-trayto…/resources/app/bin/blitz-tray) is rewritten for the current one, the tray started and the service’s login item restarted; an entry whose program is there is left alone. On Linux the icon is the bolt in colour, as on Windows (amber running, grey stopped), so it shows on light panels and dark; macOS keeps the white template icon. Linux needs a StatusNotifierItem host: KDE and most desktops have one; GNOME has it with the AppIndicator extension (on by default on Ubuntu); without one there’s no icon. - SVC-55 2026-10-01.
ConfigService.ListModels(workspace): each configured provider’s models from its API (runtime.ListModels, asblitz models), with why a provider couldn’t be listed and the default provider, for the desktop’s model fields to suggest and check names. Kept per scope for 10 minutes (1 minute when a provider failed), and dropped whenever a settings change goes throughConfigService(a new key, provider or file). Model names for gemini, anthropic and vertex-anthropic are lowercased wherever a model is chosen (runtime.ParseModelRef): their IDs are always lowercase, and a name a text field capitalized (“Gemini-3.8-flash”) is otherwise a 400 from the API. - SVC-56 2026-10-04 (the owner’s request: sign in to each provider, as well as API keys, never instead). Sign-ins with each vendor’s own tool, run by the service so the credentials land where it reads them (
pkg/signin):google(gcloud auth application-default login; signed in when the Application Default Credentials file parses, read without asking Google),anthropic(ant auth login, the profile throughANTHROPIC_PROFILE; signed in when the profile has credentials),aws(aws sso login [--profile]; signed in when the chain Bedrock uses gives credentials) andazure(az login; signed in when Entra ID gives a token for Azure OpenAI). Each also signs out (revoke,logout). The tools are found on the service’s PATH, else the login shell’s (pkg/shellpath, which the desktop app’s CLI install shares), run with a fixed argument list (a profile is letters, digits,.,_and-), no stdin, at most 10 minutes, one sign-in per provider at a time.ConfigService.GetSignIn(workspace, provider?, profile?)returns each provider’s status (the four checked at once, each within 5 s; AWS and Azure answer at once when nothing for them is set up, without waiting on a cloud’s metadata service);SignInstreams the tool’s lines with the sign-in page’s URL and a device code pulled out, thendonewith the status;SignOut. After either, the service forgets the model listings and builds again the open workspaces’ models that had failed. Errors:UNKNOWN_PROVIDER,INVALID_PROFILE,NO_TOOL(with where to get it),SIGN_IN_RUNNING,SIGN_IN_FAILED(the tool’s last lines). OpenAI has no sign-in: it takes an API key. - SVC-57 2026-10-09 (found when pyright, installed with nvm, never answered in the desktop app). A service started by the tray, launchd or systemd has the system’s bare PATH (
/usr/bin:/bin:/usr/sbin:/sbinon macOS), without the folders the user’s tools are in (nvm, Homebrew,~/go/bin). At start it takes the PATH of the user’s login shell (AdoptUserPathinpkg/shellpath: the shell’s folders first, in its order, then the service’s own), so language servers, the agent’s commands and sign-in tools find what the terminal finds. A private service (--exit-with-stdin, started by the CLI) keeps its terminal’s. Windows has no login shell to ask: nothing changes there.