Tasks & reminders

Satia includes a deferred task and proactive execution engine powered by the agent_tasks table and real-time Redis queues. It allows Sati, Transactional Agents, Python Automations, and MCP Clients to schedule future follow-ups, delegate work across agents, resolve human escalation tickets, and initiate proactive conversations.


1. Decision Matrix: Which Tool to Use?

ToolTarget RecipientExecution TimingPurpose & Channel Rules
tasks__schedule_reminderSelf (Sati or the calling agent)Future (scheduled_at)Schedules a future follow-up or verification for yourself (e.g., "Remind me tomorrow at 9 AM to review pending invoices").
tasks__delegate_to_agentTransactional Agent (by name or UUID)Immediate (default) or Future (scheduled_at)Assigns a task or instruction to a transactional agent so it executes with its own tools, permissions, and channels (or returns a human answer in an escalation ticket).
agents__trigger_conversationExternal Customer / Lead (contact_id) via a Transactional AgentImmediateWakes up a transactional agent immediately to start or resume a conversation with a customer. WhatsApp Rule: Only works within an active 24-hour customer service window; outside 24h, use templates__send_template.
messaging__send_direct_messageInternal Team Member (Staff) (contact_id from <internal_team_directory>)ImmediateSends a literal text message directly via Telegram (channel_type = 'telegram') to an internal staff member without invoking another agent's LLM loop. Never use for external customers.
templates__send_templateExternal Customer / Lead on WhatsAppImmediateSends a Meta-approved WhatsApp HSM template (templates__list_templates) to open or re-open a conversation outside the 24-hour window.

2. Exact Tool Signatures & Parameter Contracts

2.1 tasks__schedule_reminder (Self Follow-Up)

Schedules a row in agent_tasks (origin = 'Sati' when called by Sati, or origin = 'self' when called by a transactional agent):

  • instruction (string, required): Clear, actionable instruction describing what to do or check when the timer fires.
  • scheduled_at (string, required): Local date-time in the business timezone formatted as YYYY-MM-DDTHH:MM:SS (ISO 8601 without 'Z' or offset). The engine (_localize_datetime_str) automatically localizes naive timestamps using the workspace timezone and converts them to UTC for storage. (Note: The parameter name is strictly scheduled_at, never execute_at).
  • channel_type (string, optional): Preferred notification channel ('frontend', 'telegram', 'whatsapp'). Defaults to the channel where the request originated.
  • context (object, optional): Extra JSON metadata needed when the reminder executes.
tasks__schedule_reminder(
    instruction="Verificar si el pago de la factura #1042 ya fue registrado y notificar al usuario.",
    scheduled_at="2026-10-25T09:00:00",
    context={"factura_id": "uuid-factura"}
)

2.2 tasks__delegate_to_agent (Immediate or Deferred Agent Delegation)

Assigns an operational task to a transactional agent (origin = 'Sati' or 'delegation'):

  • instruction (string, required): Detailed instruction of what the transactional agent must do or tell the customer.
  • agent or agent_id (string, required outside escalation tickets): Accepts either the agent's display name (e.g., "Alejandra", resolved case-insensitively via ILIKE) or its UUID from <agents_directory>. (Note: Use agent or agent_id, never target_agent_id).
  • scheduled_at (string, optional): Local execution date-time (YYYY-MM-DDTHH:MM:SS). If omitted, defaults to now (immediate execution).
  • contact_id (string, optional): UUID of the customer contact related to the task.
tasks__delegate_to_agent(
    agent="Asesor Comercial",
    contact_id="75fc610a-29a9-4dcd-9772-a7afd5646db1",
    instruction="Escríbele a María Gómez para confirmar si recibió la cotización del Plan Empresarial y pregúntale si tiene dudas.",
    scheduled_at="2026-10-25T10:30:00"
)

2.3 agents__trigger_conversation (Immediate Proactive Customer Outreach)

Pushes an immediate automation_request event to the transactional worker queue so the specified agent initiates or resumes a conversation with the customer:

  • contact_id (string, required): Customer UUID.
  • agent_id (string, required): Transactional agent UUID.
  • instruction (string, required): Opening goal or instruction for the agent.
  • context (object, optional): Additional context dictionary passed to the agent.

WhatsApp 24-Hour Window Rule: Meta blocks free-text proactive messages on WhatsApp if more than 24 hours have passed since the customer's last inbound message. Before triggering proactive outreach on WhatsApp to cold or inactive contacts, check templates__list_templates and use templates__send_template instead.

2.4 messaging__send_direct_message (Immediate Staff Alert on Telegram)

Delivers a ready-to-send message straight to the egress queue for an internal staff member on Telegram:

  • contact_id (string, required): Staff member's contact UUID from <internal_team_directory>.
  • message (string, required): Complete text message to send.

3. Escalation Tickets & Resolution Flow (tasks__escalate_to_sati)

