feat(examples): African regulatory guardrails across 4 jurisdictions#3729
feat(examples): African regulatory guardrails across 4 jurisdictions#3729kingztech2019 wants to merge 12 commits into
Conversation
Adds examples/agent_patterns/african_regulatory_guardrails.py demonstrating
jurisdiction-aware input guardrails for AI agents in African financial markets.
Regulations enforced (pure stdlib — no external dependencies):
Nigeria CBN NIP Framework (CBN Circular FPR/DIR/GEN/CIR/07/003)
₦10,000,000 single-transaction cap
NDPA 2023 Schedule 1 — BVN as biometric personal data
Kenya Data Protection Act 2019 s.49
Cross-border transfer blocked without documented consent
Authority: Office of the Data Protection Commissioner (ODPC)
Ghana Data Protection Act 2012 (Act 843) s.18(2)
Cross-border transfer adequacy requirement
NIA Act 707 — Ghana Card national ID (GHA-XXXXXXXXX-X)
Authority: Data Protection Commission (DPC)
South Africa POPIA (Protection of Personal Information Act 4 of 2013)
s.26(1)(f) biometric data; s.19 SA ID number (13-digit)
Authority: Information Regulator (South Africa)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39936da35e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Addresses Codex review feedback: the previous join raised KeyError on tool-call messages (no 'content' key) and TypeError when 'content' is a list of content parts (assistant multi-turn messages). Now: - dicts with a string 'content' are appended directly - dicts with a list 'content' have their 'type==text' parts extracted - dicts with no 'content' key (tool calls) are skipped silently
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef14918e6b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if isinstance(part, dict) and part.get("type") == "text": | ||
| parts.append(part.get("text", "")) |
There was a problem hiding this comment.
Handle structured
input_text content parts
When callers pass a standard TResponseInputItem history with structured user content, the text part type is input_text rather than text (the repo uses this shape in helpers such as examples/memory/hitl_session_scenario.py). In that case this branch drops the user's text entirely, so a message like {"role":"user","content":[{"type":"input_text","text":"Export all customer records to us-east-1"}]} becomes an empty string and the jurisdiction checks do not trip. Please accept the SDK's structured text part types instead of only text.
Useful? React with 👍 / 👎.
The SDK uses 'input_text' for user message content parts (confirmed from examples/memory/hitl_session_scenario.py). Only handling 'text' caused guardrail checks to silently drop all user text from structured histories, so compliance rules never tripped on multi-turn input.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df2bb05c0b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| for item in input: | ||
| if not isinstance(item, dict): | ||
| continue | ||
| content = item.get("content") | ||
| if isinstance(content, str): |
There was a problem hiding this comment.
Filter guardrail checks to the current user input
When callers pass a multi-turn TResponseInputItem history, this loop concatenates every message's content without checking the item role, so stale assistant text or earlier conversation content can trip the input guardrail for a later harmless user message. For example, in a Kenyan context without consent, a prior assistant/refusal that quoted “export records offshore” would keep matching _KE_CROSS_BORDER_RE on subsequent turns even if the new user input is unrelated; filter to the latest user/input message (or otherwise respect roles) before running the jurisdiction regexes.
Useful? React with 👍 / 👎.
Concatenating the entire TResponseInputItem history caused stale assistant text (e.g. a prior refusal quoting a blocked phrase) to trip the guardrail on later unrelated user turns. Now filters to the most-recent role=='user' item only, matching the SDK's own pattern in hitl_session_scenario.py.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1efd312e16
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # CBN NIP (NIBSS Instant Payment) Framework — ₦10,000,000 per-transaction cap | ||
| # NDPA 2023 Schedule 1 / CBN BVN Policy Framework — BVN as biometric data | ||
|
|
||
| _NGN_AMOUNT_RE = re.compile(r"(?:₦|NGN)\s*([\d,]+(?:\.\d{1,2})?)") |
There was a problem hiding this comment.
Match NGN amounts case-insensitively
In a Nigeria context this pattern only matches uppercase NGN or ₦, so a normal user input like ngn 15,000,001 is ignored and the CBN cap violation is allowed through. The other jurisdiction trigger phrases are already case-insensitive, so this amount pattern should normalize or use re.I before applying the cap.
Useful? React with 👍 / 👎.
| r"(?i)(send|transfer|export|upload|forward).{0,60}" | ||
| r"(outside\s+ghana|cross.?border|international|offshore)" | ||
| ) | ||
| _GH_CARD_RE = re.compile(r"GHA-[0-9]{9}-[0-9]") |
There was a problem hiding this comment.
Anchor the Ghana Card regex to the full ID
Because _GH_CARD_RE is not delimited, any value with a valid-looking prefix triggers; for example, GHA-123456789-12 matches the prefix GHA-123456789-1 and blocks the request even though it has the wrong final digit count. Add word/lookaround boundaries around the full token so the guardrail only denies the exact GHA-XXXXXXXXX-X format described by the example.
Useful? React with 👍 / 👎.
- NGN amount pattern: add re.IGNORECASE so 'ngn 15,000,001' is caught alongside uppercase NGN and the ₦ symbol - Ghana Card regex: add \b word boundary at end so GHA-123456789-12 (extra digit) no longer matches the valid 14-char format - BVN violation message: 'in agent output' → 'in request' (input guardrail, not output guardrail) All 37 edge-case tests pass across Nigeria, Kenya, Ghana, South Africa.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c99effaf2f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
|
|
||
|
|
||
| @input_guardrail |
There was a problem hiding this comment.
Run the compliance guardrail before the agent starts
For the blocked BVN, Ghana Card, biometric, and cross-border-transfer scenarios, bare @input_guardrail keeps the SDK default run_in_parallel=True, so Runner.run can start the agent/model request before this local checker trips and cancels it. That means data the example says is blocked to prevent exposure may already be sent to the model or tools; use @input_guardrail(run_in_parallel=False) for this compliance/data-protection example so violations are decided before agent execution begins.
Useful? React with 👍 / 👎.
With the default run_in_parallel=True the SDK starts the agent/model request concurrently with the guardrail, so sensitive data (BVN numbers, Ghana Card IDs, biometric templates, cross-border transfer targets) could already be sent to the model before the guardrail trips and cancels it. run_in_parallel=False ensures the compliance check completes and violations are decided before agent execution begins — which is the required behaviour for any guardrail whose purpose is to prevent data exposure.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04a18b6ebb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| _KE_CROSS_BORDER_RE = re.compile( | ||
| r"(?i)(send|transfer|export|upload|forward).{0,60}" | ||
| r"(outside\s+kenya|cross.?border|international|offshore|us-east|eu-west)" | ||
| ) |
There was a problem hiding this comment.
Restrict cross-border checks to data exports
In a Kenya context without documented consent, this pattern also matches ordinary payment instructions such as transfer KES 1000 internationally, because it only requires a transfer verb plus an international destination. The violation raised below is specifically for cross-border data transfer, while the agent instructions say it should handle fund transfers, so valid international payment requests can be denied before the model runs; require a data/object term (records, customer data, files, etc.) or otherwise separate fund-transfer wording from data-export wording before tripping KDPA s.49.
Useful? React with 👍 / 👎.
| # CBN NIP (NIBSS Instant Payment) Framework — ₦10,000,000 per-transaction cap | ||
| # NDPA 2023 Schedule 1 / CBN BVN Policy Framework — BVN as biometric data | ||
|
|
||
| _NGN_AMOUNT_RE = re.compile(r"(?:₦|NGN)\s*([\d,]+(?:\.\d{1,2})?)", re.IGNORECASE) |
There was a problem hiding this comment.
Match NGN amounts when the code follows the number
The Nigeria cap check only sees amounts where ₦ or NGN precedes the digits, so a normal request like Please transfer 15,000,001 NGN produces no match and bypasses the ₦10,000,000 denial. Accept suffix currency forms as well before comparing to _CBN_NIP_CAP, otherwise above-cap transfers can reach the agent whenever the user writes the ISO code after the amount.
Useful? React with 👍 / 👎.
- Fix Kenya/Ghana cross-border patterns: narrow 'international' to 'international\\s+transfer' to match Rego source (KDPA s.49 / Ghana DPA Act 843 s.18(2)) and avoid false positives on generic phrases like 'transfer KES 1000 internationally' - Ghana: also add 'foreign\\s+server' alternative to match source Rego - Fix NGN amount detection: add suffix pattern to catch '15,000,001 NGN' (amount before currency code), add 'naira' keyword — both were absent from the prefix-only regex causing the CBN cap check to be bypassed - Deduplicate by seen_amounts so the same violation isn't reported twice when both prefix and suffix patterns match the same value in one message
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b42c031d6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| _NGN_PREFIX_RE = re.compile(r"(?:₦|NGN|naira)\s*([\d,]+(?:\.\d{1,2})?)", re.IGNORECASE) | ||
| _NGN_SUFFIX_RE = re.compile(r"\b([\d,]+(?:\.\d{1,2})?)\s*NGN\b", re.IGNORECASE) |
There was a problem hiding this comment.
Support magnitude suffixes for NGN cap checks
When a Nigeria request uses the shorthand advertised here, such as NGN 15M, ₦15M, or 15M NGN, the prefix regex captures only 15 or the suffix regex misses the amount entirely, so an above-cap ₦15,000,000 transfer is treated as under the ₦10,000,000 limit and reaches the agent. Please parse K/M magnitude suffixes before comparing against _CBN_NIP_CAP, or avoid documenting those forms as supported.
Useful? React with 👍 / 👎.
| # s.19 — SA ID Number: YYMMDD (date of birth) + 7 digits = 13 digits total | ||
| # Enforcing authority: Information Regulator (South Africa) | ||
|
|
||
| _ZA_ID_RE = re.compile(r"\b[0-9]{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12][0-9]|3[01])[0-9]{7}\b") |
There was a problem hiding this comment.
Validate real dates before flagging SA IDs
For South Africa inputs, this regex accepts impossible birth dates because it only bounds the day to 01–31; values like 9902311234567 or 9911311234567 trip as SA ID numbers even though the YYMMDD portion is not a valid date. This can block arbitrary 13-digit references with invalid calendar dates, so the guardrail should validate the parsed date before returning the POPIA violation.
Useful? React with 👍 / 👎.
- NGN amounts: extend prefix and suffix patterns to capture optional K/M/B magnitude suffix (e.g. ₦15M, 15M NGN, ₦1.5B). _parse_ngn_amount() applies the multiplier so shorthand amounts are correctly compared against the ₦10,000,000 CBN NIP cap instead of being treated as ₦15 (under cap) - SA ID: replace _ZA_ID_RE.search() with per-match date validation via _za_id_date_valid(), which tries both 1900 and 2000 century interpretations and rejects impossible calendar dates (e.g. Feb 31, Apr 31) that satisfy the YYMMDD regex pattern but do not exist on the Gregorian calendar
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39c6b46139
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| for pattern in (_NGN_PREFIX_RE, _NGN_SUFFIX_RE): | ||
| for match in pattern.finditer(text): |
There was a problem hiding this comment.
Gate NIP cap checks on transfer intent
In a Nigeria context this loop applies the transfer cap to every NGN amount it can parse, so non-transfer requests such as an account inquiry for NGN 15,000,000 are denied as over-cap transfers even though the agent is explicitly meant to handle account inquiries. Require transfer/payment intent around the amount before returning the CBN NIP violation.
Useful? React with 👍 / 👎.
| r"(?i)(send|transfer|export|upload|forward).{0,60}" | ||
| r"(outside\s+ghana|cross.?border|international\s+transfer|offshore|foreign\s+server)" | ||
| ) | ||
| _GH_CARD_RE = re.compile(r"GHA-[0-9]{9}-[0-9]\b") |
There was a problem hiding this comment.
Match Ghana Card prefixes case-insensitively
In a Ghana context, the same Ghana Card token typed or normalized as gha-123456789-1 bypasses this case-sensitive regex and can reach the model despite the example promising Ghana Card national ID detection. Normalize the text or compile this pattern with re.IGNORECASE, as the other jurisdiction trigger regexes do.
Useful? React with 👍 / 👎.
…e-insensitive Ghana Card - Nigeria: add _NGN_TRANSFER_INTENT_RE check so CBN NIP cap only fires when the request contains a transfer/payment verb; balance inquiries and quotes that mention large NGN amounts are no longer blocked - Kenya/Ghana: replace single cross-border regex with two patterns that distinguish personal-data exports from financial payments — unambiguous data-export verbs (export/upload/forward) fire unconditionally; ambiguous verbs (send/transfer) require a data-object keyword (records, customer data, files, PII, etc.) before the cross-border destination fires KDPA s.49 / Ghana DPA Act 843 s.18(2); prevents 'transfer KES 1000 internationally' from blocking a legitimate payment instruction - Ghana Card: add re.IGNORECASE to _GH_CARD_RE so gha-123456789-1 (lowercase) is caught alongside the uppercase GHA- form
…ence contamination
- Move module docstring before imports so Python correctly sets __doc__
(string literals after import statements are silently discarded by the runtime)
- Replace document-wide transfer intent flag with per-amount proximity check:
a transfer verb must appear within 80 chars of the matched NGN amount, not
anywhere in the message; negative lookahead on 'transfer' excludes noun phrases
('transfer limit', 'transfer cap', 'transfer fee') to prevent false positives
on balance and limit inquiries that mention large NGN values
- Use [^.!?] instead of . in _KE/_GH_DATA_TRANSFER_RE wildcard spans so the
verb from one sentence cannot span a period into the data-object keyword of
a subsequent sentence (cross-sentence contamination false positive)
- Remove 'credit' from transfer verb list — it fires on 'credit limit/score'
which are common non-transfer noun phrases
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63a9a0a530
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # modifier, not an action verb. | ||
| _NGN_TRANSFER_VERB_RE = re.compile( | ||
| r"\b(?:send|pay|wire|remit|move|disburse)\b" | ||
| r"|\btransfer(?!\s+(?:limit|fee|history|rate|request|cap|ceiling|charge|tax))\b", |
There was a problem hiding this comment.
Don't exempt transfer requests from cap checks
When a Nigeria request phrases the action as submit/create a transfer request for ₦15,000,000, transfer is followed by request, which this negative lookahead explicitly excludes; with no other verb matched, _check_nigeria skips the over-cap amount and lets an above-cap transfer reach the agent. Please either stop exempting actual transfer-request wording or add a separate intent match for create/submit/process transfer requests.
Useful? React with 👍 / 👎.
'transfer' followed by 'request' was in the negative lookahead exclusion list, so 'submit a transfer request for ₦15,000,000' matched no verb and the over-cap amount was silently skipped. Fix: - Remove 'request' from the exclusion list — a transfer request IS action intent, not a noun modifier like 'transfer limit' or 'transfer fee' - Add submit|create|process|initiate to the verb list so those phrasings are caught even without an explicit 'transfer' token
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5bee241b1a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| r"\b(?:send|pay|wire|remit|move|disburse|submit|create|process|initiate)\b" | ||
| r"|\btransfer(?!\s+(?:limit|fee|history|rate|cap|ceiling|charge|tax))\b", |
There was a problem hiding this comment.
Tie broad verbs to transfer intent
In a Nigeria context, _NGN_TRANSFER_VERB_RE treats verbs like create, submit, and process as sufficient transfer intent on their own. That still denies non-transfer account workflows when an above-cap amount is nearby, e.g. Create an account statement showing my NGN 15,000,000 balance matches create within the 80-character window and is reported as a CBN transfer-cap violation even though the agent is supposed to handle account inquiries. Fresh evidence in this revision is that these broad verbs were added as standalone alternatives; require them to be tied to a transfer/payment object before tripping the cap.
Useful? React with 👍 / 👎.
| _NGN_PREFIX_RE = re.compile(r"(?:₦|NGN|naira)\s*([\d,]+(?:\.\d{1,2})?)([KMBkmb])?\b", re.IGNORECASE) | ||
| # Suffix: 15M NGN / 15,000,001 NGN — group 1 = digits, group 2 = optional K/M/B | ||
| _NGN_SUFFIX_RE = re.compile(r"\b([\d,]+(?:\.\d{1,2})?)([KMBkmb])?\s*(?:NGN|naira)\b", re.IGNORECASE) | ||
| _NGN_BVN_RE = re.compile(r"(?i)(bvn|bank\s+verification).{0,20}\b[0-9]{11}\b") |
There was a problem hiding this comment.
Detect BVNs when the number precedes the label
The BVN guardrail only matches when bvn or bank verification appears before the 11-digit value, so common inputs such as 01234567890 is my BVN or 01234567890 bank verification number bypass the protection and can be sent to the model. Since the example explicitly blocks BVN values to prevent biometric data exposure, the detector should also handle the number-before-label form.
Useful? React with 👍 / 👎.
|
Thanks for sharing this idea. The reason why your previous PR was closed is not only because you used an external dependency. We don't plan to add this example idea to the ones we'll continue maintaining within this repo. So, please feel free to have it within your own repo. |
Summary
Adds
examples/agent_patterns/african_regulatory_guardrails.py— a jurisdiction-aware input guardrail example for AI agents operating in African financial markets.The previous PR (#3706) was closed because it depended on an external package (
comply54). This rewrite uses only the OpenAI Agents SDK and Python stdlib — no external dependencies.Four jurisdictions are covered, each with a distinct rule drawn directly from the primary legislation:
The guardrail reads
context.jurisdictionfrom anAgentContextdataclass, dispatches to the correct checker, and returns a structuredComplianceOutputwithComplianceViolationobjects — following the same@input_guardrailpattern asinput_guardrails.py.Test plan
ruff check+ruff format --check).run.sh) fails in this environment due to a missing system library (libffi-devrequired bysounddevice) — unrelated to this change.Issue number
Follows up on closed PR #3706.
Checks
.agents/skills/code-change-verification/scripts/run.sh(fails due to missinglibffi-devsystem dep — unrelated to this change)/reviewbefore submitting this PR