Agent runtime: majordomo in-process, Ollama Cloud config, chat endpoint #56

Closed
opened 2026-07-21 04:19:50 +00:00 by steve · 0 comments
Owner

Design: DESIGN.md

Context

Everything below the run loop already exists. internal/agent holds a seven-tool majordomo toolbox over the service layer, and it compiles and passes its tests against the current local majordomo (verified 2026-07-21 under -tags majordomo). What's missing is the thing that actually runs a model.

OLLAMA_CLOUD_API_KEY and PANSY_AGENT_MODEL are already configured in Komodo and live on the next deploy.

The build-tag decision

internal/agent/doc.go currently promises two separations: cmd/pansy doesn't import the agent package, and the tool wiring sits behind //go:build majordomo so the default build needs no majordomo module. Both go away here, deliberately.

A build tag that keeps the agent out of the default binary only earns its keep if you'd ever ship a build without the agent — and the agent is the point. Keeping it would mean an untagged CI that never compiles the code that matters. So: majordomo becomes a real dependency, the tag comes off, and doc.go gets rewritten to describe what's actually true rather than an aspiration that no longer holds. Do not leave that comment stale.

majordomo is stdlib-first and pure Go, so CGO_ENABLED=0 and the single static binary survive — confirmed by the tagged build. Verify it again in CI rather than trusting this note.

