Data Flow
Request lifecycle for NLQ queries and action validation
This page traces the complete lifecycle of the two primary operations: asking a question (read plane) and validating an action (write plane).
NLQ Query Flow (Read Plane)
When an agent asks "What is our monthly revenue by customer?":
1. Agent → MCP ask_question("What is our monthly revenue by customer?")
│
2. │→ Query Router: classify_intent()
│ Result: "analytical" (requires SQL generation)
│
3. │→ Verified Query Loader: find_relevant()
│ Result: 2 matching VQs about revenue
│
4. │→ Prompt Assembler: build context window
│ Includes:
│ - Schema: invoices(id, customer_id, net_amount, status, created_at)
│ - Definition: "Revenue = net_amount WHERE status = 'paid'"
│ - VQ examples: 2 verified patterns
│ - Business rules: "Exclude credits and refunds"
│
5. │→ LLM (Claude): generate SQL from context
│ Output: SELECT c.name, SUM(i.net_amount) FROM invoices i
│ JOIN customers c ON i.customer_id = c.id
│ WHERE i.status = 'paid'
│ GROUP BY c.name
│
6. │→ SQL Validator: validate_sql()
│ Checks: column existence ✓, join paths ✓, PII columns ✗, anti-patterns ✗
│ Result: PASS
│
7. │→ Database Connector: execute SQL
│ Source: production_postgres
│ Timeout: EPISTOM_QUERY_TIMEOUT_SECONDS
│ Row limit: EPISTOM_MAX_QUERY_ROWS
│
8. │→ PII Gate: scan results for PII patterns
│ Result: no PII detected
│
9. │→ Query Logger: persist outcome
│ Stores: question, SQL, results hash, confidence, timing
│
10.└→ Agent receives:
{
"answer": "Monthly revenue by customer",
"sql": "SELECT ...",
"data": [...],
"confidence": 0.92,
"query_id": "q-abc123"
}If Validation Fails
At step 6, if the SQL validator rejects the query:
6a. SQL Validator: FAIL — column 'revenue' does not exist
│
6b. Analytical tier: one correction retry with the validator errors in the prompt
│
6c. LLM: regenerate SQL
│
6d. SQL Validator: re-validate (one retry)In agent mode, AgentLoop runs generate → evaluate → correct up to MAX_ROUNDS (3). The last round is not evaluated. A capped answer is verified: false; under hard abstention it withholds its rows.
Action Validation Flow (Write Plane)
When an agent proposes salesforce.update_contact({contact_id: "123", amount: 50000}):
1. Agent → MCP tool call: salesforce.update_contact
│
2. │→ MCP Gate: intercept tool call
│ Agent identity: "sales-bot-1" (from headers)
│
3. │→ Tool Classifier: classify("salesforce.update_contact")
│ Result: ACTION (requires evaluation)
│
4. │→ Policy Engine: evaluate constraints
│ Matching constraints:
│ - "salesforce.update_*: amount <= 10000" → FAIL
│ - "salesforce.*: status != deleted" → PASS
│ Result: DENY (amount 50000 > 10000 cap)
│
5. │→ Impact Engine: compute blast radius
│ Affected: Salesforce CRM (direct), Data Warehouse (cascade)
│ Severity: medium
│ Reversible: yes (Salesforce versioning) — compensation is tracked, not executed
│
6. │→ Attestation Store: record decision
│ Signature: Ed25519(verdict + tool + agent + payload_hash + impact_hash)
│ Storage: INSERT INTO praxom_attestations (append-only)
│
7. │→ SIEM Enqueue: write delivery rows
│ Destinations: Splunk HEC, Sentinel (best-effort)
│
8. └→ Agent receives:
{
"verdict": "deny",
"reason": "Amount 50000 exceeds cap of 10000 for salesforce.update_*",
"attestation_id": "att-xyz789"
}REWRITE Flow
If the constraint specifies a REWRITE instead of DENY:
4a. Policy Engine: REWRITE verdict
Rewrites: [{"op": "cap_value", "field": "amount", "max": 10000}]
│
4b. Rewrite Engine: apply transformations
Original: {amount: 50000} → Modified: {amount: 10000}
│
4c. Attestation Store: record both original and modified payloads
│
4d. Execute with modified payload → salesforce.update_contact({amount: 10000})Timing
Where the time goes, structurally:
- Policy evaluation, routing, validation, and attestation writes are deterministic, in-process steps — no LLM call anywhere in the enforcement path, no network round-trip to a policy service.
- LLM generation (when a query reaches the analytical tier) and SQL execution on your warehouse dominate end-to-end latency by orders of magnitude.
- SIEM delivery is asynchronous and best-effort; it never blocks the decision path.
The practical consequence: the gate's overhead is negligible relative to the model call it governs. We intentionally publish no benchmark numbers here until we can publish the benchmark itself.