Files
simbarag/docs/job-delivery.md
ryan 5809ad3adb
CI / backend (pull_request) Successful in 42s
CI / frontend (pull_request) Failing after 7s
CI / backend (push) Successful in 41s
CI / frontend (push) Failing after 7s
Add durable chat runs, shared agent runtime, reliable jobs, history tools and citations
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>
2026-10-12 23:11:04 -04:00

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