One agent layer, one persistence layer, one PDF pipeline
PyGuard is a small FastAPI backend that coordinates a set of focused sub-agents over the OpenAI Agents SDK, persists per-report bundles on disk, and talks to users through Slack. The beta release only adds what was strictly needed to support in-thread Q&A and LaTeX edits.
The beta-release agent topology
A birds-eye view of the agents, data flows, and storage that make up PyGuard today. The diagram below evolved from the alpha design and reflects every orchestrator, sub-agent, and external service currently in production.

How a single Slack message is routed
This diagram reflects the beta-release state. Solid arrows are in-process calls, dashed ones are optional or external services.
flowchart TB
subgraph clients [Clients]
Slack["Slack user"]
Web["Web chat UI (existing)"]
WA["WhatsApp (via bridge, optional)"]
end
subgraph surface [Surface layer]
SlackSvc["slack.service (Socket Mode)"]
WebApi["api.routes / chat_handler"]
Bridge["bridge (Node, Baileys)"]
end
subgraph handler [process_chat_message]
Phase0{"thread_ts maps to fraud_reports row?"}
Followup["ReportFollowupAgent"]
Memory["MemoryAgent (intent + complexity)"]
end
subgraph fraud [Fraud agent stack]
FraudOrch["FraudOrchestratorAgent"]
DataAg["FraudDataAnalystAgent"]
PatAg["FraudPatternAgent"]
RepAg["FraudReportAgent"]
AlertAg["FraudAlertAgent"]
VerAg["FraudVerificationAgent"]
end
subgraph followup [Follow-up stack]
QAAg["ReportQAAgent"]
EditAg["ReportEditorAgent"]
TexEdit["tex_editor (pure Python)"]
end
subgraph storage [Local storage]
Sqlite[("SQLite: pyguard.db
messages, fraud_transactions, fraud_reports")]
Bundles[("backend/reports/fraud_report_<ts>/
report.tex, PDF, charts, history/")]
end
subgraph external [External services]
OAI["OpenAI Agents SDK + Responses API"]
Comp["Composio (Gmail, Calendar, Docs)"]
Tec["tectonic (LaTeX to PDF)"]
end
Slack --> SlackSvc --> handler
Web --> WebApi --> handler
WA --> Bridge --> WebApi
handler --> Phase0
Phase0 -->|yes| Followup
Phase0 -->|no| Memory
Followup --> QAAg
Followup --> EditAg
EditAg --> TexEdit --> Tec
TexEdit --> Bundles
Memory -->|fraud intent| FraudOrch
FraudOrch --> DataAg
FraudOrch --> PatAg
FraudOrch --> RepAg
FraudOrch --> AlertAg
FraudOrch --> VerAg
RepAg --> Bundles
RepAg --> Tec
DataAg --> Sqlite
QAAg --> Sqlite
QAAg --> Bundles
FraudOrch --> OAI
Followup --> OAI
Memory --> OAI
SlackSvc --> Bundles
Memory -.-> Comp
classDef ext fill:transparent,stroke:#2a3356,stroke-dasharray:4 4;
class OAI,Comp,Tec ext;Major components and what they own
| Component | Layer | Responsibility |
|---|---|---|
| slack.service | Surface | Socket Mode client; captures thread_ts and channel_id, reacts, posts replies, uploads PDFs. |
| api.routes + chat_handler | Surface | HTTP endpoints (/api/chat, /api/history) and the shared process_chat_message that every surface calls. |
| MemoryAgent | Orchestration | Classifies intent, complexity, and picks between fraud, rule, user_management, conversational, and action paths. |
| FraudOrchestratorAgent | Orchestration | Runs the full report generation: FraudDataAnalyst -> FraudPatternAgent -> FraudReportAgent -> FraudVerificationAgent. |
| ReportFollowupAgent | Orchestration | Phase 0 router. Picks qa, edit, or new_report for messages inside an existing report's thread. |
| ReportQAAgent | Sub-agent | Read-only expert on the saved report bundle plus the live SQL data tools. Answers questions in thread, no PDF. |
| ReportEditorAgent | Sub-agent | Applies structured LaTeX edits via the tex_editor module and recompiles with tectonic. |
| tex_editor | Primitive | Pure Python. Section-anchor edits, minimal validation, history/v{n}/ backup, atomic writes, tectonic wrapper with rollback. |
| fraud_reports (SQLite) | Storage | One row per Slack thread. Stores pdf_path, tex_path, bundle_dir, sections_json, data_snapshot_json, version. |
| backend/reports/<bundle> | Storage | Durable on-disk bundle: report.pdf, report.tex, charts/, metadata.json, history/v{n}/ after edits. |
Four paths through the system
- Inbound message
Slack event → slack.service → process_chat_message. For Slack, thread_ts and channel_id are captured on the way in so Phase 0 can match an existing report.
- Report generation
MemoryAgent classifies intent → FraudOrchestratorAgent coordinates the four fraud sub-agents → FraudReportAgent renders LaTeX and compiles with tectonic → fraud_reports row inserted keyed by thread_ts.
- In-thread Q&A
Phase 0 finds the fraud_reports row → ReportFollowupAgent classifier picks qa → ReportQAAgent reads the bundle and optionally the live SQL → plain text answer lands in the thread.
- LaTeX edit
ReportFollowupAgent picks edit → per-report asyncio.Lock acquired → tex_editor backs up current tex, applies structured edits, recompiles → Slack uploads the new PDF into the thread, or rollback restores the previous bundle.
What PyGuard depends on outside the process
- OpenAI Agents SDK
LLM orchestration. Models and reasoning levels are defined centrally in backend/config/agent_config.py.
- Composio
Used by the general orchestrator for Gmail, Calendar, and Google Docs tool calls. Not used by the fraud path.
- Tectonic
LaTeX engine invoked per compile. 120-second timeout; rollback restores the previous PDF on failure.
- SQLite (pyguard.db)
Messages, rules, users, fraud_transactions, and the new fraud_reports table all share one database. Unique index on slack_thread_ts enforces one active report per thread.
- Slack
Socket Mode app. Requires bot token + app token plus the bot scopes listed in /setup.
What changed since alpha, and why
The alpha topology generated a PDF and walked away. The beta release keeps the conversation alive inside the thread. Four targeted changes make that possible.
Durable report bundles
Alpha wrote report.tex into a tempfile.mkdtemp and only moved the PDF out. That meant the LaTeX source was effectively thrown away after generation. In beta, every report is its own folder (backend/reports/fraud_report_<ts>/) containing the PDF, the tex source, the chart PNGs, metadata.json, and a history/ directory for backups. Without this, in-place LaTeX edits are impossible.
fraud_reports table and thread keying
Alpha had no way to find a previously generated report from a Slack thread. Beta adds a fraud_reports table with a unique index on slack_thread_ts, plus insert/get/update helpers. This table is what lets Phase 0 know whether an incoming message is a follow-up.
ReportFollowupAgent and Phase 0 routing
Alpha routed every incoming message through MemoryAgent, so a follow-up question would generate a brand-new report. Beta adds a Phase 0 fast path in chat_handler.py that consults fraud_reports before the Memory Agent, and a small classifier that chooses between qa, edit, and new_report.
tex_editor module with validated rollback
Alpha had no safe way to edit the PDF after the fact. Beta introduces pure-Python helpers for section-anchor edits, minimal LaTeX validation, history/v{n}/ backups, and a composite apply_edits_and_recompile tool that rolls back on tectonic failure. Twenty-one unit tests in backend/tests/test_tex_editor.py cover every primitive.
Nothing else in the alpha topology was replaced. The Memory Agent, Orchestrator Agent, general sub-agents (calendar, email, research, report_writer), Composio wiring, SQLite schema, and Slack Socket Mode client all behave exactly as they did in alpha. The beta release is strictly additive.