Agent seam: bulk ops (FillRegion/ClearObject/DescribeGarden) + DefineTool wrappers #19

Closed
opened 2026-07-18 18:20:07 +00:00 by steve · 0 comments
Owner

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:
    • 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.
steve added the backendagent labels 2026-07-18 18:20:07 +00:00
steve closed this issue 2026-07-19 05:16:40 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#19