Skip to content

Portfolio · Government health & human services

Four state programs,and one codebase to serve them.

A state health and human services agency runs child welfare, foster care, child care management and vocational rehabilitation as four programs — four sets of rules, four sets of records, four sets of people who cannot see each other. The platform answers a caseworker question in all four: seven microservices, an agentic retrieval layer over four answer engines, and a tenancy model in which the program a question belongs to is a record rather than a branch of the code.

Four programs, one deployment. What used to be four forks of the code is four rows in a control plane.
01Sector
Government HHS — four concurrent state programs
02Services
Seven, containerised, on Kubernetes
03Answer engines
Vector RAG, Elasticsearch NLQ, Text2SQL, system metadata
04Onboarding
Under two days of configuration, from multi-week development
Act 01/ 063 figures

One codebase, four programs

A tenant is a record, not a fork.

Before this, a new state program meant a copy of the repository: its own prompts hard-coded, its own guardrails, its own database bindings, and its own defects from the moment the copy was taken. A MongoDB-backed admin control plane replaced that. A program is a record — the prompts it uses, the rails it is held to, the databases it is permitted to read — and all seven services resolve that record per request. Onboarding the next program became filling the record in: from multi-week custom development to under two days of configuration.

Prompts, guardrails and database bindings are settings a program owns — edited, not deployed.
Which program may read which database is stated as a matrix rather than assumed by the code.
Seven services, one image set, one deployment — the tenancy is in the data, not in the build.
  1. 01.01

    Configuration, not a branch

    Prompts, rails and bindings are rows a program owns, resolved at request time by every service.

  2. 01.02

    Under two days

    Onboarding a state program stopped being an engineering project and became an afternoon of configuration.

  3. 01.03

    One codebase to fix

    A defect is fixed once and every program has it, which a fork has never once managed to promise.

Act 02/ 063 figures

The agent layer

Four answer engines, and a router that knows which to ask.

A caseworker's question is not one kind of question. What the policy says about a placement move is retrieval; how many cases opened last month is SQL; find the note that mentions the school transfer is a search index; what can you actually see is the system describing itself. A ReAct agent classifies every inbound query and dispatches it across all four through tool calling on AWS Bedrock, inside a bounded ten-iteration loop so that no question can run away with the budget. In front of it sits a lightweight pre-router that resolves greetings, closings and follow-up intent before the agent is invoked at all — about 30% of queries never open the loop, and never cost what the loop costs.

One query in, four engines out — and the classification is the whole of the decision.
A bounded loop: ten iterations, then it answers with what it has rather than with more spend.
The cheapest answer is the one nothing had to think about: roughly 30% of traffic settles before the loop opens.
  1. 02.01

    Bounded at ten

    An agent loop with no ceiling is an unbounded bill; this one stops and answers with what it holds.

  2. 02.02

    A pre-router in front

    Greetings, closings and follow-up intent are resolved without invoking the agent at all.

  3. 02.03

    Follow-ups keep the thread

    Conversation memory in Redis, so the second question does not have to restate the first.

Act 03/ 063 figures

What the answers cost

Two model tiers, and the expensive one only where it earns it.

Owning the platform's AI spend meant separating the work that needs reasoning from the work that only needs structure. Structured query generation is high volume and narrow, so it runs on self-hosted Hugging Face TGI; reasoning and summarisation stay on managed Bedrock, where the quality is worth the rate. Embedding generation came off the request path entirely and onto CPU inside the batch worker, which keeps GPU capacity out of ingestion. Together the routing took inference spend down about 40%, with no change a caseworker could see.

High-volume structured generation, self-hosted — the work whose shape never changes.
Reasoning and summarisation, managed — kept for the work only the larger model does well.
About 40% off inference spend, taken out of the routing rather than out of the answers.
  1. 03.01

    Volume goes local

    Text2SQL and structured generation are narrow, repetitive and self-hostable; that is most of the traffic.

  2. 03.02

    Reasoning stays managed

    The managed model is kept for the questions where the difference in quality is visible in the answer.

  3. 03.03

    Embeddings off the hot path

    Generated on CPU in the batch worker, so ingestion never competes with serving for a GPU.

