Session Isolation
Per-Session Draft Model
Section titled “Per-Session Draft Model”Drafts are session-scoped. Each verify_member call creates a fresh draft for that session. There is no shared member index.
verify_memberalways creates a newBookingDraftkeyed bySessionID- No
FindByMemberon theDraftRepositoryinterface – drafts are only retrievable by session - Two agents verifying the same member get completely separate sessions and drafts
ExistingDraftin theverify_memberresponse is alwaysnil(no cross-session leakage)
Session Shape (v2.0 additions)
Section titled “Session Shape (v2.0 additions)”The Session struct in internal/domain/session.go was extended in v2.0 to carry the post-confirm snapshot needed by send_confirmation. These fields are populated by confirm_trip on success and read by send_confirmation to authorize the follow-up email:
| Field | Set by | Used by | Purpose |
|---|---|---|---|
LastConfirmedDraft |
confirm_trip |
send_confirmation |
By-value snapshot of the booking draft at the moment of successful submission. The ONLY source of truth for the email contents. |
LastTripIDs |
confirm_trip |
send_confirmation |
Trip UUID allowlist (one per leg for roundtrips). |
LastFriendlyIDs |
confirm_trip |
send_confirmation |
Human-readable IDs paired 1:1 with LastTripIDs. |
IsStandingOrder |
confirm_trip |
send_confirmation |
Switches email rendering between single-trip and schedule + PDF templates. |
StandingOrderID |
confirm_trip |
send_confirmation |
Allowed value for the standing_order_id arg. |
StandingOrderMeta |
confirm_trip |
send_confirmation |
Schedule details (days, dates, appointment time) so the renderer doesn’t depend on API trip propagation lag. |
send_confirmation rejects any call where the supplied trip_id / standing_order_id is not in the session’s allowlist — this is the cross-session PHI leak guard. A session that hasn’t run confirm_trip cannot dispatch confirmation emails.
Triple Ownership Check
Section titled “Triple Ownership Check”Ownership validation is enforced at the repository layer – FindByID requires a DraftFindInput struct with SessionID, MemberID, and OrgID. This ensures no code path can access a draft without ownership verification. The shared LoadSessionAndDraft function delegates to the repo, which validates automatically.
| Field | Source | Purpose |
|---|---|---|
SessionID |
MCP session | Ties draft to the agent session that created it |
MemberID |
Draft record | Ensures draft belongs to the correct member |
OrgID |
Auth context | Prevents cross-tenant access |
Optimistic Locking
Section titled “Optimistic Locking”Drafts carry a Version field (incremented on each write). Concurrent modifications to the same draft are detected with a 3-attempt retry loop – transient version conflicts are resolved automatically before surfacing errors to the caller.
Atomic Session Updates
Section titled “Atomic Session Updates”SetDraftID uses optimistic locking with verify-after-write to eliminate race conditions. Session objects carry a Version field for concurrency control. Session ID length is validated on both Create and Get paths (max 100 characters).
Stateless MCP Transport
Section titled “Stateless MCP Transport”StreamableHTTPOptions{ Stateless: true, // No in-memory session storage}The SDK does not store transport-level sessions in memory. This solves multi-pod “session not found” errors (Pod A creates session, Pod B cannot find it). All application state (sessions, drafts) is stored in Valkey, shared across all pods.
Multi-Agent Safety
Section titled “Multi-Agent Safety”Multiple AI agents can book trips for the same member simultaneously without interference:
- Each agent gets its own
SessionID(UUID v4) fromverify_member - Each session has its own
BookingDraftin Valkey - Draft ownership (
SessionID + MemberID + OrgID) enforced at the repository layer –FindByIDvalidates automatically confirm_triponly deletes the calling session’s draft – other sessions continue independently- No
FindByMemberexists – drafts cannot be discovered across sessions
Agent A booking a dialysis trip and Agent B booking a PT trip for the same member will never collide.
Security Layers
Section titled “Security Layers”Request | +-- Bearer token auth (SHA-256 hashed, Valkey-cached) +-- UUID v4 session IDs (128-bit random, unguessable, ≤100 chars validated) +-- Draft ownership validation at repo layer (SessionID + MemberID + OrgID on every FindByID) +-- Optimistic locking with retry (Version field on drafts + sessions) +-- Atomic SetDraftID (optimistic verify-after-write, no race condition) +-- No cross-session draft access (no FindByMember) +-- HIPAA audit logging on all tool invocations