Skip to content

feat(contrib): add cross-border data-policy router recipe - #2530

Merged
happyhuman merged 7 commits into
google:mainfrom
janrockdev:contrib/cross-border-data-router
Aug 18, 2026
Merged

feat(contrib): add cross-border data-policy router recipe#2530
happyhuman merged 7 commits into
google:mainfrom
janrockdev:contrib/cross-border-data-router

Conversation

@janrockdev

Copy link
Copy Markdown
Contributor

Summary

Adds contrib/python/cross-border-data-router, a Python ADK recipe demonstrating a common enterprise compliance problem: deciding which regional agent is legally allowed to process a piece of data, based on where that agent says it operates.

A root orchestrator receives a data-processing request (a classification like PII plus an origin region), evaluates it against a registry of regional processor agents, and only then delegates the work — or refuses outright if no agent qualifies. Eligibility is read directly off each candidate's real a2a.types.AgentCard via a custom capabilities.extensions entry, since the core A2A spec has no built-in notion of jurisdiction or data residency.

Why this recipe

I searched the repo first for an existing sample that evaluates policy and routes based on an agent's declared location/jurisdiction, and found none:

  • The only prior A2A AgentCard usage in the repo (core/python/long-horizon-harness) builds/serves a card but never reads location off one.
  • python/agents/global-kyc-agent routes UK vs. US sub-agents, but by an LLM reading the company being checked out of free text — not metadata declared by the candidate agent itself.
  • "Policy" elsewhere in the repo (long-horizon-harness's tool-call guardrails, core/python/safety-plugins) means something different: gating a single agent's own actions, not choosing between agents.

This recipe fills that gap with a pattern modeled on OpenEAGO (repo), a FINOS Labs specification for jurisdiction-aware governance of multi-agent workflows. OpenEAGO isn't a published Python package, so this is a from-scratch implementation of the pattern it describes (two-stage hard-filter-then-score agent selection), not a dependency on it — credited and linked in the README.

What's in it

  • app/policy/cards.py — a static registry of three regional processor cards (eu_processor, uk_processor, us_processor), each a real a2a.types.AgentCard carrying jurisdiction/data-residency metadata via an AgentExtension.
  • app/policy/engine.py — the routing policy: Stage 1/2 hard filters (data residency, jurisdiction exclusion) that scoring can never override, then a weighted score among survivors. No candidates surviving means an explicit rejection, never a silent fallback to a non-compliant agent.
  • app/agent.py + app/sub_agents/* — an ADK LlmAgent orchestrator that calls the policy tool first, then delegates to the approved regional sub-agent via AgentTool.
  • tests/unit/test_policy_engine.py, tests/unit/test_tools.py — cover the hard filters, preference scoring, and the reject-rather-than-fallback path directly.

Testing

Run locally from the repo root:

export RECIPE_PATH=contrib/python/cross-border-data-router
uv run validate "$RECIPE_PATH"                # PASS — manifest + structure
uv run ruff format "$RECIPE_PATH" && uv run ruff check "$RECIPE_PATH"   # clean
cd "$RECIPE_PATH" && uv lock && uv run pytest --ignore=tests/integration --ignore-glob="**/test_integration.py"
# 11 passed

Also manually verified uv run adk run app and uv run uvicorn app.fast_api_app:app --reload both start and route correctly against a live Gemini API key.

@janrockdev
janrockdev requested a review from happyhuman as a code owner August 17, 2026 11:40
janrockdev and others added 4 commits August 17, 2026 12:41
The env-vars CI check requires every os.getenv/os.environ read in the
recipe's source to have an uncommented declaration in .env.example.
ALLOW_ORIGINS, LOGS_BUCKET_NAME, OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
GENAI_TELEMETRY_PATH, COMMIT_SHA, OTEL_TO_CLOUD, and K_SERVICE were commented
out or missing entirely, which the checker treats as undeclared.

Uncomment them with blank default values, following the same
declared-but-blank convention used in core/python/long-horizon-harness's
.env.example — blank means "unset" and the code applies its existing
local-dev-safe default, so runtime behavior is unchanged.
@janrockdev
janrockdev force-pushed the contrib/cross-border-data-router branch from bb90248 to 76c9dac Compare August 17, 2026 21:38
Comment thread contrib/python/cross-border-data-router/README.md Outdated
Comment thread contrib/python/cross-border-data-router/app/app_utils/__init__.py
Comment thread contrib/python/cross-border-data-router/app/app_utils/telemetry.py Outdated
Comment thread contrib/python/cross-border-data-router/app/fast_api_app.py Outdated
Comment thread contrib/python/cross-border-data-router/app/fast_api_app.py Outdated
Comment thread contrib/python/cross-border-data-router/app/policy/cards.py Outdated
Comment thread contrib/python/cross-border-data-router/app/policy/cards.py Outdated
Comment thread contrib/python/cross-border-data-router/app/policy/engine.py Outdated
Comment thread contrib/python/cross-border-data-router/app/policy/engine.py Outdated
Comment thread contrib/python/cross-border-data-router/app/policy/engine.py Outdated
Comment thread contrib/python/cross-border-data-router/app/policy/engine.py
Comment thread contrib/python/cross-border-data-router/tests/unit/test_policy_engine.py Outdated
Comment thread contrib/python/cross-border-data-router/tests/unit/test_tools.py
janrockdev and others added 2 commits August 17, 2026 23:27
- Reword README pytest command row (integration tests excluded by default)
- Add missing license header to app_utils/__init__.py
- Fix doubled gs:// scheme in telemetry log hint
- Log a warning instead of silently swallowing Cloud Logging init failure
- Drop stale Terraform reference in fast_api_app.py comment
- Add geographic_location to cards.py docstring's param list
- Fix eu_processor jurisdiction EU-WEST -> EU for consistency with its
  residency list and sibling cards; fixes silent excluded_jurisdictions
  miss
- Fix engine.py docstring: two-stage -> three-stage
- Simplify redundant compliance_tags guard in _score
- Drop dead ScoredCandidate agent_id placeholder; _score now takes
  agent_id directly instead of the caller rebuilding it
- Rename us_pii test to match its actual financial classification
- Add missing uk_processor unit test coverage
@janrockdev
janrockdev requested a review from happyhuman August 18, 2026 13:18
@happyhuman
happyhuman merged commit 2679348 into google:main Aug 18, 2026
22 checks passed
@janrockdev
janrockdev deleted the contrib/cross-border-data-router branch August 18, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants