Skip to content

System reminders

System reminders are model-visible guidance injected into the request stream as <system-reminder> blocks. mevedel-reminders.el owns the reminder struct, firing policy, session and agent scoped reminder lists, the staging seam other modules deliver through, request-time injection, and the hidden injection record the view renders. The base system prompt teaches the model that these blocks are system context, not user text or tool output.

Contextual reminders remain a valid delivery mechanism; neither their count nor raw specialist-tool usage establishes whether guidance improved a task.

Keep reminder bodies specific to the firing state. Stable task and tool policy belongs in its prompt component or tool contract, not another recurring copy. Plan guidance includes authority limits and the proposal skeleton on its first delivery, then references the retained contract and the recoverable Plan manual. The task nudge supplies stale task rows and ownership recovery, leaving TaskWrite field/status syntax to that tool's contract.

Delivery and history

Deliver reminders only when their firing policy establishes relevance. Once actually delivered, retain their complete content at that point in conversation history until compaction removes the corresponding prefix. Later state changes arrive as new context; old state descriptions do not override current settings.

Retention consumes historical context and local storage; sparse firing, deduplication, and compaction control that cost. The rationale and prior delivery behavior are recorded in ADR 0115.

Staged entries and turn events share this delivery contract. Unsent events remain owner-bound and transient; cancellation must not manufacture delivered history. Position-bound text such as Read truncation notices, the /btw interruption boundary and compaction summaries remains ordinary durable transcript content.

Reminder flow

flowchart TD
    A[Session, agent, or runtime event] --> B[Evaluate reminder policy]
    B --> C{Should fire now?}
    C -- No --> D[Keep or throttle reminder]
    C -- Yes --> E[Stage typed entry or queue turn event]
    E --> F[Coalesce at the next WAIT]
    F --> G[Inject one synthetic user-role message]
    G --> H[Write hidden injection record into the transcript]

Staged reminders travel as typed entries (:type SYM :body STRING); wrapping into <system-reminder> blocks happens once at injection. At every WAIT, the synthetic message appends after the actual user message or complete tool results, matching the durable record's transcript position. This never splits a tool call from its result. Observations produced while tools run are buffer-local and bound to the active root request or retained-agent invocation. Repeated observations with the same key coalesce and are injected at the next WAIT owned by that same turn. They are discarded when the owner changes and are never carried into the next user turn. The compaction provider-dispatch wrapper is the final injection seam: continuation auto-compaction may rebuild the realized payload first, then reminders are injected, recorded, and committed against the payload that is actually dispatched.

Staging seams

  • mevedel-reminders-stage-entry appends one typed entry (plus an optional deferred commit) to the request's fsm info. Callable from any prompt transform and from WAIT-time handlers that run before the final provider dispatch: mentions (depth -90) and skills-input (-89) stage before the reminders transform (-80) collects session reminders; auto-compaction (-70) and the steering WAIT handler stage after.
  • mevedel-reminders-stage-commit defers a commit thunk alone, for consumable state whose payload rides the prompt text or the staged entries of the same request.
  • mevedel-reminders-queue-turn-event queues an owner-bound, key-coalesced observation for the current turn, delivered at the next WAIT after the tool results.
  • mevedel-session-enqueue-pending-reminder appends to the session's transient pending FIFO, consumed by the pending-events reminder on the next request. Survives a request boundary but not a restart.

Commits

Consuming a reminder is a separate step from staging it. A content function returns either its body or a :body/:commit plist. A nil :body stages only the commit, for a silent acknowledgement of context already in the prompt. Pending-event consumption, observed dates, external-change snapshots, turn events, mention deduplication, and reminder firing marks commit only after the final provider-bound payload exists at WAIT. A request that fails before injection keeps that state eligible for a later attempt.

Hook context has two entry paths. Composer preparation consumes accepted hook context when its user turn is committed to the transcript; a later dispatch failure cannot redeliver text already stored there. Context still pending when the prompt transform runs is reserved into that request's prompt text. Its WAIT commit clears the reservation, and request teardown returns an uncommitted reservation to the pending list. See hook lifecycle integration.

A trigger that mutates state has no commit channel, so a reminder whose trigger consumes still reports once per attempt rather than once per delivery.

