Routers — agents.py
The platform's core API: 15 endpoints under /agents covering agent CRUD, chat, email intelligence, task management, workflow orchestration, and dashboard aggregation — plus the shared helpers that implement the observability contract.
Purpose
backend/app/routers/agents.py (~940 lines) is the orchestration layer. It validates requests, gates tools, invokes services, persists results, and logs every automated step. It contains no prompt text and no direct OpenAI calls — those live in services.
Endpoint inventory
| Method & path | Handler | Summary |
|---|---|---|
POST /agents/ | create_agent | Create an agent from AgentCreate |
GET /agents/ | get_agents | List agents, newest first |
GET /agents/{id} | get_agent | Fetch one agent (404 if missing) |
POST /agents/{id}/chat | chat_with_agent | One chat exchange; persists messages + AgentRun |
GET /agents/{id}/runs | get_agent_runs | Run history, newest first |
GET /agents/{id}/messages | get_agent_messages | Paginated chat history (limit/offset) |
POST /agents/{id}/email/analyze | analyze_agent_email | Single-agent analysis + optional task creation |
GET /agents/{id}/tasks | get_agent_tasks | Tasks, newest first |
PUT /agents/tasks/{task_id}/status | update_task_status | Status transition with allow-list validation |
GET /agents/{id}/tool-calls | get_agent_tool_calls | Tool-call log, newest first |
POST /agents/{id}/workflows/email | run_multi_agent_email_workflow | The 4-stage pipeline (deep dive) |
GET /agents/{id}/workflow-runs | get_agent_workflow_runs | Workflow run history |
GET /agents/workflow-runs/{run_id}/steps | get_workflow_run_steps | Per-step trace, oldest first |
GET /agents/{id}/dashboard | get_agent_dashboard | Aggregated metrics for the UI |
GET /agents/{id}/stats | get_agent_stats | ⚠️ Broken practice code — actually attempts a delete; see Known Issues |
Request/response details for each endpoint live in the API Reference.
Shared helpers
These module-level functions encode the conventions every endpoint follows:
agent_has_tool(agent, tool_name) -> bool
Splits agent.enabled_tools on commas, strips whitespace, and tests membership. Called before any tool executes; the endpoint returns 403 when the tool is missing. Canonical names: Tools.
log_tool_call(db, agent_id, tool_name, ...) -> ToolCall
Constructs a ToolCall row (converting success: bool to the stored "true"/"false" string) and db.adds it without committing — the caller owns the transaction, so a tool log and its business data commit atomically.
create_workflow_run(db, agent_id, workflow_name, input_text)
Creates a WorkflowRun with status="running" and commits immediately — so a crash mid-pipeline still leaves a discoverable run to mark failed.
log_workflow_step(...)
Adds a WorkflowStep (no commit), one per pipeline stage.
normalize_due_date(deadline) -> str | None
Deadline sanitizer for task creation:
simple_deadlines = ["today", "tomorrow", "next week", "this week"] # kept as-is
# ISO dates: parsed; past dates → None (don't save stale deadlines)
# anything else: passed through unchangedsafe_float / safe_int
Defensive parsers for the string-typed estimated_cost / quality_score columns, used by the dashboard aggregation.
Orchestration in detail
Chat
Fully traced in Request Lifecycle. Key properties: the LLM call is wrapped in try/except (failures degrade to a fallback message + status="failed" run), latency wraps the whole handler, and messages + run commit in one transaction.
Email analyze (single-agent)
On any exception: a failed ToolCall named email_analyzer_or_task_creator is logged, committed, and 500 raised. Note the single-agent path logs tool calls without cost estimates — only the multi-agent workflow attaches model_name/tokens/cost (Known Issues).
Multi-agent email workflow
The largest handler (~280 lines): creates the run, executes Analysis → Task → Reply → Reviewer with a WorkflowStep + costed ToolCall per stage, finalizes status, quality_score, and a final_output JSON blob. Stage-by-stage documentation: Email Workflow.
Dashboard aggregation
Loads all tasks, tool calls, and workflow runs for the agent and computes in Python: counts by status, success/failure splits, average_latency_ms over non-null latencies, estimated_total_tokens (via safe_int), estimated_total_cost (summed as floats, reformatted to 6 decimals), and average_quality_score over parseable scores. Contract: Observability.
Error handling summary
| Condition | Response |
|---|---|
| Agent/task/run not found | 404 |
| Tool not enabled | 403 |
| Invalid task status | 400 (allow-list: open, in_progress, completed) |
| LLM failure during chat | 200 with fallback text; run marked failed |
| LLM/other failure in tools & workflows | 500 with exception text; failed ToolCall committed |
Full strategy: Error Handling.
Security notes
No authentication — every endpoint is open (Security). Exception strings are returned verbatim in 500 details, which can leak internals; acceptable for local dev, flagged for hardening.
Future improvements
- Remove or fix the
statsendpoint (it references a nonexistentAgent.agent_idattribute and is a delete in disguise). - Attach cost estimates in the single-agent analyze path for parity with the workflow.
- Extract the workflow handler into a service for testability.