Skip to content

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:

inception_recipes/smart_dispatch/docs/architecture.md