Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ replay_pid*
.project
__pycache__
/runtime

# Python venvs (added for local mock_mcp_server + bank-website)
.venv/
venv/
*.egg-info/
286 changes: 286 additions & 0 deletions bank-website/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# Camunda Bank — Demo Website + Local Chat Channel

Fake retail banking website with an embedded AI support chat, wired to
Bernd Ruecker's [`bank-support-agent-demo`](https://github.com/berndruecker/bank-support-agent-demo)
running on a local Camunda 8 (`c8run`) cluster. Replaces Bernd's hosted Cloud Run
chat admin so the demo runs fully offline (no shared cloud dependency).

![Camunda Bank dashboard with chat widget showing live BPMN trace](docs/screenshot.png)

---

## What's in here

```
bank-website/
├── main.py FastAPI backend (port 9400)
├── static/
│ ├── index.html Bank dashboard + chat widget
│ ├── style.css Fintech theme + animations
│ └── app.js Chat + SSE wiring + live trace panel
├── requirements.txt fastapi, uvicorn[standard], httpx, sse-starlette
└── README.md (this file)
```

The website:

1. **Renders a banking dashboard** for "Bernd Ruecker" (customer id `42`,
accounts `CHK042` / `SAV042` / `CC042`).
2. **Floats a chat bubble** ("Need help?"). Clicking opens a real chat panel.
3. **On send** publishes a Camunda message
(`io.camunda.chat.message.<channelUUID>`) with
`correlationKey = conversationUUID` → triggers `banking-support-agent` BPMN.
4. **Receives the BPMN reply task's PUT** at
`/api/channels/{channelUUID}/conversations/{conversationUUID}/messages/{messageId}`,
pushes the message to the browser via Server-Sent Events.
5. **Streams a live BPMN process trace** on the right side of the chat
(polls `POST /v2/element-instances/search` every 1.5s and emits each
element instance state change as an SSE event).

## Architecture

```
┌─────────────────────────┐ HTTP + SSE ┌────────────────────────────────┐
│ Browser (bank.local UI) │ ──────────────▶ │ bank-website (FastAPI :9400) │
│ HTML + vanilla JS │ ◀────────────── │ /api/chat/send │
│ │ SSE replies │ /api/chat/{cid}/stream │
└─────────────────────────┘ │ /api/chat/{cid}/trace │
│ PUT /api/channels/…/messages/…│ ◀┐
└──────────┬─────────────────────┘ │
│ REST │
▼ │
┌──────────────────────────────┐ │ HTTP from BPMN
│ Camunda c8run :8080 / :26500 │ │ chat reply task
│ Zeebe + connector runtime │ ──┘
│ banking-support-agent.bpmn │
│ → AWS Bedrock (Sonnet 4.5) │
│ → MCP localhost:9201 │
│ → A2A localhost:8081 │
│ → Cloud A2A credit-card │
└──────────────────────────────┘
```

## Prerequisites

| Tool | Version | Notes |
|----------------------|---------------------|------------------------------------------------------|
| c8run | 8.9.1 | Running on `localhost:8080` (Operate), `:26500`, `:8086` |
| `c8ctl` CLI | `≥3.0.0-alpha.1` | `npm install -g @camunda8/cli@3.0.0-alpha.1` |
| Java | 21 | For the A2A connector jar |
| Maven | 3.9+ | To build the A2A connector |
| Python | 3.12 | For this site + the mock MCP server |
| AWS Bedrock | — | Real keys needed; Claude Sonnet 4.5 enabled in `us-east-1` |

## One-time setup

### 1. Bank-website venv

```bash
cd ~/Documents/1_Development/bank-website
python3.12 -m venv .venv
.venv/bin/pip install fastapi 'uvicorn[standard]' httpx sse-starlette
```

### 2. Mock MCP server (account management tools)

```bash
cd ~/Documents/1_Development/bank-support-agent-demo/python-agents/account-management-mcp
python3.12 -m venv .venv
.venv/bin/pip install mcp 'uvicorn[standard]'
```

