I traced "change the garlic garden bed to instead be cucumbers this year" against the existing toolbox. It is nearly executable today:
describe_garden → returns each object with the names of what's planted in it, so "the garlic bed" resolves to an object id.
clear_object(objectID) → soft-removes the garlic.
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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:
describe_garden→ returns each object with the names of what's planted in it, so "the garlic bed" resolves to an object id.clear_object(objectID)→ soft-removes the garlic.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 ofdescribe_gardenbut has no route from "cucumbers" to an id. One missing tool blocks the whole flagship interaction.Verified 2026-07-21:
internal/agentstill 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 asCreatePlantalready does.source_url/vendoron create, and surface remaining lot quantity infind_plantresults so the agent can say "you only have enough for half that bed".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.fill_region's region vocabulary in particular deserves a worked example.Design notes
internal/service.create_plantis 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
Key files
internal/agent/tools.go(behind//go:build majordomountil the runtime issue removes it) ·internal/service/plants.go·internal/service/ops.go(DescribeGarden,FillRegion) ·DefineTool[Args]in../majordomo/llm/tool.goDependencies
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
describe_garden→find_plant("cucumber")→clear_object→fill_region) completes end to end and/fullshows 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_plantproduces 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 defaultgo test ./...still builds without majordomo until the runtime issue changes that.