← The record

felix

A self-hostable agents harness. Agents are YAML, not code, and the runtime they compile into is governed by default.

Year
2026
Language
Python 3.14
Licence
Apache-2.0
Release
v0.11.1
Clients
Web · TUI
Status
Early

What it is

An agent is authored as a felix/v1 YAML manifest and compiled into a running, governed agent: durable fibers, memory, skills, evaluation, approvals and sandboxes, all declared rather than wired. Changing what an agent does is a config change.

The same agent is served over four surfaces at once. A REST and SSE endpoint, an OpenAI-compatible /v1 where the manifest name is the model id, A2A JSON-RPC for agent-to-agent calls, and MCP. A client that already speaks any one of those needs no adapter.

It ships with its own operator clients: a web UI at make.felix.run and a full-screen terminal client, both running one shared engine. It is the third version of the idea, after AWS Bedrock with LangGraph and then TypeScript on Cloudflare Workers. This one runs anywhere the operator controls.

Architecture

Felix request and execution pathsA client reaches felix-api over one of four surfaces: REST and SSE, an OpenAI-compatible v1 endpoint, A2A JSON-RPC, and MCP. The web client reaches it through its own proxy Worker; the terminal client calls it directly. The API resolves a YAML manifest through the harness package, which calls models only through the separate felix_ai package. Durable runs are enqueued to a Taskiq worker; a separate scheduler enqueues cron tasks. All of them share Postgres with pgvector as the system of record, Valkey as cache and queue transport, and a pluggable object store backed by the filesystem, S3 or GCS. Optionally, the harness calls out to a workspace gateway Worker, which runs each workspace scope in a Durable Object and a Container with the internet off, checkpointed to R2. Nothing on that side calls back.client · the web UI through its proxy Worker, the TUI directREST / SSEOpenAI-compatible /v1A2A JSON-RPCMCPfelix-apiCPython 3.14 · Granian · FastAPIpackages/harnessmanifests · patterns · governancepackages/aimodels · catalogworkerTaskiq consumerschedulercron enqueuePostgreSQL+ pgvector · system of recordValkeycache · queue transportobject storefs | S3 | GCSoptional · FELIX_WORKSPACE_BACKEND=hostedworkspace-gatewayWorker · bearerDurable Objectone per tenant scopeContainermicroVM · internet offR2checkpointsevery dependency reached through a Protocol, not a vendor SDKCloudflare runs the sandbox, never the harness, and nothing there calls back
Three processes share one datastore triple, and the scheduler is separate from the worker on purpose: without it, nothing periodic fires, which looks like a bug in the agent rather than a missing process. The sandbox lane is optional and one-way. felix-api or the worker calls the gateway; nothing in Cloudflare calls the harness.

A manifest

manifests/governed.yaml (abridged)felix/v1
apiVersion: felix/v1
kind: Agent
metadata:
  name: governed
spec:
  pattern: react # or deep, reflect, plan_execute, router, parallel, groupchat
  session:
    strategy: compacting # or windowed:N, summarizing:N, semantic:N, full_replay
  execution:
    mode: durable # 202 + resume_token; resumes from the session log
  workspace:
    scope: thread # or tenant, deployment
  approvals:
    - id: memory-forget
      tools: [forget]
      allow_unattended: false

The parts that were actually hard

  • A run that dies mid-tool leaves a call with no result, and nothing outside the tool knows whether the effect landed. It is closed with an interrupted result before the thread resumes, since a provider rejects a transcript over one unanswered call. Tools declare whether they are safe to re-run and the default is no: a re-run search costs latency, a re-run payment charges twice.
  • Extended thinking is stateful once tools are involved. A later turn replaying a tool call has to replay the signed reasoning that produced it. Blocks are captured and replayed ahead of the tool_use blocks; one whose signature was not captured is dropped, because an unverifiable signature rejects the whole turn.
  • Side requests poison the prompt cache. Compaction, memory extraction, screening and branch summaries carry a different prefix. Sharing the conversation cache identity would churn the prefix the next real turn needs. They opt out.
  • A rewind on one replica races a turn on another. Each turn records the thread’s leaf epoch when it starts; a rewind or fork bumps it, an append never does. The turn’s leaf write is conditional on the epoch, so the rewind stands and the next turn continues from it.
  • A resent stream must never run twice. An idempotency key gets 409 while the first send is still streaming, then a replay of only what that request appended. Keys are scoped per principal, not per tenant, because a replay returns before inbound auth runs. A first send that appended nothing frees its key.
  • A durable run can need the client. A run in the worker has no line to the browser, so client-tool requests are recorded in the database and re-derived on every poll of the stream, never sent over an in-process bus. The same rule carries approvals.
  • A hosted shell never falls back to the host. Commands pass the same screening, then run in the scope’s microVM with the internet off. Anything that needs a real host directory is refused. The gateway names the sandbox from tenant and scope, so a leaked token reaches nothing outside that scheme.

The clients

The web UI is an instrument panel, not a chat window: its job is knowing what the agent just did, and whether to let it continue. Browser and terminal share one engine whose single event switch answers the three frames a run blocks on, a tool request, an approval, a question. Miss one and the run hangs.

The harness lives in another repository, so the wire contract is regenerated from a harness checkout, never a running server, and CI checks routes, stream events and payload fields against it. A run keeps going when the operator switches threads, and the model gets identical answers from a browser folder and a terminal working directory.

Where it actually is

v0.11.1, after eighteen releases since August 2026: 304 Python modules, 371 test files, 33 migrations, Helm, AWS and GCP deploys, and an optional Cloudflare sandbox. The web UI runs at make.felix.run, behind sign-in.

It is moving fast, not battle-tested, and nothing on this page is a usage claim. The TypeScript version is still the one that ran longest. What this one has is a smaller dependency surface and no cloud it cannot leave.

Ask about it

The assistant on the home page answers from this same record, and will tell you what is not on it.