Scope

  • go get gitea.stevedudenhoeffer.com/steve/majordomo; drop //go:build majordomo; rewrite internal/agent/doc.go.
  • Config in internal/config:
    • PANSY_AGENT_MODEL (default ollama-cloud/glm-5.2:cloud) — passed verbatim to majordomo.Parse, so a comma-separated spec gives failover for free (ollama-cloud/glm-5.2:cloud,ollama-cloud/kimi-k2.6:cloud). Don't parse or validate the model string yourself; that grammar is majordomo's job.
    • OLLAMA_CLOUD_API_KEY.
    • PANSY_AGENT_ENABLED (default: on when a key is present). An instance with no key must start cleanly and simply not offer the agent — mirror how OIDC routes 404 when unconfigured rather than failing startup.
  • Run loop in internal/agent: resolve the model, build a toolbox bound to the session's user, run the majordomo agent loop.
  • Bound the run — step cap + timeout. This is loop safety, not cost control (see below): a model that gets stuck calling describe_garden forever should stop on its own, and a hung request shouldn't hold a connection open indefinitely.
  • Wrap each turn in one change set (source='agent', agent_run_id set, summary from the user's message) via #48's WithChangeSet. This is what makes "act freely" safe: a whole turn undoes as one unit.
  • System prompt: garden context, the compass convention (-y is north in an object's local frame — the same thing fill_region documents), units, and an instruction to state what it changed.
  • POST /api/v1/agent/chat behind requireAuth: message + garden id, streaming (SSE) so tool calls surface as they happen rather than after a long silence. Return the change set id with the result so the UI can offer Undo.
  • Conversation persistence — an agent_conversations / agent_messages pair keyed to (user, garden). Multi-turn ("now do the same to the north bed") needs history, and client-held history would lose it on refresh. Cap retained turns.

Design notes

  • No spend caps, rate limiting or quotas. Decided 2026-07-21: pansy is a personal tool, and multi-tenant cost controls are a post-v2 concern (executus/budget is the precedent whenever that changes). Note the distinction from the bounded-run item above — a step cap exists so a stuck loop terminates, not to police spend. Don't let this note grow into a budget system.
  • executus audit/ is the natural sink for run-level traces, and change_sets.agent_run_id from #48 is the join back to domain changes.
  • Tool errors (a viewer hitting ErrForbidden) should reach the model as tool results it can explain, not 500s. The ACL story only works if the model can narrate the refusal.

Out of scope

  • The chat panel UI (separate issue).
  • Spend caps / rate limiting / quotas, per above.
  • MCP. Companion-planting intelligence.

Key files

internal/agent/ · internal/config/config.go · internal/api/api.go · cmd/pansy/ · gadfly is the config precedent (GADFLY_MODELS, same OLLAMA_CLOUD_API_KEY secret); ../majordomo/agent/ for the loop; ../executus/run/ for run bounding.

Dependencies

Blocked by #48 — "act freely" is conditional on undo existing. Soft-blocked by #55 for the demo task to work end to end.

Acceptance criteria

  • "change the garlic bed to cucumbers this year", typed against a real garden, empties the garlic bed and replants it with cucumbers — and the whole thing appears as one change set that reverts in one action.
  • A viewer asking the agent to plant something gets a refusal the model explains, not an error page.
  • A run that hits the step cap terminates with a sensible message rather than hanging.
  • An instance with no API key starts, serves the app, and doesn't advertise the agent.
  • make build still produces one static binary with CGO_ENABLED=0.
  • go test ./... green with no build tag needed.
Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md) ## Context Everything below the run loop already exists. `internal/agent` holds a seven-tool majordomo toolbox over the service layer, and it **compiles and passes its tests against the current local majordomo** (verified 2026-07-21 under `-tags majordomo`). What's missing is the thing that actually runs a model. `OLLAMA_CLOUD_API_KEY` and `PANSY_AGENT_MODEL` are **already configured in Komodo** and live on the next deploy. ## The build-tag decision `internal/agent/doc.go` currently promises two separations: `cmd/pansy` doesn't import the agent package, and the tool wiring sits behind `//go:build majordomo` so the default build needs no majordomo module. **Both go away here, deliberately.** A build tag that keeps the agent out of the default binary only earns its keep if you'd ever ship a build without the agent — and the agent is the point. Keeping it would mean an untagged CI that never compiles the code that matters. So: majordomo becomes a real dependency, the tag comes off, and `doc.go` gets rewritten to describe what's actually true rather than an aspiration that no longer holds. Do not leave that comment stale. majordomo is stdlib-first and pure Go, so `CGO_ENABLED=0` and the single static binary survive — confirmed by the tagged build. Verify it again in CI rather than trusting this note. ## Scope - [ ] `go get gitea.stevedudenhoeffer.com/steve/majordomo`; drop `//go:build majordomo`; rewrite `internal/agent/doc.go`. - [ ] Config in `internal/config`: - `PANSY_AGENT_MODEL` (default `ollama-cloud/glm-5.2:cloud`) — passed **verbatim** to `majordomo.Parse`, so a comma-separated spec gives failover for free (`ollama-cloud/glm-5.2:cloud,ollama-cloud/kimi-k2.6:cloud`). Don't parse or validate the model string yourself; that grammar is majordomo's job. - `OLLAMA_CLOUD_API_KEY`. - `PANSY_AGENT_ENABLED` (default: on when a key is present). An instance with no key must start cleanly and simply not offer the agent — mirror how OIDC routes 404 when unconfigured rather than failing startup. - [ ] Run loop in `internal/agent`: resolve the model, build a toolbox bound to the **session's** user, run the majordomo agent loop. - [ ] **Bound the run** — step cap + timeout. This is loop safety, not cost control (see below): a model that gets stuck calling `describe_garden` forever should stop on its own, and a hung request shouldn't hold a connection open indefinitely. - [ ] **Wrap each turn in one change set** (`source='agent'`, `agent_run_id` set, summary from the user's message) via #48's `WithChangeSet`. This is what makes "act freely" safe: a whole turn undoes as one unit. - [ ] System prompt: garden context, the compass convention (`-y` is north in an object's local frame — the same thing `fill_region` documents), units, and an instruction to state what it changed. - [ ] `POST /api/v1/agent/chat` behind `requireAuth`: message + garden id, streaming (SSE) so tool calls surface as they happen rather than after a long silence. Return the change set id with the result so the UI can offer Undo. - [ ] Conversation persistence — an `agent_conversations` / `agent_messages` pair keyed to (user, garden). Multi-turn ("now do the same to the north bed") needs history, and client-held history would lose it on refresh. Cap retained turns. ## Design notes - **No spend caps, rate limiting or quotas.** Decided 2026-07-21: pansy is a personal tool, and multi-tenant cost controls are a post-v2 concern (`executus/budget` is the precedent whenever that changes). Note the distinction from the bounded-run item above — a step cap exists so a stuck loop terminates, not to police spend. Don't let this note grow into a budget system. - executus `audit/` is the natural sink for run-level traces, and `change_sets.agent_run_id` from #48 is the join back to domain changes. - Tool errors (a viewer hitting `ErrForbidden`) should reach the model as tool results it can explain, not 500s. The ACL story only works if the model can narrate the refusal. ## Out of scope - The chat panel UI (separate issue). - Spend caps / rate limiting / quotas, per above. - MCP. Companion-planting intelligence. ## Key files `internal/agent/` · `internal/config/config.go` · `internal/api/api.go` · `cmd/pansy/` · gadfly is the config precedent (`GADFLY_MODELS`, same `OLLAMA_CLOUD_API_KEY` secret); `../majordomo/agent/` for the loop; `../executus/run/` for run bounding. ## Dependencies Blocked by #48 — "act freely" is conditional on undo existing. Soft-blocked by #55 for the demo task to work end to end. ## Acceptance criteria - **"change the garlic bed to cucumbers this year"**, typed against a real garden, empties the garlic bed and replants it with cucumbers — and the whole thing appears as **one** change set that reverts in one action. - A viewer asking the agent to plant something gets a refusal the model explains, not an error page. - A run that hits the step cap terminates with a sensible message rather than hanging. - An instance with no API key starts, serves the app, and doesn't advertise the agent. - `make build` still produces one static binary with `CGO_ENABLED=0`. - `go test ./...` green with no build tag needed.
steve added the backendagent labels 2026-07-21 04:19:50 +00:00
steve closed this issue 2026-07-21 06:22:10 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#56