Skip to content

Case study

Orchestrating AI agents across runtimes

AI Operator groups core, services and clients in a single monorepo. Active development — scope and feature set are documented step by step.

Challenge

AI agents today live in very different environments: as services in a backend, as tools inside developer IDEs and as companions on mobile devices. Each of these environments comes with its own languages, runtime and packaging constraints — but the actual agent logic (model access, tool calls, multi-step state) is the same across all of them.

Building for each environment in parallel ends in three or four subtly divergent implementations that have to be maintained separately. Prompt tweaks or a new tool eventually land in only one version, and the surfaces drift apart.

AI Operator starts from this observation: a shared toolkit that keeps core logic, tool catalogue and model integration in one place, and treats each runtime as an independent but compatible surface on top.

Approach

Four principles shape the structure:

  • Polyglot monorepo — each language gets the role it is best at: Python for model and data logic, Go for performant services, TypeScript for surfaces and shared tooling, Swift for the native client.
  • Core separate from runtime — the actual agent logic lives in reusable packages; runtimes pick from them.
  • Tools as first-class citizens — tool definitions and implementations sit in the shared toolkit rather than being duplicated per client.
  • Honest maturity — scope is under active development; internal documentation follows the code, not the other way around.

What we built

Monorepo structure

The repository is split into apps/, services/, packages/, infrastructure/, scripts/, docs/ and tests/. This split lets executable applications (apps/), long-running services (services/) and shared libraries (packages/) be built and versioned independently without losing shared history.

Go workspace

A go.work at the root binds multiple Go modules into one workspace, with .golangci.yml as a shared lint configuration. Services can be worked on in parallel without checking each module out separately.

JavaScript/TypeScript layer

package.json and pnpm-lock.yaml manage the Node side with pnpm workspaces. Surfaces, shared tools and libraries for web clients live under packages/ and apps/.

Python core

A ruff.toml and a dedicated Python root separate model and data logic cleanly from the rest of the codebase. This layer holds model integrations, prompt building blocks and tool call implementations.

Swift client

A native client consumes the same tool catalogue and exposes the agent logic on mobile devices — without duplicating the web or backend side.

Infrastructure and scripts

infrastructure/ and scripts/ group container definitions, deploy and maintenance scripts. A Makefile at the root wraps the common commands (build, test, lint).

Presentation and internal docs

presentation/ and docs/ hold internal material about project status — deliberately kept separate from the external site so documentation changes do not collide with code changes.

Architecture

AI Operator is a polyglot monorepo: four ecosystems live side by side, connected through shared interfaces and one common tool catalogue. Python carries the core logic around models and prompts, Go provides services with well-defined APIs, TypeScript delivers surfaces and shared tools, Swift serves the mobile client.

The split between apps/, services/ and packages/ follows a familiar rule: packages/ only holds reusable libraries without side effects, services/ holds long-running processes with their own deployment, apps/ holds concrete end applications. These boundaries keep dependency direction clean and prevent a UI change from forcing a backend library.

The Go workspace (go.work) and pnpm workspaces (pnpm-lock.yaml) mean changes across module boundaries can be made in one step without intermediate releases. .golangci.yml and ruff.toml keep code quality on a shared baseline for Go and Python.

Numbers & facts

MetricValue
Languages in the repoPython, TypeScript, Go, Swift (plus Shell, Makefile)
StructureMonorepo with apps/, services/, packages/, infrastructure/, docs/, tests/
Go workspacego.work + .golangci.yml (shared lint config)
JS/TS toolchainpnpm + package.json (pnpm workspaces)
Python toolchainRuff (ruff.toml)
CI/automationMakefile and scripts/
MaturityUnder active development — feature set grows step by step

What we learned

A monorepo disciplines language choice. When Python, Go, TypeScript and Swift live in the same repository, it becomes visible very fast which language actually fits which job — and how much duplication a bad choice costs. We had to decide early where logic belongs so the boundary between the ecosystems stays sharp.

Tool definitions belong in shared packages. As soon as multiple runtimes are supposed to call the same tools, any client that maintains its own schema becomes a source of drift. The shared tool catalogue is the single most important architectural lever we have.

Active development needs honest documentation. The material in docs/ is deliberately kept rough — it describes the current state, not an idealised end state. That is uncomfortable but stops new contributors from following outdated descriptions.

Next steps

  • Consolidate the tool catalogue and stabilise the interface between the Python core and client runtimes.
  • Grow test coverage in tests/, especially around the tool catalogue and the core agent logic.
  • Publish external documentation along the parts that are actually stable, without hiding the experimental rest.

Collaboration

Have similar work in mind?

Let’s have a short chat — no sales pitch.