
Initial Release
An AI operations desk for an InterSystems IRIS integration team, built on IRIS AI Hub and
Interoperability. Your team defines the agents; each one works inside a real production
and acts only with a person’s approval.

An integration team’s knowledge drifts. A runbook still says partner orders arrive by SFTP
weeks after the team moved partners to a REST API. Alerts arrive faster than anyone
can rate them. Code reviews wait, and nobody knows what the AI tools cost.
| When | Agent | What happens |
|---|---|---|
| A new decision note is filed | Documenter | Finds the old runbook it contradicts, flags the conflict and proposes corrected text. A person approves, and the note is fixed through the production |
| A disk alert arrives | Sentinel | Checks known issues, rates it high and proposes a message for the person on call |
| A developer pastes a line | Forge | Compiles it without running it, checks it against the team’s standards and names the broken rule |
| A lead asks “what did the agents cost?” | Query | Writes the SQL, validates it without running it, and shows it |
| A new need appears | Build an agent | Anyone with access creates a new agent in the UI, with its own decisions, tools and fallback rules. It runs in the production straight away, with no new class |
Agents are stored records, not generated classes. One runtime runs them all. Each agent has
its own instructions, model, permitted tools, private knowledge and memory.
No OpenAI key? The production still runs: each agent’s fallback rules decide, the run is
labelled Rules, not AI, and it goes to review.
File inbox or web UI -> DocumentIngress / AlertIngress / CodeIngress / ChatIngress (BS)
|
AgentWorkflow (BP) -> AgentRunner (BO) -> AI Hub agent + permitted tools
|
validated decision routes the message
|
+------------+------------+
v v
ResultHandler (BO) ReviewQueue (BO)
| Deterministic code | AI judgment |
|---|---|
| Saving documents, checking tool permissions, enforcing ownership, validating the decision against allowed values, routing | Choosing search terms, comparing notes, proposing a decision |
An invalid or failed reply never gets accepted: it goes to review.
| Capability | How |
|---|---|
| Decide | Every run ends in a decision from the agent’s own list. Invalid replies go to review |
| Use tools | Search knowledge, link notes, flag conflicts, read and save memory, each granted per agent |
| Propose actions | Send a notification, or update a note. Nothing runs until a person approves it in Review; approval re-checks the proposal and runs it through the production |
| Keep working without AI | If there’s no key or the provider fails, the agent’s fallback rules decide. These runs are labelled Rules, not AI and always go to review |
| Check, not guess | Forge compiles code as a throwaway routine, which never runs it. Query prepares SQL without running it, only SELECT, only the Mercurius_Model schema |
| Choose their model | Each agent has its own provider and model, chosen from the provider’s live list. Only providers with a key are offered |
| Show their cost | Each run records the model, tokens and an estimated cost |
![]() |
![]() |
![]() |
![]() |
Mercurius.Test.Eval sends 55 labelled inputs through the production, with fresh sample
knowledge for each. On build 109, 2026-11-07 and 2026-11-08:
| Agent | Model | Correct | Search found the right note | Estimated cost, whole set |
|---|---|---|---|---|
| Documenter | gpt-4.1 | 13 of 15, twice | 10 of 10 | $0.028 |
| Sentinel | gpt-4.1-mini | 14 of 15 | n/a | $0.002 |
| Forge | gpt-4.1 | 14 of 15 | n/a | $0.021 |
| Query | gpt-4.1-mini | 8 of 10 | n/a | $0.005 |
CheckSyntax. On gpt-4.1-mini it scored 9 to 11, so it uses gpt-4.1.gpt-4.1-mini it scored 12 to 15 of 15 depending on the run and the example notes, and sometimes missed the main demo conflict, so it uses gpt-4.1.Run it yourself:
echo 'Do ##class(Mercurius.Test.Eval).Run() Halt' | docker compose exec -T iris iris session IRIS -U IRISAPP
2027.1.0AI.109.0, from theOPENAI_API_KEY. The default model is gpt-4.1-mini.ANTHROPIC_API_KEY. Each agent can then use a Claude model, chosen in the agent form.Load the image and give it the short tag used by the Dockerfile:
docker image load -i iris-community-2027.1.0AI.109.0-docker.tar.gz
docker tag docker.iscinternal.com/docker-intersystems/intersystems/iris-community:2027.1.0AI.109.0 iris-community:2027.1.0AI.109.0
Clone, add your key and start:
git clone https://github.com/xJuan21/mercurius-agents.git
cd mercurius-agents
cp .env.example .env # then put your key in .env
docker compose up -d --build
The build compiles the code, seeds the sample agents and sets the production to start
with IRIS.
On an IRIS AI Hub instance, build 2027.1.0AI.109.0:
zpm "install mercurius-agents"
This installs the backend: agents, production, API and sign-in. The web UI is built by Docker,
so use the Docker install above for the full app. IRIS needs OPENAI_API_KEY in its
environment.
docker compose ps # shows (healthy)
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:52775/mercurius/ # 200
Backend checks, no LLM needed:
echo 'zpm "mercurius-agents test -only" Halt' | docker compose exec -T iris iris session IRIS -U IRISAPP
This writes test records, and demo/run.sh resets them.
Open http://localhost:52775/mercurius/ and sign in with an IRIS account. On a fresh local
build, that’s _SYSTEM with the image’s default password. To add a user, create it in the
portal and give it the MercuriusUser role.
| Page | What it does |
|---|---|
| Chat | Sends a message through the production and shows the agent’s decision, tools and trace |
| Agents | Shows the agents. Build an agent creates a stored agent that runs immediately |
| Review | Approve or reject proposed actions, and resolve possible contradictions |
| Inbox | Notifications a person approved |
| Memory, Knowledge | Show what the selected agent holds privately |
| History | Shows every run, including those from the file services |
To send the sample events through the file services instead:
./demo/run.sh
LLM judgment varies between runs. The Management Portal is at
http://localhost:52775/csp/sys/UtilHome.csp, and each decision links to its message trace.
Use Build an agent in the UI: no new class is needed. The runtime adds the reply format
from the agent’s decision values. To drive an agent from a file service too, add a Business
Service item and set its AgentName setting.
The UI is an Angular app in web/, built in a separate Docker stage. IRIS serves the output.
| Path | Purpose |
|---|---|
web/src/app/core |
Sign-in, API access and shared state |
web/src/app/layout |
The main layout |
web/src/app/shared |
Reusable components |
web/src/app/features |
One folder per page |
Sign-in uses the JWT support built into IRIS REST web applications. IRIS checks the password
and provides /login, /refresh and /logout. The access token stays in memory, and the
refresh token stays in sessionStorage, so it ends with the browser tab.
| Bonus | Where to see it |
|---|---|
| Docker | Dockerfile and docker-compose.yml: docker compose up -d --build |
| IPM (ZPM) | module.xml: zpm "install mercurius-agents" installs the backend, and zpm "mercurius-agents test" runs the tests |
| AI Hub bugs | ai-hub-eap-bugreports #25: a tool hidden by %CanList is rejected with no audit record. #26: iris-main aborts with exit 134 when the working directory isn’t writable |
Copyright (c) 2026 Juan Mota Sanchez. Released under the MIT License.
Forge’s review checklist comes from the objectscript-review skill in
iris-agentic-dev (InterSystems,
MIT License). Built on the ai-hub-dev-template.
EnsLib.AI.Agent.PassthroughOperation?IRIS ships an experimental operation and adapter for calling an agent from a production.
We tested them on build 109 and kept our own AgentRunner operation.
EnsLib.AI.Agent.* |
AgentRunner |
|
|---|---|---|
| Agent configuration | Built once per production item, at startup | Loaded from a stored record on each message |
| Agents per item | One | Any |
| Per-agent tool identity | Not with settings alone: AuthPolicy is created with a bare %New() |
Set by the runtime |
| A failed LLM call | Message error | Recorded result, routed to review |
| Message trace | Persistent LLMResponse with usage |
Our own message classes |
Subclassing the adapter and using its OnAfterAgentInit hook does give per-agent tools,
so it’s a possible later direction once the classes are no longer experimental.
Mercurius.Runtime.ToolPolicy checks every tool against the agent’s grants, andKnowledgeService enforces ownership, so relationships can’t cross agents.Mercurius.Model.AgentToolCall.MercuriusAccess resource. /mercurius/ serves only the built web files.| Symptom | Cause | Fix |
|---|---|---|
Container restarts with exit 134: Unable to find/open file iris-main.log |
The repo is bind-mounted over the working directory, and the IRIS user can’t write there | working_dir: /home/irisowner in docker-compose.yml |
<PRIVATE METHOD> from ToolManager.AddToolSetInstance() |
The method is private | Use ToolManager.AddTool(instance), which keeps instance properties through Chat() |
A tool hidden by %CanList isn’t audited when called |
The call fails as ToolNotFound, before %CanExecute and %LogDenial |
Also check permissions in your own services |
No repositories are configured from zpm in another namespace |
enable -community alone doesn’t map the repositories |
Also run zpm "enable -map -repos -globally" in %SYS |
| A static web application returns 404 at its root | It has no default document | Serve index.html from a small dispatch class, as Mercurius.API.Web does |
Every %CSP.REST route returns the same output |
A method named Page overrides %CSP.Page |
Use another method name |
An API call fails on a Role column |
Role is reserved in SQL |
Quote it |
NotifyTarget setting of ActionIngress at another operation.