← Back to blog

Anatomy of a lynox agent run

The most common reaction to lynox from engineers is the right one: “is this just Claude with extra prompting?” It’s a fair question. Plenty of products are exactly that. The honest answer for lynox is no, but explaining why requires showing you the moving parts.

So let’s walk through what happens between you hitting send in the web UI and lynox answering. No marketing claims — just the four systems involved and where each one fits.

The four systems

Every lynox run involves these four. They’re independent processes (or at least independently testable units) that coordinate over well-defined interfaces:

┌─────────────────────────────────────────────────────────────┐
│                          Engine                             │
│  Process-wide singleton. Owns config, providers, tools,     │
│  the knowledge graph, and the HTTP API.                     │
└──────────────────┬──────────────────────────────────────────┘
                   │ creates per conversation

┌─────────────────────────────────────────────────────────────┐
│                         Session                             │
│  Per-conversation context. Holds the agent loop, the LLM    │
│  client, the streaming state, the tool-permission state.    │
└──────────────────┬──────────────────────────────────────────┘
                   │ persists via

┌─────────────────────────────────────────────────────────────┐
│                       ThreadStore                           │
│  SQLite. Threads, messages, tool-call audit, attachments.   │
│  This is the durable layer. Everything else is recoverable. │
└─────────────────────────────────────────────────────────────┘

                          (orthogonal)

┌─────────────────────────────────────────────────────────────┐
│                       WorkerLoop                            │
│  Background. Recurring tasks, scheduled workflows, the      │
│  things the agent decided to do "later". Runs independently │
│  of any open Session.                                       │
└─────────────────────────────────────────────────────────────┘

The engine is single-tenant on purpose. There’s one lynox process per user/business. Multi-tenant lives one layer up in the managed-hosting control plane, where each tenant gets its own isolated container. That separation is what makes the engine itself simple — no per-request authorization gymnastics, no row-level security spilled across every query. If you’re a single user, you run one engine, full stop.

Step 1 — your message hits the engine

You send “what’s open with Acme this week?” The HTTP API (an SSE endpoint) receives it. The engine looks up or creates a Session keyed on the thread ID. The session resumes from the ThreadStore — it pulls the previous messages, the partial tool-call state if the previous run died mid-tool, the per-thread state machine.

Already this is a distinction from most agent frameworks: the durable state lives in SQLite, not in the LLM’s context window. A 10,000-message thread is fine. The agent only re-hydrates what’s needed for the current turn, not the entire history.

Step 2 — the knowledge graph attaches context

Before the prompt goes to the LLM, lynox runs an entity-extraction pass on the message — looking for known entities in the knowledge graph that match “Acme”, “this week”, and other handles. The graph is a typed in-process store: entities are nodes (companies, contacts, deals, projects, threads), edges are typed relationships (“works at”, “owner of”, “mentioned in”), and entity-resolution is its own subsystem so “Acme Inc.”, “acme corp”, and the bare “Acme” in an email all resolve to the same node.

The extracted entities and their immediate neighborhood are added to the prompt as structured context. Not as a chat-log dump — as named facts. The model isn’t summarising a wall of conversation history; it’s reading a small, typed graph slice that matches what you actually asked about.

Step 3 — the agent loop turns

The Session’s agent loop is a standard tool-use loop, with two specifics worth naming:

  1. Streaming is interleaved, not sequential. The LLM emits a partial assistant message, then a tool_use block, then more partial assistant message, then another tool_use. The web UI renders these in chronological order — you see the agent’s “I’ll check the inbox first” and the inbox tool call and the result and the follow-up reasoning, all in the order they happened. That’s the rendering the chat-store calls ContentBlock[].

  2. Tools go through a permission gate. Every tool call hits a permissionGuard before execution. The guard checks (a) is this tool in the allowed set for this session, (b) does this specific operation need user confirmation, (c) is the daily/session cost cap still under budget. The cost cap is the load-bearing one: if the session has burned through its configured ceiling, the loop stops cleanly.

The loop ends when the LLM emits a final assistant message without a tool call. The full conversation — every assistant message, every tool call, every tool result — is committed to the ThreadStore as a single transaction.

Step 4 — what gets remembered

Two things happen as the run completes:

  • Entity extraction (the second pass). New entities mentioned in the run — a person who appeared for the first time, a deal that changed stage, a thread reference — get reconciled into the knowledge graph. Entity resolution is conservative; ambiguous matches are flagged rather than silently merged.
  • Pattern detection. The pattern engine notices when you do the same thing more than once (“you replied to support emails in the last three runs”). It may surface a workflow-capture suggestion — turning the pattern into a re-usable, parameterised template you can edit like a recipe.

Neither of these requires you to ask. They run on the back of every run that completes successfully.

Step 5 — what the worker does without you

Some runs schedule something for later. “Follow up with Acme on Friday if no reply by then” is two things: a tool call that records the intent, and a worker entry that fires on Friday morning. The WorkerLoop is a separate process — it doesn’t share state with any open Session — that wakes up on cron, picks up due work items, and creates a new Session per item to actually do the thing. From the loop’s perspective, a recurring task and an interactive message land in the same agent code path; the difference is just who triggered it.

Why this shape

A few alternatives I considered and why they don’t fit:

  • A single LLM-call wrapper. Works fine for chatbots; falls apart when the agent has to do anything across more than one turn or remember anything across more than one conversation.
  • Vector search over markdown. Works for documents. Fails for the relational stuff: “deals open this week” needs a join, not a similarity score.
  • A library you wire yourself. A library leaves you with the integration problem: ThreadStore, knowledge graph, permission gate, worker — every consumer would re-implement them poorly. lynox ships the system so they’re consistent.

The single-tenant engine + multi-tenant control plane split is the choice I think is most underrated. It means the runtime stays simple, the durability story is one SQLite file, and “moving to another server” is scp ~/.lynox/. Cloud hosting is a service on top, not a different product.

What’s still rough

  • Voice is bad. Latency on the round trip kills it; the launch lineup uses text only.
  • Real-time calendar reads / writes aren’t done. CalDAV is the next big PRD.
  • The engine is single-tenant by design. If your team needs shared state, that’s the managed-hosting tier’s job, not the engine’s.
  • LLM outputs are not deterministic. Two runs of the same prompt may diverge. The system constrains where that matters (tool selection, entity resolution) but not in the message text itself.

Read the code

Everything above is in the source. ELv2-licensed — free for production and customer use, except for offering lynox as a competing hosted service (same license as Elasticsearch and Kibana). Highlights:

If you’d rather read about the philosophy than the architecture, Why we built lynox covers that.