Skip to content

Architecture & Contributing

For extending the platform with your own workflows (skills, MCP, custom tools/binaries) see Extending the platform. This document describes work on the core itself.

┌────────────────────────────────────────┐
CLI (internal/cli) │ runtime (wiring) │
HTTP API (pkg/api) ──▶│ │
Scheduler (pkg/sched)─▶ pkg/agent ── Loop, Salvage, History │
│ │ │
│ ├── pkg/router Category→Model │
│ ├── pkg/tools Registry (+Tags) │
│ │ ├── builtin (load_skill …) │
│ │ └── pkg/mcpclient (Adapter) │
│ ├── pkg/skills SKILL.md │
│ └── pkg/pool Endpoint pool │
│ └── pkg/ollama HTTP client │
└────────────────────────────────────────┘
pkg/config (JSON schema for everything)

Data flow of a request:

  1. Entry (CLI run/chat, API, scheduler job) → agent.Run(ctx, input, opts).
  2. Routing: explicit model? Otherwise router.Resolve — heuristic → LLM classify (router model, JSON schema enum, temp 0) → category; then a preference list is checked against pool.HasModel.
  3. Prompt assembly (agent/prompt.go): compact system prompt + short skill list + any pre-loaded skill.
  4. Tool loop (agent/loop.go): chat call via the pool → execute tool calls (structured or recovered via salvage) → append results as role:tool → repeat until a response without a tool call or the iteration cap is reached.
  5. Pool (pool/pool.go): selects a healthy endpoint that has the model (least-inflight, priority/latency tiebreak); on transport error/5xx, fails over to the next candidate; 4xx is passed through.

Every change should respect these principles:

  1. Context is expensive. Keep system prompts short, tool descriptions exactly one sentence, tool subsetting via category tags (registry.Select), load skills only on demand.
  2. Small models make formatting mistakes. The salvage parser (agent/parse.go) recovers tool calls from plain text: JSON in fences, JSON in prose, key variants (name/tool/action…), XML syntax (<function=x><parameter=y>). New broken formats found in practice → add them there, always with a table-driven test.
  3. Never fail hard. Classification fails → default_category. No model available for the category → models.default. History too large → summarize, discarding old rounds if necessary. Iteration cap reached → final call without tools.
  4. Many small calls instead of one big one. Classification and summarization are separate mini-calls with schema-enforced output.
PathContent
pkg/…public, importable platform (aim for API stability)
internal/cliCLI commands + runtime (wiring of all parts)
internal/testutil/fakeollamascriptable fake Ollama server for tests
internal/smokebuild-tagged tests against real Ollama
cmd/ollama-agentmain (thin)
configs/example config (source: pkg/config/example.go — keep both in sync!)
skills/bundled example skills

New subcommands: a file in internal/cli/ with func init() { register(&command{...}) } — nothing more is needed.

Terminal window
make test # unit tests, no network
go test -race ./... # recommended before every push
make vet
  • fakeollama covers almost everything: fake models, script responses (Script(...)), simulate outages (SetDown), return tool calls, arbitrary behavior via OnChat. Always test new pool/loop features against it.
  • Smoke tests against a real server (model must already be pulled):
Terminal window
OLLAMA_HOST=http://192.168.6.80:11436 SMOKE_MODEL=qwen3.5:9b make smoke
  • The most sensitive code is pkg/agent/parse.go (salvage). The TestSalvage test is a table of real model outputs — add a line there for every new case found in practice.
  • Work happens on develop (or feature branches → MR into develop).
  • main is protected; a release = MR developmain with a green pipeline, followed by a vX.Y.Z tag on main.
  • Commits follow Conventional Commits (feat(pool): …, fix(agent): …).
  • CI (.gitlab-ci.yml): vet → test → build.
  • Streaming in tool rounds: the loop only streams the final response; intermediate iterations are non-streaming (a deliberate simplification).
  • Sessions are in-memory (daemon restart = memory loss). Persistence (e.g. SQLite) would be a cleanly isolated feature in pkg/agent/session.go.
  • Job history is in-memory; could be persisted to state_dir.
  • Metrics: /v1/health is minimal; a Prometheus endpoint would be easy to add in pkg/api.
  • Embeddings exist in the client (ollama.Embed) but are not yet wired up to the agent (RAG workflows would be a good extension candidate).
  • MCP resources/prompts are not used, only tools.