12 KiB
AGENTS.md
Guidance for Codex working in this repo. Read this first.
What this is
A demo (not production) of a reversible PII de-identification round trip for HNCB: advisor query with a real name → tokenized before it leaves for the cloud → Amazon Bedrock AgentCore agent reasons on tokens only → identity restored on-prem before the advisor sees the answer. Everything runs in one AWS account; "on-prem" is a logical, tag-labelled zone standing in for the branch data centre. The demo proves data-flow behaviour, not physical residency.
Critical context
- Fusion is a shared SaaS instance. DO NOT build, host, or Terraform it. It is
configured in the Amplify AI Gateway console (see
fusion/POLICY_SETUP.md). Because it is SaaS, anything it calls must be a public HTTPS endpoint with auth — it cannot reach private VPC resources or use local AWS creds. - This means the AWS side's job is to expose two endpoints Fusion will call:
/tokenize(ingress) and/restore(egress), plus host the detector, vault, RAG tool, and agent. See the task backlog below. - Detection ≠ redaction. The detector returns typed findings; our code owns minting tokens, writing the vault, and restoring. Never redact-and-discard.
- Tokens must be random per request and stored in the vault keyed by session.
Architecture (maps to the customer's 8 steps)
Advisor UI → Fusion SaaS → [/tokenize: detect (Presidio) + mint + vault-write]
→ AgentCore Runtime (Bedrock model) → tool call via AgentCore Gateway → RAG Lambda
(resolve token → de-identified evidence) → agent talking points → Fusion SaaS
→ [/restore: vault lookup] → Advisor UI.
Repo map
terraform/ DynamoDB (vault + customers), RAG Lambda, IAM, Presidio hosting
lambda_rag/ RAG tool (real): token resolve -> evidence package
gateway_api/ TODO: /tokenize and /restore HTTPS endpoints Fusion calls (T1, T2)
agent/ Strands agent for AgentCore Runtime + tool schema
presidio/ detector service (typed findings) + Dockerfile
seed/ fake customer (Wang Xiaoming) + seed script
ui/ advisor UI (restored-vs-tokenized split view)
scripts/ deploy.sh, agentcore_setup.sh, teardown.sh
fusion/ POLICY_SETUP.md (SaaS console config — human does this, not you)
Commands
- Deploy core infra:
bash scripts/deploy.sh(review first) - Seed data:
VAULT_TABLE=... CUSTOMERS_TABLE=... python seed/seed.py --with-demo-token - AgentCore:
bash scripts/agentcore_setup.sh(verify againstagentcore --help) - Teardown (do this to stop spend):
bash scripts/teardown.sh - Terraform: run all
terraformcommands with-chdir=terraform.
Conventions & guardrails
- Region default
us-east-1(best Bedrock/AgentCore quotas). Keep it a variable. - Never commit AWS creds, tokens, or real PII. Seed data is synthetic only.
- Python: standard library + boto3; keep handlers small and dependency-light.
- Pin
bedrock-agentcore/strands-agentsversions beforeagentcore launch— these SDKs move fast; verify signatures against installed versions, don't assume. - Prefer Lambda Function URLs (with auth) or API Gateway for the public endpoints.
- After any infra change, remind the human to
teardownwhen done recording.
Task backlog (pick these up)
Each task: keep it demo-grade, add a note in the file, and update this list.
- T1 —
/tokenizeendpoint ✅ done + live-verified (gateway_api/tokenize/handler.py, terraformterraform/gateway.tf). Lambda behind a public API Gateway HTTP API (not a Function URL — this account's SCP blocks unauthenticated Function URLs), shared-secret auth (tokenize_api_key, sent asx-api-key/ bearer, checked in-handler). Calls Presidio/analyze, resolves overlapping findings (specific entity wins, so the ROC ID staysTW_ROC_IDnotPERSON), mints randomCUST_<rand>/TW_<rand>tokens, writes vault rows{token, type, value, session_id, expires_at [, customer_id]}(value= original PII for /restore;customer_idbest-effort resolved for the RAG tool), splices right-to-left, returns{deidentified_prompt, session_id}. Verified live inap-southeast-1:王小明→CUST_*,A123456789→TW_*, clean prompt, vault rows written with resolvedcustomer_id, repeat→different tokens, no/bad key→401. Infra changes made while deploying: Presidio task bumped to 2GB (1GB OOM'd on the 603MB zh model → connection refused), awslogs added, ARM64/Graviton runtime (built on Apple Silicon via podman). All AWS objects taggedOwner="conan hncb demo". Caveats: Presidio has no stable endpoint — its Fargate public IP changes per task launch, sopresidio_urlinterraform/local.auto.tfvarsmust be refreshed and the tokenize Lambda re-applied (an ALB/Cloud Map would fix this; out of scope for the demo). The default VPC was created by hand and taggedmaintenance=manual-cleanup-required(Terraform doesn't own it, soteardown.shwon't remove it). - T2 —
/restoreendpoint ✅ done + live-verified (gateway_api/restore/handler.py, terraform interraform/gateway.tf).POST /restoreon the same API Gateway, same shared-secret auth; IAM is read-onlydynamodb:Scanon the vault. Input{session_id, text}→ scans the vault for THIS session's tokens (scoped, so one session can't restore another's), swaps inline tokens back to the original PII (longest-token-first), and prepends the customer's real name at the envelope level ((客戶:王小明)). Returns{final, restored}. Verified live with the full T1→T2 round trip: tokenized prompt out → simulated token-only cloud answer →王小明+A123456789restored, no tokens left; no/bad key → 401; unknown session → text unchanged,restored:0(no cross-session leak).restore_urlis a Terraform output. - T3 — reachability from SaaS ✅ done + live-verified (
terraform/ecs.tf,terraform/gateway.tf). Presidio is now private: its SG allows 5001 only from the tokenize Lambda's SG (no0.0.0.0/0). The tokenize Lambda runs in the VPC (vpc_config+AWSLambdaVPCAccessExecutionRole) and reaches Presidio on its private IP; DynamoDB is reached via a gateway VPC endpoint (no NAT). The Fargate task keeps a public IP only to pull from ECR, but all inbound is SG-locked. Only the API Gateway (/tokenize+/restore, shared-secret auth) is public. Verified live: Presidio's public IP:5001now times out from the internet; the full round trip still works through the private path. Caveat update: the tokenize Lambda now targets Presidio's private IP (172.31.2.216currently), which still changes per task launch — refreshpresidio_urlinterraform/local.auto.tfvarsand re-applyaws_lambda_function.tokenizeif the task restarts (Cloud Map/ALB would give a stable name; out of scope for the demo). - T4 — AgentCore Gateway target ✅ done + live-verified (
terraform/agentcore.tf,scripts/agentcore_setup.sh,scripts/gateway_invoke_test.py). Created an MCP Gateway (hncb-rag-gateway,authorizerType=AWS_IAMso inbound is SigV4 — no Cognito) with the RAG Lambda registered as alambdaMCP target; the Gateway assumes a Terraform-managed IAM role that can onlylambda:InvokeFunctionthe RAG tool. Built the Gateway/target withaws bedrock-agentcore-control(Terraform lags here). Verified live:gateway_invoke_test.py(SigV4 MCP initialize→tools/list→tools/call) returned the full de-identified evidence package forCUST_000123through the Gateway; the tool shows up namespaced asrag___get_customer_activity_summary. Gotchas handled: the GatewayinputSchemais a restricted JSON-Schema subset (stripdefault/unknown keys); MCP responses can be SSE; tool names are target-prefixed. Gateway taggedOwner="conan hncb demo";teardown.shdeletes it (not in Terraform state). MCP URL is printed by the setup script. Note:.venv-t4/(gitignored) holds boto3 for the invoke test. - T5 — Agent runtime ✅ done + live-verified (
agent/agent.py,agent/requirements.txt,scripts/agentcore_setup.shT5 section). Pinned deps (bedrock-agentcore 1.16.0 / strands-agents 1.45.0 / mcp 1.28.1 / boto3 1.43.38),agentcore configure --non-interactive+agentcore launch(cloud CodeBuild, auto-created execution role + ECR + STM memory). Runtime ARN:arn:aws:bedrock-agentcore:ap-southeast-1:286171702468:runtime/hncb_advisor_agent-duWZOT5Far. Verified live:agentcore invoke {"prompt": "...CUST_000123..."}returns numbered talking points built only from the de-identified evidence — no name, no leaked token. Three gotchas fixed (all captured in code/scripts):- In
ap-southeast-1the model is INFERENCE_PROFILE-only → useapac.anthropic.Codex-3-5-sonnet-20241022-v2:0, not the raw id. - Our Gateway is AWS_IAM, so
agent.pySigV4-signs MCP requests (httpx.Auth) instead of using a bearer token. - The auto-created runtime execution role can't call the Gateway by default →
must attach
bedrock-agentcore:InvokeGatewayon the gateway ARN (the launch step inagentcore_setup.shshows the exact command). Runtime + memory added toteardown.sh; agent logic was de-risked locally before launch.
- In
- T6 — UI wiring ✅ done + live-verified (
ui/index.html,terraform/ui.tf,gateway_api/orchestrator/). Hosted on S3 + CloudFront (private bucket, OAC). For the Fusion-less dry run I added a thin demo orchestrator (POST /demo, a Lambda on the same API) that runs the full chain server-side —/tokenize→ SigV4InvokeAgentRuntime→/restore— and returns{final, deidentified_prompt, agent_tokenized}(this stands in for Fusion ONLY for the dry run; we still never build/host Fusion).index.htmlreads the live endpoint from a generatedconfig.js(so no ephemeral URL is committed) and renders the split view. CORS enabled on the API. UI:https://d3n89cj9w7ynf0.cloudfront.net. Verified live: CloudFront servesindex.html+config.js(200);/demoreturns王小明→CUST_*on the cloud side and王小明-restored talking points on the advisor side. Only the browser render itself is left as a human eyeball check. All resources tagged. - T7 — E2E rehearsal ✅ done + live-verified (README demo/deploy sections
rewritten with the real commands). Ran the full round trip via
/demo(王小明→CUST_317499out, agent talking points on tokens only,王小明restored). Money shot proven: in the cloud runtime CloudWatch trace for that session the token appears in 13 events while王小明andA123456789appear in 0 — the Bedrock/AgentCore trace is token-only. Teardown verified (non-destructively):terraform destroytargets 43 resources (Fargate, CloudFront, S3, DynamoDB, all Lambdas, API GW, VPC endpoint, SGs, IAM) andteardown.shadditionally deletes the Gateway/targets, Runtime, Memory, and both ECR repos — i.e. every billed resource. Residual is free-only: the hand-made default VPC, the AgentCore SDK exec role, an idle CodeBuild project. The actualteardown.shrun is left for the human to fire after recording (didn't auto-destroy the live demo).
Known caveats (don't "fix" these silently)
- zh-TW detection is demo-narrow (tuned to the scripted entities), not production recall.
- The agentic token-resolution loop (T4) is custom orchestration by design.
- If you touch
fusion/, it's documentation for a human — do not try to automate SaaS config.