Skip to main content

The 8 principles

Apollia is a sovereign runtime for autonomous AI agents. It runs agents locally, under governance it enforces, with tools, and without a cloud dependency. Eight principles hold that promise together. They are not style preferences; each one was forced by a real constraint, and each one shapes a default you can rely on as an adopter. This page explains what each principle is and why it matters to you. It does not restate them as rules; for the rule form and its enforcement, the same eight appear as engineering constraints.

1. Local-first​

No byte of user data leaves the machine without an explicit action. Inference can run fully local on a GGUF model, storage is a local SQLite file, and there is no telemetry and no phone-home. This exists because contractual guarantees were not enough for the organisations Apollia targets: the answer was not to promise the cloud would behave, it was to make the cloud technically unnecessary. For you, it means the safe posture is the default, not a setting you have to remember to turn on. The deeper treatment is in Sovereignty and local-first.

2. Zero external dependency​

The binary runs on a clean Linux machine with no prior install: no Docker, no Node, no external database, no separate Python runtime. Every optional dependency on an external service degrades gracefully rather than breaking the run. This matters because operational complexity is a commercial veto for the teams evaluating a sovereign runtime. One artifact to deploy is one attack surface to reason about and one thing to keep running.

3. Minimal contract​

An agent is a record: a mission, a model, tools, sources and rights. A package writes it as an [[agents]] entry of its apollia.toml manifest, and no Python object stands for it. Code is optional: a package brings tools (@tool) and workflows (@workflow), two decorators over async functions, and nothing else. The point is to keep the surface an author must learn small, so the runtime can carry the hard parts (governance, budgets, tools) rather than pushing them into every agent. It also keeps an agent in the hands of the person who uses it: what a package declares is a set of defaults, and a tool, a right or a model is changed from the interface without a new version of the code. For the exact contract the Python of a package sees, read the SDK reference.

The runtime refuses a package whose manifest does not validate. A key the schema does not know is refused with its full path, and so is what the schema cannot express, such as an agent granted a package tool that the package does not deliver. A misspelt key stops the package rather than leaving an agent running without what its author declared. The earlier contract, a class decorated with @agent holding @skill or @on_message methods, is gone (see the agent contract).

4. Fail fast​

Any error detectable at startup is detected at startup, not three steps into a run. A missing model, a malformed manifest, or an unreachable backend surfaces before work begins. This keeps failures cheap and legible: you learn what is wrong when you launch, not after the agent has spent part of its budget getting into a broken state.

5. One actor, one responsibility​

The runtime is built as Tokio actors that each own their state and talk over message channels, with no shared locks between them. This is not an aesthetic choice: shared mutable state across async tasks is where deadlocks and unexplainable behaviour live. Keeping each actor responsible for one thing is what makes the system's behaviour something you can reason about. The strategy behind this is in Solution strategy.

6. Memory at agent initiative​

Apollia never injects memory into an agent's prompt, and writes nothing to memory on its behalf. An agent recalls what it decides to recall, when it decides to, with the tool memory_search, and keeps a note with memory_write. Both tools come from one gesture, checking Memory on the sheet of the agent, and neither asks for consent: the space is the agent's own, one space named after it and kept across its conversations and its tasks, which reaches neither your data nor the machine. Automatic memory injection is convenient and quietly corrosive: it makes a run's inputs opaque and its behaviour hard to attribute. Leaving recall to the agent's initiative keeps the record of what fed a decision honest.

Your profile is the opposite case. It follows you into every conversation it is switched on for, so an agent never writes it. An agent whose Profile source is checked may propose a fact with profile_propose, and the proposal is put to you every time, with no "always".

Three exceptions exist, and none is reachable from an agent you install. The first two are confined to the built-in conversational assistant, the one behind the chat window, and live in its own prompt builder and chat manager. The third lives elsewhere, in the desktop prompt-rewrite command, which never touches the assistant's prompt at all: it answers the composer, not a run. No agent execution path goes through any of the three.

The first is the profile of the conversation, and it is the one you control. A conversation designates one of your profiles, or follows the default one. On every turn, at every autonomy tier, the assistant appends a short brief of that profile to its system prompt: name, role, function, tools, preferences and notes, whatever you filled in. It sits under a switch, "Apollia knows my profile", in the Integrations tab of the right panel of the conversation. The switch is on when a conversation is created, it is stored with the conversation, and turning it off takes effect on the next message. The context gauge of the composer says whether the prompt carries the profile, so what you read there is what the model received. Companion sessions and sessions that run an installed agent never receive it, switch or not.

The second has no switch, and is worth stating plainly. On the first message of a free chat session, the runtime searches an index of past session summaries with that message and appends up to three matches to the system prompt, under a heading naming them as previous conversations. Messages shorter than 20 bytes skip it, so a greeting recalls nothing. Only the summaries are injected, never past message content. Companion sessions are excluded outright: they must not inherit personal history.

Read that second one for what it is. Inside the chat window, a new conversation can start already carrying a trace of older ones, and the operator did not ask for it per session. It buys continuity in a product surface where a user reasonably expects to be remembered. With the profile brief, it is one of the two places in the runtime where the "at agent initiative" rule genuinely does not hold, and the only one you cannot turn off, which is why it is written here rather than left implicit.

The third exception is also operator-initiated, and happens only when explicitly triggered. When the operator clicks the "Improve prompt" button in a chat composer, the rewrite request includes three fields of the profile of the conversation (role, function, tools) if they have been filled. This is not automatic injection: nothing happens until the operator clicks, and they see the rewritten result before choosing to send it. The context helps the rewrite stay grounded in the operator's actual environment rather than inventing tools they don't use.

The memory layer itself is exportable and importable, which is why it belongs to sovereignty as much as to agency.

7. Non-negotiable safeguards​

Every autonomous run is bounded by a step budget the runtime enforces: a ceiling on reasoning steps, tool calls, and wall-clock time. An agent cannot raise or remove it. Autonomy is only delegable if it has a hard edge, so the edge lives in the runtime rather than in the agent's good intentions. This page does not re-explain the mechanism; it is one of the pillars of the accountability model. One honest detail: the ceiling of an agent run is fixed in the code, and no key of apollia.toml changes it. The full ledger of what is partial is in Risks and technical debt.

8. Human CLI, machine API​

Every surface is dual: a human reads a terminal, a machine reads JSON. A global --json flag and TTY detection mean the same command serves an operator at a prompt and a host product driving the runtime through its API. This exists because Apollia is meant to be embedded, not only used, and an embeddable runtime has to speak both languages without one compromising the other. The command surface is the CLI reference; the machine surface is the HTTP API reference.

Why these eight, together​

Taken singly, each principle is a reasonable engineering call. Taken together, they are the value proposition: a runtime you can run without the cloud, deploy without a stack, delegate to without losing control, and embed without reverse-engineering. They are the reason autonomy here is something a regulated team can actually adopt, not just admire.