Act 04/ 063 figures

Getting the documents in

Three thousand documents a month, and a status for every one.

Documents arrive as PDF, DOCX and XLSX and leave as vectors. The upload is asynchronous from the first hop — FastAPI writes to Amazon S3 through aioboto3, a message goes to SQS, and a containerised worker picks it up, parses with docling and PyMuPDF, and writes to Milvus. Every message carries its state in PostgreSQL and every retry is idempotent, so a document that fails is a row somebody can explain rather than a file that quietly did not arrive. Caseworker document search time fell about 70%.

Upload, queue, worker, index — four hops, and not one of them makes the caseworker wait.
PDF, DOCX and XLSX, parsed down to the same shape before anything is embedded.
Every message has a row and every retry is safe to run twice — a failure is explainable, not silent.
  1. 04.01

    Nothing blocks the upload

    The request ends at object storage and a queue message; parsing happens where a slow document costs nobody a screen.

  2. 04.02

    A failure is a row

    Per-message status in PostgreSQL, so the answer to where a file went is a query rather than a search.

  3. 04.03

    70% less time looking

    Sustained at over 3,000 documents a month, which is the volume the number was measured at.

Act 05/ 063 figures

The regulatory constraint

One collection, and no program can read another.

Public-sector records do not get to leak between programs, and the design starts there rather than adding it later. Retrieval runs over one unified 1024-dimension Milvus collection with program-scoped filtering, so a query is bounded before it is scored, and CrossEncoder reranking takes it the rest of the way — retrieval precision up about 35%, unsupported responses down about 45%. Around it sits the record a review asks for: conversation memory in Redis, and queryable audit logs of the model's reasoning and its tool selection.

One collection, filtered by program before anything is scored. Isolation is a precondition, not a check afterwards.
Reranked before it is answered — about 35% more precision, about 45% fewer unsupported answers.
Why it answered that: the reasoning and the tool it chose, queryable long after the conversation closed.
  1. 05.01

    Scoped before it is scored

    A filter on the collection, not a check on the result — there is no path by which the wrong program is ranked at all.

  2. 05.02

    Reranked, then answered

    A CrossEncoder pass over the retrieved set, which is where most of the precision was won.

  3. 05.03

    The reasoning is on the record

    Traceability is what a public-sector review asks for, and it is a log rather than a promise.

Act 06/ 062 figures

Reading all seven repositories

The map nobody had, and what it found.

A platform assembled service by service has a shape nobody has written down. Cross-repository technical due diligence produced it: a system contract map and a dependency graph across all seven services, and with them the things a map makes visible — two production integrations that were broken, five code paths nothing could reach, and secret-management exposures in every repository. It ended as a prioritised remediation roadmap put in front of engineering leadership, ordered by what breaks first rather than by what is easiest.

Seven services, their contracts and their dependencies, written down for the first time.
Secret handling reviewed in every repository — and found wanting in every repository.
  1. 06.01

    Two broken in production

    Integrations that were live, failing, and on nobody’s board until the map put them there.

  2. 06.02

    Five paths nothing reaches

    Unreachable code is not free: it is read, maintained and trusted by the next person to arrive.

  3. 06.03

    Ordered by what breaks first

    A roadmap leadership could act on, rather than a list of everything that could be better.

The last word

This shows how Famysys builds a platform that has to serve more than one customer without becoming more than one product: put tenancy in a control plane rather than in a branch, bound the agent that spends the money, and make isolation a precondition of retrieval rather than a check afterwards. If you are forking a codebase per client today, that fork is the thing we would start with.

Built in

  • Multi-tenant
  • Agentic RAG
  • ReAct routing
  • Text2SQL
  • Vector search
  • Document ingestion
  • Guardrails
  • Audit trail
  • Kubernetes
  • Cost routing

All projects

Let's build what's next

Technology alone doesn't transform businesses.The right partnership does.

Modernizing systems, building a new product, or exploring AI-driven transformation — Famysys helps you move forward with confidence.

  • SOC2 Type II Compliant
  • Strict Commercial NDA
  • Zero Lock-In Guarantee

Famysys

We Engineer Clarity

Scan to Connect

Instant digital business card & WhatsApp link