| <article class="doc" id="doc-modules-agents" aria-hidden="true"><div class="strip"><span class="path">modules/agents.md</span><span class="tag">Engineering, modules</span><span class="meta">~2 min read</span></div> |
| <h1>Module: <code>agents</code></h1> |
| <blockquote> |
| <p>Part of the Travi AI Agent monolith (<code>app/modules/agents/</code>). Read <code>00-overview.md</code> §3 (hard rules) before changing anything here.</p> |
| </blockquote> |
| <div class="tbl-wrap"><table class="plain col-table compact"><colgroup><col class="col-auto"/><col class="col-lg"/></colgroup> |
| <thead> |
| <tr> |
| <th></th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label=""><strong>Purpose</strong></td> |
| <td data-label="">The six bounded agents, their policy envelopes, evidence records, kill switches, and exception routing. Agents gather evidence; they never set state.</td> |
| </tr> |
| <tr> |
| <td data-label=""><strong>Owns (data)</strong></td> |
| <td data-label="">agent_action, policy envelopes (versioned)</td> |
| </tr> |
| <tr> |
| <td data-label=""><strong>Public surface</strong></td> |
| <td data-label="">goal queue (internal), <code>/admin/kill-switches</code> enforcement point</td> |
| </tr> |
| <tr> |
| <td data-label=""><strong>Depends on</strong></td> |
| <td data-label="">episode (evidence → engine), notification, ehr_adapter, Twilio voice</td> |
| </tr> |
| <tr> |
| <td data-label=""><strong>Requirements owned</strong></td> |
| <td data-label="">AUT-001/006/009</td> |
| </tr> |
| <tr> |
| <td data-label=""><strong>Constraining ADRs</strong></td> |
| <td data-label="">ADR-011 (routing targets)</td> |
| </tr> |
| <tr> |
| <td data-label=""><strong>Key references</strong></td> |
| <td data-label="">modules/episode.md (who actually moves state)</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <hr/> |
| <h1>12. Agentic Execution Layer</h1> |
| <h2 id="doc-modules-agents--h1">12.1 Orchestration Loop (deterministic core)</h2> |
| <ol> |
| <li>Workflow engine (episode module) emits a <strong>goal</strong> (e.g., <code>verify_dispense(plan_item)</code>) to the agent queue with the current policy envelope version.</li> |
| <li>Agent plans with LLM assist (optional) but may only call tools on its allowlist; every call → <code>agent_action</code> row (params redacted, result, evidence).</li> |
| <li>Agent returns <strong>structured evidence</strong>, never a state: e.g., <code>{dispense_status:"picked_up", source:"pharmacy_phone", confidence:"confirmed"}</code>.</li> |
| <li>Workflow engine validates evidence against deterministic rules and executes the §7.1 transition (five-element contract).</li> |
| <li>Retry ladder per envelope; exhaustion → L1 exception route (§12.5); kill switches checked before every tool call (cached ≤10 s).</li> |
| </ol> |
| <h2 id="doc-modules-agents--h2">12.2 Agent Catalog & Envelopes</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-lg"/><col class="col-auto"/><col class="col-lg"/></colgroup> |
| <thead> |
| <tr> |
| <th>Agent</th> |
| <th>Tool Allowlist</th> |
| <th>Max Retries</th> |
| <th>Hard Prohibitions</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Agent">Engagement</td> |
| <td data-label="Tool Allowlist">notification.send, channel.select, schedule.reminder</td> |
| <td data-label="Max Retries">3/task/day</td> |
| <td data-label="Hard Prohibitions">No PHI in payloads; no promises of clinician monitoring (CLN-008 language lint).</td> |
| </tr> |
| <tr> |
| <td data-label="Agent">Appointment</td> |
| <td data-label="Tool Allowlist">ehr.search_slots, ehr.book(with patient authorization token), patient.present_options</td> |
| <td data-label="Max Retries">3</td> |
| <td data-label="Hard Prohibitions">Never book/cancel without explicit patient authorization event; never invent availability (APT rules).</td> |
| </tr> |
| <tr> |
| <td data-label="Agent">Med Access</td> |
| <td data-label="Tool Allowlist">pharmacy.call (Twilio flow), pharmacy.status_form, patient.ask_structured</td> |
| <td data-label="Max Retries">2/barrier</td> |
| <td data-label="Hard Prohibitions">No substitutions, dose advice, or clinical guidance, script templates only.</td> |
| </tr> |
| <tr> |
| <td data-label="Agent">Records</td> |
| <td data-label="Tool Allowlist">ehr.list_documents, ehr.fetch_binary</td> |
| <td data-label="Max Retries">5</td> |
| <td data-label="Hard Prohibitions">Approved document types only; read-only.</td> |
| </tr> |
| <tr> |
| <td data-label="Agent">Brief</td> |
| <td data-label="Tool Allowlist">facts.read(published), brief.render, notify.clinician_link</td> |
| <td data-label="Max Retries">2</td> |
| <td data-label="Hard Prohibitions">Assembles <strong>published</strong> facts only; cannot trigger confirmations.</td> |
| </tr> |
| <tr> |
| <td data-label="Agent">Outcome</td> |
| <td data-label="Tool Allowlist">adt.match, hie.lookup, claims.ingest</td> |
| <td data-label="Max Retries">5</td> |
| <td data-label="Hard Prohibitions">Read/match only; flags completeness, never edits episodes.</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <h2 id="doc-modules-agents--h3">12.3 Policy Envelope (stored, versioned, enforced in code)</h2> |
| <pre><code>{ "agent": "med_access", "version": 4, |
| "tools": ["pharmacy.call","pharmacy.status_form","patient.ask_structured"], |
| "max_retries": 2, "retry_backoff_hours": [4, 24], |
| "requires_patient_authorization": false, |
| "clinical_boundaries": ["no_substitution","no_dose_advice"], |
| "cost_caps": {"tokens_per_run": 20000, "voice_minutes_per_run": 6}, |
| "kill_scopes": ["global","tenant","cohort","agent","tool","integration"], |
| "logging": "full_evidence", "pii_redaction": "params_hashed" } |
| </code></pre> |
| <h2 id="doc-modules-agents--h4">12.4 Kill Switches (AUT-009)</h2> |
| <p>Six scopes resolve most-specific-wins; state lives in <code>tenant_config</code> with Redis cache TTL 10 s; the admin endpoint requires reason + role and takes effect before the next tool call. Read-only surfaces (Today, brief) are never disabled by agent kills.</p> |
| <h2 id="doc-modules-agents--h5">12.5 Exception Routing (PRD §8.1)</h2> |
| <div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-xs"/><col class="col-md"/><col class="col-lg"/></colgroup> |
| <thead> |
| <tr> |
| <th>Level</th> |
| <th>Trigger</th> |
| <th>Terminal Route (MVP)</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td data-label="Level">L1</td> |
| <td data-label="Trigger">Retry exhaustion on operational tasks (unreachable, scheduling fail, stock-out)</td> |
| <td data-label="Terminal Route (MVP)">Caregiver prompt (if consented) → <code>blocked</code> persists → weekly exception report; hospital queue only if <code>ops.tier=2</code> (AUT-006 keeps routine ops out of hospital queues).</td> |
| </tr> |
| <tr> |
| <td data-label="Level">L2</td> |
| <td data-label="Trigger">Conflicting sources; transcript vs order; uncertain material fact</td> |
| <td data-label="Terminal Route (MVP)"><code>pending_clinical_decision</code> → previsit brief open-questions; if no appointment within window → configured transition-team endpoint digest.</td> |
| </tr> |
| <tr> |
| <td data-label="Level">L3</td> |
| <td data-label="Trigger">Severe symptom report / explicit help request</td> |
| <td data-label="Terminal Route (MVP)">Immediate approved emergency instructions (911/ED language, versioned per EDU-007) + alert to configured clinical endpoint; never an improvised agent reply.</td> |
| </tr> |
| </tbody> |
| </table></div> |
| <div class="pn"><a class="pn-prev" href="#"></a><a class="pn-next" href="#"></a></div></article> |
|
|