Agent tools: plant lookup + creation (closes the gap blocking the demo task) #55

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

Design: DESIGN.md

Context

I traced "change the garlic garden bed to instead be cucumbers this year" against the existing toolbox. It is nearly executable today:

  1. describe_garden → returns each object with the names of what's planted in it, so "the garlic bed" resolves to an object id.
  2. clear_object(objectID) → soft-removes the garlic.
  3. fill_region(objectID, plantID, "all") → replants.

Step 3 has no way to get its plantID. The toolbox has no plant tool at all — the agent can see the string "Garlic" coming out of describe_garden but has no route from "cucumbers" to an id. One missing tool blocks the whole flagship interaction.

Verified 2026-07-21: internal/agent still compiles and its tests pass against the current local majordomo under -tags majordomo. The seam is alive, just incomplete.

Scope

  • find_plant(query) → matches the actor's visible catalog (32 built-ins + their own) and returns candidates as {id, name, category, spacingCm}. Return several candidates rather than one guess — "garlic" against a catalog holding both "Garlic" and "German Red Garlic" is genuinely ambiguous, and the model is better placed to disambiguate from context than a fuzzy-match heuristic is.
  • create_plant(name, category, spacingCm, color, icon, daysToMaturity?) → so a new variety can be named in conversation ("plant German Red Garlic") without leaving the chat. Owned by the acting user, exactly as CreatePlant already does.
  • Once #50 lands: expose source_url/vendor on create, and surface remaining lot quantity in find_plant results so the agent can say "you only have enough for half that bed".
  • Once #52 lands: add_journal_entry(gardenID, objectID?, body, observedAt?) — "note that the west bed has mildew" is squarely the kind of thing you'd say out loud while walking around.
  • Review the descriptions on the existing seven tools while in here. They are the model's only documentation, and fill_region's region vocabulary in particular deserves a worked example.

Design notes

  • Tools stay thin adapters over the service layer — that is what makes ACL enforcement automatic, and it must not erode. No tool reaches past internal/service.
  • create_plant is a write on the shared catalog namespace. It should be as boring and revertible as any other write; note that #48 deliberately excludes the plant catalog from revision history, so a mistakenly-created plant is deleted, not reverted.

Out of scope

  • Companion-planting or rotation intelligence in fills.
  • Anything about the run loop or transport — that's the runtime issue.

Key files

internal/agent/tools.go (behind //go:build majordomo until the runtime issue removes it) · internal/service/plants.go · internal/service/ops.go (DescribeGarden, FillRegion) · DefineTool[Args] in ../majordomo/llm/tool.go

Dependencies

None hard. Soft-blocks the demo task in the runtime issue. The #50 and #52 additions above are gated on those issues but the core two tools are not.

Acceptance criteria

  • Given a garden with a bed of garlic, a scripted tool sequence (describe_gardenfind_plant("cucumber")clear_objectfill_region) completes end to end and /full shows a bed of cucumbers and no active garlic.
  • find_plant("garlic") returns both the built-in and a custom "German Red Garlic" rather than silently picking one.
  • create_plant produces a plant owned by the acting user; a viewer calling it against a garden they can't edit is unaffected (the catalog is user-scoped, not garden-scoped — assert this deliberately rather than assuming it).
  • go test -tags majordomo ./internal/agent/ green, and the default go test ./... still builds without majordomo until the runtime issue changes that.
Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md) ## Context I traced "change the garlic garden bed to instead be cucumbers this year" against the existing toolbox. It is nearly executable today: 1. `describe_garden` → returns each object with the **names** of what's planted in it, so "the garlic bed" resolves to an object id. 2. `clear_object(objectID)` → soft-removes the garlic. 3. `fill_region(objectID, plantID, "all")` → replants. **Step 3 has no way to get its `plantID`.** The toolbox has no plant tool at all — the agent can see the string "Garlic" coming out of `describe_garden` but has no route from "cucumbers" to an id. One missing tool blocks the whole flagship interaction. Verified 2026-07-21: `internal/agent` still compiles and its tests pass against the current local majordomo under `-tags majordomo`. The seam is alive, just incomplete. ## Scope - [ ] `find_plant(query)` → matches the actor's visible catalog (32 built-ins + their own) and returns candidates as `{id, name, category, spacingCm}`. Return **several candidates rather than one guess** — "garlic" against a catalog holding both "Garlic" and "German Red Garlic" is genuinely ambiguous, and the model is better placed to disambiguate from context than a fuzzy-match heuristic is. - [ ] `create_plant(name, category, spacingCm, color, icon, daysToMaturity?)` → so a new variety can be named in conversation ("plant German Red Garlic") without leaving the chat. Owned by the acting user, exactly as `CreatePlant` already does. - [ ] Once #50 lands: expose `source_url`/`vendor` on create, and surface remaining lot quantity in `find_plant` results so the agent can say "you only have enough for half that bed". - [ ] Once #52 lands: `add_journal_entry(gardenID, objectID?, body, observedAt?)` — "note that the west bed has mildew" is squarely the kind of thing you'd say out loud while walking around. - [ ] Review the descriptions on the existing seven tools while in here. They are the model's only documentation, and `fill_region`'s region vocabulary in particular deserves a worked example. ## Design notes - Tools stay thin adapters over the service layer — that is what makes ACL enforcement automatic, and it must not erode. No tool reaches past `internal/service`. - `create_plant` is a write on the shared catalog namespace. It should be as boring and revertible as any other write; note that #48 deliberately excludes the plant catalog from revision history, so a mistakenly-created plant is deleted, not reverted. ## Out of scope - Companion-planting or rotation intelligence in fills. - Anything about the run loop or transport — that's the runtime issue. ## Key files `internal/agent/tools.go` (behind `//go:build majordomo` until the runtime issue removes it) · `internal/service/plants.go` · `internal/service/ops.go` (`DescribeGarden`, `FillRegion`) · `DefineTool[Args]` in `../majordomo/llm/tool.go` ## Dependencies None hard. Soft-blocks the demo task in the runtime issue. The #50 and #52 additions above are gated on those issues but the core two tools are not. ## Acceptance criteria - Given a garden with a bed of garlic, a scripted tool sequence (`describe_garden` → `find_plant("cucumber")` → `clear_object` → `fill_region`) completes end to end and `/full` shows a bed of cucumbers and no active garlic. - `find_plant("garlic")` returns both the built-in and a custom "German Red Garlic" rather than silently picking one. - `create_plant` produces a plant owned by the acting user; a viewer calling it against a garden they can't edit is unaffected (the catalog is user-scoped, not garden-scoped — assert this deliberately rather than assuming it). - `go test -tags majordomo ./internal/agent/` green, and the default `go test ./...` still builds without majordomo until the runtime issue changes that.
steve added the backendagent labels 2026-07-21 04:19:19 +00:00
steve closed this issue 2026-07-21 06:05:06 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#55