All demos
FintechCrewAI
Treasury Cash Management
Cash position + live FX → a proposed INR/USD sweep
Ready to replay
0.0s / 9.9sSecurity Pipeline
Input
Sandbox
Network
PII Scan
Injection
Vault
LLM Call
Result
Real run · CrewAI · python run.py · captured 2026-06-30 · SDK 1.3.0
Run it yourself View the agent code· with Declawfintech-workflows/sandboxed/14-treasury-cashmgmt-crewai/run.py
"""Treasury Cash Management — sandboxed, real CrewAI inside microVM.
Four-agent CrewAI sequential pipeline runs inside a single Firecracker sandbox:
Cash-Position-Analyzer -> FX-Strategist -> Sweep-Planner -> Human-Review
Policy: treasury_ops_policy([LLM_DOMAINS, fbil, federalreserve])
* PII redacted (PAN, account numbers, GSTIN, EIN) before reaching OpenAI
* Tight network allowlist — only LLM + FBIL + Fed H.10 domains permitted
* Every Sweep-Planner tool call is audited in the sandbox audit log
* Human-Review node prints a clear "AWAITING OPERATOR SIGN-OFF" gate
Live FX: fbil_reference_rate("USDINR") fetched on the host before sandbox
so the live rate crosses the policy boundary audited (not raw from inside).
(sandboxed — Crew inside microVM)
"""
from __future__ import annotations
import json
import sys
import textwrap
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
sys.path.insert(0, str(REPO_ROOT / "sandboxed"))
from shared.mock_customers import CUSTOMERS # noqa: E402
from shared.mock_statements import STATEMENTS # noqa: E402
from shared.mock_transactions import neft_transactions, ach_transactions # noqa: E402
from shared.external_apis import fbil_reference_rate # noqa: E402
from shared.declaw_helpers import ( # noqa: E402
LLM_DOMAINS,
treasury_ops_policy,
llm_envs,
run_python_in_sandbox,
)
CREWAI_SCRIPT = textwrap.dedent("""
import json, os
os.environ["CREWAI_TRACING_ENABLED"] = "false"
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["OPENAI_MODEL_NAME"] = "gpt-4.1"
import urllib.request
from crewai import Agent, Crew, LLM, Process, Task
from crewai.tools import tool
llm = LLM(model="gpt-4.1")
with open("/tmp/in.json") as f:
inp = json.load(f)
customer_id = inp["customer_id"]
statement = inp["statement"]
neft_txns = inp["neft_transactions"]
ach_txns = inp["ach_transactions"]
fx_rate = inp["fx_rate"]
@tool("Get current cash positions")
def get_cash_positions(cid: str) -> str:
\"\"\"Return JSON with current account balances (PII fields redacted by policy).\"\"\"
return json.dumps({
"customer_id": cid,
"bank": statement.get("bank"),
"period": statement.get("period"),
"opening_balance_inr": statement.get("opening_balance_inr"),
"closing_balance_inr": statement.get("closing_balance_inr"),
"opening_balance_usd": statement.get("opening_balance_usd"),
"closing_balance_usd": statement.get("closing_balance_usd"),
"neft_count": len(neft_txns),
"ach_count": len(ach_txns),
})
@tool("Get FBIL reference FX rate")
def get_fx_rate(pair: str) -> str:
\"\"\"Return the pre-fetched FBIL reference rate for the given currency pair.\"\"\"
return json.dumps(fx_rate)
@tool("Propose a sweep transfer")
def propose_sweep(sweep_json: str) -> str:
\"\"\"Given JSON {from_currency, to_currency, amount, rationale},
record the proposed sweep and return a pending confirmation receipt.\"\"\"
try:
sweep = json.loads(sweep_json)
except Exception:
return json.dumps({"error": "invalid sweep JSON"})
sweep["status"] = "PENDING_HUMAN_APPROVAL"
sweep["ref"] = (
f"SWP-{customer_id}-{sweep.get('from_currency','?')}-"
f"{sweep.get('to_currency','?')}"
)
sweep["audit_note"] = "treasury_ops_policy: sweep audited by Declaw sandbox"
return json.dumps(sweep, indent=2)
analyzer = Agent(
role="Cash Position Analyzer",
goal="Load account balances and live FX rate. Summarise INR and USD positions.",
backstory="Treasury analyst. Use get_cash_positions and get_fx_rate. "
"Work with redacted position summaries — do not attempt to "
"reconstruct any redacted PII fields.",
tools=[get_cash_positions, get_fx_rate],
allow_delegation=False, llm=llm,
)
fx_strategist = Agent(
role="FX Strategist",
goal="Determine optimal INR/USD split from live FX rate and cash position. "
"Recommend buy or sell USD direction and target amount.",
backstory="FX trader. Use the FBIL rate and closing balances to decide "
"whether to move funds from INR to USD or vice versa.",
allow_delegation=False, llm=llm,
)
sweep_planner = Agent(
role="Sweep Planner",
goal="Call propose_sweep to generate an audited INR <-> USD rebalancing plan.",
backstory="Treasury operations. Always call propose_sweep with from_currency, "
"to_currency, amount, and rationale. The call is audited.",
tools=[propose_sweep],
allow_delegation=False, llm=llm,
)
human_review = Agent(
role="Human Review Node",
goal="Present the sweep plan for human operator sign-off. "
"Output AWAITING_OPERATOR_SIGN_OFF prominently.",
backstory="Treasury manager. This is a mandatory review gate. No transfer "
"executes without explicit operator approval beyond this node.",
allow_delegation=False, llm=llm,
)
analyze_task = Task(
description=(
f"Load cash position for customer_id='{customer_id}'. "
"Call get_cash_positions and get_fx_rate('USDINR'). "
"Summarise INR and USD balances and live rate."
),
expected_output="Cash position summary with INR/USD balances and live FX rate.",
agent=analyzer,
)
fx_task = Task(
description=(
f"Based on the cash position for customer_id='{customer_id}', "
"recommend optimal INR/USD allocation using the live FBIL rate. "
"Specify direction and approximate rebalancing amount."
),
expected_output="FX strategy: direction (buy/sell USD), target amount, rationale.",
agent=fx_strategist,
context=[analyze_task],
)
sweep_task = Task(
description=(
f"Create an audited sweep for customer_id='{customer_id}' by calling "
"propose_sweep with from_currency, to_currency, amount, and rationale "
"from the FX strategy."
),
expected_output="Sweep receipt JSON from propose_sweep (status=PENDING_HUMAN_APPROVAL).",
agent=sweep_planner,
context=[analyze_task, fx_task],
)
review_task = Task(
description=(
f"Present the complete sweep plan for customer_id='{customer_id}' "
"for human operator review. State AWAITING_OPERATOR_SIGN_OFF. "
"List transfer details, FX rate, and any risk flags."
),
expected_output=(
"Human review report: sweep details, AWAITING_OPERATOR_SIGN_OFF, "
"risk flags if any."
),
agent=human_review,
context=[analyze_task, fx_task, sweep_task],
)
crew = Crew(
agents=[analyzer, fx_strategist, sweep_planner, human_review],
tasks=[analyze_task, fx_task, sweep_task, review_task],
process=Process.sequential, verbose=False,
)
result = crew.kickoff()
with open("/tmp/out.json", "w") as f:
json.dump({"sweep_review": str(result)}, f)
""")
def main() -> None:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--customer", default="c-002",
choices=["c-002", "c-005"],
help="Customer ID (c-002=INR SMB, c-005=US+INR NRI)")
args = parser.parse_args()
customer_id = args.customer
print("=" * 60)
print("Treasury Cash Management Crew")
print("(sandboxed — Crew inside microVM)")
print("=" * 60)
print(f"\nCustomer: {customer_id} — {CUSTOMERS[customer_id].name}")
print("Fetching live FBIL USDINR reference rate (host-side, pre-sandbox)...")
fx_rate = fbil_reference_rate("USDINR")
rate_display = fx_rate.get("rate") or "unavailable (FBIL unreachable)"
print(f" FBIL USDINR rate: {rate_display}")
print("treasury_ops_policy: PII redacted, sweep tool calls audited")
print("Network allowlist: LLM + www.fbil.org.in + www.federalreserve.gov")
print()
statement = STATEMENTS.get(customer_id, {})
payload = {
"customer_id": customer_id,
"statement": {
k: v for k, v in statement.items() if k != "narration"
},
"neft_transactions": neft_transactions(customer_id),
"ach_transactions": ach_transactions(customer_id),
"fx_rate": fx_rate,
}
treasury_domains = LLM_DOMAINS + [
"www.fbil.org.in",
"www.federalreserve.gov",
]
pol = treasury_ops_policy(allow_domains=treasury_domains)
out = run_python_in_sandbox(
"treasury-crew",
CREWAI_SCRIPT,
pol,
payload=payload,
pip_packages=None,
envs=llm_envs(),
timeout=300,
template="ai-agent",
)
print("\n--- Treasury Sweep Plan (awaiting human sign-off) ---")
print(out.get("sweep_review", out))
if __name__ == "__main__":
main()
View raw audit JSON
[
{
"atMs": 450,
"kind": "stage",
"payload": {
"stage": "input",
"status": "done",
"detail": "read input files in-VM"
}
},
{
"atMs": 2160,
"kind": "stage",
"payload": {
"stage": "sandbox",
"status": "done",
"detail": "1 Firecracker microVM(s) · own kernel · egress-locked"
}
},
{
"atMs": 3870,
"kind": "network",
"payload": {
"event": "egress_allowed",
"detail": {
"host": "api.openai.com",
"port": 443,
"reason": "allowlist"
}
}
},
{
"atMs": 5580,
"kind": "security",
"payload": {
"event": "vault_brokered",
"detail": {
"keys": "OPENAI_API_KEY",
"host": "api.openai.com",
"injected_at": "egress proxy",
"exposure_to_vm": "none (declaw:vault-managed placeholder)"
}
}
},
{
"atMs": 7290,
"kind": "stage",
"payload": {
"stage": "llm",
"status": "done",
"detail": "model called from inside the microVM (PII redacted on the wire) · 2365.0s real",
"durationMs": 2460
}
},
{
"atMs": 9000,
"kind": "decision",
"payload": {
"text": "Sweep PROPOSED → PENDING_HUMAN_APPROVAL (no funds move; an operator signs off)"
}
}
]