When a transactional agent encounters a situation outside its authority or knowledge, it invokes tasks__escalate_to_sati(instruction, scheduled_at, context?):

  1. Ticket Creation (origin = 'escalation'):
    • The customer's conversation is placed in human/escalation handover so the transactional agent pauses.
    • An isolated escalation ticket thread (is_escalation_ticket = True, escalating_agent_id, original_conversation_id) is created and assigned to Sati (type = 'planner').
  2. Sati's Escalation Router Protocol:
    • On Initial Escalation: Sati immediately notifies the human staff — either via messaging__send_direct_message (if Telegram and staff contacts are configured) or by outputting the structured 🚨 TICKET DE ESCALACIÓN report in the ticket thread for the Satia Web UI. Do not run unnecessary database queries on initial escalation unless requested.
    • On Human Staff Reply: When a staff member replies in the ticket thread with the decision or answer, Sati calls tasks__delegate_to_agent(instruction="...").
    • Automatic Routing & Ticket Auto-Resolution: Inside an escalation ticket context (_is_escalation_ticket = True), tasks__delegate_to_agent automatically resolves agent_id to the escalating_agent_id (even if agent is omitted) and automatically updates the ticket conversation's metadata.escalation_status to "resolved".

4. Wakeup Headers & The Anti-Loop Guard

When the background task runner or an automation wakes up Sati or a transactional agent, the system injects a header at the top of the prompt turn:

  • [⏰ SCHEDULED REMINDER] (or [⏰ RECORDATORIO PROGRAMADO]): A scheduled task from agent_tasks whose scheduled_at has arrived.
  • [🤖 ACTIVE AUTOMATION] (or [🤖 AUTOMATIZACIÓN ACTIVA] / [🤖 TAREA ASIGNADA]): A proactive task triggered by an automation script or delegation.

⚠️ Mandatory Anti-Loop Rule (_is_from_task)

When woken up by a [⏰ SCHEDULED REMINDER] or [🤖 TAREA ASIGNADA]:

  1. Execute the instruction immediately in the current turn (query the database, contact the user, or deliver the report).
  2. NEVER call tasks__schedule_reminder with the same instruction again. common_libs/skills/tasks.py enforces an automatic Anti-Loop Guard: if an agent woken up by a task attempts to re-schedule the identical instruction, the tool rejects the call with an error to prevent infinite rescheduling loops.

5. Scheduling Tasks & Triggering Agents from Python (satia_sdk)

Inside automation scripts (automation_scripts) or Custom Tools (agent_tools), use satia_sdk.tasks and satia_sdk.agents:

from satia_sdk import tasks, agents

# 1. Schedule a deferred task in `agent_tasks` (`origin = 'sdk'`)
tasks.schedule(
    instruction="Hacer seguimiento post-venta al cliente y solicitar calificación",
    scheduled_at="2026-10-26T14:00:00",
    contact_id=contact_uuid,
    agent_id=agent_uuid,
    context={"pedido_id": order_uuid}
)

# 2. Wake up a transactional agent immediately via Redis queue
agents.trigger_task(
    agent_id=agent_uuid,
    contact_id=contact_uuid,
    instruction="Confírmale al cliente que su pago fue aprobado y su pedido va en camino.",
    context={"pedido_id": order_uuid}
)

6. Schema & SQL Management of agent_tasks

All scheduled reminders, delegations, and escalations are persisted in the agent_tasks system table and can be inspected or modified via database__query and database__update.

6.1 Columns of agent_tasks

ColumnTypeDescription
iduuidUnique task ID.
agent_iduuidTarget agent UUID that will execute the task (FK -> agents.id).
created_by_agent_iduuidAgent UUID that scheduled or delegated the task.
contact_iduuidRelated contact UUID (optional).
conversation_iduuidLinked conversation thread UUID (set for self-follow-ups on the same agent + contact; NULL when starting a clean thread).
instructiontextNatural-language instruction to execute when scheduled_at arrives.
scheduled_attimestamptzScheduled execution timestamp in UTC.
statustextLifecycle state: 'pending', 'executing', 'completed', 'failed'.
origintextTask source: 'self', 'Sati', 'delegation', 'escalation', 'sdk', or 'system'.
contextjsonbAdditional context dictionary (channel_type, channel_id, is_escalation_ticket, etc.).
resulttextExecution summary or response once status = 'completed'.
errortextError details if status = 'failed'.
executed_attimestamptzActual timestamp when execution finished.
created_attimestamptzCreation timestamp.

6.2 Auditing, Rescheduling, or Canceling Tasks via SQL

  • List upcoming pending tasks:
    SELECT t.id, a.name AS agent_name, t.contact_id, t.instruction, t.scheduled_at, t.origin, t.status
    FROM agent_tasks t
    LEFT JOIN agents a ON a.id = t.agent_id
    WHERE t.status = 'pending'
    ORDER BY t.scheduled_at ASC
    LIMIT 50;
    
  • Reschedule a pending task (database__update): Update scheduled_at (in UTC ISO format) while keeping status = 'pending':
    database__update(
        table_name="agent_tasks",
        data={"scheduled_at": "2026-10-27T15:00:00Z"},
        where_conditions="id = '<task_uuid>'"
    )
    
  • Cancel a pending task (database__update): Set status = 'completed' and record the cancellation reason in result:
    database__update(
        table_name="agent_tasks",
        data={"status": "completed", "result": "Cancelada por solicitud del usuario."},
        where_conditions="id = '<task_uuid>'"
    )