
12
Rust crates
4
LLM providers
AGPLv3
core licence
1
cockpit instead of 14 tools
Context
Developers juggle a dozen tools with no AI governance and no link between what was asked of the agent and what ships.
The challenge
Unify the lifecycle in one cockpit, work with any LLM, and cleanly separate the open-source core from the enterprise edition.
- 01AI
LLM gateway
`providers` crate: Claude Code, Codex, OpenRouter and Ollama behind one interface, agents defined in Markdown.
- 02Dev
Workflow engine
DAG pipelines, agent runtime, event bus and sandboxed shell in a layered Rust workspace.
- 03Dev
Desktop + CLI
Tauri 2 / React app and a ratatui TUI CLI, shared TypeScript packages (ui, tRPC client).
- 04Ops
Verified open-core
A CI script ensures the AGPLv3 code never references the enterprise edition (SSO, audit, on-premise).
Architecture
Stack
- Rust
- Tauri
- React
- TypeScript
- SQLite
- AOAgents & orchestration
- OpenRouter / Mistral
- Ollama
- GitHub Actions
Results
- ✦Core reused and narrowed down in koklo-cli
- ✦Dedicated product site, docs and design system
The story
The screens below come from Koklo’s design system (the Storybook), with demo data. The components are then copied into the desktop app, which today wires the Sessions and Transcript screens to the Rust runtime through Tauri commands.
How it works
1. The project as the entry point

You open a project, not a file. The home screen shows what is moving: active sprints, open tickets, running agents. The activity feed puts an AI proposal, a push, a ticket moved to review and a staging deploy on the same level. That is the starting idea of Koklo: stop rebuilding the history of a feature from four different tools.
2. A phased workflow with human gates

A request goes through a workflow preset. The engine knows ten of them: SDD (spec, plan, implement, test, review), eight-phase BMAD, GitHub Spec Kit, bugfix, release, review only, audit only… Each phase is handled by an agent defined in Markdown. At the end of a phase, the engine stops and asks for a decision. Here the architect has handed in its analysis: I can approve, request changes, or edit the file before resuming.

The Workflows screen keeps the history. For each run you get the phases, the agent, the status and the file produced. The note at the bottom of the right column sums up the rule: an output stays a proposal until its phase is approved.
3. Tickets linked to code

The sprint board is standard. What changes is the ticket panel: linked AI sessions, linked commits, activity log. On the Rust side, ticket-system stores tickets in SQLite and can attach a session to a ticket (link_session).
4. Any LLM

AI accounts are set in the settings. The providers crate puts Claude Code, Codex, OpenRouter and Ollama behind one trait. Agent CLIs reuse the subscription you already have, with no key. Ollama lets everything run locally.
5. Constellation, still a mockup

The Constellation view is the most ambitious one: a map of the Git history where each commit is linked to the AI session that proposed it and the human decision that accepted it. For now it exists as an HTML mockup. On the roadmap it comes in phase 9.
Under the hood
A gate, from phase to decision
The PipelineOrchestrator does not know who will decide. It publishes a GateRequired event on the bus, then hands a GateDisplay (phase, tokens, cost) to a GateHandler. There are three of them: one reads stdin for CI, one approves everything for benchmarks, and one goes through a channel to the TUI. The decision is written to the database before GateResolved announces it. A rejection or an edit pauses the session, and koklo resume picks it up later.
Picking a provider with zero setup
On first run, nothing is imposed. Detection takes the first provider that is actually ready. The detail that matters: an Ollama server that runs without the wanted model is skipped, otherwise the first run would fail. Once a provider is chosen, a FallbackProvider can chain a second one.
/// Returns `true` for errors that justify trying the fallback provider.
pub fn is_fallback_worthy(e: &ProviderError) -> bool {
match e {
ProviderError::CliNotInstalled { .. } => true,
ProviderError::CliSessionExpired { .. } => true,
// Ollama is reachable but the configured model isn't pulled — another
// configured provider may be ready, so try it rather than hard-failing.
ProviderError::OllamaModelNotFound { .. } => true,
ProviderError::PtyUnavailable => true,
ProviderError::RateLimited { .. } => true,
ProviderError::Timeout { .. } => true,
ProviderError::HttpError { status, .. } => *status >= 500,
// EmptyResponse, MissingApiKey, UnknownProvider → don't fallback
_ => false,
}
}The list of errors that justify a fallback is short on purpose. A rate limit, a timeout, a 5xx or a missing CLI: try the next one. A missing key or an empty response: stop, because another provider would hide a real configuration problem.
The open-core boundary
The core is AGPLv3, the enterprise edition lives in another repository. A CI script checks that no Rust or TOML file in the core references it:
What I take away
- Human gates live in a single function, on purpose: one place publishes, records and decides. The rest of the engine does not know whether it talks to a TUI, a desktop screen or a script.
- The scope was too wide. Five repositories, a Tauri app, three TypeScript fronts, a design system: every step forward on one front left the others behind. I took the engine, the providers and the gates into koklo-cli, a single binary, and the desktop app will wait until that core is proven.
- The design system got ahead of the product. That helps pin down the intent (Constellation is the best example), but a mockup is not a feature, and I label it as such.
Gallery




