Skip to content

feat: add change cursors for messages, tasks, actions, events and voice calls - #242

Merged
eandualem merged 2 commits into
developfrom
feat/tracer-change-cursors
Oct 6, 2026
Merged

eandualem merged 2 commits into
developfrom
feat/tracer-change-cursors

Conversation

@eandualem

@eandualem eandualem commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #240. A host that reconciles the runtime's records into its own index needs to read what changed since its last pass. Message rows are updated in place after creation, for example when tool results are recorded later or a host result is accepted on an existing assistant row. Tasks, agent messages, actions and events change status afterwards. No table recorded when a row last changed, so an id cursor missed those changes.

Changes

  • Migration 0039 adds columns and indexes only.
    • updated_at timestamptz NOT NULL DEFAULT now() and an (updated_at, id) index on messages, tasks, agent_messages, actions, events and voice_calls. Messages also get (session_id, updated_at, id). voice_calls also gets created_at.
    • Existing rows are backfilled from the latest time they already record. Voice calls use the snapshot's own times.
    • Every write sets updated_at. That includes Core updates and upserts, and a confirmation's write also updates its parent action.
  • Change cursors: <ISO 8601 updated_at>|<id>, or a bare time meaning strictly after it. Records are returned in (updated_at, id) ascending order. A malformed cursor returns 422.
  • GET /api/sessions/{id}/messages/changes?after=<cursor>&limit=N returns {messages, next_cursor, active_leaf_id}. The messages are tree rows with parent_id and updated_at, and the client rebuilds the display path locally.
  • updated_after=<cursor> on GET /api/tasks, /api/agents/{id}/messages, /api/actions and /api/events. Combining it with before, or with after on events, returns 422. Records carry updated_at. Actions and events add next_cursor only when updated_after is given; tasks and agent messages stay bare lists.
  • GET /api/voice/calls?updated_after=<cursor>&limit=N returns {calls, next_cursor}, with the same access rule as GET /calls/{id}. /api/counts now includes voice_calls.
  • On startup, stored voice calls that never finished get a new updated_at, so readers see them reported as interrupted. Their snapshots are left unchanged.
  • Docs: docs/api.md (a new "Change cursors" section, including the overlap note for transaction-time stamps), docs/persistence.md, docs/voice.md.

When the new parameters are absent, existing responses are unchanged, apart from the additive updated_at field.

Not covered: the agents table has no updated_at, and delivered steering is not in the message change feed (it is still in /messages and the single-message route).

Verification

  • make check: 3008 passed, 1 skipped; ruff and format clean.
  • Postgres, on a scratch database: migrated to 0038, inserted sample rows, upgraded to 0039, and checked each backfill. Downgrade and re-upgrade worked, tests/database passed, and a smoke script checked that every update moves updated_at and that cursor filtering and ordering work.
  • Independent review: Codex gpt-6-astra, high effort, separate read-only context, 2 rounds.
    • Round 1: one valid P2. A call interrupted by a restart never moved past a voice change cursor. Fixed in 93647f7, with tests.
    • Round 2: no findings.

Part of #83

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added change-based pagination for messages, tasks, agent messages, actions, events, and voice calls. Results are ordered oldest change first and include cursors for fetching subsequent pages.
    • Added a session message-changes endpoint, with message update timestamps in responses.
    • Voice-call listings now support cursor pagination, and call counts are included in count responses.
  • Documentation
    • Documented cursor formats, ordering, pagination behavior, and updated timestamp handling.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ef6433cb-3ab9-4772-912b-64ae68d56809
📥 Commits

Reviewing files that changed from the base of the PR and between c56a625 and ffc6d51.