### 3. A2A connector

```bash
cd ~/Documents/1_Development/bank-support-agent-demo/a2a-connector
mvn -B -q package
```

### 4. Camunda secrets (in `c8run` env)

The connector runtime expects `SECRET_*` env vars. Stop c8run, then:

```bash
export SECRET_AWS_BEDROCK_ACCESS_KEY=AKIA…
export SECRET_AWS_BEDROCK_SECRET_KEY=…
# Stubs so unused connectors don't error on missing secrets:
export SECRET_AWS_OPEN_SEARCH_ACCESS_KEY=skipped
export SECRET_AWS_OPEN_SEARCH_SECRET=skipped
export SECRET_AWS_OPEN_SEARCH_REGION=us-east-1
export SECRET_AWS_OPEN_SEARCH_URL=skipped
export SECRET_POSTGRESQL_MASTER_PASSWORD=skipped
export SECRET_GMAIL_INBOX_USER=skipped@example.com
export SECRET_GMAIL_INBOX_PASSWORD=skipped
export SECRET_CHAT_BACKEND_SERVICE_TOKEN=skipped
export SECRET_TAKTILE_API_KEY=skipped
export SECRET_TAKTILE_API_ROOT_URL=skipped
export SECRET_TAKTILE_ENVIRONMENT_URL_PART=skipped

cd ~/Documents/c8run-8.9.1 && ./start.sh
```

### 5. BPMN patches already applied to local clone

We patched the cloned `bank-support-agent-demo/camunda-solution/*.bpmn` so the
demo runs locally without external services:

| Patch | Why |
|---------------------------------------------|----------------------------------------------|
| `apiBaseUrl` → `http://localhost:9400/api/` | Replace Bernd's hosted Cloud Run chat admin |
| `Task_LoadCustomerMasterData` → `noop` | No Postgres RDS available |
| Model id → `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | Sonnet 3.5 v1 was retired |
| Removed `topP` parameter | Sonnet 4.5 rejects `temperature + topP` |
| MCP element-template → `data.transport.sse.url` + `data.connectorMode.toolOperation.*` | Schema change in c8run 8.9 |
| MCP server URL → `http://localhost:9201/sse`| Local FastMCP mock |
| Removed `Ask for applause` user task | Not needed for demo |
| Loan-agent vector + JDBC tasks → `noop` with `toolCallResult = []` | No AWS OpenSearch / Postgres |

Redeploy after any patch: `c8ctl deploy camunda-solution/*.bpmn`.

## Run

Three terminals, then a browser.

### Terminal 1 — Bank website (this repo)

```bash
cd ~/Documents/1_Development/bank-website
.venv/bin/python main.py
```

Serves at <http://localhost:9400/>.

### Terminal 2 — Mock MCP server

```bash
cd ~/Documents/1_Development/bank-support-agent-demo/python-agents/account-management-mcp
.venv/bin/python mock_mcp_server.py
```

Serves SSE at <http://localhost:9201/sse>.

### Terminal 3 — A2A connector (Java)

```bash
cd ~/Documents/1_Development/bank-support-agent-demo
java -jar a2a-connector/target/a2a-connector-runtime-0.0.1.jar
```

Health at <http://localhost:8081/actuator/health>.

### Browser

- **Bank**: <http://localhost:9400/>
- **Operate** (side-by-side, real-time process view): <http://localhost:8080/operate>
— credentials `demo` / `demo`. Search for the `banking-support-agent` process.

## Demo storyboard

Open the bank tab on one screen, Operate (showing `banking-support-agent` v6+)
on the other. In the chat widget, toggle the trace panel (`▦` icon top-right
of the chat header) to see BPMN element events stream live alongside the
conversation.

### Sample query 1 — Account inquiry (MCP path)

```
What are my account details? Customer id 42
```

Expected flow:

