fix(reminder): inject system-reminder via context hook, not tool_result#19
Closed
any-victor wants to merge 2 commits into
Closed
fix(reminder): inject system-reminder via context hook, not tool_result#19any-victor wants to merge 2 commits into
any-victor wants to merge 2 commits into
Conversation
The system-reminder injection used to append a <system-reminder> text
block onto unrelated tool_result content. That corrupted model-visible
transcript semantics for any tool that ran when the reminder was due
(read, bash, grep, find, ...) \u2014 the reminder appeared as if it were
part of the tool's actual output, making transcript debugging painful
and giving the model a false attribution.
tool_result is the wrong hook for policy/task-management reminders. The
right hook is context: it fires once before each LLM call (including
mid-agent-loop after tool results have been appended), and the returned
messages are used only for that one request, never persisted in the
session store and never appended to any tool's output.
Refactored cadence into src/reminder-cadence.ts as a small set of pure
functions so the decision logic is unit-testable without spinning up a
fake ExtensionAPI:
createCadenceState initial state
resetCadenceState wipe on session_switch
onTurnStart increment turn counter
evaluateToolResult decide if reminder should queue (mutates state)
drainReminderForContext one-shot drain when context fires
The extension default export wires these into the live hooks:
pi.on("tool_result", \u2026) cadence tracking only, never mutates content
pi.on("context", \u2026) injects the reminder as a transient user
message into the upcoming LLM call's
messages array, never persisted
Tests: 9 new cases in test/reminder-cadence.test.ts cover:
- reminder marks due after REMINDER_INTERVAL non-task turns when tasks exist
- never marks due when no tasks
- never marks due before interval elapses
- task tool usage resets cadence and clears any pending reminder
- no re-fire within the same injection cycle
- re-arms after task tool usage resets cycle
- drainReminderForContext is one-shot per cycle
- resetCadenceState wipes all four fields
Suite: 157 tests across 6 files, all passing.
Refs: documented reproduction + design discussion in
~/.pi/docs/engineering/tintin-packages-issues.md (Issue 2).
Build hygiene
-------------
Add pnpm-workspace.yaml with allowBuilds for @google/genai, koffi,
protobufjs so installs don't gate on interactive approval.
pe200012
added a commit
to pe200012/pi-tasks
that referenced
this pull request
May 16, 2026
Adapt the reminder cadence refactor to preserve PR tintinweb#15 auto-cleared task ID reminders and missing-task hardening.
Owner
|
manually merged see c04674b (master), thanks! 🙏 |
XertroV
added a commit
to clankercode/pi-tasks
that referenced
this pull request
Jun 20, 2026
Task IDs are stored as bare numbers; the "#" was purely a display prefix that GitHub auto-links to issues/PRs in any rendered text. Switch the human-facing format to "T123" and centralize format/parse in a new src/task-ref.ts (formatTaskRef + TASK_REF_RE), routing all previously inlined "#"+id sites through it. No storage/schema change. Genuine GitHub PR refs in CHANGELOG ((tintinweb#19) etc.) left untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #20.
Problem
The system-reminder injection currently appends a
<system-reminder>text block onto unrelatedtool_resultcontent:This corrupts model-visible transcript semantics for any tool that runs when the reminder is due (
read,bash,grep,find, …): the reminder appears as if it were part of the tool's actual output. Real consequences:<system-reminder>'inside'bashoutput even thoughbashnever wrote ittool_resultis appropriate for observing or rewriting that tool's own output; it isn't appropriate for injecting policy/task-management reminders unrelated to what the tool produced.Fix
Use two hooks:
pi.on("tool_result", …)for cadence tracking only — never mutates content.pi.on("context", …)to inject the transient reminder into the upcoming LLM call's messages.contextfires once before each LLM request (including mid-agent-loop after tool results have been appended), and the returned messages are used only for that one request — not persisted in the session store and not appended to any tool's output.The reminder is appended as a
role: "user"text message so models that don't support custom message types still receive it.What changes
src/reminder-cadence.ts(new): pure cadence logic factored out for unit-testability.createCadenceState,resetCadenceState,onTurnStartevaluateToolResult(state, toolName, hasTasks, config)drainReminderForContext(state)src/index.ts: hook wiring uses the helpers;tool_resultno longer mutates content; newcontexthandler injects when due.test/reminder-cadence.test.ts(new): 9 cases covering the decision logic.pnpm-workspace.yaml(new):allowBuildsfor@google/genai,koffi,protobufjs.Test plan
Cases pinned in the new test file:
REMINDER_INTERVALnon-task turns when tasks existdrainReminderForContextis one-shot per cycleresetCadenceStatewipes all four fieldsBehaviour preserved
REMINDER_INTERVAL = 4turns