Building your application on jutul-agent
You can build a complete application on top of jutul-agent, with its own
simulator, skills, tools, and web front end, without forking jutul-agent or
editing any of its code. Your project is a normal Python package that depends
on jutul-agent and registers what it adds through entry points. Upgrading
jutul-agent then never costs you a merge.
There are three things you can contribute, independently or together:
| You want to add | Mechanism |
|---|---|
| Your own simulator (adapter, skills, Julia env) | a jutul_agent.simulators entry point |
| Tools, skills, subagents, or prompt text (per simulator and per interface) | a jutul_agent.extensions entry point (a Capability) |
| A web app for your users | a front end over the documented WebSocket/REST protocol |
The rest of this page shows each one. The agent loop, the server, the Julia kernel, and the wire protocol all stay in jutul-agent; you only add data and small pieces of glue.
Add your own simulator
A simulator is a folder with an adapter, skill markdown, and a Julia environment
template (see adding a simulator for the folder's
contents). To ship one from your own package, lay it out the same way inside your
package and publish its SimulatorAdapter under the jutul_agent.simulators
entry-point group:
mypackage/
mysim/
adapter.py # MYSIM = SimulatorAdapter(name="mysim", module_dir=Path(__file__).parent, ...)
skills/ # your SKILL.md folders
julia_env/ # Project.toml (+ optional warm package)
# in your pyproject.toml
[project.entry-points."jutul_agent.simulators"]
mysim = "mypackage.mysim.adapter:MYSIM"
Install your package alongside jutul-agent and mysim appears everywhere the
built-in simulators do (the registry, --sim mysim, the server's
/simulators). The adapter's module_dir is what anchors your skills/ and
julia_env/, so they are read from your package. The shared JutulAgent Julia
runtime is copied from the jutul-agent install automatically, so your
julia_env/Project.toml only needs your simulator's packages plus the
[sources] entries (copy them from a bundled simulator).
Building on an existing simulator. Because an installed adapter overrides a
bundled one of the same name, you can publish a customised jutuldarcy (your
own skills and Julia config, based on JutulDarcy) and it replaces the built-in
one. Or give it a new name to offer it alongside.
Add tools, skills, subagents, and prompt text
How a simulator is used differs by interface: a web app might let the agent drive
controls or a diagram that a terminal never shows. So the unit of customization
is a Capability (see the server interface),
which can contribute tools, skill directories, subagents, and a prompt fragment,
optionally scoped to a surface (tui, web, cli).
Publish a Capability (or a function returning one) under the
jutul_agent.extensions entry-point group:
# mypackage/capability.py
from jutul_agent.agent.capabilities import Capability
def my_capability() -> Capability:
return Capability(
name="mysim-web",
tools=(make_my_tool,), # factories taking the Session
skill_dirs=(("/abs/path/skills", "MySim web"),),
prompt_fragment="How to use the app's controls...",
surfaces=("web",), # only on the web surface; omit for all
)
[project.entry-points."jutul_agent.extensions"]
mysim_web = "mypackage.capability:my_capability"
Installed capabilities are discovered automatically and composed into the agent alongside the base tools and the active simulator. Further options exist for specific needs:
- UI-control tools emit a
uicommand that your front end applies (move a slider, highlight a node, pan a map), and the front end sendsui_events back that the agent reads. The envelope is generic; the actions are yours. - Declarative HTTP tools let an application in any language expose its routines: send tool specs (name, description, schema, endpoint) when you create a session and the agent gets tools that call them. No Python plug-in needed.
- Host context tells the agent what your application currently has selected,
so it can pass your identifiers to those tools instead of asking the user for
them. Launch the UI with
?data=<base64 JSON>, and?apiurl=<base64 URL>if your application has an API of its own; the bundled front end does the rest. Your capability's tools read them fromsession.host_contextandsession.host_api. See the server interface for how both are kept current across sessions and while one is open. - Branding replaces what the bundled front ends call the session. Without it they introduce the simulator, which is the wrong name when the user opened your application. See below.
Name the welcome screen after your application
By default the web welcome screen reads "What would you like to explore with
JutulDarcy?" and offers the simulator's starter prompts. Add a Branding to your
capability and it introduces your application and your workflow instead:
from jutul_agent.agent.capabilities import Branding, Capability
Capability(
name="mysim-web",
surfaces=("web", "tui"),
branding=Branding(
display_name="MyApp",
tagline="Build a model from what the app has selected, then analyse it.",
example_prompts=(
"Build the model with default values and show me the result.",
"What is in this dataset? List it before we build anything.",
),
),
)
Every field is optional and falls back on its own: an empty display_name keeps
the simulator's name, an empty tagline keeps the front end's standard blurb, and
empty example_prompts keeps the simulator's. The web UI reads it from
GET /simulators (top-level branding), before any session exists, so only
installed capabilities can brand it; the tool specs and host context a request
carries describe one launch, not the product. When several layers declare
branding, later ones win field by field.
Build your web app
The server exposes a stable HTTP + WebSocket contract (see the server interface). Your front end can be anything that speaks it. Two starting points:
- Use the bundled UI.
jutul-agent web --sim mysimserves a complete chat interface. Good for getting going and for internal tools. - Build your own. Code against the protocol with whatever stack your app
needs (React, Svelte, MapLibre, ...). The bundled UI and the
examples/app are reference implementations to copy.
Interactive plots come back over the protocol as a viz (a self-contained
WebGL/WGLMakie HTML you embed) and as image artifacts; the agent builds the
figures, so they work for any simulator.
What you never touch
You do not edit jutul-agent's registry, builder, prompts, server, or kernel.
Adding a simulator is an entry point and a folder; adding behavior is an entry
point and a Capability; adding a UI is code against the protocol. That is the
seam, and it is the supported way to build a product on top of jutul-agent.