- `Task_ChatThinkingUpdate` → "Thinking..." appears in chat
- `Task_LoadCustomerMasterData` (noop) → completes instantly
- `AI_CustomerSupportAgent` → Bedrock decides to call `CallActivity_AccountSupportAgent`
- Child `account-support-agent` starts
- `Task_MCP_AccountManagementTools` → real SSE MCP call to `localhost:9201`
- Mock MCP returns Bernd's profile + 3 products (checking / savings / credit card)
- Agent composes a structured reply with balances and routing numbers
- Reply renders in chat. Trace shows all elements completed.

### Sample query 2 — Lost credit card (A2A path)

```
Please report my credit card 5664 as stolen for quick replacement, and possibly store a file somewhere
```

Expected flow:

- Bank agent recognizes card-related intent
- Calls `Tool_A2A_CreditCardAgent` (real A2A call to the hosted credit-card-loss
agent at `demo-credit-card-support-a2a-…run.app`)
- Card agent performs: freeze, file lost report, order express replacement
→ returns structured action list to bank agent
- Bank agent sees the "store a file" part doesn't match any tool → routes to
`Tool_OtherInquiry` → spawns user task `UserTask_HandleOtherInquiries`
(visible in Tasklist at `localhost:8080/tasklist`)
- Trace panel shows: `Card Support Agent (A2A Client) COMPLETED` and
`Other inquiries ACTIVE`
- Reply: agent confirms the card was reported, asks a clarification about the
file storage request

### Sample query 3 — Loan inquiry (vector-search stubbed)

```
I'd like to apply for a personal loan of €5,000 for 24 months
```

Expected flow:

- Bank agent routes to `CallActivity_LoanSupportAgent`
- Loan agent reasons via Bedrock
- Knowledge-base search tools (`embeddings-vector-database` + `connector-jdbc`)
are stubbed to `noop` returning `[]` — they no longer fail with
"security token included in the request is invalid"
- Agent gets empty knowledge results → reasons about it → asks the customer
for confirmation before starting the application (per system prompt's
Offer → Apply gate rule)
- For a polished demo: pre-load the OpenSearch index with loan terms and
remove the stubs.

### Bonus — Hello (clarification path)

```
Hi
```

Triggers the routing prompt's "ambiguous" branch — agent calls
`Tool_AskCustomer`, sends a clarifying question, BPMN waits at
`Event_WaitForChat` until the user replies. Demonstrates the real
back-and-forth correlation via `conversationUUID`.

## What's real vs mocked

| Component | Real / Mocked |
|------------------------------------------|--------------------------|
| Zeebe + connector runtime (c8run 8.9.1) | Real |
| AWS Bedrock (Claude Sonnet 4.5) | Real — billed API |
| AI Agent connector | Real Camunda OOTB connector |
| MCP remote-client connector | Real (`mcpremoteclient:1`) |
| A2A Java connector | Real (built from repo) |
| Credit-card A2A agent | Real (hosted Cloud Run) |
| Message correlation | Real Zeebe message subscriptions |
| Process trace | Real `/v2/element-instances/search` polling |
| Bank-website backend + frontend | Real (this repo) |
| Customer data + accounts (via MCP) | **Mocked** — hardcoded in `mock_mcp_server.py` |
| `Task_LoadCustomerMasterData` (Postgres) | **Mocked** — `noop` |
| Loan vector search + Postgres memory | **Mocked** — `noop` returning `[]` |
| Bank dashboard balances / transactions | **Mocked** — static HTML |

## Limitations

- Loan agent has no real knowledge base; tools return empty so the agent
always falls back to "Ask the customer" or "Escalate to specialist".
- The applause user task was removed from `account-support-agent`.
- Each `c8ctl create pi` or chat message-publication starts a **new**
`banking-support-agent` instance unless an `Event_WaitForChat` is currently
subscribed for the conversation. For a continuous multi-turn demo, phrase
the first message so the agent calls `Tool_AskCustomer` (e.g. "Hi" or
anything ambiguous).
- In-memory conversation state — server restart loses history.
- No authentication on `:9400`. Do not expose publicly.

## License

Demo code. Bernd's underlying `bank-support-agent-demo` is licensed separately
(see `bank-support-agent-demo/LICENSE`).
Loading