Adds both halves of the new release as composer chips: `qwen/qwen3.8-27b` on OpenRouter and ggml-org's Q4_K_M build served by the EVOX2 LM Studio. The local one exposed a wrong assumption. Provider was inferred from the id's shape — a `/` meant OpenRouter — but LM Studio serves this model as `ggml-org/qwen3.8-27b`, prefix and all, while OpenRouter has a `qwen/qwen3.8-27b` of its own. The two differ by provider, not by shape, so the runner now resolves the provider from `~/.pi/agent/models.json` (a model must be listed there for pi to run it locally anyway) and only falls back to the shape for ids it doesn't know — which keeps the whole OpenRouter catalogue runnable without registering ids by hand. That also fixes `laguna-s-2.1`, which the bare-id rule aimed at LM Studio's port instead of its own llama.cpp one. Session pricing carried the same assumption, so a local run with a prefixed id would have been billed at OpenRouter's estimate; it now prices from the declared provider and stays $0. Qwen 3.6 stays pickable, demoted from `latest`; the pi default model follows to 3.8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
claude-sidecar
A tiny host-side FastAPI wrapper that launches claude -p sessions on behalf
of the ai-agent viewer.
Why a host process (not a container)
The ai-agent viewer runs in Docker and can't reach the host's claude CLI — its
auth (~/.claude/.credentials.json), RTK/vault hooks, and skills all live in the
host user's home. This sidecar runs natively on the host as the repo owner, so
a session it spawns is exactly like one started from a terminal.
Flow
Frontend (sticky input)
│ POST /api/spawn {prompt}
▼
ai-agent backend (container)
│ generates a session UUID, POST /spawn to the sidecar
▼ http://host.docker.internal:8790/spawn (Bearer SIDECAR_TOKEN)
claude-sidecar (host)
└─ claude -p <prompt> --session-id <uuid> --output-format json \
--permission-mode bypassPermissions --model opus \
--remote-control (detached)
--remote-control is added by default so every spawned run registers with
Claude Code Remote Control and shows up in the Claude app — you can watch and
drive the conversation from your phone. Set SIDECAR_REMOTE_CONTROL=0 to opt
out.
Claude writes its transcript to
~/.claude/projects/-home-gabrielvidal-homelab/<uuid>.jsonl, which the ai-agent
container already watches read-only. The new conversation appears in the viewer
within ~2s and the frontend redirects to it once it has synced.
The conversation view has a matching sticky composer that continues an existing
thread: it POSTs /api/spawn's sibling /api/resume, which the backend proxies
to POST /resume here — claude -p <prompt> --resume <sessionId> in the
session's original cwd. Because resume reuses the session id, the new turns
append to the same transcript and stream straight into the open conversation.
Install (host)
sidecar/install.sh
Creates a venv, writes a systemd user unit (~/.config/systemd/user/claude-sidecar.service),
and starts it. Reboot survival needs sudo loginctl enable-linger $USER.
Config comes from the repo .env: SIDECAR_TOKEN, SIDECAR_PORT (default 8790).
The ai-agent container reads SIDECAR_URL + SIDECAR_TOKEN (see its
docker-compose.yml, which also adds host.docker.internal:host-gateway).
Endpoints
GET /health→{ok, cwd, claude}POST /spawn(Bearer auth){prompt, sessionId?, model?, cwd?}→{sessionId, pid, log}— spawns detached, returns immediately.POST /resume(Bearer auth){sessionId, prompt, model?, cwd?}→{sessionId, pid, log}— continues an existing conversation by runningclaude -p <prompt> --resume <sessionId>. Reuses the original session id, so the new turns append to the same transcript and stream into the open conversation.--resumeis directory-scoped, so pass the session's originalcwd.POST /interrupt(Bearer auth){sessionId}→{sessionId, pid, signal, ok}— sends the run's process group aSIGINT(like Ctrl+C), so Claude aborts the turn, writes a[Request interrupted by user]marker and exits.404if no live process is tracked for that session.
Per-session stdout/stderr is captured under logs/<sessionId>.log; the pid is
recorded in logs/<sessionId>.pid so /interrupt can find the run.
Manage
systemctl --user status claude-sidecar
systemctl --user restart claude-sidecar
journalctl --user -u claude-sidecar -f