Building Agents on Open-Source Apps
There's a temptation, when you set out to build something agentic, to build the whole thing. A new data model, a new UI, a new place for the work to live — and, somewhere in there, the agent. It's mostly wasted motion. The hard parts of a CRM, a wiki, a design tool, or a file store have already been solved, in the open, by projects that have absorbed years of edge cases you haven't met yet.
The leverage is in treating those projects as the substrate for agents rather than competitors to them. Don't rebuild the system of record — wrap it. The four we reach for most:
- Outline — the team knowledge base. Markdown docs with a real API and webhooks; a natural home for anything an agent reads, drafts, or keeps in sync.
- Twenty — open-source CRM. Structured business objects (people, companies, opportunities) that agents can query and update as first-class records, not scraped text.
- Penpot — design and prototyping, open formats. Agents can generate and manipulate real design files instead of throwaway images.
- Nextcloud — files, sync, and sharing. The durable place deliverables land so humans and agents touch the same artifacts.
Each is in the Kilter catalog — one kilter add away from
running on the same cluster as your agents. But getting the app running is the
easy 20%. The interesting question is how you extend it without forking it, and
how you keep an autonomous agent from quietly corrupting your system of record.
That comes down to three layers.
The three layers
An agent bolted directly onto an app's API is a demo. A system has three layers, and skipping any one is what turns the demo into a 2am incident.
- Infrastructure — durable, long-running workflows, events, and persistent state. The agent run is a workflow that survives restarts, not a request that dies with the process.
- Safety & performance — isolation, RBAC, quotas, and tracing around the agent, so an autonomous writer can't exceed its blast radius inside your CRM or your wiki.
- Experience — the structured surface humans and agents share: the app's own UI for people, a typed API and event stream for agents, the same records underneath.
The OSS app gives you most of the Experience layer for free — a polished UI, an API, a permission model. Your job is to supply the Infrastructure and Safety layers around it, and to wire the agent in without touching the app's source.
Durable vs. non-durable: keep the model's mind out of your state
The single most important distinction when building on top of someone else's app is which parts of your system are allowed to be non-durable.
An LLM call is non-durable by nature. It's stochastic, it times out, it rate-limits, and it holds no state you can trust between invocations. That's fine — reasoning should be ephemeral. What's not fine is letting the agent's in-memory plan be your state. When the function dies at step 9 of 14 — half the Twenty records updated, half not — there's no resumable plan and no record of what already happened.
So we draw a hard line:
| Layer | Durable? | Lives in |
|---|---|---|
| The agent's reasoning / tool selection | No — ephemeral, retryable | The LLM call |
| The workflow that orchestrates steps | Yes | Temporal |
| The business records it reads & writes | Yes | Outline / Twenty / Nextcloud |
| The record of what the agent did | Yes, append-only | Immutable log |
The agent thinks; the workflow remembers. Each write to the underlying app is a durable workflow step with its own retry boundary, so a crash resumes at step 9, not step 1 — and never double-applies step 8.
# The agent loop as a durable workflow, not a fire-and-forget function.
@workflow.defn
class EnrichAccount:
@workflow.run
async def run(self, account_id: str):
ctx = await step(fetch_twenty_account, account_id) # durable read
plan = await step(llm_plan, ctx) # ephemeral reasoning
for change in plan.changes:
# each write is its own retryable step + an audit record
await step(apply_twenty_update, account_id, change)
await step(append_audit, account_id, change)The model's output is the only non-durable thing in that loop, and it's quarantined to a single step. Everything that touches your system of record is replayable.
Two ways to extend an app: infrastructure and architecture
Once the durability line is drawn, there are exactly two clean ways to graft agent behavior onto an app you don't want to fork.
Extend via infrastructure
Surround the app with platform services it doesn't ship with, and let those services carry the agent. Twenty doesn't have a durable workflow engine; you put Temporal next to it. Outline doesn't emit a clean event stream for arbitrary consumers; you put Debezium on its Postgres and turn every row change into a Redpanda event. The app stays exactly as upstream shipped it — the new capability lives in the infrastructure beside it.
This is the k8gentic bet from the platform side: the same catalog gives you the OSS application and the durable, event-driven plumbing to make it agentic — composed, not hand-wired.
Extend via architecture
The other axis is event-driven and read through the app's own extension points
— its API, its webhooks, its CDC stream — rather than reaching behind it. An
agent doesn't poll Outline; it reacts to a document.updated event, runs its
workflow, and writes back through the public API like any other well-behaved
client. The app's permission model still applies. The agent is just another actor
on the same surface humans use, which means everything the app already enforces —
access control, validation, history — keeps enforcing.
The two compose: infrastructure gives you the durable, replayable spine; architecture keeps the agent a polite outside client of an unforked app.
Immutable logging for traceability
The third piece is non-negotiable the moment an agent can write. When something autonomous edits your CRM or your wiki, "what did it do, when, and why?" has to be answerable months later — for debugging, for trust, and for the audit conversation you will eventually have.
So every agent action lands in an append-only log before and after it touches the app: the trigger, the model's intended change, the actual write, the result. Not mutable application state you can overwrite — an immutable record you can replay. Combined with Temporal's own execution history, you get two independent traces: the workflow (every durable step and retry) and the intent log (every decision the model made and why). When an account gets mangled, you don't guess. You read the tape.
Two we built: Wikiplan and Deckee
This isn't theory. Two internal agents run on exactly this pattern, and — tellingly — both treat a single unmodified Outline as their substrate. One app, used as both knowledge base and system of record, goes a long way.
Deckee is a deck-generation agent (FastAPI + Pydantic AI), and its defining
move is to put even its own prompts in the wiki rather than in code. The system
prompt is an Outline document — agents/demandgen/deckee/system — fetched and
TTL-cached at run time, so editing that doc changes the agent's behavior with no
redeploy. Ask it for a deck and it generates Marp markdown, then writes the result
back into Outline as a dated child document. A path jail confines every write
to three designated areas — outputs, memory, scratch — resolving parents by
title from the subtree's own navigation, never from caller-supplied IDs, so the
agent can produce and remember but can't touch anything else in the wiki. That's
the safety envelope from above, made concrete. Decks, their revisions, and every
generation run persist to Postgres, with Temporal provisioned to give the
runs a durable spine.
Wikiplan lives on the operating side of the same wiki. It reviews last week and plans the next straight from the markdown in Outline — reading collections and week docs through the API, drafting the next plan, and writing it back as an ordinary document the team can keep editing by hand. The Outline token never leaves the server: every call funnels through one server-side client, so the browser never sees a credential. The wiki stays vanilla Outline; Wikiplan is just a disciplined client of it.
Neither required forking an app. Both reach an unmodified Outline only through its own front door, scope every write, and lean on Postgres and Temporal for the durable parts — non-durable reasoning wrapped in durable infrastructure.
Why agentic infrastructure comes first
It's tempting to read all of this as over-engineering — surely you can wire an LLM to an API and iterate. You can, right up until you want more than one agent, or agents that act on things that matter. Then the absence of infrastructure stops being a shortcut and becomes the ceiling.
It's the prerequisite for scaling in both directions at once:
- Building software with agents. Coding agents extend these same OSS apps the same way — composing services, wiring workflows, validating before deploy — but only if the platform exposes a structured, low-token surface they can drive. Durable workflows and an audit log are what make it safe to let an agent build and ship, because you can always replay what it did.
- Running business workflows with agents. Wikiplan and Deckee are early examples of work that used to need a person. Multiply that across a company and you have hundreds of autonomous actors touching systems of record. Without durability they lose work; without isolation they overstep; without immutable logging you can't trust — or debug — any of it.
You don't reach that scale by making the agents smarter. You reach it by standing them on infrastructure that makes their actions durable, isolated, and traceable — so each new agent inherits those properties instead of reinventing them badly. The open-source apps give you the substrate. The three layers are how you make it hold up under agents.
Where to go next
- Read the k8gentic manifesto for the philosophy behind the three layers.
- See why we bet on Kubernetes for durable agent workloads.
- Browse the Kilter Platform catalog — Outline, Twenty,
Penpot, and Nextcloud are all one
kilter addaway. - Start your free trial — the full platform, 7 days, no card.