Skip to content

Todos

The todo module enables Loquent to autonomously create and execute tasks from phone call transcriptions. After a call ends, an LLM analyzes the transcription, extracts actionable items matching predefined types, and queues them for human review. Approved todos execute automatically using system tools.

Call ends → Transcription saved
→ extract_todos(call_id, org_id)
LLM analyzes transcription against org's TodoTypes
→ Creates todos with status "pending_review"
User reviews in dashboard → Approve or Reject
Every 30 seconds:
→ execute_todos_job picks up "approved" todos
→ execute_todo(todo_id)
LLM agent runs with system tools (e.g. send email)
→ Status: "completed" or "failed"

An org-defined category of task. Tells the system what kinds of actions to look for in call transcriptions.

pub struct TodoType {
pub id: Uuid,
pub name: String, // e.g. "Follow-up Email"
pub description: String, // What this type of task does
pub tools: Vec<SystemTool>, // Which system tools to use during execution
}

A specific task extracted from a specific call, linked to a TodoType.

pub struct Todo {
pub id: Uuid,
pub call_id: Option<Uuid>,
pub type_id: Uuid,
pub type_name: String, // Denormalized from TodoType
pub description: String, // LLM-generated context for this specific task
pub status: String,
pub error_message: Option<String>,
pub created_at: String,
pub completed_at: Option<String>,
}
StatusMeaning
pending_reviewExtracted by LLM, awaiting human approval
approvedHuman approved, queued for execution
rejectedHuman rejected, no action taken
in_progressCurrently being executed by the AI agent
completedExecuted successfully
failedExecution failed — check error_message

extract_todos runs after a call transcription is saved.

Inputs: call_id, organization_id

Flow:

  1. Fetch the call and its transcription
  2. Load all TodoTypes for the org (exits early if none defined)
  3. Send transcription to GPT-5 with structured output, asking it to identify actionable items matching the available types
  4. Each extracted item includes a type_id (UUID) and description
  5. Validate each type_id against the org’s types — skip unknown IDs
  6. Insert valid todos with status pending_review

LLM prompt:

System: You are analyzing a phone call transcription to extract actionable to-do items.
Available to-do types:
- ID: <uuid>
Name: <name>
Description: <description>
For each actionable item you find, add it to the todos array.
Only extract to-dos that match one of the available types.
If no actionable items are found, return an empty todos array.
User: <transcription text>

The response is constrained to a JSON schema (ExtractedTodos { todos: Vec<ExtractedTodo> }).

The execute_todos_job runs every 30 seconds, picking up all todos with status approved.

For each todo, execute_todo runs:

  1. Guard: reject if status isn’t approved
  2. Set status to in_progress
  3. Load the TodoType and its assigned system tools
  4. Fetch call transcription for context (if linked to a call)
  5. Run an LLM agent (GPT-5) with the tools, capped at 10 tool-calling steps
  6. On success: set completed + completed_at
  7. On failure: set failed + error_message

Todos execute sequentially. The in_progress status prevents duplicate execution if the job fires again while a todo is still running.

SystemTool is an enum of constrained actions the AI can invoke during execution. The AI provides parameters, but sensitive values (like email recipients) come from the system.

ToolWhat It Does
SendEmailToCallerSends a follow-up email to the caller’s contact
  1. Look up the call’s contact_id
  2. Find the contact’s preferred email from contact_email table
  3. GPT-5 composes subject and body using the task description and call transcription
  4. Send via bases::email::send_email

All endpoints require an authenticated session.

RouteMethodDescription
/api/todosGETList all org todos (newest first)
/api/todos/{id}/approvePUTApprove a pending todo
/api/todos/{id}/rejectPUTReject a pending todo

Approve and reject only work on todos with status pending_review.

RouteMethodDescription
/api/todo-typesGETList all org todo types with tools
/api/todo-types/{id}GETGet a single todo type
/api/todo-typesPOSTCreate a todo type
/api/todo-types/{id}PUTUpdate name, description, and tools
/api/todo-types/{id}DELETEDelete a todo type (hard delete)

Create and update accept TodoTypeData:

pub struct TodoTypeData {
pub name: String,
pub description: String,
pub tools: Vec<SystemTool>,
}

Tool updates use a delete-all-then-reinsert strategy on the todo_type_tool join table.

src/mods/todo/
├── api/ # CRUD for todos and todo types
├── components/ # UI: todo list, cards, type management
├── jobs/ # execute_todos_job (every 30s)
├── services/ # extract_todos, execute_todo
├── types/ # Todo, TodoType, TodoData, SystemTool
├── utils/ # send_email_to_caller
└── views/ # TodoListView, TodoTypeListView, CreateTodoTypeView, TodoTypeDetailsView
  • Call — todos are extracted from call transcriptions
  • Agent — agents handle the calls that generate todos
  • Supporting Modules — dashboard shows todo status distribution