In managed mode the extension switches models by rewriting the daemon config's defaultModel and restarting it, but loadState() preferred the persisted model, so the daemon came back on the old one every time. State now records the default it was saved under; a different default wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J39imRDT8L7xYbWzsVWd8A
acd: local completion daemon
Builds fill-in-the-middle prompts in exactly the format the models were trained on (it shares
../shared/ with the training pipeline) and serves completions through a llama-server it manages.
Editors talk to it over localhost HTTP. Design notes: ../docs/architecture.md.
You usually don't run this yourself: the VS Code extension bundles it and runs it in managed mode. Run it standalone for external mode, for other editors, or when developing.
Running standalone
Requires Node ≥ 22.18 (runs TypeScript directly) and a llama-server binary (brew install llama.cpp on macOS,
or a llama.cpp release on Linux).
npm install
npm start # node src/main.ts --config acd.config.json
node src/acctl.ts status # or: mode simple|symbols|raw · model <name> · on · off
npm run smoke # end-to-end test (needs the models from ../models, via Git LFS)
acd.config.json uses llama-server from your PATH with -ngl 99 (Apple GPU), and the models in ../models.
- Linux / CPU: copy it to
acd.config.local.json(git-ignored), set"binary"to your llama-server and"args": ["-t", "<physical cores>"], then runnode src/main.ts --config acd.config.local.json. - Serving another machine (e.g. an M1 Max serving a laptop): set
"host": "0.0.0.0"and point the editor'sserverUrlat it, usingdaemon.mode: "external". There's no auth, so only do this on a trusted network. - Start on login:
acd.serviceis a systemd user unit.
Context modes
| mode | prompt | models |
|---|---|---|
symbols |
repo header + language-service symbols (imports' signatures, repo-local types), from ../shared/symbols.ts, the same code that built the training data. Requires the editor to send workspaceRoot; otherwise the request runs as simple. |
stage-c |
simple |
repo name + file path header | stage-c |
raw |
plain suffix-first FIM | stage-b (spm), base-0.5b (psm) |
The mode and model are global to the daemon and persisted in acd.state.json. VS Code, Zed and acctl all see the same toggle.
How symbols mode stays fast:
- Context is built in a worker thread (one TypeScript language service per workspace) and cached per anchor: the file, the enclosing top-level declaration and the import block. While you type inside one function the header is identical and the KV cache keeps hitting; the smoke test shows the same ~2 re-processed tokens per keystroke as
simple. - When the anchor changes, the daemon waits up to
symbols.waitMsfor fresh context, else uses the file's previous context (stale-while-revalidate). - Member completions after
.are off (symbols.members): every.would change the header and force a cold prefill.
this. repair (thisRepair.enabled, any mode, needs workspaceRoot): when the identifier you're typing only resolves as a member of the enclosing class, the daemon completes as if this. were there. It returns replaceBefore + filterText so the editor replaces getU with this.getUser(id).
Edit recordings: POST /v1/edits appends opted-in editor edit events to recording.dir (default recordings/, git-ignored). These are the future training data for next-edit prediction.
API
POST /v1/fim:{ requestId?, repo, path, languageId?, prefix, suffix, prefixLineOffset?, mode?, singleLine?, maxLines? }returns{ text, mode, singleLine, timings, … }. A newer request cancels the in-flight one; closing the connection cancels too.GET|PUT /v1/config:{ enabled, model, mode }. Changing the model restarts llama-server.POST /v1/feedback: editor events, written to the local log only whentelemetry.enabled.POST /v1/edits:{ events }from editors that opted in to edit recording.- The
/v1/fimrequest may includeworkspaceRoot(absolute path on the daemon's machine). Responses may includereplaceBefore/filterText(thethis.repair) andsymbols: { status: hit|fresh|stale|none, items }. GET /health