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?
| Tool | Target Recipient | Execution Timing | Purpose & Channel Rules |
|---|---|---|---|
tasks__schedule_reminder | Self (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_agent | Transactional 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_conversation | External Customer / Lead (contact_id) via a Transactional Agent | Immediate | Wakes 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_message | Internal Team Member (Staff) (contact_id from <internal_team_directory>) | Immediate | Sends 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_template | External Customer / Lead on WhatsApp | Immediate | Sends 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 asYYYY-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 strictlyscheduled_at, neverexecute_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.agentoragent_id(string, required outside escalation tickets): Accepts either the agent's display name (e.g.,"Alejandra", resolved case-insensitively viaILIKE) or its UUID from<agents_directory>. (Note: Useagentoragent_id, nevertarget_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_templatesand usetemplates__send_templateinstead.
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?):
- 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').
- 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ÓNreport 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_agentautomatically resolvesagent_idto theescalating_agent_id(even ifagentis omitted) and automatically updates the ticket conversation'smetadata.escalation_statusto"resolved".
- On Initial Escalation: Sati immediately notifies the human staff — either via
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 fromagent_taskswhosescheduled_athas 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]:
- Execute the instruction immediately in the current turn (query the database, contact the user, or deliver the report).
- NEVER call
tasks__schedule_reminderwith the sameinstructionagain.common_libs/skills/tasks.pyenforces 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
| Column | Type | Description |
|---|---|---|
id | uuid | Unique task ID. |
agent_id | uuid | Target agent UUID that will execute the task (FK -> agents.id). |
created_by_agent_id | uuid | Agent UUID that scheduled or delegated the task. |
contact_id | uuid | Related contact UUID (optional). |
conversation_id | uuid | Linked conversation thread UUID (set for self-follow-ups on the same agent + contact; NULL when starting a clean thread). |
instruction | text | Natural-language instruction to execute when scheduled_at arrives. |
scheduled_at | timestamptz | Scheduled execution timestamp in UTC. |
status | text | Lifecycle state: 'pending', 'executing', 'completed', 'failed'. |
origin | text | Task source: 'self', 'Sati', 'delegation', 'escalation', 'sdk', or 'system'. |
context | jsonb | Additional context dictionary (channel_type, channel_id, is_escalation_ticket, etc.). |
result | text | Execution summary or response once status = 'completed'. |
error | text | Error details if status = 'failed'. |
executed_at | timestamptz | Actual timestamp when execution finished. |
created_at | timestamptz | Creation 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): Updatescheduled_at(in UTC ISO format) while keepingstatus = '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): Setstatus = 'completed'and record the cancellation reason inresult:database__update( table_name="agent_tasks", data={"status": "completed", "result": "Cancelada por solicitud del usuario."}, where_conditions="id = '<task_uuid>'" )