📒 Files selected for processing (46)
  • alembic/versions/20261006_000000_change_cursors.py
  • docs/api.md
  • docs/persistence.md
  • docs/voice.md
  • src/assistant_runtime/app/assistant/_session_persistence.py
  • src/assistant_runtime/app/assistant/_session_store.py
  • src/assistant_runtime/app/event_log/_store.py
  • src/assistant_runtime/app/event_log/interface.py
  • src/assistant_runtime/app/event_log/models.py
  • src/assistant_runtime/app/routes/_cursors.py
  • src/assistant_runtime/app/routes/actions.py
  • src/assistant_runtime/app/routes/agents.py
  • src/assistant_runtime/app/routes/counts.py
  • src/assistant_runtime/app/routes/events.py
  • src/assistant_runtime/app/routes/sessions.py
  • src/assistant_runtime/app/routes/tasks.py
  • src/assistant_runtime/app/routes/voice.py
  • src/assistant_runtime/app/tasks/_store.py
  • src/assistant_runtime/app/tasks/interface.py
  • src/assistant_runtime/app/tasks/models.py
  • src/assistant_runtime/app/voice/_persistence.py
  • src/assistant_runtime/app/voice/_state.py
  • src/assistant_runtime/app/voice/interface.py
  • src/assistant_runtime/base/cursors.py
  • src/assistant_runtime/services/actions/_store.py
  • src/assistant_runtime/services/actions/interface.py
  • src/assistant_runtime/services/actions/models.py
  • src/assistant_runtime/services/database/models.py
  • src/assistant_runtime/services/database/repositories/_actions.py
  • src/assistant_runtime/services/database/repositories/_events.py
  • src/assistant_runtime/services/database/repositories/_ordering.py
  • src/assistant_runtime/services/database/repositories/_sessions.py
  • src/assistant_runtime/services/database/repositories/_tasks.py
  • tests/unit/app/assistant/test_session_store.py
  • tests/unit/app/routes/test_actions.py
  • tests/unit/app/routes/test_agents.py
  • tests/unit/app/routes/test_counts.py
  • tests/unit/app/routes/test_events.py
  • tests/unit/app/routes/test_sessions.py
  • tests/unit/app/routes/test_tasks.py
  • tests/unit/app/tasks/test_interface.py
  • tests/unit/app/voice/test_transport.py
  • tests/unit/app/voice/test_voice.py
  • tests/unit/base/test_cursors.py
  • tests/unit/services/database/test_models.py
  • tests/unit/services/database/test_repositories.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds updated_at tracking and cursor-based listing for messages, tasks, agent messages, actions, events, and voice calls. It adds session message-change and voice-call list routes, includes voice calls in time-range counts, and documents the cursor APIs and persistence behavior.

Changes

Change Cursor Feeds

