Commit Graph
9 Commits
Author SHA1 Message Date
steveandClaude Fable 5 f985c264f8 Agent: sharing tools that ask first, and a hard delete for a misplaced plop
Build image / build-and-push (push) Successful in 10s
Gadfly review (reusable) / review (pull_request) Successful in 4m18s
Adversarial Review (Gadfly) / review (pull_request) Successful in 4m18s
list_shares, share_garden, remove_share and public_link (get / enable /
rotate / disable) wrap the sharing service. They change who can see a garden
beyond the screen, so they are gated twice: the prompt tells the model to say
exactly what it would do and ask, and the tools refuse without confirmed=true,
which their descriptions allow only after a yes in the conversation. The
refusal names the action, so the question the model asks is precise.

share_garden changes the role of an existing share instead of failing on it;
remove_share takes the email list_shares reports; an unknown email explains
that the person has to sign in once first. public_link returns the address
(PANSY_BASE_URL + /g/<token>, via the new Service.PublicShareURL), never a
bare token.

delete_planting is the hard delete for a plop that was never really planted,
as opposed to remove_planting's "it came out"; it is recorded, so undoable.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:12:51 -04:00
steveandClaude Fable 5 b4c8007977 Agent: catalog and garden tools, and a ready date on every describe group
Build image / build-and-push (push) Successful in 11s
Gadfly review (reusable) / review (pull_request) Successful in 4m42s
Adversarial Review (Gadfly) / review (pull_request) Successful in 4m42s
- update_seed_lot / delete_seed_lot: correct or drop a recorded purchase
  ("it was three packets, not two"); the plant a lot is for stays fixed.
- delete_plant: remove a duplicate from the user's catalog. The service
  already refuses while plantings (past seasons included) or a lot reference
  it; the tool turns that sentinel into words the model can pass on, and
  tells it not to clear those references to get its way.
- create_garden: a new place, with the service's defaults; the prompt says a
  plan is still a copy_garden.
- describe_garden groups carry readyAround — planting date plus days to
  maturity for the plops still in the ground — so "what can I pick this
  week?" is a lookup rather than arithmetic the model got wrong live.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:05:19 -04:00
steveandClaude Fable 5 deec7bb917 Agent: undo for real, past seasons, and tools that correct the record
Build image / build-and-push (push) Successful in 11s
Gadfly review (reusable) / review (pull_request) Successful in 10m8s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m8s
Six tools the live assistant kept needing and a prompt that knows about them:

- undo_change wraps RevertChangeSet(source=agent). A revert is its own change
  set, so Run reports the last one as the turn's handle when the turn changed
  nothing else — an undo-only reply keeps its "Undo this", which is now a redo.
- describe_garden takes a year: the season view (GardenFull(year)), pulled
  plops included, with removed/removedAt per group and per plop; list_years
  says which years have records. Rotation questions finally have data.
- update_planting corrects a plop's date, count, label, radius or seed lot in
  place; remove_planting, remove_plantings and clear_object take a removedAt so
  a harvest can be backdated.
- update_journal_entry / delete_journal_entry correct a note instead of
  stacking a contradicting one.
- update_garden renames/resizes/re-units a garden and rewrites its notes — and
  the notes now go into the system prompt as the gardener's standing facts, so
  "remember we're in zone 6a" persists across conversations.

describe_garden also reports the garden's notes, version and grid, which the
new tools need. Prompt, CLAUDE.md and DESIGN.md updated to match; UI step
labels for the new tools.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 01:50:10 -04:00
steveandClaude Fable 5 bc14bbed0d Agent: what a day of live use asked for
Build image / build-and-push (push) Successful in 19s
Gadfly review (reusable) / review (pull_request) Successful in 10m2s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m2s
Twenty-one prompts against the live assistant found one fabricated success,
a model that believed it was 2025, and a describe_garden that was ~450 plop
entries per turn. This is the set of fixes, each traceable to a finding:

- The gardener's LOCAL day travels with the turn (`today` on POST /agent/chat,
  sent by the UI like plantedAt) into the system prompt and every dated tool
  default. Left to guess, the model dated journal entries a year back; left to
  the server, a 9 pm fill landed on UTC's tomorrow.
