A prior-authorization intake built with Kaleido: a caller identifies a member, says what imaging service is needed, and the request moves from a generic Intake process to the Radiology process that owns it, through the questions Radiology needs, to submission. The same steps serve a web UI, an AI agent over MCP, and service-to-service calls.
This README is a short walkthrough. The conventions and details live in
AGENTS.md (auth model, history, audit fields, configuration, ports) and
docs/HANDOFF.md (the cross-processor handoff, backend and UI).
| Folder | Holds |
|---|---|
src/ |
the services (Services/*), the router, the EventCollector, the seeder, and Common (the steps, handlers, query sources, data and dev auth they share) |
ui/ |
the Angular app |
compose/ |
the compose files for running everything in containers |
docs/ |
design notes (the handoff) |
tests/ |
tests, when there are any |
data/ |
the SQLite databases, created at runtime (not committed) |
flowchart LR
ui["priorauth-ui<br/>(Angular)"]
agent["AI agent<br/>(MCP client)"]
router["Router :8080<br/>registry aggregate, /mcp, dev login"]
intake["Intake :8085"]
radiology["Radiology :8088"]
history["History :8089"]
member["Member :8084"]
provider["Provider :8083"]
others["CodeSet :8082<br/>Configuration :8087<br/>ReferenceData :8081"]
events["EventCollector :8086"]
ui --> router
agent --> router
router --> intake
router --> radiology
router --> history
intake -- "handoff" --> radiology
intake --> member
radiology --> member
radiology --> provider
intake -- "records" --> history
radiology -- "records" --> history
intake --> others
radiology --> others
intake -. "events" .-> events
radiology -. "events" .-> events
| Host | Role |
|---|---|
| Router | One entry point: the aggregated registry of every service, the MCP endpoint (/mcp), the dev login (POST /auth/login), and a reverse proxy to the services. The UI is a client in front of it, not served by it. |
| Intake | The front door process: start, find the member, capture the requested service, hand off to the product process. |
| Radiology | The product process: MRI/CT questions, providers, clinical questions, review, submit. |
| History | One record per request with its latest snapshot; the request list in the UI and the agent's first tool. |
| Member, Provider, CodeSet, Configuration, ReferenceData | Query sources (member search, provider search, procedure codes, questionnaires and routing rules, plans and states). |
| EventCollector | Receives the services' events (redacted PHI) so they can be inspected. |
Each service has its own SQLite database under data/; Intake and Radiology also keep Kaleido's
process state, outbox and idempotency records in a *-process.db.
-
Get the Kaleido packages. The sample uses Kaleido's preview packages from its GitHub Packages feed (
nuget.config). The feed needs a GitHub classic personal access token withread:packages, even though the packages are public (GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)). Add it to your user-level NuGet configuration, never to a file in this repository;Read-Hostkeeps it off the command line:$t = Read-Host 'token' -MaskInput dotnet nuget add source https://nuget.pkg.github.com/no1ross/index.json --name kaleido --username <your GitHub user> --password $t Remove-Variable t
For the containers, set
KALEIDO_FEED_TOKENto the same token beforepodman compose up --build: the images restore with it as a build secret, never stored in an image. Building needs the .NET 9 SDK (global.json).To build against a Kaleido checkout next to this repository (
..\Kaleido) instead of the packages, copyKaleido.Local.props.exampletoKaleido.Local.props, or pass-p:UseLocalKaleido=true. OpenKaleido.Samples.PriorAuth.Local.slnxto see the Kaleido projects too. SeeAGENTS.md, "Local Kaleido source". -
Seed the shared reference databases (once, and after reference data changes):
dotnet run --project src/Seeder/Kaleido.Samples.PriorAuth.Seeder.csproj -- --domains=ReferenceData,CodeSet,Configuration,ProviderSearch,MemberService
-
Start everything, either way:
- Containers (Production settings):
podman compose up --buildincompose/runs the router (http://localhost:8080) and the services; the Aspire dashboard is athttp://localhost:18888. - Locally (Development settings):
dotnet runthe router, the services and the EventCollector (projects undersrc/Router/,src/Services/*/,src/EventCollector/). For traces,npx @microsoft/aspire-cli dashboard run.
Then run the UI, which is not containerized, either way:
npm startinui/(or Start onui/priorauth-ui.esprojin Visual Studio), and openhttp://localhost:4200. - Containers (Production settings):
-
Sign in with a persona from the UI's persona picker (the router's dev login issues a signed token; nothing here is a real identity provider).
The databases are created at startup with EnsureCreated, which never changes an existing file:
after an entity change, delete the affected .db (see AGENTS.md).
| Persona | Roles | Can |
|---|---|---|
alice |
none | use Intake and the searches; is refused at the hand-off to Radiology |
bob |
radiology |
run a request through Intake and Radiology to submission |
carol |
admin, radiology |
the same as bob in this sample |
Every host enforces authorization (ZeroTrust): each service declares who may use it, and a
capability without a rule is not published at all. The registry is filtered per caller, so alice's
UI and agent never see Radiology's steps. When Intake hands a request to Radiology it calls on
behalf of the user (the same name and roles, plus the calling service as the actor), so Radiology
still knows it is bob; some capabilities accept only service-to-service calls. The rules are in
AGENTS.md.
Signed in as bob, in the UI or through an agent:
| # | Process | Step | What happens |
|---|---|---|---|
| 1 | Intake | Start | A process is created; its id travels on every later call, searches included. |
| 2 | Intake | Validate Member, Capture Member | The member comes from a lookup over the Member search (name, member ID, date of birth); the caller picks the match. |
| 3 | Intake | Capture Requested Service | The procedure code decides the product (Configuration's routing rules): an imaging code hands the request to Radiology with the same process id. The response names the target processor; the consumer reads Radiology's state next (docs/HANDOFF.md). |
| 4 | Radiology | Capture MRI Information, or Confirm CT Instead Of MRI | An information request: Radiology asks the questions configured for the service (some only when an earlier answer makes them apply). The UI shows a form; an agent asks the user round by round. |
| 5 | Radiology | Capture Requesting Provider, Capture Servicing Provider | Providers come from searches that only Radiology may call; Radiology publishes them to the caller as views over those internal searches. |
| 6 | Radiology | Capture Clinical Information | Clinical questions, when the service requires them. |
| 7 | Radiology | Review, Submit | A review of everything captured, then an attestation as the last information request. |
After every step that changes what a reviewer would see, the process records its snapshot in History (the step fails if History does not record it). Each row in the Intake, Radiology and History databases also records who created and last changed it, through which service, and when (audit fields). The History page lists requests; a request can be resumed from there.
To follow one request across services, filter the Aspire dashboard by its process id or request id: every hop carries both (correlation headers), and the events the services publish reach the EventCollector with PHI removed.
The router's MCP endpoint is http://localhost:8080/mcp. It needs a persona's token:
./scripts/mcp-login.ps1 bob signs in and stores the token where the
repository's MCP configuration reads it. The agent starts with a short list of tools (the request
list and the entry steps); the next steps, their inputs and the searches they need arrive with each
response as the process moves on.
AGENTS.md: conventions, auth model, history recording, audit fields, configuration per environment, portsdocs/HANDOFF.md: the Intake to Radiology handoff, its contract and the UI's handling- Kaleido and its consumer guide: Kaleido's concepts with a smaller, tested example
- MCP adapter: tool listing, information requests over MCP