The Claude engine collects configured reminders once at prompt submission, using the same session or invocation context and firing policy as gptel. Pending root hook context joins that delivery. Mid-turn turn events, changed selected observations, new direct children and mail ride native tool-batch hooks, and native compaction restores current guidance. Exact SDK receipt, not submission, commits firing marks, including silent commits, and consumes only captured pending events and hook context; later arrivals stay pending. Receipt boundaries, the hook size limit and continuation prompts are described in native context delivery. Local transcript estimates do not produce context-pressure reminders for externally owned model history.

Reminder eligibility is evaluated from current session state; delivered observations remain historical context rather than current authority. Root and retained-agent queues remain isolated.

The injection record

Every delivery writes one trusted hidden record (:type injected-reminders, phase turn-start or mid-turn) containing each entry's type and complete body: a gptel injection when its payload is realized, a Claude delivery only on exact SDK receipt. Bodies are not truncated: the record supplies reconstruction as well as inspection. Missing insertion markers or recording failures prevent committing delivery; injection failures retain pending state. A Claude record states delivered guidance, not that a gptel payload exists; mail delivered in the same batch follows it as ordinary mailbox blocks. The Claude wire correlation marker (<!-- mevedel-delivery:... -->) never enters the transcript, so the view, excerpts and engine transfer never see it.

The encoded record stays out of provider text. mevedel-history.el decodes it as a separate message during prompt preparation, including fresh-process restore. Compaction evidence includes its instruction bodies, and local token estimates count the decoded guidance instead of encoded metadata. The record continues to be excluded from collaboration projection and persists through session segments. Compaction can summarize or retire obsolete guidance.

The view renders it as one grouped collapsed row -- ◇ N system reminders (labels…) -- above the user turn for turn-start injections and inline in the assistant turn for mid-turn injections, expanding to per-entry bodies. Inline <system-reminder> text (fork disclosures and the btw boundary) keeps the single-block ◇ System reminder (N lines) row.

Agent requests

Agent invocations carry their own reminder roster (configured reminders and max-turns warning), cloned at spawn. mevedel-reminders--agent-transform runs in every agent request's transform list and collects that roster with the invocation as firing context; delivery, commits, and the injection record ride the shared WAIT injector. The max-turns warning is instead evaluated at every agent WAIT, where each model sample is counted, so it fires inside long tool loops (Agents). Turn events queue against the invocation as owner exactly as on the root path.

Claude children use the same warning and final-sample producers at native sample boundaries; see Agents for their sample accounting.

Implemented reminders

Session state and mode guidance (evaluated from current state)

  • Plan-mode workflow: the first delivered plan-mode reminder describes read-only project inspection, unavailable Eval, session-only ApplyPatch in standalone/sticky Plan, and no writes in directive Planning. It retains exploration-first behavior, replacement semantics, exact proposal tags, and the preferred proposal shape. Subsequent turns, including continuation after compaction, use a one-line reference with exact tags and a recovery pointer to Proposal guidance. A new sticky Plan episode or directive planning conversation rearms the template; feedback does not.
  • Mode constraints: one advisory on initial delivery and each effective permission-mode change, including request overrides and returning to Ask. Unchanged modes never repeat it. Full Access, default protected paths, unconstrained live Eval, remaining hard denies/Plan/integrity checks, and deliberate destructive calls are disclosed together. There are no separate full-auto entry/exit reminders.
  • Fork provenance: the sparse (interval 20) fork-provenance reminder regenerates a fork's provenance from durable session slots via mevedel-session-fork-provenance-body: source session and, for worktree forks, worktree directory, branch, and base commit. The one-time worktree restore report (restored count, unrestored files, external shared paths, dropped state) is enqueued on the pending FIFO at fork time and delivered on the child's first request; the FIFO is transient, so a restart before that request drops the report detail while the provenance reminder keeps the durable facts.
  • Plan-file reference: the one-shot plan-reference reminder surfaces bounded contents of the approved plan on later turns when it may still be relevant. Main-session compaction resets its fired mark (mevedel-reminders-rearm-plan-reference), because the summarized prefix may have carried both its earlier delivery and the implementation prompt's full plan text; the reference then re-fires once with the plan address. The trigger suppresses it when an active Goal carries that exact accepted-plan reference, because the Goal's retained current-context delivery supplies the binding plan address. A Goal with no plan or a different plan does not suppress it. Standalone Plan Direct handoff does not use this reminder. Native Claude compaction restores an eligible reference immediately through the same producer; it does not wait for the next user prompt.
  • Recent-edit verification: verification-suggestion requires at least two recently modified implementation files in the latest committed or active turn, and fires at most once every ten turns, never twice in a delivered turn. Documentation, isolated edits, generated/dependency/build paths, reads and old edits do not qualify. The conservative path/extension heuristic intentionally misses large single-file changes; the model can still choose verification. Collection performs no file reads or new mutation accounting. When an accepted plan's verification is pending, the suggestion also asks for evidence that the plan was executed. Spawning a verifier clears that plan flag. The generic suggestion remains applicable to recent edits.

