The end goal: tell an agent "fill one corner with garlic, one with basil, and the remaining half with beans" and watch the garden update. Pansy's side of that is (a) bulk service operations designed for natural-language-shaped requests and (b) thin majordomo llm.DefineTool[Args] wrappers over the same service layer the REST API uses — so agent calls inherit ACL enforcement for free. The agent harness itself (model loop, chat surface) lives in the majordomo/executus stack outside this repo.
Scope
internal/service/ops.go:
Region type: rect or circle in object-local coordinates, plus NamedRegion(object, name) resolving "nw"|"ne"|"sw"|"se" corner (quarter), "north"|"south"|"east"|"west" half, "left|right|top|bottom half", "all" → a Region (document the compass convention: -y is north in the local frame).
FillRegion(ctx, actor, objectID, region, plantID, spacingOverride *float64) ([]Planting, error): lays plops to cover the region — simplest correct v1: hex-packed plop centers at 2×radius pitch with default radius from #15's formula (max(1.5×spacing, 15cm)), clipped to the region; skip positions whose plop would sit entirely inside an existing active planting. Returns what it created.
ClearObject(ctx, actor, objectID) (n int, err error): removed_at on all active plantings (one UPDATE).
DescribeGarden(ctx, actor, gardenID) (DescribeResult, error): structured summary — garden dims, objects (kind/name/size/position), active plantings per object (plant, count, rough location as a NamedRegion reverse-lookup), suitable for prompting.
internal/agent/tools.go: majordomo llm.DefineTool[Args] registrations — list_gardens, describe_garden, create_object, move_object, place_planting, fill_region, clear_object — each a thin adapter: typed args struct → service call with an actor injected at toolbox construction (agent.NewToolbox(svc, actorUserID)). Keep majordomo out of the core server binary if practical (build tag or separate package consumers import).
cmd/pansy-agent-demo (or an integration test): a minimal harness wiring the toolbox into a majordomo agent loop to execute the corner/half scenario against a dev DB.
Optional (decide while implementing): mort-style HTTP surface POST /api/agent/v1/... gated by PANSY_AGENT_API_KEY + subtle.ConstantTimeCompare — only if driving pansy from an external harness over HTTP turns out to be wanted; direct package import may be enough.
Out of scope
The chat UI / conversational surface (lives in mort/executus land). MCP. Companion-planting intelligence in fills (post-v1).
Key files & patterns
DefineTool[Args]: /Users/steve/Projects/majordomo/llm/tool.go (derives JSON schema from the args struct). Registry/permissions ideas: /Users/steve/Projects/executus/tool/registry.go. HTTP-surface reference: /Users/steve/Projects/mort/cmd/mort/agentkit_api.go.
Derived-count/radius formulas shared with #14/#15 — reuse, don't duplicate.
Dependencies
Blocked by #14. Do after #16 so ops inherit final ACL behavior (tools must return ErrForbidden for viewers).
Acceptance criteria
The demo harness executes: fill NE corner with garlic, NW corner with basil, south half with beans → /full (or DescribeGarden) shows three distinct plant groups in the right places, counts consistent with spacing; screenshot-check in the editor shows the expected layout.
FillRegion on a rotated bed still fills the correct local-frame corner.
A viewer-role actor calling fill_region gets ErrForbidden; unit tests cover NamedRegion for all names + FillRegion packing on a known geometry (deterministic counts).
go test ./... green; core server still builds without majordomo if the separation was kept.
Part of epic #20 · Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md)
## Context
The end goal: tell an agent "fill one corner with garlic, one with basil, and the remaining half with beans" and watch the garden update. Pansy's side of that is (a) bulk service operations designed for natural-language-shaped requests and (b) thin majordomo `llm.DefineTool[Args]` wrappers over the **same service layer** the REST API uses — so agent calls inherit ACL enforcement for free. The agent harness itself (model loop, chat surface) lives in the majordomo/executus stack outside this repo.
## Scope
- [ ] `internal/service/ops.go`:
- `Region` type: rect or circle in **object-local** coordinates, plus `NamedRegion(object, name)` resolving `"nw"|"ne"|"sw"|"se" corner` (quarter), `"north"|"south"|"east"|"west" half`, `"left|right|top|bottom half"`, `"all"` → a Region (document the compass convention: -y is north in the local frame).
- `FillRegion(ctx, actor, objectID, region, plantID, spacingOverride *float64) ([]Planting, error)`: lays plops to cover the region — simplest correct v1: hex-packed plop centers at `2×radius` pitch with default radius from #15's formula (`max(1.5×spacing, 15cm)`), clipped to the region; skip positions whose plop would sit entirely inside an existing active planting. Returns what it created.
- `ClearObject(ctx, actor, objectID) (n int, err error)`: `removed_at` on all active plantings (one UPDATE).
- `DescribeGarden(ctx, actor, gardenID) (DescribeResult, error)`: structured summary — garden dims, objects (kind/name/size/position), active plantings per object (plant, count, rough location as a NamedRegion reverse-lookup), suitable for prompting.
- [ ] `internal/agent/tools.go`: majordomo `llm.DefineTool[Args]` registrations — `list_gardens`, `describe_garden`, `create_object`, `move_object`, `place_planting`, `fill_region`, `clear_object` — each a thin adapter: typed args struct → service call with an actor injected at toolbox construction (`agent.NewToolbox(svc, actorUserID)`). Keep majordomo out of the core server binary if practical (build tag or separate package consumers import).
- [ ] `cmd/pansy-agent-demo` (or an integration test): a minimal harness wiring the toolbox into a majordomo agent loop to execute the corner/half scenario against a dev DB.
- [ ] Optional (decide while implementing): mort-style HTTP surface `POST /api/agent/v1/...` gated by `PANSY_AGENT_API_KEY` + `subtle.ConstantTimeCompare` — only if driving pansy from an external harness over HTTP turns out to be wanted; direct package import may be enough.
## Out of scope
- The chat UI / conversational surface (lives in mort/executus land). MCP. Companion-planting intelligence in fills (post-v1).
## Key files & patterns
- `DefineTool[Args]`: `/Users/steve/Projects/majordomo/llm/tool.go` (derives JSON schema from the args struct). Registry/permissions ideas: `/Users/steve/Projects/executus/tool/registry.go`. HTTP-surface reference: `/Users/steve/Projects/mort/cmd/mort/agentkit_api.go`.
- Derived-count/radius formulas shared with #14/#15 — reuse, don't duplicate.
## Dependencies
Blocked by #14. Do after #16 so ops inherit final ACL behavior (tools must return `ErrForbidden` for viewers).
## Acceptance criteria
- The demo harness executes: fill NE corner with garlic, NW corner with basil, south half with beans → `/full` (or DescribeGarden) shows three distinct plant groups in the right places, counts consistent with spacing; screenshot-check in the editor shows the expected layout.
- `FillRegion` on a rotated bed still fills the correct local-frame corner.
- A viewer-role actor calling `fill_region` gets `ErrForbidden`; unit tests cover NamedRegion for all names + FillRegion packing on a known geometry (deterministic counts).
- `go test ./...` green; core server still builds without majordomo if the separation was kept.
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.
Part of epic #20 · Design: DESIGN.md
Context
The end goal: tell an agent "fill one corner with garlic, one with basil, and the remaining half with beans" and watch the garden update. Pansy's side of that is (a) bulk service operations designed for natural-language-shaped requests and (b) thin majordomo
llm.DefineTool[Args]wrappers over the same service layer the REST API uses — so agent calls inherit ACL enforcement for free. The agent harness itself (model loop, chat surface) lives in the majordomo/executus stack outside this repo.Scope
internal/service/ops.go:Regiontype: rect or circle in object-local coordinates, plusNamedRegion(object, name)resolving"nw"|"ne"|"sw"|"se" corner(quarter),"north"|"south"|"east"|"west" half,"left|right|top|bottom half","all"→ a Region (document the compass convention: -y is north in the local frame).FillRegion(ctx, actor, objectID, region, plantID, spacingOverride *float64) ([]Planting, error): lays plops to cover the region — simplest correct v1: hex-packed plop centers at2×radiuspitch with default radius from #15's formula (max(1.5×spacing, 15cm)), clipped to the region; skip positions whose plop would sit entirely inside an existing active planting. Returns what it created.ClearObject(ctx, actor, objectID) (n int, err error):removed_aton all active plantings (one UPDATE).DescribeGarden(ctx, actor, gardenID) (DescribeResult, error): structured summary — garden dims, objects (kind/name/size/position), active plantings per object (plant, count, rough location as a NamedRegion reverse-lookup), suitable for prompting.internal/agent/tools.go: majordomollm.DefineTool[Args]registrations —list_gardens,describe_garden,create_object,move_object,place_planting,fill_region,clear_object— each a thin adapter: typed args struct → service call with an actor injected at toolbox construction (agent.NewToolbox(svc, actorUserID)). Keep majordomo out of the core server binary if practical (build tag or separate package consumers import).cmd/pansy-agent-demo(or an integration test): a minimal harness wiring the toolbox into a majordomo agent loop to execute the corner/half scenario against a dev DB.POST /api/agent/v1/...gated byPANSY_AGENT_API_KEY+subtle.ConstantTimeCompare— only if driving pansy from an external harness over HTTP turns out to be wanted; direct package import may be enough.Out of scope
Key files & patterns
DefineTool[Args]:/Users/steve/Projects/majordomo/llm/tool.go(derives JSON schema from the args struct). Registry/permissions ideas:/Users/steve/Projects/executus/tool/registry.go. HTTP-surface reference:/Users/steve/Projects/mort/cmd/mort/agentkit_api.go.Dependencies
Blocked by #14. Do after #16 so ops inherit final ACL behavior (tools must return
ErrForbiddenfor viewers).Acceptance criteria
/full(or DescribeGarden) shows three distinct plant groups in the right places, counts consistent with spacing; screenshot-check in the editor shows the expected layout.FillRegionon a rotated bed still fills the correct local-frame corner.fill_regiongetsErrForbidden; unit tests cover NamedRegion for all names + FillRegion packing on a known geometry (deterministic counts).go test ./...green; core server still builds without majordomo if the separation was kept.