Back to Notes

AskTGE — System Design Document

Team Global Express · AI Centre of Excellence How the four interconnected services that make up the AskTGE platform work together. Last mapped: 2026-06-10


1. Purpose & Scope

AskTGE is TGE's enterprise multi-agent AI platform. What users call "AskTGE" is not one app — it is a constellation of four independently-deployed services sharing one AWS account, one Azure AD tenant, and (critically) one AgentCore Memory store.

This doc maps the four repos, what each does, how they talk, and the data/identity that binds them.

Repo (local dir)RoleRuntime / Deploy
asktgeUser-facing web hub + orchestratorFlask + Gunicorn (eventlet), ECS Fargate
multiagent-backendThe actual conversational agent ("multipurpose"/OutlookAgent/Documind/TenderIQ)LangGraph on AWS Bedrock AgentCore
datamateSQLMatic — NL→SQL agent over Redshift via MCPLangGraph on AWS Bedrock AgentCore
historyapiRead/manage chat historyAsync FastAPI, ECS Fargate

All four live under GitLab gitlab.com/teamglobalexp/tge_ai_agents/*.


2. System Context (C4 Level 1)

                         ┌──────────────────────┐
                         │   TGE Employee        │
                         │   (browser)           │
                         └──────────┬───────────┘
                                    │ HTTPS + Azure AD SSO (OAuth2/PKCE)
                                    ▼
                         ┌──────────────────────┐
                         │       asktge          │   ← Flask hub / orchestrator
                         │  (routes→services)    │     SSE streaming to browser
                         └───┬───────┬───────┬───┘
            invoke_client.py │       │ history_client.py
                             │       │       │
        ┌────────────────────┘       │       └──────────────────┐
        ▼                            ▼                          ▼
┌───────────────┐          ┌──────────────────┐       ┌─────────────────┐
│ multiagent-   │          │   historyapi     │       │   datamate       │
│ backend       │          │  (FastAPI/ECS)   │       │  (SQLMatic)      │
│ AgentCore     │          │  reads history   │       │  AgentCore       │
│ LangGraph     │          └───────┬──────────┘       │  + Redshift MCP  │
└──────┬────────┘                  │                  └────────┬────────┘
       │ writes turns              │ reads turns               │ SQL
       ▼                           ▼                           ▼
┌─────────────────────────────────────────────┐      ┌─────────────────┐
│  Shared AWS data plane:                      │      │ Amazon Redshift  │
│  • AgentCore Memory  (memory_*_env-…)        │      │ Serverless       │
│  • DynamoDB (sessions/turns)                 │      └─────────────────┘
│  • Bedrock KBs + Guardrails + S3 citations   │
└─────────────────────────────────────────────┘

The binding fact: asktge, multiagent-backend, and historyapi all set the same AGENTCORE_MEMORY_ID per environment:

  • Production: memory_production_env-3N0XlR7T10
  • Staging: memory_staging_env-Px3te82OhR

multiagent-backend writes conversation turns into that memory; historyapi reads them back; asktge orchestrates both. That shared memory ID is the spine of the whole platform.


3. The Four Services

3.1 asktge — Web Hub & Orchestrator

Stack: Flask 3.1.3, Flask-SocketIO, Flask-Session (filesystem), Gunicorn eventlet workers, Python 3.10+. Docker → ECS Fargate.

Architecture: strict layering — Routes (Blueprints) → Services → Utils/Tools → External clients. Lazy init of heavy clients; background warmup threads post-startup.

Route blueprints (routes/):

  • auth_routes — /login /logout /callback /refresh (Azure AD OAuth2/PKCE)
  • chat_routes — /chat /stream /send_message (SSE streaming to browser)
  • file_routes — /upload /process_file
  • history_routes — /history /load_session
  • feedback_routes — /feedback /analytics
  • utility_routes — /health /version /config
  • simple_download_routes — /download

Service layer (services/): ChatService (AI orchestration), NewChatStorageService (persistence/retrieval).

Two outbound clients are the integration seams:

ClientTalks toEndpoint(s)
invoke_client.pythe agent backends…sqlmatic…elb…/documind/api/invoke; https://tge-ai.awsagreenspace.local/multipurpose-agent/api/invoke
history_client.pyhistoryapiinternal-AskTGE-HistAPI-{env}-…elb…:{8512 prod / 8812 staging}

invoke_client.invoke_agent(...) posts a payload {sessionId, userId, agentId, prompt|messages, kb_id} to the agent /invoke endpoint. agentId is the router key — "Documind", "OutlookAgent", "TenderIQ", etc. select behaviour inside the AgentCore backend.

Agent registry (config/agents.py): dataclass AgentConfig registry (AskTGE, DataMate, AskLegal, Documind, TenderIQ, OutlookAgent, Nick Persona…). Visibility filtered per-request by AD group (Nick Persona is restricted). KB IDs live in config/knowledge_bases.py (Documind 0PKBENEOJZ, TenderIQ WAZKC2CY69, SQLMatic B5YEKE26GQ).

Local heavy lifting done in-process (not in the agents): document ingest (PDF/OCR, DOCX, XLSX, PPTX, images), invoice analysis, PII redaction, document/Excel/PPT/board-memo generation, SerpAPI search, Mermaid ERD from DataMate results.


3.2 multiagent-backend — The Conversational Agent

This is the "AI" behind AskTGE, Documind, TenderIQ, OutlookAgent. Despite the dir name (origin: outlook-agent repo), app.py builds a BedrockAgentCoreApp — i.e. it is deployed on AWS Bedrock AgentCore, invoked via the AgentCore runtime / the /multipurpose-agent/api/invoke and /documind/api/invoke paths that asktge calls.

Stack: LangGraph + LangChain, bedrock_agentcore SDK, boto3, Claude Sonnet 4 (BEDROCK_MODEL_ID), Python.

Module map:

  • ai/graphs.py — LangGraph graph builder (get_graph), LLM factories (_get_llm, _get_llm_fast); ai/spreadsheet_deepagent.py — deep spreadsheet sub-agent; ai/prompts.py, ai/models.py (+ observability).
  • event_processing/ — event_parser (LangGraphEventParser), event_formatter (ConsumerEventFormatter), response_accumulator (StreamingResponseAccumulator). Turns LangGraph astream_events into SSE for asktge.
  • storage/memory.py — _load_short_term, _store_turn, _store_complete_response, _summarize_old_turns → writes to AgentCore Memory (AGENTCORE_MEMORY_ID). storage/dynamodb.py — _store_session_metadata.
  • tools/ — kb_search (Bedrock KB retrieve + _generate_presigned_url for S3 citations), deep_extraction, rate_limiter (internet search throttle).
  • Guardrails: utils/error_handling detects/extracts Bedrock Guardrail interventions → user-friendly refusal messages.

Notable behaviour: citation enrichment (_enrich_citations_async) runs after the token stream so presigned-URL S3 calls never block streaming; parallelised via asyncio.gather + asyncio.to_thread. Config: AVAILABLE_AGENT_IDS, CONTEXT_TURNS/OUTLOOK_CONTEXT_TURNS, CONTEXT_CHAR_LIMIT, DEEP_EXTRACTION_ENABLED.


3.3 datamate — SQLMatic (NL→SQL over Redshift)

Stack: LangGraph agent (datamate/agent/ mirrors multiagent-backend's layout) on Bedrock AgentCore, Claude 4 Sonnet. Plus a separate MCP layer (datamate/mcp/): my_redshift_mcp + semantic-mcp, FastMCP servers.

How it queries data (MCP_REDSHIFT_INTEGRATION.md):

User question → LangGraph agent → LangChain tools (4 Redshift tools)
   → HTTP MCP client (JSON-RPC 2.0) → MCP Redshift server (FastMCP)
   → Amazon Redshift Serverless

Tools: redshift_list_schemas(), list tables, describe columns, run read-only SQL. Agent chains them; read-only safety enforced. Has its own cost/ module (bedrock_pricing.json + calculator) and ENCRYPTION_IMPLEMENTATION.md.

Deployment artifacts (top-level JSONs) describe the AgentCore deployment:

  • artifact.json — ECR container …/datamate/agent:<sha>
  • network.json — VPC mode, sg sg-0ac5853078b7ff59c, two subnets
  • authorizer.json — custom JWT authorizer = Azure AD (tenant 0767fb79-…, audience 77ca29af-…)
  • headers.json — allowlist Authorization
  • protocol.json — HTTP

asktge reaches SQLMatic via the internal-sqlmatic-…elb… load balancer; results feed asktge's Mermaid ERD / chart generation (KB B5YEKE26GQ). Schema references for the underlying DBs sit in asktge/DB_Schema_Files/ (Goldhopper, MYTEAMGE, Rockhopper_NSW, TAPS, TED-EDS, TED-RDS).


3.4 historyapi — Chat History Service

Stack: async FastAPI (AgentCore History API - ASYNC), aioboto3 shared session via lifespan, structlog with per-request correlation IDs. Docker → ECS, ports 8512 (prod) / 8812 (staging).

Routers:

  • api/routes/history.py (prefix=/history) — v2 + v1 endpoints:
    • GET /history/v2/user/session, …/v2/user/sessions/summary
    • DELETE /history/v2/user/session, PATCH /history/v2/user/session/rename
    • v1 legacy: /history/user/session, /user/agent, /user, /user/recent
  • api/routes/admin.py — admin ops.

Services: memory_service (AgentCore Memory client — reads the same AGENTCORE_MEMORY_ID), dynamodb_service, encryption_service + local_encryption_service, secure_history_service. Starts in degraded mode if AGENTCORE_MEMORY_ID unset (memory client test non-fatal).

asktge/history_client.py consumes these (TTL-cached 5 min, USE_V2_ENDPOINT=true by default) to render the history panel and restore sessions.


4. Identity & Trust Boundary

One Azure AD tenant 0767fb79-566f-449b-a7eb-c665c261bc2e secures everything:

  • Browser → asktge: OAuth2 PKCE interactive SSO (auth_module.py), server-side encrypted Flask sessions, auto token refresh.
  • asktge → datamate (AgentCore): custom JWT authorizer validates Azure AD token (audience 77ca29af-…), only Authorization header passed through.
  • asktge → multiagent-backend: AgentCore runtime invoke (ARN via AGENTCORE_ARN / AGENTCORE_ARN_STAGING).
  • AD group gating: restricted agents (Nick Persona) filtered in get_visible_agents().
  • Internal ECS traffic over *.elb.amazonaws.com / *.awsagreenspace.local (HTTP, self-signed — SSL warnings disabled in clients), inside VPC.

5. End-to-End Request Flows

5.1 General chat (Documind / OutlookAgent / TenderIQ)

1. Browser POST /stream (asktge chat_routes)        [SSE opened]
2. @login_required → session token validated
3. ChatService builds prompt; files already PII-redacted & extracted locally
4. invoke_client.invoke_agent(agentId, sessionId, userId, prompt, kb_id)
5. multiagent-backend (AgentCore): LangGraph runs
      → KB retrieve (Bedrock) + tools + Guardrails
      → astream_events → SSE tokens back to asktge → browser (word-by-word)
6. Backend storage/memory: _store_turn / _store_complete_response → AgentCore Memory + DynamoDB
7. Citations enriched async (S3 presigned) after stream completes
8. On-demand: asktge generates DOCX/PDF/XLSX/PPT if download intent detected

5.2 DataMate (NL→SQL)

1. Browser selects DataMate agent → asktge
2. invoke to SQLMatic ELB (Azure AD JWT)
3. datamate LangGraph → Redshift MCP tools (JSON-RPC) → Redshift Serverless
4. Rows returned → asktge tools/ build Mermaid ERD + bar/pie charts
5. Streamed back via SSE

5.3 History load / restore

1. Browser opens history panel → asktge history_routes
2. history_client GET /history/v2/user/sessions/summary (TTL-cached)
3. historyapi reads AgentCore Memory + DynamoDB (shared memory ID), decrypts
4. Session restore → resume conversation context

6. Shared State & Coupling Map

Shared resourceProducerConsumer(s)Coupling type
AgentCore Memory (memory_*_env)multiagent-backend (write)historyapi (read), asktgetight, implicit
DynamoDB sessions/turnsmultiagent-backendhistoryapi, asktgetight
Azure AD tenant—all fouridentity
Bedrock KBs + GuardrailsBedrockmultiagent-backend, asktgeconfig (KB IDs)
agentId contractasktge (sends)multiagent-backend (routes)API contract
Redshiftdatamate MCPdatamate onlyisolated

Risk note: the AgentCore Memory ID coupling is the platform's biggest hidden dependency — it is wired only through matching .env values across three repos. A drift between asktge / multiagent-backend / historyapi memory IDs silently breaks history without breaking live chat. Worth a single sourced config or a startup assertion.


7. Deployment Topology

ServiceBuild → Run
asktgeDockerfile → ECS Fargate; behind ALB; Gunicorn eventlet
historyapiDockerfile → ECS Fargate; ALB ports 8512/8812
multiagent-backendDockerfile → Bedrock AgentCore runtime (ARN)
datamate (agent)ECR image → Bedrock AgentCore (VPC, JWT authz)
datamate (mcp)FastMCP server(s) in VPC, reached over HTTP/JSON-RPC

Two staging/prod splits everywhere via ENVIRONMENT env var, separate ELBs, separate memory IDs, separate AgentCore ARNs.


8. Open Questions / Things To Verify

  • invoke_client default base_url is the sqlmatic ELB /documind/api, yet code also calls tge-ai.awsagreenspace.local/multipurpose-agent/api/invoke. Confirm which agents route through which ELB in prod (paths appear co-located behind one LB).
  • multiagent-backend/config/settings.py default memory ID (memory_56vzf-b1iQHU78Br) differs from the .env values — confirm .env always overrides in deployed envs.
  • historyapi only exposes GET/DELETE/PATCH — turn writes happen exclusively in multiagent-backend. Confirm no other writer.
  • datamate top-level README.md is the GitLab stub; real docs live in agent/README_MODULAR.md, agent/AI_GRAPHS_DOCUMENTATION.md, agent/MCP_REDSHIFT_INTEGRATION.md.

Appendix A — Endpoint Quick Reference

From → ToURL
asktge → DataMate (prod)internal-sqlmatic-1910921802.ap-southeast-2.elb.amazonaws.com
asktge → multipurpose agenthttps://tge-ai.awsagreenspace.local/multipurpose-agent/api/invoke
asktge → historyapi (prod)internal-AskTGE-HistAPI-production-369019032.ap-southeast-2.elb.amazonaws.com:8512
asktge → historyapi (staging)internal-AskTGE-HistAPI-staging-565808081.ap-southeast-2.elb.amazonaws.com:8812
Bedrock AgentCoreARN via AGENTCORE_ARN / AGENTCORE_ARN_STAGING

Appendix B — Knowledge Base IDs

AgentKB ID
Documind0PKBENEOJZ
TenderIQWAZKC2CY69
SQLMaticB5YEKE26GQ