Read-only role and report contracts live in the frozen agent prompt, restored for every request even when the transcript is compacted. They are not repeated as reminders. Explicitly configured role reminders retain their own lifetimes.

Specialist tools appear in the retained tool listing with their calling shapes, and ToolSearch supplies full contracts by capability or name. The listing is a context observation that updates only when it changes, not a per-turn reminder. There is no generic ToolSearch/ToolCall availability reminder. Ordinary Read/Grep calls and open editor buffers do not trigger navigation workflow advice; a registered tool alone does not establish that its backend works for a particular file.

Changing prompt observations

Named workspace guidance, environment/date, memory indexes, compact skill catalogs, resource availability and active Goal facts are separate from the stable system prefix. Each changed section delivers its complete current contents with an explicit notice that other previously supplied state remains applicable. Unchanged sections remain in retained history. Delivery is checked against actual selected history; absent observations are redelivered after compaction, filtering or restore. If all selected sections are absent, all are delivered again. Explicitly configured custom date reminders remain available. See retained instruction context.

Runtime status and event reminders

  • Task nudge: when open session work has not been written for the stale interval (eight turns by default), the root receives TaskList-shaped rows with full owner paths. The reminder distinguishes retained children's tasks from root's own work and asks root to reconcile their RESULT or follow up before changing status. It fires at most once per stale interval, including when multiple requests collect reminders at the same turn count. Successful child settlement closes that child's open work; failed/interrupted turns leave it open for recovery.
  • Goal budget: every Goal charge (root settlement or agent progress) reports each newly crossed 50%, 80%, or 100% threshold once, as a turn event for a still-running root turn or a pending reminder otherwise; budget changes queue one event with old and new limits. At each root tool-result boundary the pipeline also checks known in-flight usage and queues newly crossed thresholds as turn events delivered at the same WAIT; the fsm guard suppresses the settlement duplicate. Agents charged to the Goal receive a notice of the highest threshold reached at their next WAIT (Goals).
  • Mention expansions: @ref/@file/@mcp/@agent contents and rejection notices are staged entries (typed by mention key). Deduplication commits only once the payload exists, so a cancelled request never marks content as shown.
  • Skill attachments: inline user $skill bodies and recursively required authored !$skill bodies reuse staged entries of type skill-attachment. Required contributions are flattened dependency first into the same pending collection; no new reminder type or late hidden reminder path is used. The corresponding attachment placeholder stays in the prompt or parent body.
  • Compact file-reference: manual compaction enqueues pending-FIFO reminders for file references whose contents were not retained; auto compaction stages a compact-file-references entry on the in-flight fsm instead, delivered at its next WAIT. Native Claude compaction has no gptel request to stage on and enqueues the pending-FIFO reminder.
  • Path-scoped workspace instructions: a successful Read below the session working directory queues changed AGENTS.md and AGENTS.local.md files as turn events, ordered broad to narrow and deduplicated by owner, path, and content. Delivery is acknowledged only at payload injection or exact native SDK receipt, so cancellation leaves the guidance eligible for retry. Fresh directive requests keep their own hashes; root history cannot suppress their guidance. Local compaction, rewind, and cold resume reset the relevant local-history delivery acknowledgements. Native conversations keep their learned scopes across reopening; prompts re-send only changed instructions, and native compaction restores all of them (native context delivery). This post-read discovery helps subsequent model decisions: it is not an edit gate and cannot affect another tool already scheduled in the same batch. The shared task policy therefore still requires inspecting applicable project guidance before changing code. Resource reads such as session working-file work:// addresses do not discover workspace policy.
  • Recovery reconciliation: cold resume and abort of a live root request queue one warning that processes or tool effects may be partial.
  • User-revised patch: the one-shot user-revised-patch reminder repeats the applied-content-is-authoritative directive on the turn after a user-edited ApplyPatch review.
  • Compaction availability, token usage, agent listing delta, path-scoped skill activation, max-turns warning, edited files: state snapshots and deltas, each regenerated from session or invocation state.
  • Hook outcome: hooks record blocking outcomes through mevedel-hooks-record-session-reminder as turn events; additional hook context still rides the prompt text as <hook-context>.

