Canon Smart Dispatch¶
Location: inception_recipes/smart_dispatch/
Status: Production
Stack: React + Vite · Node.js/Express · FastAPI · LangGraph · Oracle ADB · OCI GenAI · FastMCP SQLcl
What it does¶
Canon Smart Dispatch is a multi-agent field service system. A caller describes a device issue; the system identifies the matching service record, diagnoses the root cause from Oracle DB, surfaces KB guidance, and recommends a governed next action — all in a single conversational session with full Oracle-backed memory across turns.
Customer → Chatbot UI → Call-intake agent → Memory agent → Contract agent (Gemini 2.5 Pro)
↓
MCP SQLcl → Oracle ADB
↓
Dispatch agent → OCI Fusion Studio
Service Map¶
| Service | Port | Start command |
|---|---|---|
| SQLcl MCP server | 3001 |
uv run python -m src.server_token |
| Python FastAPI agents | 7001 |
uv run python -m src.api.dispatch_api |
| Node.js Express gateway | 7002 |
node server/index.js |
| React UI (dev) | 8000 |
npm run dev |
Authentication¶
Smart Dispatch uses two complementary auth flows depending on the client.
Web UI — IDCS OAuth2 Authorization Code¶
Browser
│ GET /login
▼
IDCS /oauth2/v1/authorize (redirect)
│
▼ callback with ?code=
Node.js /callback
│ exchange code → access_token at IDCS /oauth2/v1/token
│ decode JWT → displayName, sub
│ create server-side session (Map<sessionId, {accessToken, ...}>)
│ set HttpOnly cookie: sd_session=<random 32-byte hex>
▼
Browser (token never in URL or localStorage)
│ subsequent requests carry sd_session cookie automatically
▼
Node.js reads session → forwards Bearer token to Python FastAPI
│
▼
Python contract_agent → MCP SQLcl (BearerAuth)
Key security properties:
- Token stored server-side only — never sent to the browser
- sd_session cookie: HttpOnly, sameSite: lax, secure in production
- CSRF state nonce: random 16-byte hex generated per login, validated and consumed on callback
- Logout: deletes session, invalidates OCI token cache, resets MCP connection, clears cookie
CLI — OCI API Key Token Exchange¶
No browser. The CLI authenticates using the machine's OCI API key (RSA-SHA256 HTTP Signature) and exchanges it for a short-lived IDCS Bearer token.
CLI
│ POST OCI_IDENTITY_TOKEN_URL (signed with RSA-SHA256 API key)
│ grant_type=urn:ietf:params:oauth:grant-type:token-exchange
▼
IDCS returns Bearer token (cached in memory, auto-refreshed 60s before expiry)
│
▼ probe: connect(sidecaradb_high) → ALTER SESSION → SELECT * FROM DUAL
│
▼ "Connected to SIDECARADB_HIGH"
│
▼ REPL ready — token forwarded to workflow on every turn
Run the CLI:
cd inception_recipes/smart_dispatch/smart_dispatch_agents
uv run python -m src.cli.smart_dispatch
# Resume a session
uv run python -m src.cli.smart_dispatch --session <session_id>
# Supply a pre-acquired token (skips API key login)
uv run python -m src.cli.smart_dispatch --token <bearer_token>
REPL commands: exit · new (fresh session) · id · logout (clears token cache)
LangGraph Dispatch Workflow¶
All business logic in src/agent_workflows/dispatch_workflow.py. Built once at startup, reused on every turn.
START
│
▼
call_intake_node (llama-4-scout-17b)
│ Conversational chat · extract CallIntakeEntity · merge-save to OracleStore
│ Load dispatch_confirmation_pending from OracleStore("dispatch_state",)
│
├─ dispatch_confirmation_pending=True + affirmative reply → dispatch_agent_node
├─ dispatch_confirmation_pending=True + decline/short → farewell_node
└─ entity incomplete → END (ask for more info)
│
▼ entity complete
memory_agent_node (llama-4-scout-17b)
│ Load CallIntakeEntity + DispatchEntity from OracleStore
├─ HIT → answer from memory (~2s) → append dispatch question → END
└─ MISS → contract_node
│
▼
contract_node (gemini-2.5-pro · deep_research_agent)
│ MCP SQL lookup (up to 20 calls)
│ Extract DispatchEntity · save to OracleStore("dispatch_research",)
│ Append dispatch question · set dispatch_confirmation_pending=True
│
END
── HITL (next turn) ────────────────────────────────────────
dispatch_agent_node → invoke_fusion_async() + simulate_dispatch_action() → END
farewell_node → session summary → END
Agent Details¶
| Agent | Model | Pattern | Role |
|---|---|---|---|
| Call-Intake | llama-4-scout-17b |
create_oci_agent |
Conversational triage, entity extraction |
| Memory | llama-4-scout-17b |
Single LLM call | Cache hit/miss router (~2s) |
| Contract | gemini-2.5-pro |
create_deep_research_agent |
Deep MCP SQL research, SKILL.md |
| Dispatch | llama-4-scout-17b |
Direct Python calls | Fusion Studio ping + dispatch report |
All agents built as singletons at startup to eliminate 15-30s per-turn init overhead.
Memory Architecture¶
| Store | Type | Namespace | Contents |
|---|---|---|---|
OracleDBSaver |
Short-term | keyed by session_id |
Full LangGraph message history |
OracleStore |
Long-term | ("sessions",) |
CallIntakeEntity per session |
OracleStore |
Long-term | ("dispatch_research",) |
DispatchEntity after DB research |
OracleStore |
Long-term | ("dispatch_state",) |
HITL dispatch_confirmation_pending flag |
OracleStore |
Long-term | ("filesystem",) |
Agent intermediate reasoning (StoreBackend) |
MCP SQLcl Integration¶
All DB access routes through inception_mcp_servers/mcp_sqlcl/ on :3001 — neither the Node.js gateway nor the Python agents hold direct DB connections for queries.
The Node.js gateway acquires MCP tokens via OCI API key exchange (natively, using crypto.createSign RSA-SHA256). Python agents receive the Bearer token from the session store and pass it to FastMCPClient(BearerAuth(token)).
On connection:
1. connect(sidecaradb_high, confirm_switch=True)
2. ALTER SESSION SET CURRENT_SCHEMA = SELECT_AI_USER
3. SELECT * FROM DUAL ← liveness probe
Frontend¶
src/App.jsx
├─ /api/session (on mount) → {loggedIn, displayName, sub}
├─ AP Response Data panel → /api/signal-intake + /api/dispatch-control (MCP SQL)
└─ Customer Support Chatbot → POST /api/chatbot (session cookie sent automatically)
server/index.js (Node.js Express :7002)
├─ GET /login → IDCS redirect (CSRF state nonce)
├─ GET /callback → token exchange → server-side session → HttpOnly cookie → redirect /
├─ GET /api/session → {loggedIn, displayName} (no token)
├─ GET /api/health → MCP probe + connectionVerified
├─ GET /api/signal-intake → MCP execute_sql → paginated signal rows
├─ GET /api/dispatch-control → MCP execute_sql → dispatch map
├─ POST /api/chatbot → reads token from session store → Python FastAPI :7001
└─ GET /logout → clear session + OCI cache + MCP connection + cookie
Startup Sequence¶
# 1 — SQLcl MCP server
cd inception_mcp_servers/mcp_sqlcl
uv run python -m src.server_token
# 2 — Python FastAPI agents (~30-60s first-time build)
cd inception_recipes/smart_dispatch/smart_dispatch_agents
uv run python -m src.api.dispatch_api
# 3 — Node.js gateway
cd inception_recipes/smart_dispatch/smart_dispatch_ui
node server/index.js
# 4 — React UI (dev)
npm run dev
# → open http://localhost:8000
Key Design Decisions¶
| Decision | Rationale |
|---|---|
| Server-side session + HttpOnly cookie | Token never in URL, localStorage, or JS — follows production security standards |
| OCI API key token exchange for CLI | No browser interaction; asymmetric key auth avoids storing client secrets |
| Memory agent as first-pass router | Eliminates 60-120s DB research for follow-up questions; store hit answers in ~2s |
ContractAgentBundle singleton |
create_deep_research_agent init takes 15-30s; built once at startup |
| Dispatch runs direct Python, not agent loop | ReAct tool-calling loop takes 90s+; direct calls reduce latency to ~3s |
dispatch_confirmation_pending in OracleStore |
No per-session graph checkpointer — HITL flag must survive across HTTP turns |
| RECORD_ID resolved before LLM | Prevents hallucinated IDs and redundant MCP queries on follow-up turns |
Full Architecture Reference¶
For the complete LangGraph state machine, memory namespaces, create_deep_research_agent middleware stack, and SKILL.md injection flow, see the canonical architecture document at: