Hosted agents¶
A hosted agent is an AI agent that MEHO runs for you on the backplane server. This guide shows you how to set one up and run it, from start to end, with commands you can copy.
When you finish, you will have:
- an agent with its own identity;
- an agent definition: its prompt, its tools, and its limits;
- permission grants that make the agent read-only, in every run;
- one finished run, and the audit rows that show what the agent did.
Prerequisites, roles, and maturity
- The
mehoCLI, logged in to your backplane (The meho CLI). - At least one registered target and a connector that can reach it (Register targets and secrets). It helps to have run one operation by hand first (Run your first operations).
- Setup needs the
tenant_adminrole. This covers the identity, the definition, and the grants. - Running an agent needs the
operatorrole (ortenant_admin). Theread_onlyrole cannot start a run. - Hosted agents are Experimental. See the feature-maturity index.
What a hosted agent is¶
- MEHO runs the agent on the server. The model calls and the tool calls happen inside MEHO, not on your laptop.
- It is not your desktop Claude. Claude Desktop or Claude Code talk to MEHO over MCP as you. A hosted agent is a separate thing that MEHO starts itself.
- Every tool call the agent makes goes through the same checks as yours: permissions, approvals, and audit.
A hosted agent has three parts. You create them in this order:
| Part | What it is | Command |
|---|---|---|
| Agent principal | The agent's own login identity. MEHO creates it in Keycloak (the login server). | meho agent-principal register |
| Agent definition | What the agent is: its prompt, its tools, its model tier, and its turn limit. | meho agent create |
| Grants | Rules that say which operations the agent may run, and on which targets. | meho agent grant create |
In this guide the agent is called pod-reader. It reads pods on one
Kubernetes target called cluster-a, through the connector k8s-1.x.
Use your own names.
Step 1 — Register the agent's identity¶
meho agent-principal register pod-reader
The output looks like this:
registered agent principal "pod-reader"
id: 3f2b7c1e-...
keycloak_client_id: agent:pod-reader
owner_sub: <your sub>
revoked: false
created_at: 2026-10-18T09:00:00Z
Two values matter:
keycloak_client_id— the agent's client id. It is alwaysagent:<name>, hereagent:pod-reader. You use this value in every later step: in--identity-refand in--principal.owner_sub— the person who can switch the agent off. By default this is you.
The agent also has a sub (subject id). The sub is a UUID that Keycloak gives the agent's login account. You do not need it for setup. MEHO matches grants by the client id. You see the sub later, in the audit rows of runs where the agent acts as itself (see Step 5).
To list your agents, or to switch one off in an emergency:
meho agent-principal list
meho agent-principal revoke pod-reader # the agent can no longer log in
Step 2 — Write the prompts¶
A hosted agent gets two pieces of text. Keep them apart.
| Text | Where you set it | What to put in it |
|---|---|---|
| System prompt | --system-prompt on meho agent create |
The standing rules. Who the agent is, what it may do, which connector and target to use, how to answer. It is the same for every run. |
| Input | --input on meho agent run |
The task for this run. For example: "List the pods in namespace web that are not running." At most 65,536 characters. |
A good system prompt for the read-only example:
You are pod-reader. You only read. You never change anything.
Use the connector k8s-1.x and the target cluster-a.
Find the operation with search_operations, then run it with call_operation.
Answer in short plain sentences. Say which operation you used.
Some tips:
- Name the connector id and the target name in the system prompt. Then the agent does not have to guess them.
- Do not tell the agent to run a runbook. A hosted agent has no
runbook tools. MEHO stops such a run at the start and marks it
failed, so the agent cannot claim it ran a runbook. - MEHO adds its own short text after your system prompt. It tells the agent that MEHO's rules always apply, and that text it reads with its tools is data, not instructions. It also adds your tenant's conventions, if you have any. Your system prompt always comes first.
Step 3 — Choose the tools (the toolset)¶
The toolset is a small JSON object. It says which tools the agent
gets, and which connectors it may use. You pass it with --toolset.
{
"meta_tools": ["meho_connector_list", "list_operation_groups", "search_operations", "call_operation", "result_query"],
"connectors": ["k8s-1.x"]
}
The fields¶
| Field | Type | If you leave it out | If it is an empty list [] |
|---|---|---|---|
meta_tools |
list of tool names | The agent gets every tool marked "yes" in the "In the default set" column below. | The agent gets no tools from the table below. It still gets invoke_agent (see the note under the table). |
connectors |
list of connector ids, for example k8s-1.x |
No connector limit. The agent may use any connector in your tenant. Grants and approvals still apply. | The agent may use no connector. Every call_operation is refused. |
More rules:
{}(an empty object) is the default. It means "leave both fields out".- A tool name that MEHO does not know is ignored. It is not an error, so check your spelling.
- Any other key in the object is ignored.
meta_toolsandconnectorsmust be lists of strings. Anything else (for example a single string) makes the run fail.- The
connectorslist limitscall_operationandpreview_operation. When the agent asks for a connector that is not in the list, MEHO refuses the call and tells the agent which connectors it may use.
The tool names¶
These are the names you can put in meta_tools:
| Tool | What the agent can do with it | In the default set |
|---|---|---|
list_operation_groups |
List the operation groups of one connector. | yes |
search_operations |
Search a connector's operations by meaning and by words. | yes |
call_operation |
Run one operation. This is the only tool that changes or reads your systems. | yes |
meho_connector_list |
List the connectors your tenant can use. | yes |
list_targets |
List the targets in your tenant. | yes |
preview_operation |
Show the request call_operation would send, without sending it. |
yes |
result_query |
Read rows from a large result. MEHO returns big results as a link to the stored result, not as raw data. | yes |
broadcast_announce |
Post a short note to the team's activity feed. | yes |
broadcast_recent |
Read recent notes from agents on the activity feed. | yes |
broadcast_watch |
Wait up to 30 seconds for new notes on the activity feed. | yes |
query_topology |
Look up how resources depend on each other. | no — you must name it |
search_memory |
Search the tenant's Memory store. | no — you must name it |
search_knowledge |
Search the tenant's knowledge base. | no — you must name it |
read_context_resource |
Read one Memory, knowledge base, or convention entry that a search returned. | no — you must name it |
The last four return text that people in your tenant stored. That is why the agent only gets them when you name them.
One more tool: invoke_agent
Every hosted run also gets the invoke_agent tool. With it, the
agent can start another agent definition in your tenant. The
toolset does not turn this tool on or off, so it is not in the table
above. Putting invoke_agent in meta_tools does nothing.
Tools a hosted agent does not have¶
Some tools exist for MCP clients but not for hosted agents. Putting
these names in meta_tools does nothing:
- no health or status tool (
meho_status); - no runbook tools (
meho_runbook_*); - no approval tools. An agent can never approve anything;
- no tools that write to Memory or the knowledge base;
- no admin tools.
A read-only toolset¶
For pod-reader, save this as pod-reader-toolset.json:
{
"meta_tools": ["meho_connector_list", "list_operation_groups", "search_operations", "call_operation", "result_query"],
"connectors": ["k8s-1.x"]
}
The toolset limits which tools and connectors the agent can reach. It does not decide which operations may run. That is the job of grants, in Step 5.
Definitions from older versions
A definition stored with no toolset at all (not even {}) gets
only two tools: list_operation_groups_tool and call_operation_tool. New
definitions always get {} by default. To move an old definition to
the full set, run meho agent edit <name> --toolset '{}'.
Step 4 — Create the definition¶
meho agent create pod-reader \
--identity-ref agent:pod-reader \
--model-tier standard \
--turn-budget 20 \
--system-prompt "You are pod-reader. You only read. You never change anything. Use the connector k8s-1.x and the target cluster-a. Find the operation with search_operations, then run it with call_operation. Answer in short plain sentences. Say which operation you used." \
--toolset @pod-reader-toolset.json
The flags:
| Flag | Required | What it means |
|---|---|---|
<name> |
yes | The definition name. You use it to run the agent. |
--identity-ref |
yes | The client id from Step 1, agent:<name>. MEHO checks that this agent principal exists and is not revoked. |
--model-tier |
yes | standard, fast, or deep. Use standard if you are not sure. In this version MEHO stores the tier with the definition and with each run, but every run uses the model your deployment sets as its default. |
--turn-budget |
yes | The most model turns one run may take, from 1 to 1000. One turn is one request to the model. When a run reaches the limit, it stops and fails with turn budget exhausted. |
--system-prompt |
yes | The standing rules from Step 2. |
--toolset |
no | The JSON from Step 3. Write it inline, as @<file>, or as @- to read it from stdin (for example from a pipe). Default: {}. |
--disabled |
no | Create the definition switched off. Nobody can run it until you switch it on. |
Check the result:
meho agent show pod-reader
To change a definition later, use meho agent edit. Only the flags you
pass change:
meho agent edit pod-reader --turn-budget 30
meho agent edit pod-reader --toolset @pod-reader-toolset.json
meho agent edit pod-reader --disabled # switch it off
meho agent edit pod-reader --enabled # switch it on again
Step 5 — Set permissions (grants)¶
Every operation has a safety level: safe, caution, dangerous,
or destructive. You see it as safety_level in the
search_operations results. The safety level sets what happens when
no grant matches.
What the agent may do with no grants¶
| Safety level | With no grant |
|---|---|
safe (reads) |
Runs without approval. |
caution |
Waits until a second person approves. |
dangerous |
Denied. |
destructive |
Denied. |
So a new agent can already run every safe operation on every
connector and target, with no grant at all. To make it truly read-only
on one thing, you add grants. See the recipe below.
Who the grants apply to¶
Grants belong to the agent principal (agent:pod-reader). They apply
to every run of the agent:
- A run started by the scheduler, or by an event trigger. The agent logs in with its own identity. Its grants apply. The audit rows show the agent's sub.
- A run you start by hand with
meho agent run. The run acts on your behalf. MEHO checks each call against your permissions and against the agent's grants. The stricter answer wins. So the run may do only what both you and the agent may do. The audit rows show your sub.
The safety-level table above applies to both kinds of run. With no
grant, a caution operation waits until a second person approves, and
a dangerous operation is denied, even when you may run it yourself.
See Approvals and break-glass for which
calls wait for a second person.
So the read-only recipe below works for both ways to start a run. Step 6 shows both ways.
How a grant works¶
A grant has four parts:
| Flag | What it means |
|---|---|
--principal |
The agent's client id, agent:<name>. |
--op |
Which operations. A pattern: * matches anything, ? matches one character. Examples: *, k8s.*, k8s.pod.list. |
--target |
Which target, by its UUID. Leave it out, or use *, for any target. |
--verdict |
auto-execute (run without approval), needs-approval (wait for a second person), or deny. |
When MEHO checks a call, it follows these rules:
- It keeps only the grants whose
--oppattern matches the operation and whose--targetmatches the target. - The most specific pattern wins. MEHO counts the characters before
the first
*,?, or[. More characters means more specific. Sok8s.pod.listbeatsk8s.*, andk8s.*beats*. - If two matching grants are equally specific, the stricter verdict wins.
- If no grant matches, the safety-level default from the table above applies.
- Last, MEHO applies a ceiling. A grant can never make an operation more open than this:
| Safety level | The most open verdict a grant can give |
|---|---|
safe |
auto-execute (no limit) |
caution |
needs-approval |
dangerous |
needs-approval |
destructive |
deny. No grant can open it for an agent. |
So a grant can always make an operation stricter. It can make a
dangerous operation possible, but only with a second person's
approval. A caution or dangerous operation never runs on its own.
Grants only check operations that run through call_operation. The
discovery tools (meho_connector_list, list_operation_groups,
search_operations, list_targets) are not affected by grants.
Recipe: a read-only agent for one operation on one target¶
Goal: pod-reader may run k8s.pod.list on cluster-a, without
approval. Everything else is denied.
First, find the target's UUID. It is the id: line:
meho targets describe cluster-a
Then add two grants:
# 1. Deny every operation on every target.
meho agent grant create --principal agent:pod-reader \
--op '*' --verdict deny
# 2. Allow one read operation on one target, without approval.
meho agent grant create --principal agent:pod-reader \
--op k8s.pod.list --target <cluster-a-uuid> --verdict auto-execute
Why this works:
- On
cluster-a, both grants matchk8s.pod.list. The patternk8s.pod.listis more specific than*, soauto-executewins. - On any other target, only the
*grant matches. The verdict isdeny. - Every other operation matches only
*. The verdict isdeny, even forsafereads.
Check the grants:
meho agent grant list --principal agent:pod-reader
To remove a grant, use its id from the list:
meho agent grant revoke <grant-id>
For a grant that should end by itself, add --expires with a UTC time,
or use meho agent grant elevate. When the time passes, the grant
stops working.
Step 6 — Run the agent¶
There are two ways to start a run. The grants from Step 5 apply to both.
| How you start the run | Whose permissions apply | Whose sub is in the audit rows |
|---|---|---|
By hand, with meho agent run |
Yours and the agent's. The grants from Step 5 apply. The stricter answer wins. | Yours |
Through the scheduler, with meho scheduler create |
The agent's. The grants from Step 5 apply. | The agent's |
A run by hand, on your behalf¶
Any user with the operator or tenant_admin role can run a
definition in their tenant.
A run by hand is limited by you and by the agent
This run acts on your behalf. Each call must be allowed for you and
by the agent's grants from Step 5. The stricter answer wins. So
pod-reader can only run k8s.pod.list on cluster-a, even when
you may run more. meho agent run-status <run-id> shows both, for
example identity: agent:pod-reader for <your sub>.
The agent also acts with the operator role at most, like its own
login. So a run that a tenant_admin starts cannot do tenant-admin
work.
meho agent run pod-reader \
--input "List the pods in namespace web on cluster-a that are not running."
The CLI waits for the answer. A short run prints its run_id, its
status, and its output. If the run takes longer than the server's
wait limit (30 seconds by default), MEHO keeps the run going and gives
you its run id instead.
For a long task, do not wait. Start the run in the background:
meho agent run pod-reader --async \
--input "List the pods in namespace web on cluster-a that are not running."
Then check it with the run id:
meho agent run-status <run-id>
run-status shows the status, the number of turns used, and,
when the run is done, the output or the error. The status is one
of: pending, running, succeeded, failed, cancelled.
Sometimes the agent calls an operation that needs a second person's
approval. The run then waits, and run-status still shows running.
If a run seems to stop moving, check meho approvals list. Approve or
reject the request with meho approvals approve <id> (see
Approvals and break-glass). After about
30 minutes with no decision, the agent is told that no decision came.
More run commands:
meho agent run-list --agent-name pod-reader # the runs of this agent
meho agent run-list --status running # runs that are still going
meho agent run-cancel <run-id> # stop a run
meho agent run-events pod-reader --input "..." # start a run and watch each step live
A run as the agent, with its grants¶
The scheduler starts runs with the agent's own
identity. In such a run the grants from Step 5 apply, so pod-reader
can only run k8s.pod.list on cluster-a. Use it for runs that start
without a person.
The scheduler takes the definition's id, not its name. Look the id up first:
meho agent show pod-reader
Copy the value of the id: line. It is a UUID, for example
3f2a9c6e-1b4d-4e8f-9a7c-2d5e6f7a8b9c. With --json, the same value
is the id field.
A one-off trigger fires once, at a UTC time you give. Pick a time a few minutes from now, and pass the id you copied:
meho scheduler create --kind one_off \
--agent-definition 3f2a9c6e-1b4d-4e8f-9a7c-2d5e6f7a8b9c \
--fire-at 2026-10-20T02:00:00Z \
--inputs '{"task": "List the pods in namespace web on cluster-a that are not running."}'
--agent-definition must be the UUID. The CLI rejects a name before it
creates the trigger. --inputs is a JSON object. MEHO puts it into the
agent's prompt when the trigger fires. Creating a trigger needs
tenant_admin.
Before the first scheduled run, two things must be in place:
- The scheduler needs the agent's login secret. The
scheduler guide explains where to store it. Without
it, the scheduler skips the fire and shows
last_skip_reason=credentials_unresolved. - The grants from Step 5 must exist. Otherwise the agent runs with the
safety-level defaults, which allow every
safeoperation.
After the time passes, find the run and check it:
meho scheduler list
meho agent run-list --agent-name pod-reader
meho agent run-status <run-id>
For a run that repeats, use --kind cron with --cron-expr and
--timezone. The scheduler guide shows the full set of flags.
Step 7 — Check what the agent did¶
Each operation the agent runs through call_operation writes an audit
row. The row carries the run id in the agent_session_id field. So the
run id is all you need to find the operations of one run.
Not every tool call writes a row. In a hosted run, the discovery tools
(meho_connector_list, list_operation_groups, search_operations,
list_targets), preview_operation, and result_query do not write
audit rows. So the audit shows which operations the agent ran, not
every tool it touched. The run itself, with its status, its turns, and
its output, is in meho agent run-status <run-id>.
List every row of the run:
meho audit query --session-id <run-id>
Or show the run as a tree, with each step under the step that caused it:
meho audit replay <run-id>
In each row, check:
op_id— which operation the agent ran;target_name— on which target;policy_decision—auto-execute,needs-approval, ordeny;result_status—ok,error, ordenied;principal_sub— who acted: the agent's sub for a scheduled run, your sub for a run you started by hand.
meho audit query needs the operator role. meho audit replay needs
tenant_admin. The
audit forensics guide explains all filters.
What can go wrong here¶
| What you see | Why | What to do |
|---|---|---|
403 with insufficient_role on agent create, agent edit, or agent grant create |
Setup needs tenant_admin. |
Ask a tenant admin, or log in with a tenant_admin account. |
agent create rejects --identity-ref |
The agent principal does not exist, is revoked, or the value is not agent:<name>. |
Run Step 1 first. Copy keycloak_client_id exactly. |
agent grant create says the principal "does not name a registered, non-revoked agent principal" |
--principal is not the client id. |
Use agent:<name>, not a UUID. |
| The agent says it has no tool for something | The tool is not in meta_tools, the name is misspelled, or hosted agents do not have that tool. |
Check the tool table in Step 3. |
| The agent says a connector is not allowed | The connector is not in the connectors list. |
Add the connector id to connectors, or remove the list. |
A call is denied |
A grant or the safety level denies it. | Check meho agent grant list --principal agent:<name> and the operation's safety_level. |
| A run you started by hand is denied an operation that you may run | The agent's grants deny it. In a run by hand, the stricter answer wins. | Add a grant with meho agent grant create --principal agent:<name>, or run the operation yourself. |
The run fails with turn budget exhausted |
The agent used all its turns. | Raise --turn-budget with meho agent edit, or make the task smaller. |
| The run fails right away and the error names a runbook | The prompt or input tells the agent to run a runbook. Hosted agents cannot run runbooks. | Remove that instruction. |
agent run gives back a run id but no output |
The run took longer than the wait limit, so it went on in the background. | Run meho agent run-status <run-id>. |
409 on agent run |
The definition is switched off. | Run meho agent edit <name> --enabled. |