Shared runtime - One agent entry point (runtime.invoke_agent_for_user / prepare_agent_turn) for web, email, iMessage, WhatsApp, scheduled and cron, so identity, permissions, memories, summaries and metrics match across channels. - Serialize turns per checkpoint thread; /query validates input and returns 409 while a durable run owns the conversation. Reconnectable chat (durable runs) - POST /runs creates an idempotent, owner-scoped run; GET .../events replays an ordered SSE log by cursor, so generation survives refresh and device switches. Stop cancels; disconnect only detaches. /stream-query is a compatibility adapter. - Coalesce streamed deltas (150 ms) and wake same-process subscribers instead of polling; keep buffered text when a run is stopped. - Drain runs on shutdown; serve via Hypercorn with a shutdown timeout covering the drain (and no longer in debug mode). CHAT_RUN_* settings are forwarded. Scheduled messages and cron - Atomic delivery claims, attempt history and explicit retries; ambiguous sends become "uncertain" and are never resent automatically. - Cron triggers need a run_id/Idempotency-Key; legacy jobs need an explicit owner. - Cancelling never rewrites a started delivery; series stop via tombstones. Legacy sent rows are not forked into new series. Malformed bodies return 400. History, integrations and retrieval - Message-content search, pinned conversations, JSON/Markdown export and a memory editor. - Integration status panel and YNAB data-freshness provenance. - Date-filtered document/note search with stable citations and a source viewer. Migration 20 is generated by aerich. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.6 KiB
Scheduled and cron job delivery
Priority2 uses durable delivery claims and attempt history. Apply migration 20
(migrations/models/20_20261012231017_application_foundation.py) before running the
updated backend. It adds job_deliveries and job_attempts, unique delivery
identity (kind, job_id, run_id), claim tokens/timestamps, scheduled-message
series_id, unique previous_occurrence_id, deleted_at, and nullable cron
created_by_id. Migration 20 does not assign owners to existing cron jobs.
Downgrading drops delivery history and ownership/series fields.
Scheduled messages
The panel preserves creation, cancellation and deletion. It polls the message
list every ten seconds and exposes list/action errors, including permission or
claim conflicts. Delivery history is loaded on demand with authenticated
GET /api/scheduled-messages/<id>/deliveries. Refresh history to see later changes;
a retry refreshes both the list and any open history, even when it fails.
History includes each delivery's run ID, status/error, and every attempt's status,
error, start time and finish time. An unfinished attempt is explicitly labelled.
| Status | Meaning / action |
|---|---|
pending |
Waiting for its scheduled time; cancel or delete is available. |
processing |
A worker claimed the delivery; retry is unavailable. |
sending |
Attempt/history status: provider call is underway. |
sent |
Provider acknowledged delivery; retry is unavailable. |
failed |
Failure known to be safe to retry; explicit retry is available only when retryable: true. |
uncertain |
Delivery might already have happened. Reconcile with provider records or the recipient before considering any resend. |
cancelled |
Future work cancelled; delete is available. |
Retry uses authenticated POST /api/scheduled-messages/<id>/retry with no schedule
or status mutation. It executes the existing delivery identity and records a new
attempt. A retry can return HTTP 409 with a changed message status (including
uncertain); the UI surfaces the error and reloads the state. Editing a failed
message's scheduled time does not retry it. The frontend update service accepts
only cancelled as a status mutation and rejects pending at runtime, so updating
status cannot bypass the explicit retry endpoint.
Uncertain outcomes include ambiguous provider failures and abandoned claims. They are never automatically retried. This panel has no reconciliation or force resend action. Resolve the evidence through an operational reconciliation process; do not create a replacement message or new run ID simply to bypass uncertainty.
Recurring occurrences have separate rows and delivery identities, linked by
series_id and previous_occurrence_id. Cancelling a recurring occurrence cancels
the series' future work. The panel labels this action Cancel series, including
on completed or uncertain occurrences where future work must be stopped. Already
started provider calls may still complete; cancellation cannot recall a delivered
message, and attempt history remains evidence of the outcome.
Deleting a pending/cancelled recurring occurrence uses the backend's soft delete
and durable cancellation tombstone: the selected occurrence is hidden, and future
series work is cancelled so recurrence repair cannot revive it. Other cancelled
occurrences can remain visible. Nonrecurring pending/cancelled rows use the existing
delete API. The UI never simulates cancellation/deletion by altering local series
rows; it reloads the backend result.
Cron callers and ownership
POST /api/cron/<id>/trigger requires X-Cron-Secret and a nonblank run identifier,
maximum 255 characters. Supply either a JSON run_id or an Idempotency-Key header;
the header takes precedence. Choose a stable identifier for each logical execution
(for example the cron occurrence's UTC timestamp), and reuse it across repeated
trigger requests for that execution. A new logical occurrence needs a new ID.
Repeating a trigger with the same ID returns the existing outcome without resending;
it does not retry a failed or uncertain delivery.
POST /api/cron/<id>/trigger
X-Cron-Secret: <configured secret>
Idempotency-Key: daily-report:2026-10-13T12:00:00Z
Content-Type: application/json
{}
Legacy cron jobs require an explicitly assigned existing user. An administrator
must use authenticated PUT /api/cron/<id> with
{"created_by":"<existing-user-uuid>"} after reviewing the correct owner. Execution
fails safely until ownership is assigned; it never selects an arbitrary admin.
New jobs get the authenticated creator as owner. Agent execution uses that owner's
context and permissions. Assigning ownership does not itself retry an earlier
failed execution.
Authenticated GET /api/cron/<id>/deliveries returns the same history structure.
For a safe failed execution only, an administrator can use authenticated
POST /api/cron/<id>/retry with {"run_id":"<original-run-id>"}. Reuse the failed
run's ID; do not invent an ID to evade delivery claims. Processing, sending, sent,
cancelled and uncertain executions are ineligible. Uncertain cron deliveries need
the same reconciliation as scheduled messages. Database claims prevent duplicate
workers from winning the same delivery; they cannot prove exactly-once external
delivery when a provider acknowledgement is lost.
Frontend validation
No additional packages are required. Tests use Node's test runner, the installed
TypeScript transpiler and React's static renderer, following the existing renderer
harness in raggr-frontend/tests/history-management.test.cjs.
cd raggr-frontend
node --test tests/job-delivery*.cjs
./node_modules/.bin/tsc --noEmit --target ES2020 --lib DOM,ES2020