- describe_garden groups plops by plant — count, where, planted date, days to
  maturity — and lists ids only for groups of ≤ 8; list_plantings spells a big
  group out on demand and remove_plantings acts on one plant in a bed ("take
  the beets out, leave the garlic"), which used to mean 116 single removals.
- New tools: move_planting (keeps the planting date; across beds via the new
  MovePlanting, which is why the store's UPDATE now writes object_id),
  update_plant, read_history, copy_garden (the "<garden> — <year>" plan
  convention). fill_region takes an explicit local rectangle and a seedLotId;
  place_planting's radius defaults to one plant (spacing/2) instead of a guess.
- The system prompt states the date and the gardener's units, forbids claiming
  a change no tool made, says it cannot undo and points at the Undo button,
  asks before clearing beds on an ambiguous sentence, and stops narrating its
  own plantings into the journal.
- A mutation aimed at ANOTHER garden inside a turn is recorded under that
  garden as its own change set, not filed into the open scope.
- UI: the thread scrolls inside the Assistant panel so the composer stays
  put; every tool has a step label; wide tables stay inside the bubble.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:14:41 -04:00
steve cf37e57808 Seed-packet scan UI: camera/upload → proposal → confirm (#102)
Build image / build-and-push (push) Successful in 6s
Adds the mobile-first UI for the seed-packet capture backend (live since #94): a capability-gated "Scan a packet" entry in the Plants catalog opens a camera/upload → editable proposal → confirm flow that creates a plant (new or matched) + a seed lot. Frontend only. Closes #102 — the last open child of epic #96.

Co-authored-by: Steve Dudenhoeffer <[email protected]>
2026-07-22 17:07:26 +00:00
steveandClaude Opus 4.8 d82db48e4b Rough edges: mobile 100dvh, session-expiry in chat, sticky error toasts, journal date filter (#85)
Build image / build-and-push (push) Successful in 30s
Gadfly review (reusable) / review (pull_request) Successful in 5m41s
Adversarial Review (Gadfly) / review (pull_request) Successful in 5m41s
The safe, self-contained wins from the #85 bundle:

- **Mobile viewport.** The editor used `100vh`, which on mobile Safari/Chrome is
  the LARGEST viewport (URL bar hidden), so with the bar visible the canvas
  bottom and Fit button were pushed under the browser chrome. `100dvh` fixes it.
- **Session expiry mid-chat.** A 401 during a chat turn was mapped to "the
  assistant is not available right now" — sending the user to debug a config
  problem that isn't there. Now it says the session expired and redirects to
  /login, preserving the path, like the rest of the app treats 401.
- **Error toasts persist.** Error toasts were the primary report that a mutation
  failed, yet auto-dismissed at 4s with no way to retrieve them — look away and
  it's gone. Errors now stay until dismissed; info toasts still time out; both
  get a close button.
- **Journal date filter.** `from`/`to` existed in the API and JournalFilter but
  had no UI, so "show me last spring" was unreachable. Added two date inputs
  (with a Clear) that feed the existing filter. No backend change.

Deferred to their own follow-ups (too big for this bundle, or need a decision):
revision-history retention/pruning, the agent toolbox's missing corrective tools,
plop-level journal entries from the UI, and the editor's max-width cap (a layout
call worth confirming rather than changing blind). The DESIGN.md API-listing
drift the issue mentioned was already fixed in #89.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 23:31:10 -04:00
steveandClaude Opus 4.8 9ab454373b Address Gadfly findings on #90 (blocking round)
Build image / build-and-push (push) Successful in 17s
- Remove config.AgentConfig.Ready() — dead after the refactor (no non-test
  callers), and its doc comment ("route registration and capabilities gate on
  this") was now false. EffectiveAgent.Ready() carries the same logic and IS
  used, see below.
- agentHolder now uses eff.Ready() and eff.APIKey instead of an inline check and
  a redundant apiKey field it held separately. This makes EffectiveAgent.APIKey/
  Ready() production-used rather than test-only, drops the duplicated readiness
  check, and simplifies newAgentHolder's signature.
- settingsPayload no longer swallows an EffectiveAgent error into a misleading
  empty "effective" view (which would read as "nothing configured"). It returns
  the error; the handlers surface it as a 500. EffectiveAgent re-reads the row
  GetInstanceSettings just returned, so a failure there is a real DB fault.
- Frontend streamChat handles 503 (assistant disabled at runtime) distinctly
  from 404 (endpoint absent) — the backend returns 503 AGENT_DISABLED now, which
  the old code mislabelled.
- Fixed the api.go comment that still said a disabled request gets a 404 — it's
  a 503.
- updateSettings uses the shared parseNullable[bool] instead of a bespoke
  parseNullableBool (now removed).
- NewRunner drops its empty-spec guard; agentmodel.Resolve already owns that
  check, so one place decides what a valid spec is.
- rebuild-on-resolve-error now documents WHY it keeps the current Runner rather
  than tearing down a working assistant on a transient DB blip: the state is
  persisted, the next rebuild reconciles, and killing a live assistant on a read
  hiccup is worse than a brief stale window.

Not changed: the store's version-conflict fallback returning GetInstanceSettings'
error instead of ErrVersionConflict when that read also fails. That's the exact
pattern every other version-guarded update uses (gardens/objects/plants); making
only this one differ would be the inconsistency. A DB read failing immediately
after the guarded UPDATE on the same local SQLite file is a disk-fault edge case,
and surfacing that error is defensible.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 22:17:25 -04:00
steveandClaude Opus 4.8 41c28592a2 Admin-gated Settings: runtime model selection, enforce is_admin (#79)
Gadfly review (reusable) / review (pull_request) Failing after 30s
Adversarial Review (Gadfly) / review (pull_request) Failing after 30s
Build image / build-and-push (push) Successful in 14s
Moves the agent model out of env-only config into an admin-editable Settings
section, and enforces is_admin for the first time — it has been in the schema
since migration 0001, plumbed to the client, and checked nowhere.

Backend:
- Migration 0010: instance_settings, a single-row (CHECK id=1) table — pansy's
  first instance-level state. Holds agent_model ('' = inherit env) and
  agent_enabled (NULL = inherit env), version-guarded like every mutable row.
  SECRETS STAY IN ENV: OLLAMA_CLOUD_API_KEY is never stored here.
- requireAdmin at the service seam (authoritative) plus a cheap middleware
  early-403. Non-admin gets 403, not 404 — settings existence isn't masked.
- EffectiveAgent resolves DB-over-env (model, enabled); key always from env.
- The live Runner is hot-swapped, not built once. agentHolder holds it behind
  an atomic.Pointer; the chat routes are now registered UNCONDITIONALLY and
  nil-check agent.get(), so a settings change turns the assistant on/off/onto a
  new model with no restart and no race against in-flight readers. /capabilities
  reads the pointer, so it reports what's live, not what booted.
- internal/agentmodel is a new leaf package holding the one place that knows how
  to turn a spec into a model. Both agent (to run) and service (to validate a
  spec before storing it) import it; it can't live in agent, which imports
  service. Settings PATCH validates the spec via Parse, so a typo is a 400 now
  rather than a broken assistant on the next turn.

Frontend:
- /settings route (admin guard), a Settings page (model field, tri-state
  enabled, live status), nav link shown only to admins.
- useCapabilities drops staleTime:Infinity — the assistant can now change under
  a running page — and the settings save invalidates it.

Contract change: chat routes always exist, so "assistant off" is a runtime 503
+ capabilities:false, not a missing route. Updated the test that asserted the
old shape.

Verified live against the built binary: disable flips capabilities to false and
logs it; re-enable with a new model swaps it back; a bad spec is rejected 400;
the setting persists across a restart. Swap is race-clean under `go test -race`.

Docs: README (precedence + key-stays-in-env), DESIGN (decision + routes),
CLAUDE (don't re-add conditional route registration; key never in the DB).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 20:27:36 -04:00
steve 8dbbc5439d Chat panel in the garden editor (#57) (#71)
Build image / build-and-push (push) Successful in 6s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
2026-07-21 06:43:22 +00:00