The gardener
The background pass that finds duplicates, staleness, and drift - and proposes, because nothing rewrites your knowledge behind your back.
The Seamless gardener is the background pass that keeps an agent-written memory
store from rotting: it detects near-duplicate memories by embedding similarity,
memories gone stale, and plans abandoned with steps still open, and files each
finding as a proposal. Nothing is applied until you approve it with
gardener_apply - the gardener has no write path of its own.
Any store that agents write to accumulates cruft: two memories saying the same thing in different words, a runbook for a system that no longer exists, a plan abandoned three weeks ago with its steps still open.
The gardener finds those. Then it stops and asks.
Propose-only is the whole contract
Every pass the gardener makes ends in a proposal for you to review. It never edits, merges, or archives on its own.
This is not timidity. The entire premise of Seamless is that the knowledge is yours and legible - a store that quietly rewrites itself while you sleep is exactly the black box the project exists to avoid. If an automated pass can decide your constraint is stale and archive it, then you do not actually know what your agents will be told tomorrow.
So the surface is two tools, not one button: gardener_proposals to read what it
found, gardener_apply to act on one.
What it looks for
| Pass | Trigger | Proposes |
|---|---|---|
| dedup | Two memories more similar than gardener.dedup_threshold (0.88) |
merge: one is kept as-is, the other superseded and pointed at it |
| staleness | A memory untouched for gardener.staleness_days (90) |
archive: marked invalid, still readable |
| digest | Enough recent activity over gardener.digest_days (30) |
a digest note summarizing it |
| stale-plan | A plan idle for gardener.stale_plan_days (14) with steps still open |
surfacing it, so it is abandoned deliberately rather than by neglect |
| stale-stage | A stage memory whose Status: header is done, missing, or unrecognized, unchanged for gardener.stale_stage_days (14) |
archive: a stage that gates nothing should not hold a permanent briefing pin |
| dead-weight | A memory briefings injected 20+ times in 30 days without a single recall hit, prompt match, or read | archive: exposure without demand means it costs tokens and steers nothing |
| memory-wanted | The same recall query returned zero hits in 2+ sessions inside 14 days | memory_wanted: write the knowledge agents keep searching for; applying opens a task in the queue |
| tool-error | The same normalized tool or hook error, 3+ times inside 14 days and still recent; tool errors must span 2+ sessions | tool_error: investigate and fix the recurring failure; applying opens a task with the observed error evidence |
The memory-wanted pass is the one place the gardener asks for knowledge
instead of curating what exists: recurring zero-hit recall queries are demand
for a memory nobody wrote, grouped per project so a one-off miss never fires.
Applying the proposal opens a task ("Write a memory: ...") in the ready queue -
the memory itself is only ever written by whoever picks the task up.
The tool-error pass closes the other side of that loop. A single failure may be a typo; the same normalized error again and again is evidence of a broken workflow. It also watches hook errors, which are swallowed fail-open and never reach an agent - recurrence here is the one place they surface. Applying its proposal opens a repair task rather than pretending the gardener can fix code or configuration itself.
Two more proposal types come from requests rather than the timer:
- reproject - a memory filed under the wrong project, moved to a project that already exists.
- split - one project divided into new child projects. This creates projects
and a shared parent, so it is planned as a unit, not as a pile of moves. That
is why it is a separate tool (
gardener_split) and not just a reproject to a name that does not exist yet.
Constraints and stages are exempt from staleness
Age-filtering and staleness-archiving never touch constraint or stage
memories.
A constraint does not become less true by sitting still. "Never use CGO" is not stale at 90 days - it is settled, and the absence of recent edits is evidence it is working, not evidence it is rotting. Time-based archival encodes the opposite assumption, so the pass simply skips the kinds where that assumption is wrong.
Stages get their own pass instead, because staleness cannot see them at all:
a pinned stage is re-injected into every briefing, so by the activity metric it
never goes quiet. The stale-stage pass keys off the update time and the
Status: header - a stage still carrying a live gate
(open/in_progress/blocked) is never proposed at any age, while one that is
done, headerless, or unparseable is proposed for archiving once it has sat
unchanged for gardener.stale_stage_days. Both passes respect [[links]]: a
memory another body points at is not proposed.
Asking for it in words
gardener_request "fold the two console theme memories together"
gardener_request takes natural language and turns it into the same reviewable
proposals as the timed passes. It is a way to aim the gardener, not a way to
bypass the review step - what comes back is still a proposal.
Where you review
- The console -
/console/gardenerlists proposals with what each would do. gardener_proposals- the same, for an agent.
The gardener runs every gardener.interval_minutes (60) when
gardener.enabled is on. Everything in this page is tunable - see
Configuration.