How the agent thinks

One loop. Not a workflow engine, not a graph, not a hierarchy of sub-agents.

The model receives a system prompt and a tool set, decides what to call, reads the results, and either calls again or answers. It stops after ten steps. That is the whole design, and the interesting content of this page is why so little machinery, and what had to be built instead.

Workflows are tools, not a layer

The distinction between a workflow — model and tools orchestrated through predefined code paths — and an agent — the model directs its own process — is about reliability, not architecture. A workflow wins when the sequence is known and the model demonstrably gets it wrong. Everywhere else it loses, because you have to guess the sequences in advance.

So there is no engine:

A workflow is a tool the agent can call, deterministic on the inside.

  • "Prepare my week" → four tool calls the model chains itself. Agent.
  • "Archive these 200 emails" → a for loop inside one tool; the model only decides to enter it. Workflow.

Neither needs a framework. The registry that would hold such tools exports an empty object and stays empty until a real task fails reproducibly — at which point it becomes one file and one line, and nothing in the loop moves.

Context is the actual engineering

Context is a finite resource, and attention is quadratic in pairwise relationships, so recall degrades as length grows. The goal is not "make it fit", it is the smallest set of high-signal tokens.

What the model is told about itself

The system prompt has five sections, each a separate file so editing prose never touches code:

SectionContents
<identity>who it is, and who it is acting for
<environment>uid, display name, time and timezone, the capabilities available this turn, ~5 memory facts
<instructions>ten rules
<data_handling>the untrusted-data contract, stated to the model
<examples>three canonical cases

What is deliberately not in it: the tool list, argument schemas, and per-tool rules. Those arrive through the protocol, or live in a tool description or an error message — where they are paid for only at the moment they matter.

The three examples are the highest-value part. Not an enumeration of edge cases: one per behaviour that rules alone do not produce — targeted retrieval, confirmation before an effect, and stopping on a permission refusal.

<environment> distinguishes "has tools" from "can reach the workspace". Conflating those two once produced the worst failure this system has had: the agent answered confidently, in detail, while every single tool call was failing. It was reading the environment block and treating it as knowledge. → the full story

Retrieval happens during the turn, not before it

There is no pre-built context package. The tools are the retrieval: files_search narrows, files_read returns a slice plus a hint telling the model how to continue, files_grep returns a passage and an offset to read on from.

That is not only a token argument. A pre-built index of what the user "might need" would be a permission snapshot, and a share revoked five minutes ago has to disappear now. The hybrid part is small and stable: uid, name, language, date, timezone, active capabilities, a handful of memory facts.

Two rungs of compaction, and no third

Rung 1 — clear stale tool results, every step. A 4 kB file read is essential on the turn it arrives and dead weight three turns later; the model already took what it wanted. Results of actions are kept, because they are the record that something happened.

This is idempotent, which matters because the pruned list is carried forward and pruned again. There is a test for that.

Rung 2 — summarise, at about half the window. Three constraints, each load-bearing:

  1. The cut must not orphan a tool result from the assistant message that called it, or the provider returns 400.
  2. The summary is reinjected as a user message inside an untrusted-data envelope, never as a system message. It was written by a model, and a poisoned document could have influenced what it wrote.
  3. Never compact between an approval request and its answer. The pending call would vanish and the user's confirmation would land on nothing.

The summary prompt maximises recall first: decisions taken, actions already performed with their exact targets — never "some changes were made", which is not repeatable — exact paths and ids, and the open question. It is stored and reloaded, so a conversation pays for summarisation once per compaction event rather than once per turn.

Rung 3 — sub-agents. Not used. With this many tools in one domain, the coordination cost exceeds the gain.

Memory, and why writing to it needs a confirmation

Two ordinary tools, memory_search and memory_write, with one deliberate asymmetry: memory_write is annotated destructive even though nothing is destroyed.

Memory is read by the model on every later turn, which makes it a persistent instruction channel. If a poisoned document could get a fact written silently, a one-shot injection would become a permanent backdoor. Routing writes through the approval mechanism means the user sees and confirms every fact.

The source of each fact is recorded — user or agent — so everything the agent attributed to itself can be purged separately if it ever misbehaves.

Failures are content, not exceptions

A tool failure is signalled in-band and its text reaches the model. That is desirable: the agent can say "you don't have access to that folder" instead of producing a stack trace. It has two consequences.

Code has to check for the error flag explicitly — a failed call is a successful response carrying a failure.

And the error text becomes something the model reads, so it must never carry infrastructure detail, and it must say what to do next. A bare "403 Forbidden" makes an agent retry forever. → Errors are instructions

Every call lands in the audit database with a failure kind that separates "Nextcloud refused" — the system working — from "the model could not use the tool", which means the tool design is wrong. That distinction is the feedback loop on the tool surface. → Data