What the edited-file reminder watches

The edited-file reminder reports every file in the workspace file cache whose content moved outside mevedel's tools. Two boundaries keep that from turning into per-turn noise and per-turn cost.

mevedel's own session bookkeeping never enters the cache. A session directory holds append-only logs, transcript segments, and sidecars that mevedel rewrites every turn, so caching one would report mevedel's own writes back as external edits for the rest of the session, and let a multi-megabyte telemetry log evict every cache entry describing real work. Sibling sessions under the same sessions root are excluded too — reading a second live session's files churns identically — each confirmed by its sidecar rather than assumed from its location. The workspace artifact store lies outside session directories and stays watched: its files are authored deliverables, and an outside edit to one is worth reporting. The interaction record is still written for an excluded path.

Content is bounded twice. A file the filesystem reports as larger than mevedel-file-cache-max-file-bytes is cached as a fingerprint — timestamps and reported size, no content — so it is still seen to move but claims none of the mevedel-file-cache-max-bytes budget. Content past mevedel-reminders-edited-file-max-diff-bytes is not diffed at all: mevedel-generate-diff spools both sides to temporary files and forks diff, which is not worth paying every turn for the mevedel-reminders-edited-file-max-diff-lines lines that survive. Both cases report the change and its size and tell the model to re-read the file.

The diffs are prepared before the request is dispatched: the provider-wait handler detects the changes, runs one diff helper per changed file, and stages the reminder from those changes once the diffs return. The dispatch does not block while it waits. A cancelled request stops a pending preparation and ends the turn. A diff that fails leaves its file reported without one, as does any evaluation of the reminder outside a prepared dispatch.

Diff spooling explicitly uses UTF-8, encoding decoded text while preserving literal bytes from the file cache. It never asks the user to choose a coding system during prompt preparation, even when cached files contain non-ASCII text.

PDF and large-attachment guidance

Large PDFs read without a pages selector receive an appended <system-reminder> telling the model to prefer bounded Read(..., pages="START-END") requests (a Read result rider, thus positional). Large PDFs attached through @file get the same guidance as part of the mention's staged entry.

Edit diagnostics

The edit-diagnostics state machine is its own owner, mevedel-edit-diagnostics.el: the patch tool drives it, and the reminder module only delivers what it queues through the generic turn-event channel. Diagnostics are observed only after a successful ApplyPatch. Before the first edit of a visited file in a request, mevedel captures that file's current Flymake and Flycheck diagnostics as its baseline. After the edit, an unmodified stale buffer is safely reverted, active checkers are started, and the tool callback waits on Flymake report callbacks and Flycheck's completion hook, with a fixed 30-second timeout. A Flycheck buffer with no selected checker is treated as immediately ready and never starts that timeout. Modified stale buffers are never reverted, and rejected or failed edits produce no diagnostic observation.

The first fresh result is compared with the baseline: new or changed diagnostics are completion work, while pre-existing diagnostics are context only unless they block the requested work. Later edits compare with the last fresh result and do not repeat the pre-existing category. Resolved diagnostics are telemetry only. Model-visible output prioritizes new diagnostics, sorts by severity, caps output at 10 diagnostics per file and 30 total, and reports one aggregate omitted count. Telemetry records counts and outcomes, never diagnostic text or file paths.

Default session reminders are installed idempotently through mevedel-reminders-install-defaults. Lifecycle events use the session pending-reminder FIFO and pending-events; observations use the owner-bound turn queue.