Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Huskarl AI colleagues on your own Mac Starf An activity monitor for Apple silicon that never invents a number etrans An SSH and SFTP client for macOS Elyra VM Virtual machines for macOS, Linux and Windows on your Mac Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Start here
Concepts
Release notes
What's new
Elyra
The desktop assistant

The desktop assistant

Félagi Desktop has an assistant: a panel where somebody asks, in words, for what the app's own commands can do — "which issues are mine this cycle", "move ACM-42 to review". It is built on Elyra's assistant, and Elyra's assistant asks Claude.

It can ask through this server. Then no key is on anybody's laptop: the desktop sends the Messages API request it would have sent Anthropic, with the person's own API token, and the server adds its key, checks what is being asked, and passes the answer back.

Off by default, like the other two places where Félagi's server calls a model (minutes, tidying up prose — see Configuration). An installation that decided nothing sends nothing anywhere.


Turning it on

FELAGI_ASSISTANT_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-…

The key and the address are the AI SDK's own (ANTHROPIC_API_KEY, and ANTHROPIC_URL, which defaults to https://api.anthropic.com/v1) — the ones minutes already use. anthropic is the only provider: the desktop sends the Messages API's own body. Anything else, or no key, and the route answers 503 without calling anybody.

Variable Default
FELAGI_ASSISTANT_PROVIDER empty — off anthropic turns it on
FELAGI_ASSISTANT_MODELS claude-opus-5-5,claude-sonnet-5-5 The models the desktop may ask for, comma-separated. Anything else is a 422
FELAGI_ASSISTANT_MAX_TOKENS 16000 A larger max_tokens is lowered to this. Thinking counts against it, and Opus 5.5 always thinks — a cap of 4,096 cuts answers off
FELAGI_ASSISTANT_PER_MINUTE 30 Requests per person per minute, across all their devices and tokens. Over it is a 429 with retry-after
FELAGI_ASSISTANT_STREAM false Pass stream: true through as Claude writes it. Off because Askr holds a streamed response until it is finished; the desktop then asks for each answer whole
FELAGI_ASSISTANT_FALLBACKS true Ask the API to answer a refusal with its fallback model ("fallbacks": "default" and the server-side-fallback-2026-07-01 beta). Only against api.anthropic.com

GET /api/v1/me says whether it is on, as features.assistant, so the desktop hides the assistant instead of offering one that answers 503.


The route

POST /api/elyra/assistant/v1/messages
Authorization: Bearer fat_…
Content-Type: application/json
anthropic-version: 2026-10-21

{ "model": "claude-opus-5-5", "max_tokens": 16000,
  "system": "…", "messages": [ … ], "tools": [ … ] }

Elyra's contract (Backend::assistant() in the desktop), not the API's, which is why it is outside /api/v1: it versions with Elyra and with Anthropic. Any of the person's API tokens — a read token is enough, since asking a question moves nobody's work — and the API's own rate limit applies as well.

The answer is Claude's, unchanged: its status, its body and its retry-after, a 429, 529, 5xx or 400 included, so the desktop's own handling reads the real thing. On success the server dispatches App\Events\AssistantUsed — the person, the workspace, the model, inputTokens, outputTokens, never the prompt — and logs the same. Nothing else is stored.

What is checked, in order

Every refusal is in the Messages API's error shape, which the desktop shows:

{ "type": "error", "error": { "type": "invalid_request_error", "message": "Streaming isn't proxied." } }
Answer
Not switched on 503 api_error — The assistant isn't set up on the server. Nothing is sent anywhere
No token, or a bad one 401, as everywhere in the API
Over FELAGI_ASSISTANT_PER_MINUTE 429 rate_limit_error, with retry-after in seconds
A body that is not a JSON object 400 invalid_request_error
A model not in FELAGI_ASSISTANT_MODELS 422 invalid_request_error — The model `…` isn't allowed here. Exact ids: a dated id is refused
stream: true with streaming off 422 invalid_request_error — Streaming isn't proxied. Elyra reads this as "this server does not stream" and asks again whole
A tool with a type other than custom — web search, code execution, any of Claude's own 422 invalid_request_error — Server tools aren't allowed here. The desktop's tools are its commands
max_tokens above the cap Lowered to it, not refused. Missing, it is the cap
Claude cannot be reached, or does not answer in 120 seconds 502 api_error

A proxy, deliberately raw

The body is read as JSON and written back as JSON with only max_tokens (and fallbacks) touched, and sent with Laravel's HTTP client — not rebuilt through an SDK, which knows the fields of the day it was released and drops the rest. A field the desktop starts sending next month reaches Claude. Objects stay objects: a tool's "properties": {} is not turned into [].

anthropic-version is passed on (2026-10-21 when the desktop sends none), and so is anthropic-beta, with the fallback beta added.

Streaming

With FELAGI_ASSISTANT_STREAM=true, stream: true is passed through as Claude writes it, as text/event-stream with X-Accel-Buffering: no; the usage is read off the events on the way. Leave it off behind anything that buffers.


What leaves the server

Whatever the desktop's assistant puts in the request: the person's words, the window's note of where they are, and what the app's commands returned to it — issue titles, comments, names. That is the honest cost, and it is why this is off until an operator decides. The key never leaves the server, and nothing about a request is logged but who, which model and how many tokens.