fix(reminders): surface the autonomy hold instead of failing silently (#1806) - #1807
Merged
Conversation
…#1806) A reminder set on an agent with autonomy off is never armed: the scheduler's get_active_reminders filters `ao.autonomy_enabled = 1`, so no job is created and there is no skip to log. The API reported a bare `pending` next to a fire_at that quietly slid into the past — fire_attempts null, error null, no scheduler line, and no mention of autonomy in the reminders docs. The gate is deliberate (a paused agent must not wake itself) and is NOT changed. Only its visibility is. Nor is create rejected: chat is not autonomy-gated, so an agent can be mid-conversation when a user asks for a reminder, and queue-until-unpaused is the useful behavior — it just has to be stated. - models: `autonomy_hold` on ReminderSummary (inherited by Reminder), derived at read time from agent_ownership.autonomy_enabled. No column, so no dual-track migration. Defaults False so any construction path is safe. - routers/reminders: `_autonomy_hold()` helper applied to create, list and cancel responses. Terminal reminders are never flagged (and skip the lookup entirely). The list path resolves autonomy ONCE per page — every row shares one agent, so a per-row resolve would be a pointless N+1. Fail-open: a settings-read error reports no hold rather than inventing a warning. - scheduler: log a COUNT of held reminders once per reconcile pass, only when non-zero, best-effort. This is the only place that can report them, since they never enter the job store. - MCP set_reminder: an explicit `warning` sentence when held (not just the boolean — the caller is an LLM about to tell a user "done"), plus the dependency in the tool description. - docs: agent-reminders.md gains the autonomy dependency, mirroring what scheduling.md already documents for schedules. Related to #1806 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vybe
approved these changes
Jul 27, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
Validated via /validate-pr: derived autonomy_hold flag (read-time, no column — correctly avoids a dual-track migration), all three surfaces in sync (router+models, MCP tool with an explicit LLM-facing HELD warning, scheduler once-per-pass held count). db.get_autonomy_enabled verified present (db/agent_settings/autonomy.py:53). Gate behavior unchanged; fail-open on settings-read failure; terminal rows skip the lookup; list path avoids N+1. 7 named regression tests + user-docs update. Security scan clean. Added 'Fixes #1806' for status auto-promotion.
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.
What
Makes the autonomy hold on self-reminders visible. The gate is not changed — a paused agent still must not wake itself. Only the silence is fixed.
Why
get_active_remindersfiltersao.autonomy_enabled = 1, so a reminder on a paused agent is never armed. No job is created, so there is no skip to log — and the API reported a healthy-looking row:Clean A/B on a fresh 0.8.5 instance, two agents, identical 60s reminder set in the same second, only
autonomy_enableddiffering:Worse than the sibling schedules case (#1796) on every observability axis: schedules at least log
skipped: … autonomy is disabled, andscheduling.mddocuments the dependency. Reminders had neither.Why not reject at create
Considered and rejected. Chat is not autonomy-gated, so an agent can be mid-conversation with a user who says "remind me in an hour". A 409 would fail a legitimate request, and queue-until-unpaused is genuinely useful — the reminder does fire past-due once autonomy returns (verified: held reminder fired ~1 reconcile cycle after enabling). The problem was never the holding; it was not saying so.
Changes
models.pyautonomy_holdonReminderSummary(inherited byReminder), derived at read time — no column, no dual-track migration. DefaultsFalse.routers/reminders.py_autonomy_hold()applied to create, list, cancel. Terminal reminders never flagged and skip the lookup. List resolves autonomy once per page (all rows share one agent — a per-row resolve would be a pointless N+1). Fail-open on a read error.src/schedulerset_reminderwarningsentence when held — not just the boolean, since the caller is an LLM about to tell a user "done" — plus the dependency in the tool description (Invariant #13).docs/user-docs/automation/agent-reminders.mdscheduling.md.Verification
Unit —
tests/unit/test_1806_reminder_autonomy_hold.py, 11 cases, all passing: held forpending/firing, not held when autonomy is on, never flagged forfired/cancelled/failed(asserting the lookup is not even attempted), fail-open on a read error, precomputed autonomy short-circuits the per-row lookup, the shared response shaper carries the flag, and both models default toFalse.Live, patched into a running 0.8.5 instance (its backend source is bind-mounted):
Then toggling autonomy on, without touching the row:
The instance was restored to stock afterwards and verified clean.
MCP server compiles (
tscvia the image build, exit 0).Not verified
The scheduler log line is unit-and-inspection level only — the scheduler image is not bind-mounted on my test stack, so I did not rebuild it to watch the line appear. The query is the exact complement of
get_active_reminders' predicate and is wrapped best-effort.Follow-up worth its own issue
A reminder held for days fires immediately on unpause, and several would fire at once. "Remind me in an hour" arriving three days late may be worse than never arriving. Deliberately out of scope here — flagging it rather than widening this fix before the release.
Related to #1806
🤖 Generated with Claude Code
Fixes #1806