Newer
Older
navi-1 / docs / recall.md
@Eugene Sukhodolskiy Eugene Sukhodolskiy on 15 May 7 KB docs: document scheduled recall system

Scheduled Recalls

Self-recall (scheduled callback) system — allows the agent to schedule future headless work that runs without user interaction.

Overview

A recall is a delayed or recurring task that the agent schedules for itself. When the trigger time arrives, the agent wakes up, reads its self-instruction, and continues working using other tools. The user can watch the execution live via WebSocket streaming.

Use cases:

  • Check build logs in 30 minutes
  • Poll an API every 5 minutes
  • Offload heavy multi-tool work without blocking the chat
  • Continue work after a server restart

Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  schedule_recall │────▶│  PostgreSQL     │────▶│  recall loop   │
│  manage_recall   │     │  session_recalls │     │  (poll + fire) │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                                        │
                                                        ▼
                                               ┌─────────────────┐
                                               │  Agent.run_stream │
                                               │  (headless)      │
                                               └─────────────────┘
                                                        │
                                                        ▼
                                               ┌─────────────────┐
                                               │  WebSocket       │
                                               │  streaming to UI  │
                                               └─────────────────┘

Data model

Table: session_recalls

Column Type Description
id TEXT PK UUID
session_id TEXT FK References sessions(id) ON DELETE CASCADE
call_type TEXT once, recurring, immediate
trigger_at TIMESTAMPTZ When to fire (UTC)
interval_seconds INTEGER Repeat interval (required for recurring)
internal_comment TEXT Human-readable note
additional_context_message TEXT Self-instruction injected as system message
status TEXT pending, fired, cancelled
created_at TIMESTAMPTZ Creation time
updated_at TIMESTAMPTZ Last mutation

Constraints:

  • Only one pending recall per session (unique partial index on session_id WHERE status = 'pending').
  • immediate recalls fire ASAP — trigger_at is set to now().

Tools

schedule_recall

Parameters:

Param Type Required Description
call_type string yes once, recurring, immediate
when string for once/recurring Trigger time. Relative formats preferred: 30m, 2h 15m, in 3 hours, tomorrow at 09:00. ISO 8601 only if timezone is known.
timezone_offset string optional ±HH:MM — required for absolute times if user's timezone is known
interval_seconds integer for recurring Repeat interval in seconds
internal_comment string optional Human-readable note
additional_context_message string yes Self-instruction — write it as a todo for yourself

Rules for additional_context_message:

  • Must mention specific tools, files, or URLs
  • Must read like a todo item, not a chat reminder
  • BAD: "Tell the user that 2 hours have passed"
  • GOOD: "Read /tmp/build.log with filesystem. If errors, read last 50 lines and report."

manage_recall

Parameters:

Param Type Required Description
action string yes cancel, skip, list
session_id string optional Target session (defaults to current)

Actions:

  • cancel — deletes the pending recall. Standard pattern before scheduling a new one.
  • skip — advances a recurring recall by interval_seconds. Uses GREATEST(trigger_at, now) to avoid scheduling in the past.
  • list — shows all recalls (pending/fired/cancelled) for the session.

Scheduler loop

navi/core/scheduler.py::recall_scheduler_loop() — background task that:

  1. Polls for pending recalls with trigger_at <= now()
  2. Acquires a semaphore (max 3 concurrent headless runs)
  3. For each due recall, calls _fire_recall()
  4. Sleeps until the next pending recall (or 60 s if queue is empty)

_fire_recall behavior

  1. Defer if busy — if a websocket run is active for this session (_runs), reschedule +60 s
  2. Load session — if session deleted, mark recall cancelled
  3. Set user contextcurrent_user_id, current_user_role, current_user_info so tools work correctly
  4. Block user messages — add session to _busy_sessions (dict of session_id → asyncio.Event)
  5. Register _AgentRun — so reconnecting clients can replay events
  6. Send stream_start — clients begin showing the streaming message
  7. Run agent.run_stream(is_recall=True) — stream all events to EventBus + WebSocket + replay buffer
  8. On success: mark fired (once) or reschedule (recurring), publish RecallUpdate
  9. On MaxIterationsReached: treated as success, not failure
  10. On exception: mark cancelled (once) or reschedule (recurring)
  11. Finally: remove from _busy_sessions and _runs, send session_sync

Stopping a recall

POST /sessions/{id}/stop signals the recall's stop_event (same mechanism as stopping a normal run).

WebSocket integration

During a headless recall run, the client receives the same events as a normal chat:

stream_start
thinking_delta
tool_started
tool_call
stream_delta
stream_end
session_sync

Plus out-of-run recall_update events whenever recall state changes.

UI behavior

  • Banner — shown in chat header when chatStore.recall is set. Displays trigger time + Cancel/Skip buttons.
  • Recall messages — user messages with is_recall=true styled differently (light text, clock badge).
  • Sidebar — sessions with pending recall show a clock icon. Filter button in header toggles "pending recalls only".
  • Real-time updates — banner and sidebar update live via recall_update WebSocket events.

API endpoints

See api.md for full schemas:

  • GET /sessions/{id}/recall — get pending recall
  • DELETE /sessions/{id}/recall — cancel
  • POST /sessions/{id}/recall/skip — skip next recurring occurrence

Files

File Role
navi/core/scheduler.py RecallScheduler, DDL, loop, _fire_recall
navi/tools/schedule_recall.py ScheduleRecallTool
navi/tools/manage_recall.py ManageRecallTool
navi/tools/_internal/time_parser.py parse_when() — natural language time parser
navi/api/routes/sessions.py REST endpoints for recall CRUD
navi/api/websocket.py _busy_sessions, _notify_session, _on_recall_update
navi/core/events.py RecallUpdate event class
navi/llm/base.py Message.is_recall flag