Skip to content

feat(finance): add a KYC agent for the analyst's questions and the regulator's letter - #29

Merged
rparundekar merged 3 commits into
mainfrom
feat/agent-kyc
Nov 11, 2026
Merged

rparundekar merged 3 commits into
mainfrom
feat/agent-kyc

Conversation

@rparundekar

Copy link
Copy Markdown
Collaborator

What this does

Adds agent.py beside the KYC workflow for the two odd jobs in the workflow-vs-agent guide.

  • The customer who moved (interactive): the analyst asks three questions, each depending on the last answer. The agent answers from the handbook and the workflow's own check. Code fails any answer that hides where the handbook and the workflow disagree.
  • The regulator's letter (one-off): it asks for every 2025 account opened on a mobile phone bill, and what was done about each. The workflow checks each file. Code reads every 2025 address scan itself to check that the agent found every account. The guide says no code can do that check, and this one does it.

Live results (5 runs each, using SIE Serverless, 10 Nov 2026)

  • Conversation: 5 of 5 as expected.
  • Letter: 2 of 5 as expected.
    • Two misses were right answers that the quote check refused, because "closed" is a one-word quote.
    • One miss was an SIE timeout that sent the job to a person.
  • k25-011: the agent never found it on its own. The completeness check sent it back every time. The README draws the conclusion: the "find" half of the letter is a workflow, and the agent earns its cost on "what was done about each".

recipe.py gains one unused text field, and its results are unchanged. The README also records a kyc-06 run where the models failed in a different way from before.

Depends on the docs PR for the AGENTS.md layout and the CI step.

🤖 Generated with Claude Code

rparundekar and others added 3 commits November 10, 2026 19:59
…gulator's letter

agent.py runs two jobs the workflow can't. In the first, the analyst asks
about a customer who moved and has no bill at the new address; the agent
answers from the handbook and the workflow's own check, and code fails any
answer that hides where the two disagree. In the second, the regulator asks
for every 2025 account opened on a mobile phone bill and what was done about
each; the workflow checks each file, and code reads every 2025 address scan
itself to check the agent found every account.

The conversation came out as expected on 5 of 5 runs, the letter on 2 of 5.
The agent never found k25-011 on its own; the completeness check sent it back
every time. recipe.py gains one unused text field; its results are unchanged.
The README also records a kyc-06 run where the models failed differently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The post isn't published yet, so its links returned 404, and its claims will
change in the blog update. The README now links the root README's test and
states each point on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- check_disagreement: once the latest KYC check shows address_document
  missing, any answer or email that talks about the address document
  (its id, its declared type, "proof of address", or a counts word,
  negated or not) must carry section 4.2, the sign-off and the
  workflow claim. Reads the email too.
- Waivers: an analyst quote from a negated or question sentence signs
  nothing off.
- Tools reach only the job's records: job a kyc-13, job b the cases of
  accounts opened in 2025. Anything else is a ToolError.
- md_safe escapes text from documents, tools and Claude in the .md
  outputs; answers, emails and the summary go in fenced blocks longer
  than any backtick run inside.
- Section numbers match on word boundaries; read_handbook and citations
  accept "Section 4.2" and "§4.2".
- nothing_recorded fails beside a note recording an approved exception;
  exception_approved fails when a later document met every address
  requirement, or no KYC check ruled that out.
- Claude API errors: connection, timeout, rate limit, 5xx, 408 and 409
  end the job with no_answer and the reason; any other stops the run
  with its own message first. Tokens are counted per answering model and
  priced only where rules.yaml lists that model.
- Verdict line opens "**Verdict:** Miss." README numbers from 5 new live
  runs: 9 of 10 (5 of 5 conversations, 4 of 5 letters).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rparundekar

Copy link
Copy Markdown
Collaborator Author

Self-review

Two review passes ran on this PR. One read the code: correctness, silent failures, tests and comments. The other covered security and checked the README against the code. Every finding below was fixed in 0a08207. Each fix has a selfcheck case that fails if the fix is undone.

Important (3, all fixed)

  • The disagreement check missed most ways of saying yes. It looked only for a "counts" sentence with no negation word anywhere, and it never read the email. So "Yes, it counts, so no bill is needed", "she can use the tenancy agreement" and "it's valid proof of address" all passed without section 4.2, the sign-off or the workflow's address_document missing result. Fixed: while the latest KYC check shows the proof of address missing, any answer or email about the proof of address must carry all three. The check is now stricter, and the README says so.
  • The sign-off check took a refusal as a sign-off. "Don't sign off the tenancy agreement yet", "I can't sign off ... until ..." and "Should I sign off ...?" were accepted, and the email dropped the proof-of-address ask. Fixed: a quote from a negated or question sentence is refused.
  • Tools reached every customer. A line in an uploaded scan could get the agent to look up another case and paste its address into the email, and the email check only caught surnames and ids. Fixed: each job's tools reach only its own cases (kyc-13 for the conversation, the 2025 accounts for the letter). "Names no other customer" now holds by construction, and the email check stays as a backstop.

Suggestions (all fixed)

  • Escaping: values in the .md outputs are escaped, and answers and emails are fenced.
  • Section numbers: matched on word boundaries, and "Section 4.2" and "§4.2" are accepted.
  • Row checks: nothing_recorded fails when a note records an approved exception, and exception_approved fails when a later document met every requirement or was never checked.
  • Claude errors: classified as transient or fatal, and tokens are priced on the model that answered. The verdict lines now open Pass. or Miss.

Reruns after the fixes (5 live runs per job)

9 of 10 came out as expected, up from 7 of 10.

  • Conversation: 5 of 5. The stricter check never had to send an answer back.
  • Regulator's letter: 4 of 5. The right four rows came out every run. The one miss is the one-word quote "closed" again, and the README reports it.
  • Account k25-011: the agent still never found it on its own. The completeness check in code sent it back on all 5 runs.

@rparundekar
rparundekar marked this pull request as ready for review November 11, 2026 03:00
@rparundekar
rparundekar requested a review from a team as a code owner November 11, 2026 03:00
@rparundekar
rparundekar merged commit 0f7523f into main Nov 11, 2026
49 checks passed
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.

1 participant