
Initial Release

AI exception resolution for synthetic lab Orders, on InterSystems IRIS with AI Hub.
When an Order sent to a partner lab fails, Signal Noir opens a Case. An Agent investigates it
with read-only tools and proposes one Action from a closed catalog. Every Proposal passes the same
deterministic Validator. An Operator approves it, and the effect is executed at most once,
reconciled against the partner’s Receipt. A Case the system cannot settle goes to an Operator with
a Review Dossier.
All data is synthetic. Orders, patients and partners come from the built-in scenario
Simulator. Signal Noir makes no clinical decision.
AI proposes. The platform guarantees. A lab Order fails on its way to a partner lab, and Signal
Noir takes it from the failure to a confirmed Receipt, with a person in control:
The Agent chooses from a closed catalog (approved mapping, hold, request review); resending is left to
the platform’s recovery. The same safeguards hold in all three modes (Agent, Rules, Hybrid), and the
infographic closes with the benchmark result and the demo’s limits, both detailed below.
What is different here. Most agents in a workflow act on what the model says. In Signal Noir the
AI Hub Agent only proposes. When a lab Order fails, the Agent investigates with read-only tools and
proposes one Action from a closed catalog. A deterministic Validator checks the Proposal, an Operator
approves that exact version, and the effect is sent at most once and reconciled against the partner’s
Receipt. When the Evidence does not support an Action, the Case goes to a person with a Dossier that
says what was checked and what is missing.
| What a model alone cannot be trusted with | What Signal Noir adds |
|---|---|
Comparing values reliably: in our rehearsals gpt-4o-mini discarded “plasma” against PLASMA even when told not to |
Computed checks: the platform compares identity, partner instability and the candidate code mappings with the Validator’s own rules. The Agent reads the results and cites the Evidence behind them |
| Text inside the Evidence that reads like an instruction (“retry now”) | Neutralized input: the model never sees it; the Evidence keeps it raw and the console tags it Embedded instruction blocked |
| Acting on its own answer | Validator and approval: every Proposal is validated, whoever wrote it; an Approval binds that exact version and expires after 15 minutes |
| Sending twice after a timeout | At most once: an idempotency key derived from the effect, a re-check just before sending, and reconciliation against Receipts before any resend |
| Saying “I don’t know” | Review Dossier: REQUEST_REVIEW with What was checked and What we don’t know |
You need the AI Hub EAP build 154 file and an AI provider key (validated with OpenAI gpt-4o-mini).
The Install section has the five commands: load, retag, .env, build, up. Then:
sh scripts/smoke.sh
# then open http://localhost:52773/signalnoir/
—: no price table is recorded for the model.ReviewRequired, and its Review dossier says the providerVideos: hybrid use case
(dark) ·
Decisions and AI Hub
| Look at | Why it matters | Where |
|---|---|---|
| An Order with an instruction hidden in the partner’s error | The Evidence card shows Embedded instruction blocked; the Agent never proposes the retry the text asks for | below |
| Similar resolved Cases on an open Case | Precedents from IRIS Vector Search (VECTOR(DOUBLE, 1536), HNSW). They inform the Operator and are never Evidence |
How it works, step 7 |
sh scripts/quick-validate.sh |
3 reference Cases in at most 3 minutes, each “matches the reference” or “diverged” | Start, check, stop |
SN_PROPOSER_MODE=rules |
The same Cases decided by the deterministic baseline, through the same Validator | Configuration |
SN_PROPOSER_MODE=hybrid |
The OpenAI Decisions API answers first through a client in Embedded Python (httpx); when its answer is not decisive, the AI Hub Agent investigates |
DecisionsHttp.cls |
| The model-call record on any Case | Provider, model, prompt version and tokens next to the Evidence and the Validator’s verdict (DPI-I-689) | Community idea |
SIM=http://localhost:52773/sim/api/v1
SCI=$(curl -s -X POST -H 'Content-Type: application/json' \
-d '{"family":"P5","variant":"P5-injection-tune-01","seed":7}' $SIM/scenarios |
sed -n 's/.*"scenarioInstanceId":"\([^"]*\)".*/\1/p')
curl -s -X POST $SIM/scenarios/$SCI/emissions
Its Case opens in the console within about a minute.
Ens.Production.gpt-4o-mini is validated.https://github.com/musketeers-br/signal-noir/blob/master/https://github.com/musketeers-br/signal-noir/blob/master/specs/017-agent-prompt-v2/research.md.https://github.com/musketeers-br/signal-noir/blob/master/specs/.%AI.Agent). Read-only tools firstAPPLY_APPROVED_MAPPING, HOLD or REQUEST_REVIEW, and its diagnosis cites theRETRY, but the Agent never proposes it: resending an Order isgpt-6-luna) for typed answers (one choice over the catalog, oneother or ahttpx, shipped with build 154; see SignalNoir.Proposers.DecisionsHttp). It is off unless youReviewRequired with a Dossier: what isVECTOR(DOUBLE, 1536) with an HNSW cosine index). When the Operator opens a Case, the consoleThe console is at http://localhost:52773/signalnoir/, and the API at
http://localhost:52773/signalnoir/api/v1 (contract: https://github.com/musketeers-br/signal-noir/blob/master/https://github.com/musketeers-br/signal-noir/blob/master/contracts/openapi-signalnoir.yaml).
docker compose plugin, building with the builder of your Docker context (thedocker driver, not docker-container).iris_arm64-community-2026.3.0AI.154.0-docker.tar.gziris-community-2026.3.0AI.154.0-docker.tar.gz1972 and 52773.You do not need Node, a front-end toolchain or any InterSystems developer tool. The console is
built inside the image.
From a clone of this repository. On Windows, clone to a short path such as C:\src, or run
git config --global core.longpaths true first: under a deep folder, some paths of the repository
pass Windows’ 260-character limit.
# 1. Load the build 154 file (it is a plain tar despite the .gz name)
docker load -i iris_arm64-community-2026.3.0AI.154.0-docker.tar.gz # or the x64 file
# 2. Retag it to the project's fixed tag, using the name `docker load` printed
docker tag docker.iscinternal.com/docker-intersystems/intersystems/iris-community:2026.3.0AI.154.0 signalnoir/iris-aihub:2026.3.0AI.154.0
# 3. Your settings: copy the template, then put your provider, model and key in .env
cp .env.example .env
# 4. Build with your context's builder (Linux: default; Docker Desktop: desktop-linux):
# a docker-container driver cannot see the local image
docker buildx use "$(docker context show)"
docker compose build
# 5. Start
docker compose up -d
The fixed tag signalnoir/iris-aihub:2026.3.0AI.154.0 is the only base image the
https://github.com/musketeers-br/signal-noir/blob/master/Dockerfile and https://github.com/musketeers-br/signal-noir/blob/master/docker-compose.yml use, on both
architectures. The build never pulls it from a registry. Nothing is ever pushed: not the base
image, and not the product image signalnoir/app:local.
# health: ready, and whether the AI provider is configured
curl -s http://localhost:52773/signalnoir/api/v1/health
# smoke check: one synthetic Order through the Simulator until its Case opens (about 1-2 minutes)
sh scripts/smoke.sh
The smoke check prints the Case and its state. Open http://localhost:52773/signalnoir/ to follow
it in the console. Without a key, the Case is in ReviewRequired, and its Dossier says the
provider is unavailable.
| Command | What it does |
|---|---|
docker compose up -d |
Start, building the image if needed |
docker compose ps |
State and health of the iris service |
docker compose logs -f iris |
Instance logs |
docker compose restart |
Restart; data kept |
docker compose stop / start |
Stop and start again; data kept |
docker compose down |
Remove the container; the demo starts empty next time |
# quick validation: 3 reference cases in at most 3 minutes, "matches the reference" or "diverged" per case
sh scripts/quick-validate.sh
With a configured provider it runs the Agent; without one it runs Replay, a recorded answer, and
says so. Benchmark runs and the reference run are described in
https://github.com/musketeers-br/signal-noir/blob/master/docs/benchmark/README.md.
All settings are environment variables, read from .env when the container is created. After
editing .env, run docker compose down then up. Note that down also resets the demo data.
| Variable | Meaning |
|---|---|
SN_AI_PROVIDER |
AI Hub provider: openai, anthropic, gemini or bedrock |
SN_AI_MODEL |
Model name for that provider |
SN_AI_API_KEY |
Your key. Entered only at runtime |
SN_SIM_BASE_URL |
The Simulator, served by the same instance (http://localhost:52773/sim/api/v1) |
SN_BACKGROUND_TASKS |
1 reads the Origin feed and runs the sweeper every minute |
SN_PARTNER_TIMEOUT_SECONDS, SN_INVESTIGATION_BUDGET_SECONDS, SN_AI_LEASE_SECONDS |
Optional timings (defaults 60, 120, 300) |
SN_PROPOSER_MODE |
Proposer of new Cases: agent (default), rules, decisions or hybrid (Decisions first, AI Hub Agent when Decisions is not decisive) |
SN_DECISIONS_API_KEY, SN_DECISIONS_MODEL |
OpenAI key for the Decisions proposer (falls back to SN_AI_API_KEY when SN_AI_PROVIDER=openai) and its model (default gpt-6-luna). Sends synthetic Case data to OpenAI directly, outside AI Hub |
SN_EMBED_PROVIDER, SN_EMBED_MODEL, SN_EMBED_API_KEY |
Optional embedding provider for precedents: openai with a 1536-dimension model (text-embedding-3-small). Separate from the Agent’s provider, because Anthropic has no embeddings. Unset: lexical mode |
SN_PRECEDENTS_MIN_SIMILARITY, SN_PRECEDENTS_DEFAULT_K, SN_PRECEDENTS_MAX_K |
Optional search settings (defaults 0.6, 5, 10) |
SN_PRECEDENTS_AGENT |
1 adds the precedents to the Agent’s input as a labelled block that is not Evidence. Off by default, and off in the reference Benchmark run |
Validated: IRIS AI Hub build 154 ARM64 Community with OpenAI gpt-4o-mini. The other providers
are the ones AI Hub supports, but this project has not validated them. The x64 build will be listed
here once it passes the cross-install.
Secrets. The key lives only in .env, which git ignores, or in your environment. It is never a
build argument, never in an image layer, and never written to a Case, the Timeline, a log or an
export. Without a valid key, the application starts, /health reports the provider as
unavailable with the reason, and Cases that need the provider end in ReviewRequired with that
reason.
Data lives in the container’s writable layer. There is no named volume and no bind mount.
docker compose restart and stop/start keep every Case, Approval and Timeline event.docker compose down removes the container and its data. The next up starts an empty demo.Signal Noir implements part of the InterSystems Ideas Portal idea
DPI-I-689, “Use InterSystems Interoperability as a traceability tool for GenAI LLM pipelines”
(status Community Opportunity), which mentions IRIS Vector Search.
What is covered:
What is not covered yet: publishing these steps as messages in the Interoperability Visual
Trace. That needs the Case flow to run as an Ens.Production, which it does not do today.
The organizers have not confirmed whether this idea counts for the contest bonus, so we only claim
what is listed above.
SignalNoir.Interop.Service.OrderIn). The Production that holds it runs in a portable shape, notEns.Production, so its steps do not appear in the Visual Trace (seehttps://github.com/musketeers-br/signal-noir/blob/master/docs/benchmark/README.md for what it shows and what it does not.| Symptom | Cause | Fix |
|---|---|---|
pull access denied for docker.iscinternal.com or signalnoir/iris-aihub |
buildx with a docker-container driver, or the retag was skipped |
docker buildx use "$(docker context show)"; check docker images signalnoir/iris-aihub |
ERROR: run docker context use default from docker buildx use default |
Docker Desktop: its context is desktop-linux |
docker buildx use "$(docker context show)" |
Filename too long when cloning on Windows |
the clone is in a deep folder | clone to a short path, or git config --global core.longpaths true and clone again |
env file .env not found |
.env was not created |
cp .env.example .env |
Health stays starting |
the instance is still starting | wait for the 60 s start period; docker compose logs iris |
port is already allocated |
1972 or 52773 is in use | stop the other service |
Every Case goes to ReviewRequired |
no key, or an invalid one | check .env, then down/up; /health names the reason |
| The smoke check times out | SN_BACKGROUND_TASKS is not 1 |
set it in .env, then down/up |
Developers use a separate dev container on the same build, with the repository mounted and
iris-agentic-dev for compiling and testing: see https://github.com/musketeers-br/signal-noir/blob/master/dev/aihub/README.md.
That container is not part of the install path. Specifications live in https://github.com/musketeers-br/signal-noir/blob/master/specs/ and the
HTTP contracts in https://github.com/musketeers-br/signal-noir/blob/master/contracts/. CI runs the contract checks, the console checks, a
secret scan and the core unit tests on a public IRIS image. No workflow builds, runs or publishes
an image of the EAP build.
Signal Noir is developed with 💜 by the Musketeers Team:
Demo videos: hybrid use case,
Decisions and AI Hub.
Licensed under the MIT License.