larik v0.3.0

Guide

Server mode

larik serve exposes the current directory's agent over a local HTTP API. Each session streams its events over Server-Sent Events (SSE), so editors, web UIs and scripts can drive Larik. One server can run several sessions at once. They share MCP connections, language servers and the sandbox.

shell
./larik serve                          # 127.0.0.1:4096
./larik serve --addr 127.0.0.1:0       # pick a free port
./larik serve --model ollama/qwen3-coder --mode accept-edits   # defaults for new sessions

On startup the server prints one JSON line to stdout, {"url": "...", "token": "..."}, for programs that launch it.

Auth and safety:

  • Every request except GET /v1/health needs Authorization: Bearer <token>.
  • The token comes from --token, then $LARIK_SERVER_TOKEN, and otherwise is generated at random.
  • GET …/events also accepts ?token=, because browser EventSource can't set headers.
  • The server only listens on loopback. Requests whose Host isn't a loopback name are rejected, which blocks DNS rebinding.
  • --allow-remote lifts both restrictions. Anyone who can reach the port and has the token can then run commands.
shell
T=<token>; U=http://127.0.0.1:4096
ID=$(curl -s -XPOST $U/v1/sessions -H "Authorization: Bearer $T" -d '{}' | jq -r .id)
curl -N "$U/v1/sessions/$ID/events?token=$T" &                 # live events
curl -s -XPOST $U/v1/sessions/$ID/prompt -H "Authorization: Bearer $T" \
  -d '{"text":"summarize this repo","wait":true}'              # blocks, returns the answer
Endpoint Purpose
GET /v1/health Liveness check (no auth)
GET /v1/info Version, cwd, providers, sandbox
GET /v1/sessions Sessions in this directory, with loaded/busy flags
POST /v1/sessions New session {model, effort, mode}, or load one with {resume: id} / {continue: true}
GET /v1/sessions/{id} Model, mode, busy, usage, pending permissions, running tasks
PATCH /v1/sessions/{id} Change model, effort or mode
DELETE /v1/sessions/{id} Stop and unload (the transcript stays on disk)
GET /v1/sessions/{id}/messages Full transcript (works for unloaded sessions too)
POST /v1/sessions/{id}/fork Branch into a new loaded session. {at: i} keeps the messages before index i (which must be a prompt) and returns that prompt's text; with no at, everything is kept
GET /v1/sessions/{id}/events SSE event stream
POST /v1/sessions/{id}/prompt {text} starts a run and returns 202 (409 if busy); {text, wait: true} returns the final answer
POST /v1/sessions/{id}/cancel Interrupt the current run
GET /v1/sessions/{id}/permissions Pending permission requests
POST /v1/sessions/{id}/permissions/{request_id} Answer: {allow, always, reason}. An always answer returns {allowed, persisted} and an error if saving failed; other answers return 204.
POST /v1/sessions/{id}/compact · /undo · /clear Same as the TUI commands
GET /v1/sessions/{id}/tasks · DELETE …/tasks/{task_id} List or stop background tasks

The event stream:

  • Each SSE message has id: <seq> and event: <type>. Its data is JSON: the agent event (the same schema as -p --output json) plus seq, session, and request_id on permission requests.
  • The server adds four event types:
    • status (busy true/false)
    • user_message
    • permission_resolved (allowed, denied, or expired when the run was cancelled)
    • session_closed
  • Reconnect with Last-Event-ID (or ?after=<seq>) to replay missed events from a 4096-event buffer. Without it, a stream starts with new events only.
  • A run's permission requests wait until some client answers them or the run is cancelled.
  • When background tasks finish while a session is idle, the server starts a turn by itself to hand their results to the model.

Generated from README.md · section “Server mode”. Edit that file to change this page.