Agent runtime: majordomo in-process, Ollama Cloud config, chat endpoint (#56) (#70)
Build image / build-and-push (push) Successful in 6s
Build image / build-and-push (push) Successful in 6s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #70.
This commit is contained in:
@@ -23,6 +23,7 @@ SQLite, centimeters everywhere (display-side imperial conversion only), `version
|
||||
- **garden_objects** — one polymorphic table: `kind` (`bed`|`grow_bag`|`container`|`in_ground`|`tree`|`path`|`structure`), name, `shape` (`rect`|`circle`; `polygon` + `points` JSON reserved in schema, not in the v1 editor), center `x_cm`,`y_cm` in garden space, `width_cm`,`height_cm` (circle: width=diameter), `rotation_deg`, `z_index`, `plantable` bool, nullable `color` override, `props` JSON for kind-specific extras (bed height, tree species…), notes.
|
||||
- **seed_lots** — one purchase: vendor, source URL, sku, lot code, purchase date, packed-for year, quantity + unit, cost, germination %. `owner_id` is on the LOT, not inherited from the plant, so you can record buying generic built-in Garlic from Johnny's and keep it private. `plantings.seed_lot_id` (ON DELETE SET NULL) attributes a plop to a purchase; **`remaining` is derived** (quantity − summed effective count of active plantings), never stored — a decremented column drifts the moment a planting is edited behind its back and can't be audited afterwards. Lots are never shared along with a garden.
|
||||
- **plants** — catalog, plus `source_url`/`vendor` provenance for the variety itself. `owner_id NULL` = built-in (~30 seeded via migration, read-only, clone to customize). name, category (`vegetable`|`herb`|`flower`|`fruit`|`tree_shrub`|`cover`), `spacing_cm` (mature spacing), `color` hex, `icon` (emoji string — zero assets in v1), nullable `days_to_maturity`, notes. Users see built-ins + their own.
|
||||
- **agent_conversations** / **agent_messages** — the assistant's chat, one thread per (user, garden). Only the user/assistant TEXT of each turn is stored, not the model's full transcript: continuity needs what was said and what came back, and replaying a stored tool call would replay a decision made against a garden that has since moved on. `agent_messages.change_set_id` is the handle the chat panel uses to offer Undo on the turn that caused it.
|
||||
- **journal_entries** — the grow log: `garden_id` (NOT NULL), nullable `object_id`/`planting_id` to narrow the target, author, body, `observed_at`. `notes` on a garden/object/plant says what the thing IS and a new note overwrites the old; a journal entry says what HAPPENED and when, and entries accumulate. `garden_id` is set even for a bed- or plop-level entry so permission checks reuse `requireGardenRole` unchanged rather than inventing a second ACL path. `observed_at` is distinct from `created_at` — you write up Saturday's observations on Sunday. Deliberately **not** in the revision history: entries are already append-shaped and individually versioned.
|
||||
- **change_sets** / **revisions** — the undo substrate. A change set groups the row-level revisions one logical operation produced (`source` ui/agent/api, `summary`, nullable `agent_run_id`, nullable `reverts_id`); a revision is `(entity_type, entity_id, op, before, after)` with full JSON row snapshots. The unit of undo is the **operation, not the row**. Revert is itself a change set, so an undo can be undone; it is version-guarded per entity and reports conflicts rather than clobbering a later edit. Garden *deletion* is deliberately not revertible (the cascade is invisible to the service, and would take the history with it).
|
||||
- **plantings** ("plops") — object_id, plant_id, center `x_cm`,`y_cm` **in the parent object's local frame** (moving/rotating a bed moves its plants for free), `radius_cm` (a plop is a circular patch), nullable `count` (NULL = derived: `max(1, round(π·r² / spacing_cm²))`), nullable label, `planted_at`/`removed_at` dates. "Clear bed" sets `removed_at`; v1 shows rows where `removed_at IS NULL`. Year-over-year history and a future plan/scenario layer build on these dates without schema surgery.
|
||||
@@ -64,6 +65,10 @@ POST /gardens/:id/objects PATCH,DELETE /objects/:id
|
||||
POST /objects/:id/plantings PATCH,DELETE /plantings/:id
|
||||
GET,POST /plants PATCH,DELETE /plants/:id (own plants only)
|
||||
GET,POST /seed-lots GET,PATCH,DELETE /seed-lots/:id (own lots only; private)
|
||||
GET,POST /gardens/:id/journal PATCH,DELETE /journal/:id (editor writes; author edits own)
|
||||
GET /gardens/:id/journal/counts ← entries per object, for the "has notes" indicator
|
||||
POST /agent/chat ← SSE: step events, then the finished turn (editor only)
|
||||
GET,DELETE /gardens/:id/agent/history (the actor's own thread)
|
||||
GET,POST /gardens/:id/shares PATCH,DELETE /gardens/:id/shares/:userId (invite by email)
|
||||
```
|
||||
|
||||
@@ -77,6 +82,7 @@ GET,POST /gardens/:id/shares PATCH,DELETE /gardens/:id/shares/:userId (invit
|
||||
cmd/pansy/main.go config load → store.Open → migrate → service → api → ListenAndServe
|
||||
internal/config/ PANSY_PORT, PANSY_DB, PANSY_BASE_URL, PANSY_REGISTRATION,
|
||||
PANSY_OIDC_* (issuer/client_id/secret/button label), PANSY_LOCAL_AUTH,
|
||||
PANSY_AGENT_MODEL / OLLAMA_CLOUD_API_KEY / PANSY_AGENT_ENABLED,
|
||||
trusted proxies
|
||||
internal/store/ sqlite.go (open/pragmas), migrate.go, migrations/*.sql (embedded),
|
||||
queries per entity (users.go, gardens.go, objects.go, plantings.go,
|
||||
@@ -89,7 +95,8 @@ internal/service/ ★ THE SEAM. All permission checks + business ops. aut
|
||||
internal/api/ thin gin handlers (decode → service → encode), session middleware,
|
||||
ginserver setup, routes.go, oidc.go, spa.go (embed fallback)
|
||||
internal/webdist/ dist.go: //go:embed all:dist (web build copied in by Makefile)
|
||||
internal/agent/ later: llm.DefineTool[Args] wrappers over the SAME service methods
|
||||
internal/agent/ tools.go (llm.DefineTool wrappers over the SAME service methods),
|
||||
runtime.go (model resolution, run loop, one change set per turn)
|
||||
web/ Vite app
|
||||
Makefile (cd web && npm run build) → copy dist → CGO_ENABLED=0 go build
|
||||
```
|
||||
@@ -123,7 +130,8 @@ React 19 + TypeScript + Vite + Tailwind 4 (`@tailwindcss/vite`), `@tanstack/reac
|
||||
6. **Plops** — focus-zoom, place/move/resize, derived counts, semantic zoom. *The core vision lands here.*
|
||||
7. **Sharing** — invite by email, roles, viewer read-only mode.
|
||||
8. **Polish** — imperial toggle, mobile ergonomics, clear-bed, keyboard nudging.
|
||||
9. **Agent seam** — `ops.go` bulk ops + `internal/agent` DefineTool wrappers; optional mort-style API-key agent endpoint. The agent harness itself lives in majordomo/executus, outside pansy.
|
||||
9. **Agent seam** — `ops.go` bulk ops + `internal/agent` DefineTool wrappers.
|
||||
10. **Garden assistant** — majordomo in-process, Ollama Cloud, streaming chat. Each turn runs inside ONE change set (`source='agent'`), so a turn that clears a bed and replants it undoes as one action; that is what makes acting without a confirmation prompt defensible. Bounded by a step cap and a timeout — loop safety, not spend control. The `majordomo` build tag is gone: a tag that keeps the agent out of the binary only earns its keep if you'd ship a build without it, and the agent is the point.
|
||||
|
||||
## Deliberate v1 limits
|
||||
|
||||
|
||||
Reference in New Issue
Block a user