Layer / File(s) Summary
Timestamp and cursor foundation
alembic/versions/20261006_000000_change_cursors.py, src/assistant_runtime/base/cursors.py, src/assistant_runtime/services/database/models.py, src/assistant_runtime/services/database/repositories/_ordering.py, src/assistant_runtime/app/routes/_cursors.py, docs/api.md, docs/persistence.md, tests/unit/base/*, tests/unit/services/database/*
Migration 0039 adds and backfills change timestamps and indexes. Cursor utilities parse and compare timestamps and IDs. Database ordering helpers and route validators support cursor queries.
Session message changes
src/assistant_runtime/app/assistant/_session_persistence.py, src/assistant_runtime/app/assistant/_session_store.py, src/assistant_runtime/services/database/repositories/_sessions.py, src/assistant_runtime/app/routes/sessions.py, docs/api.md, tests/unit/app/assistant/test_session_store.py, tests/unit/app/routes/test_sessions.py
Session messages carry change timestamps and can be listed by cursor. The new route returns message rows, the active leaf ID, and a continuation cursor.
Task, event, and action change feeds
src/assistant_runtime/app/tasks/*, src/assistant_runtime/app/event_log/*, src/assistant_runtime/services/actions/*, src/assistant_runtime/services/database/repositories/_tasks.py, src/assistant_runtime/services/database/repositories/_events.py, src/assistant_runtime/services/database/repositories/_actions.py, src/assistant_runtime/app/routes/{tasks,agents,events,actions}.py, docs/api.md, tests/unit/app/routes/{tasks,agents,events,actions}.py, tests/unit/app/tasks/test_interface.py
Task, agent-message, event, and action listing supports updated_after cursors. Writes update timestamps, and cursor-based results use oldest-change-first ordering. Existing paging modes remain separate from cursor mode.
Voice call listing and counts
src/assistant_runtime/app/voice/*, src/assistant_runtime/app/routes/{voice,counts}.py, docs/voice.md, docs/api.md, docs/persistence.md, tests/unit/app/voice/*, tests/unit/app/routes/test_counts.py
Voice calls have persisted creation and change timestamps, cursor-based listing, restart timestamp updates, and range counts. /api/counts now includes voice_calls.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionChangesRoute
  participant SessionStore
  participant MessageRepository
  Client->>SessionChangesRoute: Request message changes after cursor
  SessionChangesRoute->>SessionStore: Fetch changes for session and limit
  SessionStore->>MessageRepository: Query rows in cursor order
  MessageRepository-->>SessionStore: Return changed message rows
  SessionStore-->>SessionChangesRoute: Return message records
  SessionChangesRoute-->>Client: Return rows, active leaf, and next cursor
Loading

Merge Risk: ⚪ Minimal · up to ffc6d

The change feeds appear ready to merge after normal checks; no unresolved issue is established.

Security Architecture Review

Security architecture risk: 🔵 Low · up to ffc6d

The inspected read paths retain authentication, ownership checks, and administrator restrictions. No introduced authorization bypass was established. Synchronization remains best-effort: transaction timestamps do not guarantee commit-order delivery, and voice restart marking can fail without blocking startup. Production upgrade and failure-recovery behavior remain only partially established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed — The new bulk-read capability spans conversation-tree rows and voice snapshots within authorized caller scope. Administrators can additionally read installation-wide action and event feeds and voice-call counts; ordinary task and agent-message reads remain creator-scoped.

Trust Boundaries and Controls

  • observed — The session changes endpoint authenticates the request principal and checks session access before retrieval. Cached and hydrated contexts use the same owner/admin predicate, and persisted message queries remain constrained by session ID.
  • observed — Task and agent-message change queries retain creator and agent filters rather than treating a supplied cursor as authority. The agent-message route also authorizes access to the named agent before listing.

Resilience and Maintainability Implications

  • observed — Voice finalization cancels and awaits checkpoint work before writing the terminal snapshot and releasing the session reservation. Startup newly advances unfinished persisted calls' timestamps, but marking is best-effort and leaves snapshots unchanged; failure therefore does not establish that a cursor consumer will observe restart interruption. Optional checkpoint failure handling predates this PR.

Hardening Proposals

  • proposed — If a downstream mirror becomes authoritative for permissions or action execution, require durable replay or bounded, verifiable reconciliation before relying on these feeds. Timestamp overlap and best-effort restart marking alone do not supply that stronger guarantee.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.35% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 201 functions across 43 files. (3 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding change cursors for the listed record types.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 30.35% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 201 functions across 43 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
🧰 Additional context used
📚 Code guidelines (4)
AGENTS.md — auto-discovered
docs/persistence.md — auto-discovered
docs/voice.md — auto-discovered
docs/api.md — auto-discovered

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

eandualem and others added 2 commits October 6, 2026 01:58
…ce calls

A host reconciling the runtime's records needs to read what changed since
its last pass. Message rows are updated in place after creation (tool
results recorded later, accepted host results), and tasks, agent
messages, actions and events change status afterwards. No table recorded
when a row last changed, so an id cursor missed those changes.

Migration 0039 adds updated_at, set by every write, and an (updated_at, id)
index to messages, tasks, agent_messages, actions, events and voice_calls
(voice calls also get created_at). Existing rows are backfilled from the
latest time they already record.

- GET /api/sessions/{id}/messages/changes?after=<cursor>: changed tree
  rows, the next cursor, and the active leaf
- updated_after=<cursor> on tasks, agent messages, actions and events,
  in (updated_at, id) order
- GET /api/voice/calls?updated_after=, and voice calls in /api/counts
- a confirmation's change moves its action past the cursor

A cursor is "<ISO updated_at>|<id>" or a bare time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stored call the restarted process does not hold is reported as
interrupted, but its updated_at never changed. So a reader that had seen
it active never saw it end. Startup now gives unfinished stored calls a
new updated_at, best effort, and leaves their snapshots as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eandualem
eandualem force-pushed the feat/tracer-change-cursors branch from 93647f7 to ffc6d51 Compare October 5, 2026 22:58
@eandualem

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@eandualem
eandualem merged commit a561b49 into develop Oct 6, 2026
6 checks passed
@eandualem
eandualem deleted the feat/tracer-change-cursors branch October 6, 2026 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant