Subchapter 13.1
references/dedupe-and-memory.mdMarkdown19 KBView on GitHub
How a scout decides what to do with a candidate observation, how it writes durable scratchpad entries, and the noise patterns common across PostHog projects. Author your scout’s Decide and Save-memory sections around these — they’re how the fleet avoids re-filing and gets smarter every run. This mirrors .
signals-scout-general/references/conventions.mdEvery scout classifies each candidate finding against prior runs, the inbox, and the scratchpad before authoring a report. Bake this classifier into the scout’s Decide section:
emit_report if it clears the report bar (see report-contract.md).edit_report it — use append_evidence for the new observation, append_note for a reading of it, or rewrite title/summary on a report the scout authored.
Don’t mint a near-duplicate.
Live reports only: edit_report never changes a report’s status, so if the prior report is suppressed or resolved and the issue is genuinely back, author a fresh report (citing the prior report_id in the summary) rather than editing a closed one nobody will see.addressed: / noise: / dedupe: prefix names the entity with a “team aware” note. → Skip; note it in the run summary.The scratchpad is durable, per-team prose keyed by string.
It has no tags: the category is encoded in the key prefix so a future run finds an entry with a single text= search.
Re-using a key rewrites the entry in place (the idempotent refresh — use it to confirm a quiet observation without duplicating entries).
An entry can carry an optional expires_at: expired entries drop out of searches (unless include_expired=true) and a daily janitor deletes them two weeks past expiry, so set it on anything with a natural shelf life (a watch: on a live issue, a cursor) and leave baselines and allowlists unexpiring.
One keyspace, several writers. Every scout on the team shares it, and so do the two report-pipeline stages: the research run and the self-driving implementation run.
Each search result carries created_by_skill, which reads a scout’s skill name for a scout entry and pipeline:report-research or pipeline:implementation for a pipeline one. It names the entry’s original creator only: a rewrite of an existing key keeps the creator, so the field does not say who last wrote the content, and the current writer of a shared key is unknown.
Two rules follow. Search the identity of the thing (the issue id, the flag key, the file path) rather than only your own prefix, or you find your own past work and nothing else.
And only ever forget keys under your own prefix that you created: scout-scratchpad-forget deletes by exact key without checking the writer, so removing another writer’s cursor or dedupe: row, or a key of yours a sibling has since taken over, makes it repeat work or lose its place.
| Prefix | Use for |
|---|---|
pattern: | Durable observation about how this team’s data normally shapes (baselines). |
watch: | A live issue being tracked but still below the report bar: what to re-check and the condition that would clear the bar. |
followup: | Harness-defined validation queue, keyed followup:<skill-name>:<topic>: a probe a later run should re-run to confirm a reported finding is fixed or still live. The run prompt tells every scout to read its own queue each run (text=followup:<skill-name>: with the trailing colon) and decide when a run becomes a validation run; write the probe and its state header so a future run can execute it cold. |
noise: | Patterns to ignore (single-user, dev-only, recurring with no fix path). |
addressed: | Team-confirmed fix shipped, or topic the team has moved on from. |
dedupe: | Gates future runs on a specific issue / fingerprint so the scout doesn’t re-file it. |
allowlist: | Vetted entities the scout should never re-surface. |
not-in-use: | Close-out memo for “product/surface not in use on this team”. |
mcp-gap: | Scout-noticed gap in the MCP surface worth raising later. |
improve: | Custom scouts only: an evidence-backed suggested change to this scout’s own skill body, written for the scout’s owner to review and apply (or reject). Keyed improve:<skill-name>:<topic> — skill name, not domain, since scratchpad keys are team-wide and two scouts sharing a domain would clobber each other. The harness prompt invites these on custom scouts; canonical scouts never write them (applying one would diverge the seeded row). On the report channel, a suggestion that re-confirms across runs (or a single material failure) also gets escalated as an inbox report about the scout itself, with the report_id stashed in this entry as the pointer. The scout clears its own entry once a later run confirms the suggestion was addressed. |
reported: | Canonical scouts only: a record that a gap in the scout’s own canonical skill body was already fed back upstream to the PostHog team via agent-feedback (feedback_type: "scout"). Keyed reported:<skill-name>:<topic>, dates in the content, so future runs don’t re-submit a known gap without materially new evidence. Cleared once a later skill version fixes the gap. |
report: | A report this scout authored — stores the report_id, keyed report:<domain>:<entity>, so the next run edits/dedups against it instead of re-filing. See report-contract.md. |
reviewer: | A resolved owner (bare lowercase GitHub login), keyed reviewer:<domain>:<area>, so the next run sets suggested_reviewers without re-resolving. |
Format: <prefix>:<domain>:<entity> — e.g. pattern:error_tracking:baseline, noise:logs:rabbitmq-deploy-window, dedupe:csp_violations:a1b2c3d4.
The self-driving implementation run writes here too, under pattern:impl:<repository>:<area>, recording what it worked out about that repository while acting on a report.
Each canonical specialist has its own <domain> label (error_tracking, logs, llm_analytics, experiments, feature-flags, session-replay, web-analytics, pipelines, health, …) — not a closed set.
A new scout introduces its own domain label and reuses the prefixes; match the label a surface’s existing entries already use.
| Situation | Action |
|---|---|
| Confirmed, well-formed finding no existing report covers. | Author a report (emit_report). |
| Existing report covers it and there’s new evidence. | edit_report (append a note, or rewrite a report the scout authored). |
| Pattern observed but not yet defensible as a standalone report. | Scratchpad pattern: entry; keep investigating. |
| Investigated and ruled out; would waste a future run if rechecked. | Scratchpad noise: / addressed: entry. |
| Scratchpad or inbox already covers it; no change. | Skip; note in summary. |
| Issue currently quiet but worth re-checking later. | Rewrite the existing entry (same key) with a fresh timestamp + condition. |
Good entries are future-run actionable — the next scout reads them and changes behavior:
key: dedupe:error_tracking:019de34e-2026-05-01
content: "2026-05-01: surfaced UndefinedTable on access_control_propertyaccesscontrol
(issue 019de34e...) — 434 users hit it 11:31-13:22 UTC, then stopped. If a future
run sees this issue still firing, escalate; if quiet since 13:22, treat as
already-surfaced."Why it works: dated, names the entity id, gives a clear conditional (“still firing → escalate; quiet → skip”), bounded by a precise time anchor, and the key prefix makes it findable.
Bad entry: key note-1, content “we have errors today, FYI” — no actionability, no entity, no condition, uncategorized key the next run can’t find or act on.
Give your scout 2–3 worked example entries scoped to its surface so each run matches the format instead of inventing its own.
These are noise across essentially all PostHog projects — list the relevant ones in your scout’s Disqualifiers so it skips them unless there’s a real escalation:
service / properties.env is dev / local / test.
Filter before weighing.TimeoutExpired, sandbox sync failures, agentsh errors.
Internal harness operations, not user-facing.The team’s scratchpad extends this list per-project as the scout learns — which is exactly why the save-memory discipline matters.