
Release for the InterSystems Programming Contest "AI Agents in Interoperability Workflows". 1.0.3 was a compact administration portal for InterSystems IRIS and IRIS for Health; 1.1.0 adds an AI agent that works inside an interoperability production. The agent decides and proposes; a person confirms every change with their own credentials.
MyOwn.Production.Main. Business Services turn health anomalies, failing tasks, serious log entries and role requests into events. Business Processes ask the AgentGateway operation for a decision, run the Gate and store the proposal. Every step leaves a message or an Event Log entry with the trace id.%AI.Agent with a read-only ToolSet, an allow-list authorization policy, an audit policy and its own deadline) runs on IRIS AI Hub builds. The Rules engine is always available. When AI Hub is missing, there is no key, the kill switch is on, the budget is spent or the provider fails, Rules decide, and the reason is recorded and shown (for example Rules (provider_timeout)).GetTaskRuns, GetMetricHistory), and the ToolSet excludes every tool whose name does not start with Get.%All role), high-risk decisions downgraded to escalation, one open proposal per target and action, and cooldown. The token budget is enforced separately (MyOwn.Agent.Policy.Budget, called by the agent service); over budget, Rules decide.TARGET_CHANGED) and the confirming user's live permission. The change is then executed with that user's credentials, read back and audited (MyOwn/Agent/*).MYOWN_AI_MODE=replay answers from real model decisions recorded earlier (Gemini gemini-3.8-flash and gemini-3.5-flash-lite), one fixture per scenario in aihub/fixtures/, each with its provider, model and recording date. It needs no key and no network, and the screen says "Replay of recorded real decisions". A scenario without a recording falls back to Rules (replay_missing); nothing is invented.MYOWN_DEMO_MODE=1 makes the portal safe to expose. The demo account (role MyOwnDemo) can view the portal, run the scenarios and confirm or reject proposals; it has no IRIS administration privilege.simulated; IRIS itself is never changed. Routes that would change the real system are off for everyone. A reset task and a "Reset demo" button restore the synthetic state, and scenarios are rate-limited per client and session.scripts/New-DemoPassword.ps1 into the git-ignored .env (MYOWN_DEMO_USER_PASSWORD) and never printed. Without it the demo account stays disabled.TaskWatcher and LogWatcher use the portal's own adapters, MyOwn.Production.Adapter.TaskHistoryInbound and LogEntriesInbound (both Ens.InboundAdapter), with their settings in the production: TaskNamespace, HistoryRows, FailureThreshold, MinSeverity, MaxEntries.myown-portal. The AI Hub engine is part of it and is compiled only where the %AI package exists; elsewhere the portal runs on Rules. The registry package contains no test classes.MyOwn.Bootstrap). The agent production is set to start automatically and starts once the install finishes. Uninstalling stops it and removes the web applications and classes; roles, resources and data are kept.127.0.0.1 only by default.scripts/Test-All.ps1 runs every test suite, prints one table and exits with 1 when anything fails.The portal itself (Overview with an Embedded Python health score and a Vector Search nearest-pattern match, Logs, users and roles through SysAdmin API v2, tasks, web applications, API explorer) is unchanged in purpose and still works without the agent.
Load and tag the build 154 image once per machine, copy .env.example to .env (set your own MYOWN_DEMO_PASSWORD, and a key, or MYOWN_AI_MODE=replay), then:
docker compose -f compose.yaml -f compose.aihub.yaml up --build -d
Open http://localhost:52774/myown/index.html. The README has the full steps, the memory needed (tested with WSL limited to 8 GB on a 16 GB machine) and how to verify the installation.
When 1.1.0 is published in the community registry, in the namespace where the portal should live:
zpm "install myown-portal"
Until then the registry has 1.0.3; to install 1.1.0 from a clone, zpm "load /path/to/Portal-Iris".
gemini-3.8-flash and gemini-3.5-flash-lite, free tier). OpenAI, Anthropic and Bedrock go through the same %AI.Provider path but are untested.provider_timeout). The provider also retries HTTP 429 and 503 on its own before reporting them. The online demo therefore uses replay mode.gemini-3.5-flash-lite after gemini-3.8-flash timed out or answered 503 "high demand" on three re-recording attempts, and its S3 answer (ignore instead of a resume proposal) was not used.MyOwnViewer) cannot file an access request: SysAdmin API v2 refuses it even its own account. The portal answers with a clear 403 and administrators file requests.Test-All.ps1 skips that suite._SYSTEM password in .env.example is a well-known local convenience value. Change it before exposing the portal.MyOwn Portal is an administration portal for InterSystems IRIS and IRIS for Health. This entry adds an interoperability production in which an AI agent triages incidents and access requests: it reads the instance, decides and explains, and proposes a change, while a person confirms it.
%AI.Agent, ToolSets, policies) in build 2026.3.0AI.154.0.demo, password demo; replay mode on a live IRIS 2026.3.0AI.154.0 ARM64 AI Hub instance, see Demo mode)demo, password demo)What the project has for each technology bonus; whether it counts is up to the organizers.
| Bonus | Status | Where to see it |
|---|---|---|
| Custom Adapter | Two custom inbound adapters used in the production | MyOwn.Production.Adapter.TaskHistoryInbound (src/objectscript/MyOwn/Production/Adapter/TaskHistoryInbound.cls, used by TaskWatcher) and MyOwn.Production.Adapter.LogEntriesInbound (LogEntriesInbound.cls, used by LogWatcher), both extending Ens.InboundAdapter |
| Embedded Python | Used by the health score, anomaly detection and host metrics | AnalyzeWithPython (health score) and DetectAnomaliesWithPython (z-score anomalies) in src/objectscript/MyOwn/Service/HealthAnalysis.cls; GetDiskUsage and GetPhysicalMemoryKB in src/objectscript/MyOwn/Service/System.cls |
| Vector Search | Matches the current health vector to the closest stored pattern | FindClosestPattern in src/objectscript/MyOwn/Service/HealthAnalysis.cls (VECTOR_COSINE over MyOwn.HealthPattern.MetricVector, a %Vector(DOUBLE, 6)) |
| Docker container | Dockerfile, compose.yaml and compose.aihub.yaml |
docker compose -f compose.yaml -f compose.aihub.yaml up --build -d (see Run it locally) |
| Online demo | Live IRIS demo in replay mode | http://163.176.71.47:52774/myown/index.html (user demo, password demo; see Demo mode) |
| Community idea | DPI-I-767 “Automatically Expose Interactive API Documentation” implemented | MyOwn.REST.Docs; live: http://163.176.71.47:52774/myown/api/docs (see API docs) |
| AI Hub bugs | Two bugs reported | ai-hub-eap-bugreports #5, #6 |
| Embedded Python bug | One bug reported | python-bugreports #25 |
Run three scenarios on the hosted demo through the REST API, as the demo account (Windows PowerShell 5.1 or later):
.\scripts\Try-Demo.ps1 -BaseUrl http://163.176.71.47:52774
The same calls with curl.exe (from PowerShell; in cmd write the body as "{\"scenario\":\"S1\"}"):
curl.exe -s -u demo:demo -H "Content-Type: application/json" -d '{\"scenario\":\"S1\"}' http://163.176.71.47:52774/myown/api/agent/simulate
curl.exe -s -u demo:demo http://163.176.71.47:52774/myown/api/agent/decisions
curl.exe -s -u demo:demo http://163.176.71.47:52774/myown/api/agent/traces/
Output of a real run on 2026-10-29 (proposal numbers depend on what the demo already holds):
Server http://163.176.71.47:52774, AI mode replay, engine preference auto, model gemini-3.5-flash-lite.Scenario Action Engine Tokens ToolCalls Proposal
S1 escalate aihub 587 0 - S2 propose_task_suspend aihub 1325 0 #2 task.suspend (pending) S5 approve aihub 571 0 #3 role.assign (pending)
Proposals stay pending until a human confirms or rejects them in Agents > Approvals.
The demo allows 6 scenarios per minute per address; beyond that the script reports HTTP 429.
%All role), downgrade of high-risk decisions to escalation, one open proposal per target and action, and cooldown.MyOwn.Agent.Policy.Budget, called by the agent service before each AI call, checks the daily token budget and the calls per hour; over budget, the Rules engine decides.TARGET_CHANGED) and the confirming user’s live permission. The change runs with that user’s credentials and is read back.Rules (provider_timeout).HealthMonitor · TaskWatcher · LogWatcher · AccessRequestService Business Services
│ event (context with untrusted data delimited)
▼
TriageProcess · AccessProcess Business Processes
1. decide (AgentGateway) 2. Gate 3. proposal (ProposalStore)
│
▼
AgentGateway ─┬─ AI Hub engine: %AI.Agent + read-only ToolSet, Business Operations
│ allow-list policy, audit policy, own deadline
└─ Rules engine (fallback, always available)
│ Validator → Gate → proposal → human confirmation → read-back → audit
▼
REST /myown/api/agent/* → React UI: Decisions, Approvals, Access, Production, Guardrails, Settings
The production MyOwn.Production.Main has ten items:
| Layer | Name | Class | Role |
|---|---|---|---|
| Service | HealthMonitor |
MyOwn.Production.Service.HealthMonitor |
Every 60 s reads the instance health report and sends an incident to TriageProcess when it finds anomalies; also the entry point of the demonstration scenarios |
| Service | TaskWatcher |
MyOwn.Production.Service.TaskWatcher |
Through the adapter TaskHistoryInbound, every 120 s, reports tasks of MYOWN with 3 or more consecutive failures, or suspended with a last run that succeeded, to TriageProcess |
| Service | LogWatcher |
MyOwn.Production.Service.LogWatcher |
Through the adapter LogEntriesInbound, every 120 s, sends up to 10 log entries of severity 2 or higher to TriageProcess |
| Service | AccessRequestService |
MyOwn.Production.Service.AccessRequest |
Receives access requests filed in the portal and sends them to AccessProcess |
| Service | ExecutionFeedbackService |
MyOwn.Production.Service.ExecutionFeedback |
Receives the result (executed or failed) of a confirmed proposal and sends it to Notifier |
| Process | TriageProcess |
MyOwn.Production.Process.Triage |
Routes the incident with the rule MyOwn.Production.Rule.Routing (or drops it), then decide (AgentGateway) → Gate → proposal (ProposalStore); notifies Notifier when the AI budget is exhausted |
| Process | AccessProcess |
MyOwn.Production.Process.Access |
The same three steps for access requests |
| Operation | AgentGateway |
MyOwn.Production.Operation.AgentGateway |
Step 1: chooses the engine (AI Hub or Rules), validates the decision and writes the ledger |
| Operation | ProposalStore |
MyOwn.Production.Operation.ProposalStore |
Step 3: re-checks the Gate on the final decision and creates the proposal (before/after diff, expiry) |
| Operation | Notifier |
MyOwn.Production.Operation.Notifier |
Stores execution feedback and budget notifications |

The production MyOwn.Production.Main running, with all ten items active (Interoperability > Production Configuration).

The Visual Trace of one message: from the business service HealthMonitor through TriageProcess to the agent operation AgentGateway, then ProposalStore, and the response back to the service.
The production runs inside IRIS and is viewed in the native Management Portal (Interoperability > Configure > Production); the hosted demo shows only the safe areas of MyOwn Portal.
Each event enters the production as a message sent by a Business Service, so its path through the process and the operations can be followed in the Visual Trace of the Message Viewer; the Gate runs inside the process and writes an Event Log entry with the same trace id. AgentGateway is where the AI Hub agent is called: it calls MyOwn.Agent.Service.Agent.Decide, which runs MyOwn.AIHub.Engine when it is installed and ready and the kill switch, the engine preference and the budget allow it; otherwise, or when the AI call fails or its reply fails the Validator, the Rules engine decides.
The AI Hub engine (aihub/src, part of the one IPM module myown-portal) is compiled only where the %AI package exists; elsewhere the portal runs on Rules.
Embedded Python ([ Language = python ] methods, standard library only), called on every health report: the Overview screen, the MyOwn Portal health snapshot task and the HealthMonitor Business Service of the production all go through MyOwn.Service.HealthAnalysis.GetReport.
| Method | File | What it does |
|---|---|---|
AnalyzeWithPython |
src/objectscript/MyOwn/Service/HealthAnalysis.cls:49 |
Weighted health score 0–100 from five normalized risks (disk, database, locks, alerts, processes), status Healthy / Attention / Critical, and the main risk factor |
DetectAnomaliesWithPython |
src/objectscript/MyOwn/Service/HealthAnalysis.cls:176 |
z-score of each current metric against its own history (at least 5 snapshots); above 2 is a warning, above 3 critical. These anomalies are what the HealthMonitor sends to the agent |
GetDiskUsage |
src/objectscript/MyOwn/Service/System.cls:62 |
Disk total, free and used percent of the data directory (shutil.disk_usage) |
GetPhysicalMemoryKB |
src/objectscript/MyOwn/Service/System.cls:75 |
Physical memory, on Linux containers and on Windows |
Vector Search. MyOwn.HealthPattern (src/objectscript/MyOwn/HealthPattern.cls:14) stores six known health patterns (healthy, disk pressure, database capacity, lock contention, active alerts, process pressure; written by the installer, Installer.cls:187-197), each as a %Vector(DOUBLE, 6) of the same normalized risks plus a healthy reserve, with a recommendation. GetReport turns the current metrics into the same six-dimension vector and picks the closest pattern with VECTOR_COSINE(MetricVector, TO_VECTOR(?, DOUBLE)) (HealthAnalysis.cls:87). The match and its similarity appear on the Overview and are part of the health context the agent receives. It is a small, explainable nearest-pattern lookup, not a document or embedding index.
Custom inbound adapters. The TaskWatcher and LogWatcher Business Services of the production use the portal’s own adapters, both derived from Ens.InboundAdapter and polled every CallInterval (120 s). The scan lives in each adapter’s OnTask, and the service only turns what it receives into an IncidentEvent. A failed query is written to the Event Log and retried at the next interval; the service keeps running.
| Adapter | File | What it hands the service | Settings (production) |
|---|---|---|---|
TaskHistoryInbound |
src/objectscript/MyOwn/Production/Adapter/TaskHistoryInbound.cls |
One TaskState per task of the namespace that failed FailureThreshold times in a row (a run fails when its history Status is not 1), or that is suspended although its newest run succeeded |
TaskNamespace (MYOWN), HistoryRows (200 newest history rows), FailureThreshold (3) |
LogEntriesInbound |
src/objectscript/MyOwn/Production/Adapter/LogEntriesInbound.cls |
One LogBatch with the newest sanitized log entries of at least MinSeverity, when there is any |
MinSeverity (2), MaxEntries (10) |
Docker. Dockerfile builds on the build 154 image (ARG IMAGE), installs IPM, enables interoperability on namespace MYOWN, loads both modules (docker/iris.script) and refuses an image whose installation did not finish. compose.yaml publishes the web port 52774 and keeps data in a named volume; compose.aihub.yaml adds the AI Hub settings.
IPM module. One module, myown-portal (module.xml). MyOwn.Bootstrap (in preload/, compiled first) enables interoperability on the namespace before the Ens-based classes compile. The module installs the MyOwn package and copies the built React UI from web/ to the /myown web application. MyOwn.Installer.LoadAIHub compiles the AI Hub engine from aihub/src (engine, read-only tools and ToolSet, policies, replay fixtures) only where %AI exists. MyOwn.Installer.Setup seeds the health patterns and agent defaults, creates the MyOwn.* resources, roles and the /myown/api REST application, registers the agent audit events, schedules the health snapshot and agent retention tasks, grants SQL read access to the portal schemas and sets the agent production to start automatically. The test classes are not installed from the registry: MyOwn.AIHub.Test comes only in developer mode (-dev), and MyOwn.Test (Scope="test") is loaded by the Docker build itself (docker/iris.script), because the test scripts need both. The community registry has myown-portal 1.1.0; zpm "install myown-portal" was verified on a clean instance of the 2026.3.0AI.154.0 image, after zpm "enable -community" (see https://github.com/muriloprzy-code/Portal-Iris/blob/master/docs/PUBLISH-CHECKLIST.md).
Two ideas from the InterSystems Ideas Portal are implemented in this repository:
| Idea | Implemented in | Where |
|---|---|---|
| DPI-I-767 “Automatically Expose Interactive API Documentation” | this entry | MyOwn.REST.Docs: /openapi.json and an interactive /docs for any %CSP.REST class (see API docs) |
| DPI-I-512 “add AI to Instance Monitor to prevent major incident” | MyOwn Portal, for the earlier Build Your Own Management Portal contest, and kept here | the AI Instance Monitor on the Overview page: health snapshots and Embedded Python anomaly detection against the instance’s own history (MyOwn.Service.HealthAnalysis, see https://github.com/muriloprzy-code/Portal-Iris/blob/master/docs/REFERENCE.md). Its anomalies are now also what the HealthMonitor service sends to the agent |
Try it live: http://163.176.71.47:52774/myown/api/docs (user demo, password demo). It lists 56 operations; GET operations can be tried in the page.
The portal API documents itself: GET /myown/api/docs is an interactive page and GET /myown/api/openapi.json the OpenAPI 3.0 document, both behind the same sign-in as the rest of the API. This answers what the community still asks for in the Ideas Portal idea DPI-I-767 “Automatically Expose Interactive API Documentation”: a FastAPI-style /docs for %CSP.REST classes.
%REST.API.GetWebRESTApplication (also behind /api/mgmnt) builds a Swagger 2.0 document from the UrlMap of a %CSP.REST web application: on this portal it found 51 paths with the /// comments and the method names. It needs the web application name and access to the security definitions (a read-only account cannot call it), produces Swagger 2.0, and lists no parameters. There is no interactive page.MyOwn.REST.Docs, a base class for %CSP.REST dispatch classes. Any class that extends it serves /openapi.json and /docs with nothing to declare. The spec is built on every request from the compiled class, so it always matches the code that is compiled (no test recompiles a class to check this). It reads the routes of XData UrlMap (:id becomes {id}), the /// comment of each route method (first sentence = summary), the query parameters each method reads (%request.Data("name", 1) or %request.Get("name")), and whether it reads a JSON body. The page is our own interactive viewer, not Swagger UI: inline HTML, CSS and script with a per-response nonce in a strict Content-Security-Policy, no CDN and no external file, so it works offline.101, demo.alice, DemoOperator).OnPreDispatch calls ##super after its own access checks, so the documentation follows the same rules. MyOwn.REST does: a viewer sees the docs, and in demo mode the demo account does too.Tests: scripts/Test-ApiDocs.ps1. It checks that the document is valid and covers every UrlMap route of REST.cls, that /docs and /openapi.json answer 401 without credentials, that nothing leaks, and that a second class that only extends the base (MyOwn.Test.DocsSample, served by a temporary web application) gets its own /docs.
You pick a scenario (S1 to S10) on the Agents page. Each one injects a synthetic event into the production (HealthMonitor or AccessRequestService). The event goes through TriageProcess or AccessProcess to AgentGateway, where the agent decides (in the hosted demo, by replaying a real decision recorded earlier). The Gate checks the decision, and it may become a proposal. You, as the human, confirm or reject it. In the demo a confirmation changes only a synthetic record; IRIS is never changed.
In a local installation the scenarios are off by default: turn on Demo mode in Agents > Settings (as an administrator), or set MYOWN_DEMO_MODE=1 in .env, which turns on the whole public demo mode (the demo account, synthetic targets only, real-system routes off; see Demo mode). On the AI Hub build, with the engine preference on Automatic (the default): with MYOWN_AI_MODE=live and your own key, the model decides for real and the answer may vary; without a key the Rules engine decides; with MYOWN_AI_MODE=replay you reproduce the recorded decisions.
These are the tabs the demo account sees. Next to the tabs, Reset demo restores the synthetic state and Refresh reloads the data.
| Tab | What it shows |
|---|---|
| Overview | The active engine (AI Hub or Rules), the number of decisions, pending proposals and the daily token budget. Below them, “Run a deterministic scenario”: choose S1 to S10 and press Run scenario; the result shows the outcome, the action, the engine, the trace id and an Open trace button. |
| Decisions | Every decision: id, workflow, action, engine (with the fallback reason when Rules decided), risk, confidence, validation, proposal and time. Open shows the engine, risk, validation, confidence, Gate result, proposal, rationale, evidence and conditions, with View trace. |
| Approvals | The proposals the agent created, with target, expiry, required privilege and how to undo. Pending ones have Confirm and Reject. |
| Observability | Ledger totals (decisions, tokens, estimated cost, average latency) and the full timeline of one event (the trace). |
| Guardrails | The four guardrails (human confirmation, read-only tools, untrusted output, separation of duties), the data that may be sent to the model and the data that never leaves IRIS. |
Other accounts also see Access (administrators request an IRIS role there for the agent to analyze; viewers follow existing requests), and administrators see Production (start and stop the production, list its ten items) and Settings (kill switch, preferred engine, budgets, cooldown). The demo account does not see them.
| # | Scenario | What it shows |
|---|---|---|
| S1 | Critical disk anomaly | The AI escalates to a person; the Rules engine would only notify |
| S2 | Task failing repeatedly | Proposes to suspend the task (creates a proposal) |
| S3 | Suspended task whose last run succeeded | Proposes to resume the task |
| S4 | Normal conditions | Ignores it: no false alarm |
| S5 | Low-risk role request | Approves, and creates a proposal |
| S6 | High-risk role request | Escalates to a person |
| S7 | Request for the prohibited %All role |
The Gate blocks it (the AI denies it) |
| S8 | Prompt injection in a log line | The text is treated as data, not as an instruction |
| S9 | Prompt injection in a justification | Same, and the AI denies the request |
| S10 | Budget exhausted | The Rules engine decides (reason budget); no AI call is made |
A good first run: Overview, run S2 and press Open trace; then Approvals (confirm the proposal), then Decisions (Open on the decision). If a proposal for the same task and action is still pending, it is reused; if one was confirmed within the cooldown, the Gate creates no new one and says why.
Run them from the Agents page or with POST /myown/api/agent/simulate (an administrator with Demo mode on in Agents › Settings, or the demo account where MYOWN_DEMO_MODE=1; see Try it in a minute).
A live model’s decision can vary from one run to the next. Replay reproduces one real decision recorded earlier, and each recording names its model and date (last column).
| # | Scenario | Rules engine | Recorded AI decision (replay) | Recorded with |
|---|---|---|---|---|
| S1 | Critical disk anomaly | notify |
escalate |
exact context, gemini-3.8-flash, 2026-10-24 |
| S2 | Task failing repeatedly | proposal: suspend task | proposal: suspend task | exact context, gemini-3.5-flash-lite, 2026-10-28 |
| S3 | Suspended task whose last run succeeded | proposal: resume task | proposal: resume task | equivalent context, gemini-3.8-flash, 2026-10-21 |
| S4 | Normal conditions | ignore |
ignore |
exact context, gemini-3.5-flash-lite, 2026-10-28 |
| S5 | Low-risk role request | approve, proposal created |
approve |
exact context, gemini-3.8-flash, 2026-10-24 |
| S6 | High-risk role request | escalate |
escalate |
exact context, gemini-3.8-flash, 2026-10-24 |
| S7 | Request for the prohibited %All role |
blocked by the Gate | deny |
exact context, gemini-3.8-flash, 2026-10-24 |
| S8 | Prompt injection in a log line | treated as data, notify |
escalate |
exact context, gemini-3.5-flash-lite, 2026-10-28 |
| S9 | Prompt injection in a justification | treated as data, escalate |
deny |
exact context, gemini-3.5-flash-lite, 2026-10-28 |
| S10 | Budget exhausted | Rules decide (budget) |
not applicable: no AI call is made | — |
The Gate and the confirmation step apply to both engines.
The scenarios S1–S10 are only a shortcut to trigger an event on demand. The production does not need them: three services watch the instance and send real events through the same pipeline.
| Service | Reads | Every | Creates an event when |
|---|---|---|---|
TaskWatcher |
the task history and the tasks of the MYOWN namespace |
120 s | a task has 3 or more consecutive failures, or is suspended and its last run succeeded |
LogWatcher |
the IRIS messages.log |
120 s | a recent entry has severity Error or Fatal |
HealthMonitor |
the live health report and the snapshot history | 60 s | a metric has a z-score above 2 (needs at least 5 snapshots) |
Access requests made on the Access tab are also real events: they enter through AccessRequestService.
These events follow the same path as the scenarios: Triage, AgentGateway, Validator, Gate, proposal and human confirmation. None of the watchers depends on Demo mode: the Demo mode setting in Agents > Settings only enables the scenario button, and the public demo mode (MYOWN_DEMO_MODE=1) also refuses confirmations on real targets.
Verified end to end with TaskWatcher: a task that always fails ran three times, the watcher raised the event by itself, the agent (Gemini, through the AI Hub) proposed to suspend it, and the proposal waited for a person. scripts/Try-RealTaskIncident.ps1 repeats this check. It needs the MyOwn.Test package, which the Docker build loads (the IPM install does not).
| Guard | What it does |
|---|---|
| The agent only decides | It has two read-only tools (GetTaskRuns, GetMetricHistory). MyOwn.AIHub.Policy.ReadOnlyAuth denies any other tool call. |
| Proposal, then a person | A change exists only as a proposal that expires (30 minutes by default, “Proposal lifetime” in Agents > Settings). It runs only after a person confirms it with their own credentials, and the confirmation revalidates state, expiry, separation of duties (a requester cannot confirm their own request), prohibited targets and the target fingerprint. |
| Validator and Gate | The Validator accepts one JSON object of schema v1, with allowed fields and actions and a target from the server’s candidate list. The Gate applies the kill switch, prohibited targets (%All, _SYSTEM, the portal’s own roles, web applications and production), the downgrade of high-risk approvals to escalation, read-only mode, one open proposal per target and action, and the cooldown. Over the token budget, the Rules engine decides. |
| Untrusted data stays data | Log lines, task errors and justifications are sanitized and reach the model only inside <untrusted_data> delimiters, with < and > escaped. |
| Audit | Eleven IRIS audit events MyOwn/Agent/* (decision, proposal created, confirmed, rejected or expired, execution result, config change, kill switch, tool denied, demo refused, demo reset). MyOwn.AIHub.Policy.LedgerAudit, a %AI.Policy.Audit, records every tool call and denial in the ledger. |
| See it work | S7: the decision denies a request for %All, and the Gate still marks the target as prohibited, escalates it and creates no proposal; an approval of %All is turned into an escalation the same way (cases ADV-R07 and ADV-R08 of the corpus below). S8 and S9 (prompt injection treated as data), and the Guardrails tab. scripts/Test-AgentAdversarial.ps1 runs the corpus of 92 hostile model outputs. |
Code: Gate, prohibited targets and Validator.
%AI.Provider silently retries HTTP 429 and 503 (four requests per Chat), with no setting and no signal to the caller.timeout_ms does not stop a slow provider request.%Double.MyOwn.REST.Docs: an OpenAPI 3 document and an interactive /docs page for %CSP.REST classes; the portal API lists 56 operations (see API docs).iris-agentic-dev) was used to compile classes, run SQL and read the %AI signatures on the build 154 container.Before you start: this was tested with WSL limited to 8 GB on a machine with 16 GB of RAM. Close other programs during both docker image load and the build: each of them left Windows with only about 250 to 300 MB free here.
The image build starts IRIS for Health, enables interoperability (which also loads the FHIR packages) and compiles the portal. Measured inside the Docker VM: about 1 GB in use before the build and about 2.3 GB at its peak; the running container uses about 1 GB. On the Windows side, a build started with 4.9 GB free dropped to about 250 MB free during the FHIR load and still finished. Lower WSL limits are not tested.
The WSL limits live in %UserProfile%\.wslconfig (run wsl --shutdown and restart Docker Desktop after changing it). The tested configuration:
[wsl2]
memory=8GB
swap=8GB
If docker compose up is stopped for lack of memory: close programs or raise memory= in .wslconfig, then run the same docker compose ... up --build -d again; Docker reuses the layers that finished. Closing the terminal does not stop a build already running inside Docker (docker buildx history ls shows it), so wait for it before starting another one. An image whose installation did not finish is never used: the build writes the marker docker/.install-complete only after the portal compiled, fails without it, and the container refuses to start an image that lacks it, with a message saying to rebuild.
Image. InterSystems IRIS AI Hub Community build 2026.3.0AI.154.0 (IRIS and IRIS for Health are both accepted; this repository defaults to IRIS for Health). Download it from the AI Hub Early Access portal, then load and retag it once per machine:
docker image load -i irishealth-community-2026.3.0AI.154.0-docker.tar.gz
# prints: Loaded image: docker.iscinternal.com/docker-intersystems/intersystems/irishealth-community:2026.3.0AI.154.0
docker tag docker.iscinternal.com/docker-intersystems/intersystems/irishealth-community:2026.3.0AI.154.0 irishealth-community:2026.3.0AI.154.0
If docker image load prints a different name, tag that name instead (docker images lists it). Loading the 5 GB archive took 4 to 8 minutes here, and it needs memory too: Windows free memory fell to about 300 MB while it ran (see Memory).
For the IRIS edition, load iris-community-2026.3.0AI.154.0-docker.tar.gz, tag it iris-community:2026.3.0AI.154.0 and set IRIS_IMAGE in .env.
Configuration. Copy .env.example to the git-ignored .env and set:
| Variable | Meaning |
|---|---|
MYOWN_AI_PROVIDER |
openai, anthropic, gemini or bedrock |
MYOWN_AI_MODEL |
model name accepted by your key |
MYOWN_AI_API_KEY |
your key; never committed, never shown by the portal |
MYOWN_AI_MODE |
live (default) or replay (recorded decisions, no key, no network) |
MYOWN_AI_TIMEOUT_SECONDS |
the portal’s own deadline per decision, 5 to 55 (default 20) |
MYOWN_DEMO_PASSWORD |
initial _SYSTEM password of a new container volume; choose your own |
IRIS_BIND_ADDRESS |
where the ports are published; default 127.0.0.1 (this computer only) |
The defaults in .env.example are what we tested: provider Gemini, model gemini-3.5-flash-lite (free tier, tested on 2026-10-28; gemini-3.8-flash was used from 2026-10-21 to 2026-10-24 and later stopped answering). OpenAI (for example MYOWN_AI_MODEL=gpt-4.1-mini), Anthropic and Bedrock are accepted by the same %AI.Provider path but not tested by us; see Limitations. Without a key the portal starts and uses the Rules engine.
Start:
docker compose -f compose.yaml -f compose.aihub.yaml up --build -d
Open http://localhost:52774/myown/index.html (the full path: the container’s web server serves no default page for /myown/) and sign in with _SYSTEM and the password you set in MYOWN_DEMO_PASSWORD.
The default password in .env.example is for local use only; change it before exposing the portal. The ports are published on 127.0.0.1 only (IRIS_BIND_ADDRESS), so the portal is reachable from this computer and not from the network until you change that setting.
Verify (replace <password> with your MYOWN_DEMO_PASSWORD; Invoke-RestMethod -Credential does not work here, because Windows PowerShell sends the credential only after a challenge that IRIS does not send):
curl.exe -s -u "_SYSTEM:" http://localhost:52774/myown/api/health
curl.exe -s -u "_SYSTEM:" http://localhost:52774/myown/api/agent/status
$cred = Get-Credential _SYSTEM
.\scripts\Test-Agents.ps1 -BaseUrl http://localhost:52774 -Credential $cred -IrisImage irishealth-community:2026.3.0AI.154.0 -Edition irisforhealth
.\scripts\Test-Agents-AIHub.ps1 -BaseUrl http://localhost:52774 -Credential $cred
.\scripts\Test-AgentSecretScan.ps1
/health answers "status":"ok"; /agent/status shows aiHubAvailable 1 on build 154 and activeEngine rules until a key (or replay mode) is set. The secret scan covers tracked files in a git clone and every text file in a folder without git (a ZIP download), never .env.
Reinstalling over an earlier version. IRIS keeps its accounts, passwords and data in the Docker volume named by MYOWN_DATA_VOLUME (default myown-portal-data). An existing volume keeps the old accounts and passwords, and a new MYOWN_DEMO_PASSWORD is not applied to it. For a clean install, either set a new MYOWN_DATA_VOLUME in .env or remove the old volume first (this deletes its data):
docker compose -f compose.yaml -f compose.aihub.yaml down
docker volume rm myown-portal-data
The container is always named myown-portal-iris. If an earlier copy of the portal, in another folder, still has its container (even a stopped one), up builds the image and then fails with The container name "/myown-portal-iris" is already in use. Run docker compose -f compose.yaml -f compose.aihub.yaml down in that folder first (it keeps that folder’s volume), then the same up again; the image is not rebuilt.
Do not push the AI Hub image, or any image built on it, to a public registry. Share only the Dockerfile and Compose files.
On any InterSystems IRIS or IRIS for Health 2024.1+ with IPM, in the namespace where the portal should live:
zpm "install myown-portal"
On a new instance with no repository configured, run zpm "enable -community" first.
This installs the published version from the community registry: myown-portal 1.1.0, verified with zpm "install myown-portal" on a clean instance of the 2026.3.0AI.154.0 image (after zpm "enable -community"). To install the code of a clone instead, give IPM the folder as the server sees it: zpm "load /path/to/Portal-Iris". The test classes are not part of either install (the Docker build adds them; see docker/iris.script).
%AI package; elsewhere the Rules engine decides.<web server>/myown/index.html and give users MyOwnViewer or MyOwnAdministrator.zpm "uninstall myown-portal" stops the agent production, clears its auto-start, and removes /myown, /myown/api and the classes. It keeps the roles, resources and the portal’s data.Tested on build 154 (IRIS for Health) in a new namespace without interoperability: install from the code 105 s (most of it enabling interoperability), then package, uninstall with the production running and reinstall from the package twice (2 s and 8 s); the production was running after each install and the Event Log had no error.
Installing into a local IRIS instance is described in https://github.com/muriloprzy-code/Portal-Iris/blob/master/docs/REFERENCE.md.
The online demo runs with MYOWN_AI_MODE=replay: each AI answer is a real model decision recorded earlier, not generated live. Every fixture in aihub/fixtures/ names the provider, model and recording date; the screen shows the badge “Replay of recorded real decisions”. Every scenario except S3 now uses its exact context: S1, S5, S6 and S7 were recorded with gemini-3.8-flash (2026-10-24), S2, S4, S8 and S9 with gemini-3.5-flash-lite (2026-10-28, after gemini-3.8-flash stopped answering). S3 keeps its 2026-10-21 recording from an equivalent context: the flash-lite answer for S3 was ignore instead of a resume proposal, so it was not used. Replay still goes through the tool manager, policies, Validator, Gate and ledger.
The hosted demo shows only the safe areas (Overview, Logs, Agents) for the low-privilege demo account. Administration pages (users and roles, web applications, task actions, API explorer, Settings) are switched off in demo mode and answer 403; the full portal runs in a local installation with an administrator account. The demo runs on a live IRIS instance, not on recorded HTTP responses: only the model’s answer is replayed from a recording of a real decision. It is served over plain HTTP and its credentials are public by design.
Running the agent live requires a local installation with your own key (MYOWN_AI_MODE=live).
With MYOWN_DEMO_MODE=1 the portal is safe to expose:
demo with the role MyOwnDemo: it can view the portal, run the demonstration scenarios, and confirm or reject proposals. It has no IRIS administration privileges, no Settings, no access to the provider configuration or key, and every administration route answers 403. Database and SQL read access are granted only inside /myown/api (application role MyOwnPortalData, shared with the viewer role). On the hosted demo its password is the public demo; if you host your own instance, generate yours with scripts/New-DemoPassword.ps1 (below).demo.alice, demo.bob) and roles (DemoOperator, DemoAuditor). A confirmation revalidates as usual and then changes only that synthetic record; the proposal ends as simulated, and IRIS is never changed. A confirmation on any other target is refused (DEMO_TARGET_REFUSED) and audited (MyOwn/Agent/DemoRefused). Prohibited targets such as %All stay blocked.MYOWN_DEMO_RESET_HOURS (default 6) by the task MyOwn Demo reset, and by the “Reset demo” button that only the demo account sees; both are audited (MyOwn/Agent/DemoReset).MYOWN_DEMO_SIMULATIONS_PER_MINUTE (default 6) scenarios per minute per client address and per session; beyond that the API answers 429 with Retry-After.Instructions for whoever hosts the demo:
.\scripts\New-DemoPassword.ps1 # random password, written only to .env (MYOWN_DEMO_USER_PASSWORD), never printed
# then set MYOWN_DEMO_MODE=1 and MYOWN_AI_MODE=replay in .env
docker compose -f compose.yaml -f compose.aihub.yaml up --build -d
At every start docker/demo-start.sh creates or updates the demo account from the environment, and disables it when demo mode is off. Give visitors the user demo and the password from .env. Never publish the _SYSTEM password; set your own in MYOWN_DEMO_PASSWORD before the first start.
Run every suite once, with a final table (passed / failed / skipped per suite) and a non-zero exit code if anything failed:
.\scripts\Test-All.ps1 -BaseUrl http://localhost:52774 -Credential (Get-Credential _SYSTEM)
The UI suite needs Node.js on PATH or in .tools\node (tested with Node 24) and, the first time, network access to install the pinned Playwright 1.55.0 and its Chromium (an isolated browser, never yours). Without them Test-All.ps1 skips that suite and prints UI: skipped (<reason and how to enable it>); add -RequireUI to count that as a failure. The other suites need only Windows PowerShell and the running container.
Reports go to test-results/all/ (one log and JSON report per suite, plus test-all.json). The last row checks that the run left no error in the interoperability Event Log. -SkipEndToEnd skips the adapter end-to-end check (a real task failing three times, about 3 to 5 minutes); -DemoCredential adds the REST part of the demo suite when demo mode is on.
GitHub Actions (.github/workflows/checks.yml) runs on every push and pull request without secrets: the secret scan, a check that no .env, key or archive is tracked, module.xml and the compose files, and the front-end build. It does not compile the ObjectScript classes or run Test-All.ps1, because those need the AI Hub build 154 image, which must not be pushed to a registry; run them locally.
| Suite | Script | IRIS 2026.2 (local) | Build 154 (IRIS for Health) |
|---|---|---|---|
| Portal smoke | scripts/Test-MyOwn.ps1 |
pass | pass |
| SysAdmin API mutations | scripts/Test-SysAdminMutations.ps1 |
pass | pass |
| Agent REST scenarios | scripts/Test-Agents.ps1 |
24 pass, 1 optional skip | 24 pass, 1 optional skip |
| Adversarial corpus (92 hostile model outputs) | scripts/Test-AgentAdversarial.ps1 |
92 / 92 | 92 / 92 |
| Core and interoperability smoke | scripts/Test-AgentSmoke.ps1 |
14 / 14, 19 / 19 | 14 / 14, 19 / 19 |
| AI Hub engine (replay: timeouts, invalid JSON, provider error, budget, kill switch, blocked write tool, ToolSet Exclude, query tools, fixtures) | scripts/Test-Agents-AIHub.ps1 |
skipped (no %AI) |
20 / 20 |
| Agents UI (isolated browser) | scripts/Test-AgentsUI.ps1 |
15 / 15 | 15 / 15 |
| Inbound adapters (failure counting, empty scan, query failure, window, hand-off, Event Log left clean; end to end: a real task fails three times and the TaskWatcher ends in a suspend proposal) | scripts/Test-Adapters.ps1 |
8 / 8 (end to end not run) | 9 / 9 |
API docs (OpenAPI 3 from the UrlMap and comments, every route, 401 without credentials, no secrets, a second class gets its own /docs) |
scripts/Test-ApiDocs.ps1 |
4 / 4 in-IRIS (HTTP part not run) | 9 / 9 |
Viewer role without %All (screens allowed, administration refused) |
scripts/Test-PortalViewer.ps1 |
5 / 5 | 5 / 5 |
| Public demo (synthetic targets, refusals, reset, rate limit; REST checks with the real demo account) | scripts/Test-AgentDemo.ps1 |
8 / 8 in-IRIS (REST part needs demo mode) | 16 / 16 with MYOWN_DEMO_MODE=1 |
| Secret scan (tracked and staged files) | scripts/Test-AgentSecretScan.ps1 |
pass | — |
Reports are written to test-results/. The full regression passed on a fresh build 154 container on its first run.
gemini-3.5-flash-lite, 2026-10-28; gemini-3.8-flash 11 to 18 s before it stopped answering), but it also returned HTTP 429 (quota) and 503 (“high demand”), or no answer at all. The %AI provider retries 429 and 503 internally, so they often look like a slow provider. The portal stops waiting at its own deadline and the Rules engine decides, with the badge Rules (provider_timeout) or Rules (provider_error).gemini-3.5-flash-lite, above the default MYOWN_AI_TIMEOUT_SECONDS of 20 s, so live it falls back to Rules with the reason provider_timeout. Set MYOWN_AI_TIMEOUT_SECONDS=40 to see the AI decision in that scenario. The engine always records the fallback reason (ledger, decision, badge).%AI.Provider code path but have not been run.gemini-3.8-flash did not answer the re-recording on 2026-10-24, 26 and 28 (timeouts and 503 “high demand”); gemini-3.5-flash-lite answered the other scenarios in 10 to 27 s, but chose ignore for S3.MYOWN_DEMO_MODE=1 and a password are set.MyOwnViewer) cannot file an access request: the privilege delta is read through SysAdmin API v2 with the requester’s own credentials, and that API refuses a viewer even its own account (it needs %Admin_Secure, which a read-only role should not have). The portal answers ACCESS_REQUEST_NEEDS_SECURITY_READ and the Access tab explains it; administrators file requests.What worked well:
%AI.Agent, sessions and GetStats() gave a working agent loop quickly; per-session token statistics feed the ledger.%LogDenial and never runs.ToolMgr.ExecuteTool runs a tool through the same policies without a model, which made an offline replay mode and policy tests possible.What was hard:
timeout_ms does not interrupt a provider call in progress; the portal runs each decision in a separate job and stops it at its own deadline.extra_params at the top level of the request; Gemini rejected a native generationConfig and accepted reasoning_effort.%AI trace log is enabled.ListModels() reports zero pricing, so cost needs a local price table.IRIS Agentic Dev (MCP server iris-agentic-dev) was used to compile classes, run SQL and read the %AI signatures on the build 154 container. Details in https://github.com/muriloprzy-code/Portal-Iris/blob/master/docs/AI-HUB-NOTES.md.
The agent workflow sits on MyOwn Portal, built for the earlier Build Your Own Management Portal contest: system overview and metrics, an AI Instance Monitor (Ideas Portal DPI-I-512), users, roles and permissions, web applications, tasks, logs and certificates, all through the official SysAdmin API v2 where an endpoint exists. See https://github.com/muriloprzy-code/Portal-Iris/blob/master/docs/REFERENCE.md.
I’m an ObjectScript developer, and I used AI assistants as a pair-programming partner on this project. The idea, the safety design and every decision about what ships are mine; the assistant sped up the typing and the testing. I reviewed and verified the work.