Initial Release
A hands-on starting point for building and testing native InterSystems
%AI.Agent classes with the AI Hub preview, packaged with Docker Compose.
Learn the agent lifecycle in ObjectScript, run guided terminal demos, then
explore the same agents in a browser-based chat workspace.
This is more than a chat UI: the agent classes, tools, skills, sub-agents, and
manual testing examples are part of the project. The browser is another way
to exercise those capabilities, not a replacement for learning the SDK.
It demonstrates:
This README explains how to run and explore the project. For implementation
details, class relationships, and configuration, see https://github.com/pietrodileo/My-First-Agent-Studio/blob/main/dev.md.
This folder contains the IRIS backend, React frontend, Python MCP server,
synthetic dataset, and container configuration. It can be run independently
of the other projects in the parent repository.
The project brings together a first-agent tutorial and a native agent
playground. Start with a small arithmetic workflow, move on to synthetic
dataset analysis, and inspect how tools, skills, and delegation affect a
conversation.
The project was built around Ollama with locally running models so you
can explore AI Hub features independently, without a paid model API or a
cloud-provider API key. The included examples let you test tools, skills,
external MCP calls, and sub-agent delegation using your own machine. You
still need the AI Hub-enabled IRIS image and a tool-capable model that your
hardware can run; results and performance depend on the model you choose.
The project addresses two complementary ideas from the
InterSystems Community Bounty Program: “Idea to Application” — Round 2.
| Capability | In this project |
|---|---|
| Learn the native SDK | Guided demos and manual ObjectScript sessions show initialization, tools, skills, and sub-agent delegation. |
| Select an existing agent | Discover concrete application subclasses of %AI.Agent in FIRST_AGENT, without a registration table. |
| Inspect and exercise capabilities | View declared toolsets and skills, select skills for the next turn, and inspect execution statistics. |
| Try local models | Select an Ollama model while keeping provider configuration in the agent class. |
| Continue conversations | Save SDK sessions and message history in IRIS and reopen them from Recent chats. |
The bundled MCP server demonstrates consuming external tools from an agent;
this project is not the separate MCP Data Exposure Toolkit.
Native agent APIs are supplied by the InterSystems AI Hub preview. The
official AI Hub EAP repository
provides documentation and examples; download an AI Hub-enabled IRIS container
image from the Early Access Program portal.
The included Dockerfile expects this base image:
docker.iscinternal.com/docker-intersystems/intersystems/iris-community:2026.3.0AI.136.0
Load the downloaded archive before building:
docker image load -i /path/to/downloaded-iris-ai-hub-image.tar.gz
Check that the loaded image name and architecture match the Dockerfile’s
FROM line. If your EAP download uses a different tag, update that reference
and verify SDK compatibility. Do not assume the internal registry is publicly
accessible or that a standard IRIS image includes these preview APIs.
AI Hub EAP is pre-release software and is not intended for production use.
Consult the upstream documentation
when upgrading; preview APIs and access-control features can change.
Run the following commands from this folder:
ollama list
cp .env.example .env
Configure .env:
OLLAMA_BASE_URL=http://host.docker.internal:11434/v1/
OLLAMA_MODEL=your-installed-model-name
Replace the placeholder with an exact model name from ollama list. The
Ollama URL must be reachable from inside the IRIS container; adjust it if
Ollama runs on another machine.
Start the backend for the terminal demos:
docker compose up -d --build --wait --wait-timeout 180 iris
Or start the backend and browser UI together:
docker compose --profile ui up -d --build --wait --wait-timeout 180
ui profile)FIRST_AGENTThe build imports the classes under src and runs the dataset setup. You
can also open this folder in VS Code and use FA - Docker: Build and Start
Agent Only or FA - Docker: Build and Start Everything.
Open an IRIS terminal in the FIRST_AGENT namespace:
docker compose exec iris iris session IRIS -U FIRST_AGENT
You can use VS Code task as well to build the backend only or the full project with UI.
Command: Do ##class(Test.BasicDemo).Run()
1. Creates Test.MathAgent
2. Loads the calculation-review skill through a prompt
3. Calls get_mcp_info, an external Python MCP tool
4. Calls add_numbers(17, 25), another external Python MCP tool
5. Delegates verification to the code-defined CalculationReviewer sub-agent
6. Delegates an explanation to a mental-math teacher spawned by the main agent
7. Loads the Caveman skill programmatically through agent.UseSkill
8. Uses the Caveman skill to make the explanation more compact
The exact prose and token counts depend on the selected model, but the execution
flow should resemble the following output:
Agent: Test.MathAgent
Model: granite4:3b
Available Tools: [Execute, ReviewCalculation, add_numbers, get_mcp_info, ...]
=================================
Prompt>: First load the calculation-review skill...
[agent iteration 1/5]
Token usage: {"completion_tokens":81,"prompt_tokens":1267}
Total tool calls: 2
--- Response: ---
MCP Server: FIRST_AGENT_MCP_7F3A
=================================
Prompt>: Now call add_numbers with first=17 and second=25...
[agent iteration 1/5]
Total tool calls: 4
--- Response: ---
17 + 25 = 42
=================================
Prompt>: Call ReviewCalculation to delegate verification...
[agent iteration 1/5]
[CalculationReviewer] called as tool
Total tool calls: 5
--- Response: ---
Verification: PASS. 17 + 25 = 42 is correct.
=================================
Prompt>: Call Execute to delegate to mental-math teacher...
[agent iteration 1/5]
[Delegation] Creating sub-agent with role 'mental-math teacher'
[Delegation] Sub-agent completed
Total tool calls: 6
<Expect a long output here>
=================================
Prompt>: Using caveman skill, compact the concepts explained from the math teacher
[agent iteration 1/5]
Token usage: {"completion_tokens":20,"prompt_tokens":2099,"total_tokens":2119}
Total tool calls: 6 | Total tool duration (ms): 3078
--- Response: ---
<Expect a very short caveman-like output here>
Parent stats: {"total_tool_calls":6,...}
Spawned sub-agents: 3
- Calculation reviewer system prompt
Mental-math teacher system prompt
Command: Do ##class(Test.PatientAnalysisDemo).Run()
1. Creates Test.Agent
2. Loads the synthetic-healthcare-analysis-brief skill
3. Calls SummarizePatients(diagnosis="Diabetes") -> 115 patients
4. Calls SearchPatients(limit=3) -> P001, P002, P013
5. Calls summarize_measurements for the treatment costs
6. Generates an evidence-bound briefing
7. Delegates the evidence check to the EvidenceReviewer sub-agent
8. Delegates the safety check to the SafetyReviewer sub-agent
9. Delegates the explanation to a Data Analyst sub-agent
Output wording may vary by model; the following transcript illustrates the
expected phases and evidence:
=================================
Prompt>: Load the synthetic-healthcare-analysis-brief skill...
[agent iteration 1/10]
Total tool calls: 4
--- Parent analysis ---
Synthetic Healthcare Briefing - Diabetes Cohort
SCOPE:
- Dataset: synthetic_healthcare_data.csv
- Filter: Diagnosis = Diabetes
- Rows: 115 patients
- Examples: P001, P002, P013
FINDINGS:
- Average Age: 53.38 years
- Average BMI: 28.84
- Average Treatment Cost: $2,398.24
- Total Treatment Cost: $275,798.10
PATIENT EXAMPLES:
┌─────────┬──────┬──────┬──────────┬────────┬─────────────┐
│ Patient │ Age │ BMI │ Cholest. │ Smoker │ Treatment $ │
├─────────┼──────┼──────┼──────────┼────────┼─────────────┤
│ P001 │ 20 │ 24.7 │ 245 │ No │ 3,328.60 │
│ P002 │ 47 │ 38.5 │ 268 │ Yes │ 1,041.69 │
│ P013 │ 54 │ 22.9 │ 207 │ Yes │ 2,548.13 │
└─────────┴──────┴──────┴──────────┴────────┴─────────────┘
COST DISTRIBUTION:
- Mean: $2,306.14
- Median: $2,548.13
- Max: $3,328.60
- Min: $1,041.69
LIMITATIONS:
- Synthetic data, no real-world inference
NEXT ANALYSIS:
Compare costs above/below mean of $2,306.14
================================= evidencePrompt>: Call ReviewEvidence to delegate an evidence review... [agent iteration 1/5] [EvidenceReviewer] called as tool Total tool calls: 8
--- Evidence sub-agent --- Synthetic Healthcare Briefing - Diabetes Cohort [Reviewed briefing output]
================================= ReviewSafety>: Call ReviewSafety to delegate a safety review... [agent iteration 1/5] [SafetyReviewer] called as tool Total tool calls: 9
--- Safety sub-agent --- Synthetic Healthcare Briefing - Diabetes Cohort [Reviewed briefing output]
================================= Prompt>: Call Execute to delegate this task to a sub agent... [agent iteration 1/5] [Delegation] Creating sub-agent with role 'data analyst' [Delegation] Sub-agent created [Delegation] Sub-agent completed Total tool calls: 10--- Generic delegated sub-agent --- Evidence-Bound Briefing on Synthetic Diabetes Cohort
[Detailed analysis with tables] [Interpretation] [Limitations] [Next steps]
================================= Parent stats: {"total_tool_calls":10,"total_interactions":14,...} Spawned sub-agents: 1Sub-agent system prompt: You are a data analyst assistant. Context: [full briefing data]
Active skills: [synthetic-healthcare-analysis-brief]
Available Tools: [Execute, ReviewEvidence, ReviewSafety, SearchPatients, SummarizePatients, add_numbers, summarize_measurements, ...]
Start a persistent chat session directly from the IRIS terminal. Create and
initialize the agent once; after that, change the value of prompt and repeat
the final three commands whenever you want to send another message. Reusing the
same session preserves the conversation context between turns.
Set agent=##class(Test.Agent).%New() Set sc=agent.%Init() Set session=agent.CreateSession() Set monitor=##class(Test.DemoMonitor).%New()
Set prompt="What can you safely explain about this synthetic healthcare dataset?" Set response=agent.Run(session,prompt,10,monitor) Do ##class(%AI.System).RenderMarkdown(response.Content)
Continue the conversation without recreating either the agent or the session:
Set prompt="Your next question here (e.g. How much is the most expensive treatment for smokers vs non smokers?)"
Set response=agent.Run(session,prompt,10,monitor)
Do ##class(%AI.System).RenderMarkdown(response.Content)
You can also activate the bundled Caveman skill and request a more compact
answer:
Set sc=agent.UseSkill("Test.Skill.Caveman")
Set prompt="Using the caveman skill ultra, explain your findings to me."
Set response=agent.Run(session,prompt,10,monitor)
Do ##class(%AI.System).RenderMarkdown(response.Content)
Write session.ActiveSkills.%ToJSON(),!
To demonstrate delegation, ask the generic Execute tool to create a child
agent for a suitable task:
Set prompt="Call Execute to delegate this task to a data-communication specialist: explain in two short sentences why synthetic data is useful for demonstrations but cannot support clinical conclusions. Use specialistRole='data-communication specialist'."
Set response=agent.Run(session,prompt,10,monitor)
Do ##class(%AI.System).RenderMarkdown(response.Content)
Write agent.SubAgents.Count(),!
UseSkill("Test.Skill.Caveman") registers the skill as available; it does not
guarantee activation by itself in this SDK preview. The terminal prompt asks
the model to load it. Check session.ActiveSkills to verify activation. The
browser UI instead applies the selected skills before running the next prompt.
Execute follows a different path: it creates a child agent with an isolated
session, runs the delegated request, and returns the child’s answer to the
parent as a tool result. ActiveSkills and SubAgents.Count() provide
structured evidence that the two operations occurred.
Test.Agent provides the synthetic healthcare tools and safety instructions,
while the user retains complete control over the value of prompt.
Start the complete profile to use the same agents through the browser:
docker compose --profile ui up -d --build --wait
/api/tags; the configured model is used as a fallback.The Model dropdown is populated from the models already pulled into your
configured Ollama instance, retrieved through its /api/tags endpoint. It
is not a fixed list or a cloud-model catalog: you can test the same agent
with different locally available models without a paid model API.
After pulling another model into that Ollama instance, reload the browser
to refresh the list. Use ollama list on the Ollama host to check which
models are installed. If discovery returns no models or Ollama cannot be
reached, the UI falls back to OLLAMA_MODEL; this does not download the
model or guarantee that Ollama is available.
Click New chat, select the model, and send your first message. The model
is saved when the conversation is created; changing the dropdown does not
switch the model of an existing conversation. To compare models, start a
fresh chat for each one and use the same agent, skills, and prompt. Choose
tool-capable models for the tool and delegation demos; response quality,
tool use, speed, and hardware requirements vary by model.
New chats start with every skill Off. Click a skill to toggle it On;
the selection takes effect before the next submitted message. No special
activation prompt is required. Changing skills is disabled while a reply runs.
You can combine skills, switch from one to another, or turn all of them off.
Turning a skill off removes its active instructions for the next turn; it does
not erase earlier messages. Conflicting skill instructions and model behavior
can still affect response quality. After a successful reply, the UI reflects
the session’s actual active skills. Reopening a chat restores its saved state;
an unsent toggle is not saved.
Chat history and session state are stored in IRIS, not browser storage, so a
page reload does not erase them. The sidebar’s collapsed state is stored in
the browser. This Compose setup has no explicitly configured persistent IRIS
data volume: do not rely on history surviving container replacement or removal.
Back up IRIS data before recreating the backend.
The UI shows server error details. An empty model reply is rejected without
saving that turn; retry or choose another model. Legacy null assistant replies
are removed from model context on the next send to prevent the provider error
invalid message content type: <nil>; visible chat history is preserved.
This is a local demo with an unauthenticated API and shared chat history, not
a production multi-user service. Do not expose it to untrusted networks.
For API examples, implementation details, and regression checks, see the
browser UI developer guide.
Start with the simple chat agent,
Test.Agents.SimpleAgent, for a
general-purpose conversational assistant. It extends %AI.Agent directly
and shows how to define instructions, a description, an example prompt,
and an Ollama provider using OLLAMA_BASE_URL and OLLAMA_MODEL.
Select it in the browser and try: “I have three tasks to finish today. Can
you help me decide which one to do first?” Send a follow-up to explore
conversation context, or enable its Poet, Echo, or Caveman skill to experiment
with response styles. It declares no toolsets, but does register delegation
and reviewer tools during initialization, so it is not a tool-free agent.
When adapting it, keep only the skills and registered tools your agent needs.
For task-specific examples, use
Test.MathAgent to explore arithmetic with
external MCP tools, or inspect Test.Agent for
synthetic healthcare analysis, native IRIS tools, and reviewer registration.
%AI.Agent subclass under src, directly or throughFIRST_AGENT.% are excludedDiscovery does not prove that an agent can initialize: missing model or
provider configuration can still prevent a conversation from starting.
Nginx serves the React frontend on port 5174 and forwards /api/* to the
IRIS REST application at /agent-ui/api/*. IRIS owns agent discovery,
execution, and conversation storage; there is no Node middleware. The
bundled Python MCP server supplies external calculation tools used by the
demos.
With the backend running, execute the SDK regression checks:
docker compose exec iris iris session IRIS -U FIRST_AGENT '##class(AgentTestUI.Test).Run()'
Expected output includes:
PASS: native %AI.Agent discovery
PASS: skill defaults, activation, replacement, unload, restore, validation
PASS: legacy null reply recovery preserves valid history
These checks exercise discovery and SDK session state without making an LLM
request. They do not verify model responses or the complete browser workflow.
For an end-to-end manual check:
Test.MathAgent and an installed model in the UI. Enable itsTest.Agent and ask it to summarize the syntheticModel wording, tool choices, and token counts vary. The examples are learning
and testing aids, not exact-output assertions.
Chat workspace: Test.Agents.SimpleAgent answers a question about database
management systems using Ollama. The sidebar shows recent chats, agent and
model selection, and skill controls; the response includes a Markdown table.

Local model choice: the dropdown lists models available in the configured
Ollama instance, letting you choose a different model for a new conversation
with the same native IRIS agent.

Skill selection: the selected agent’s Caveman skill is switched on while
Echo remains off, illustrating the per-skill controls available before the
next message.

For class relationships, API examples, and additional checks, see
https://github.com/pietrodileo/My-First-Agent-Studio/blob/main/dev.md.
To stop services while retaining the containers:
docker compose --profile ui stop
To remove the complete demo environment, including project volumes and service
images, use the following command. Back up any IRIS data or chat history you
want to retain first:
docker compose --profile ui down --volumes --remove-orphans --rmi all
my-first-agent/
├── .env.example
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.frontend
├── frontend/ # React UI, Markdown rendering, Nginx API proxy
├── README.md
├── https://github.com/pietrodileo/My-First-Agent-Studio/blob/main/dev.md
├── data/
│ └── synthetic_healthcare_data.csv # 500 synthetic patients
└── src/
├── AgentTestUI/ # REST router, runtime, persistent chats, regression tests
└── Test/
├── Agents/ # Test.Agent, Test.MathAgent
├── Data/ # Patient persistence
├── Demo/ # BasicDemo, PatientAnalysisDemo, Abstract
├── Monitor/ # DemoMonitor
├── Setup/ # Installation
├── Skill/ # HealthcareAnalysis, CalculationReview
├── SubAgents/ # EvidenceReviewer, SafetyReviewer, CalculationReviewer
├── Tools/ # Patients, DelegateTasks
└── ToolSet/ # Local, StatisticsMCP
The project imports all 500 synthetic patient records from
https://github.com/pietrodileo/My-First-Agent-Studio/blob/main/data/synthetic_healthcare_data.csv
during container installation. The dataset is derived from the
Synthetic Healthcare Patient Records Dataset by dnation on Kaggle.
Each record contains a patient code, age, gender, BMI, blood pressure,
cholesterol level, smoker and diabetic status, diagnosis, treatment cost,
admission and discharge dates, and outcome. There are no real patient records
or names.
Test.Data.Patient is the project’s patient
persistence class, stored in the Test_Data.Patient SQL table. Its importer
separates blood pressure into systolic and diastolic columns and converts
dates and yes/no fields to native IRIS date and boolean types.
Test.Setup invokes the importer during installation.
The dataset is intended solely for demonstration and must not be used for
real clinical work.
The workspace includes the following tasks: