Operate agents¶
What an orchestrator, human or agent, reads and sets to run a team of agents
with its own tools. Every --json output below is a stable schema: a field
can be added, none changes meaning or disappears without a new schema number.
Instants are UTC epoch seconds (floats, suffix _ts), null when unknown,
never 0; durations are whole seconds.
Session policy¶
| Policy | Effect |
|---|---|
par-lot (default) |
a fresh session when the lot of the turn differs from the lot of the current session; rotation on size and turn duration stays as a safeguard |
taille |
rotation on size and turn duration only (no rotation on a change of lot): for orchestrators |
jamais |
no rotation |
ameesh set <agent> session_policy=par-lot|taille|jamais # empty value = default
The runner's default is AMEESH_SESSION_POLICY (par-lot). The canon profile
has no key for it: the setting goes through ameesh set only.
- Lot of the turn: the lot (
--lot) of the turn's messages if there is a single one; else the only open lot assigned to the agent; else unknown, and an unknown lot never triggers a rotation. - Rotation: a summary is produced in the old session, the old id is kept in
the session history, the thread keeps a trace, and the first turn of the new
lot opens on the summary. If the summary fails, the turn runs in the old
session (no retry at every turn; the
session_too_bigalert keeps watch). - An orchestrator receives messages from every lot: set it to
taille, otherwise every change of lot would open a session.
ameesh set¶
ameesh set <agent> model=… effort=… tier=… session_policy=… mode=execute|externe
mode(v1.4.0):execute(default) for an agent run by a runner under a lease, orexternefor a human's session that only uses the mailbox and is never woken by ameesh. See Agent mode.tier: service tier passed to the harness through its descriptor, likemodelandeffort. Codex:-c service_tier="<tier>"(for examplefast,flex). A harness that declares none ignores it, andsetsays so.effortandtierare written in the host's local state and in the database, solist --jsonshows them whatever the host.- Every setting takes effect at the next turn.
Agent mode and stop reason¶
The rule of design decision 0030 is no work without a way to wake the agent: a lot, a delegation or an orchestrator role goes only to an agent ameesh knows how to wake. Each agent therefore has an explicit mode:
| Mode | Meaning |
|---|---|
execute |
run by a runner under a lease; woken by its mail, events and nudges |
externe |
a session started by a human; mailbox only (through a session binding), never claimed nor woken by ameesh |
- An external agent must have a responsible human;
setandshowsay so otherwise.ameesh listprefixes its status withext/. - An agent created by a hook without a lease is born
externe; a hook never changes the mode of an existing agent.ameesh attachstays allowed on an external agent, without changing its mode. - When an agent stops, a structured stop reason is recorded:
manuel(agent-runner stop),bail_expire(the turn died, the lease expired) orretire_du_canon(removed bycanon sync);externeanderreurare reserved. It is cleared as soon as the agent runs again.
ameesh list --json: schema ameesh-agent/1¶
Each agent row keeps the historical keys and adds:
| Key | Content |
|---|---|
schema |
"ameesh-agent/1" |
harness, model, effort, tier |
harness and settings, or null |
session_policy, session_policy_set |
effective policy; the agent's own setting or null |
state |
working, idle, paused or stopped (same rules as ameesh progress) |
state_since_ts, state_for_s |
since when |
lot |
{id, title, state, source}: the session's lot ("session"), else the most recent open lot assigned ("assigned"), or null |
title |
title of the current lot, else the status text |
unread, oldest_unread_ts |
mail not delivered yet |
turn_started_ts |
start of the running turn, or null |
last_turn_reread_tokens |
tokens re-read at the last turn (input + cached input) |
restart_pending |
an ameesh restart request waits to be applied |
mode |
execute or externe (v1.4.0) |
stop_reason |
manuel, bail_expire, retire_du_canon (externe, erreur reserved), or null while the agent runs (v1.4.0) |
The text view of ameesh list counts the lots once at the bottom and shows,
for each agent, a LOTS column with its open assigned lots.
Cost per turn, plan gauges, provider balance¶
ameesh cost turns [--agent A] [--since 24h] [--limit 500] [--json] # ameesh-turns/1
ameesh cost gauges [--harness claude|codex] [--since 7d] [--json] # ameesh-gauges/1
ameesh cost balance [--provider deepseek] [--record] [--since 7d] [--json] # ameesh-balance/1
- turns: the per-turn ledger as is, most recent first: agent, harness, turn, model, session, USD, input, cached input and output tokens.
- gauges: history of the subscription gauges, recorded where they are
already read (budget guard,
cost report,progress): one row when a gauge changes, or at most every ten minutes. A history failure never breaks the budget guard. - balance: the balance of a pay-per-token provider (DeepSeek in this
version), read-only and free, with the real spend per hour and per day
computed from the drops between successive readings (a rise is a top-up).
The key comes from
DEEPSEEK_API_KEYand goes only into the request header, never into a log, a message, a thread or the database; without a key nothing is read. Requests require HTTPS and never follow a redirect. Readings are taken by--recordand by the runner everyAMEESH_BALANCE_INTERVALseconds (default 900, 0 = never). Limit: spend made in the same interval as a top-up is not seen.
Alerts: ameesh alerts¶
ameesh alerts [--follow] [--json] [--interval 30] [--long-turn 1800]
[--idle-mail 300] [--dead-grace 30] [--session-tokens 15000000]
[--stale-lot 21600] [--orphan-lot 1800] [--delegation-grace 300]
One JSON object per line (ameesh-alert/1):
type |
When |
|---|---|
long_turn |
a running turn longer than the threshold |
idle_with_mail |
an idle agent with unread mail older than the threshold |
dead_runner |
a lease expired without renewal, beyond the grace period |
session_too_big |
tokens re-read at the last turn of the current session above the threshold (default 15 M) |
stale_lot |
an unmerged lot without activity for the threshold (default 6 h), with what it waits for: verdict, correction, merge or a human decision |
host_pressure |
a reading of the host crosses a limit of its policy.resources (v1.3.0); fields host, critical, breaches |
orphan_resource |
a resource of a turn (process group, labelled container) that outlives its turn, or that a dead runner left behind (v1.3.0); it is reported, never removed |
stopped_with_mail |
a stopped or dead agent with unread mail older than --idle-mail, except a manual stop (v1.4.0); fields stop_reason, mode, responsible |
orphan_lot |
an open lot that nobody will move forward (v1.4.0); reason: inconnu (assignee unknown to the registry), arrete (assignee in execute mode stopped or dead), externe_sans_responsable, or sans_tour (an intake/build lot whose last activity and the assignee's last turn are older than --orphan-lot, default 30 min) |
delegation_expired |
a delegation past its deadline and not yet processed by a runner (reason: en_retard, after --delegation-grace), or a lot handed back to the delegator in the last hour (reason: rendu) (v1.4.0) |
A lot without assignee, or assigned to a human, is never orphan; an external
agent is never counted as sans_tour (it does not run ameesh turns). The
responsible field of stopped_with_mail, orphan_lot and
delegation_expired names the human who should act: the recipient of
pushed alerts.
Without --follow: the current alerts. With --follow: a raised line when an
alert appears (an alert that lasts is not repeated) and a resolved line when
it disappears. Thresholds can also be set with AMEESH_ALERT_LONG_TURN,
AMEESH_ALERT_IDLE_MAIL, AMEESH_ALERT_DEAD_GRACE,
AMEESH_ALERT_SESSION_TOKENS, AMEESH_ALERT_STALE_LOT,
AMEESH_ALERT_ORPHAN_LOT, AMEESH_ALERT_DELEGATION_GRACE and
AMEESH_ALERT_INTERVAL.
Pushed alerts: ameesh notify¶
ameesh alerts is pulled; ameesh notify (v1.4.0) follows the same alerts
(same thresholds, same options, same de-duplication as --follow) and
pushes them to the responsible human: desktop notification, ntfy, Slack.
ameesh notify [--once] [--dry-run] [--json] [--interval 30] [thresholds…]
ameesh notify --test human:<id> [--json]
- Types pushed by default:
stopped_with_mail,orphan_lot,dead_runner,idle_with_mail,delegation_expired(settingnotify.types). - Recipient, first found: the alert's
responsible; the agent's responsible human; the responsible of the lot's work package; the responsible of the host'sHostcard;notify.default_human. An alert without a recipient or channel is logged once and kept: if a route appears while it lasts, it is sent. - Reliability: the delivery state is kept in
<state>/notify/state.json(0600), so a lasting alert is not sent again after a restart and an alert resolved meanwhile gets its resolution. Onenotifyper state directory (a second one exits with code 1). Beyondrate_per_minuteper human, alerts are folded into a summary. A failed channel does not stop the others and is retried up tomax_attemptstimes. --json: one object per delivery, schemaameesh-notify/1(event,type,agent,lot,host,human,source,delivery,channels,title,text,ts).
Configuration (the notify key, channels, and the rule that secrets come only
from the environment or a 0600 file): see
Configuration. An example
systemd user unit ships as deploy/systemd/ameesh-notify.service (secrets in
~/.config/ameesh/notify.env, 0600). To put it in service:
ameesh notify --test human:alice # one test message per channel
ameesh notify --once --dry-run # what the service would send now
systemctl --user enable --now ameesh-notify
journalctl --user -u ameesh-notify -f
The desktop channel of a user service needs a graphical session (session
D-Bus).
Guarded assignment and delegation¶
ameesh work add --title T --assignee AGENT [--externe] …
ameesh work assign <id> <agent> [--externe] [--actor A]
ameesh work delegate <id> <agent> --within 30m [--actor A]
ameesh work expire-delegations [--dry-run] [--json]
Guarded assignment (v1.4.0). A lot goes only to a wakeable agent:
known to the registry, in execute mode, ephemeral and not expired, admitted
on its host for its current profile, and with a responsible human when a canon
is configured. Otherwise the command refuses with the reason (exit 1),
writes nothing, and says what to do: register the agent, assign to a human
(human:<id>), or --externe for an external session with a responsible
human. Details:
- an unknown name is refused; a bare name that designates a known human
(a
Memberof a configured canon,AMEESH_HUMANS) is normalised tohuman:<id>with a warning; - a stopped agent is accepted with a warning (the lot waits for it, and the
orphan_lotalert follows it); - a canon that is momentarily invalid does not block an assignment (warning); claiming stays closed until the next valid sync;
work assignreassigns an open lot, logs "assigned to X (before: Y)" in the lot's events, and clears any delegation in progress.
Delegation with a deadline. work delegate hands a lot to a wakeable
agent (same guard; a human is refused, use work assign) and drops an event
in its mailbox, which wakes it. The delegator is --actor, else the current
assignee; the lot comes back to them. At the deadline (--within: 30m, 2h,
1d or seconds), processed by the runner at every pass:
- if the delegate has done nothing on the lot since the delegation (no turn on
that lot, no transition, note, milestone, action or linked message), the lot
goes back to the delegator, with an event in their mailbox if it is an
agent (a human is told by the
delegation_expiredalert); - otherwise the delegation is settled: the deadline is cleared and the lot stays with the delegate.
A new delegation replaces the previous one. work list, work show and
ameesh progress say "delegated by X, due in 12 min" or "overdue by …".
Session bindings¶
The working directory never gives an identity (decision 0030). A session started by a human (an external agent) receives its mail through the hook only if it is explicitly bound:
ameesh mail bind <agent> --session <id> --harness claude|codex|deepseek --pid <harness PID>
ameesh mail bindings [--all] [--json]
ameesh mail unbind --session <id> --harness <harness>
The session id is the one the harness passes to its hook (Claude Code shows it
in /status and in its transcript name; Codex in its session file name).
--pid is recommended: the hook then requires that PID among its ancestors.
Without a binding the hook delivers nothing and writes nothing. An agent in
execute mode is refused (an external session would steal its mail) unless
--force; reclassify it first with ameesh set <agent> mode=externe if it is
in fact a human's session. Inside a bound session, agent-mail whoami shows the
agent, the source session and the binding.
Host resources: ameesh hosts¶
Since v1.3.0, each runner publishes a reading of its host every
AMEESH_RESOURCE_INTERVAL seconds (default 60): available memory, swap used,
1-minute load, free disk of the working directory, turns in progress.
ameesh hosts [HOST] [--history 10] [--json] # ameesh-host/1
Before each turn, the runner compares its last reading with the limits of the
host (policy.resources of its Host card, else prudent defaults: 1 GiB of
available memory, 8 GiB of swap, a load of twice the CPU count, 2 GiB of free
disk). Crossing a limit holds new turns (running turns finish); a critical
crossing pauses the agents of lowest priority that have no running turn,
never in the middle of a turn. See
Runner and leases.
Fresh session on a brief: ameesh restart¶
ameesh restart <agent> --brief FILE|- [--wait S] [--json]
Stops the running turn cleanly, forgets the session, and makes the brief the
first message of the fresh session. Without a live lease it is applied at once;
with one, the request is written and the runner is woken: it stops the turn,
puts that turn's prompt back behind the brief, and applies the request under its
lease. --wait waits for the application. Refused for a stopped agent (use
ameesh resume) or an empty brief.
Adopt an interactive session: ameesh adopt¶
ameesh adopt <agent> --session ID --harness claude|codex|deepseek
[--account ACCOUNT] [--cwd DIR] [--brief FILE|-] [--chantier C]
[--force] [--json]
Putting an existing interactive session under the runner is an ameesh operation (v1.4.0), not a prompt. Run it on the session's host:
- The session file is looked for under the folder of each declared account of
the harness (
--accounttargets one); the account where it is found becomes the session's account of origin. Not found: refused, with the folders searched. - The session must be closed: no process holds its file or carries its id
on its command line. Otherwise the command lists the PIDs;
--forceoverrides, with a warning in stderr and in the thread. - Refused if the agent holds a live lease.
- In one statement:
mode=execute, harness, host, working directory (--cwd, else the one recorded in the session log, else the registry's), session and account of origin, statusqueued, and the brief (--brief, else a standard resume instruction) at the head of the pending prompt. An unknown agent is created (--chantier). An audit event goes to the project's thread, and the agent's session bindings are revoked.
The human can then come back with ameesh attach <agent>, on the same session
and under the lease. If, at the first turn, the active account is not the
session's account and the session cannot move between accounts, the runner
rotates with a summary written under the account of origin.
Restart a stopped agent: ameesh resume¶
ameesh resume <agent> [--brief FILE|-] [--fresh] [--json]
Restarts a stopped, dead or idle agent. Refused for an unknown
agent, an external agent (use adopt), an agent that is not wakeable for
another reason, or a running turn.
- The recorded session is kept when the account the next turn would use
can resume it (same account, a session portable between accounts, or no
declared account). The choice is simulated:
resumeswitches no account and records nothing. - If the session is not portable but its old account is still usable, it is kept and the runner rotates at its first turn (summary under the old account, fresh session under the new one).
- Otherwise, or with
--fresh, or without a session, the session is forgotten and the fresh one opens on a deterministic resume brief built by ameesh without calling a model: identity and role, open assigned lots with what they wait for, the last exchanges of the project's thread, the unread count, the path of the old transcript, then the instruction (--brief). The brief is capped at 16 KB.
agent-runner register <agent> <harness> without --session keeps the
recorded session; to start afresh, ameesh resume <agent> --fresh.
Direct interruption: ameesh interrupt¶
ameesh interrupt <agent> <message…> [--from NAME]
Drops an urgent event: the runner stops the running turn and serves the message
first. Only senders listed in AMEESH_INTERRUPT_SENDERS may do it (the same
rule as the runner); anyone else is refused with exit code 1 and nothing is
dropped.