← Onidia
Agents

Teach her to do things.

An agent is a small program in a folder that she can call. No rebuild, no plugin manager, no restart-and-pray: drop the folder in, restart, and the model can use it the next time you ask. Five ship with her already — including the ones that play your music and make her dance while it does.

Any language Zero dependencies ~30 lines to start Nothing to rebuild

The whole idea, in one exchange

When you ask for something she cannot do alone, she calls an agent. The whole contract is one line in and one line out — which is why an agent can be a Python script, a shell script or a compiled binary and the app does not care.

You“play some jazz”
ShePicks an agent from her catalog, fills in the arguments and runs it. You never name it and never see the call.
the wire protocol, agent-line-v1
brain → agent   RUN {json object}   one line, validated before your code starts

agent → brain   INFO <text>        # optional, repeatable. Logged, never shown to you
agent → brain   PET action dance   # optional, at most one, before OK. Makes her act
agent → brain   OK <message>       # terminal. Joins her reply
agent → brain   ERR <text>        # terminal. Shown as the failure
Why it is this small

No sockets, no daemon, no handshake. One process per call, working directory set to the agent's own folder, then it answers and exits. Anything it starts itself has to be detached — she will not wait for your background job. Parameters are checked before your code starts: a wrong type, a missing required one or an unknown name means your agent is never spawned at all, and she says so instead.

What already ships with her

Five bundled agents. On a package install they live in /opt/onidia/share/agents and are switched on already — you never install anything to get these.

play_song

Fuzzy-matches your music folder and starts a detached player. She dances along. “play some jazz”

play_movie

The same for video, fullscreen by default. “put on the marathon”

media_control

Pauses, resumes or stops whatever is playing, and answers “what's this?” The chat window's transport buttons call this same agent.

pet_control

Drives her animation directly: expressions, overlay effects, named animations and movement. “do a happy dance”

hello_world

The reference agent, and the one to copy when you write your own. Greets you by name.

Walkthrough: playing a song

This is the whole play_song agent end to end. Nothing is hidden from the app — the lines below are exactly what crosses the pipe.

1

It needs two things

Some audio files in a folder, and a player on PATH. It tries mpv, cvlc, vlc, ffplay and takes the first one it finds. The folder defaults to ~/Music; point it elsewhere with music-dir.

~/.config/chat-app/chat-app.ini
music-dir = /mnt/pi/audio/music
2

Ask in plain English

Any of these reaches it. She reads the catalog entry — the description in the manifest — and decides when to use it, so you never have to remember a command.

You“play some jazz”
You“put on So What”
You“shuffle something”
3

What she sends

One line of JSON. query is the only parameter; leave it out and the agent picks a track at random.

RUN line
RUN {"query":"jazz"}
4

What comes back

It walks your music folder, scores every filename against the query with a fuzzy match and takes the best one. Then it starts the player detached, so the agent can exit without taking the player with it, records the session so the transport buttons know which process to drive, asks her to dance, and answers.

agent output
INFO 812 tracks in /home/pi/Music
INFO player: mpv --no-video --really-quiet "/home/pi/Music/Miles Davis - So What.flac"
PET action dance
OK Playing Miles Davis - So What

INFO goes to the log, PET makes her dance, and the OK message is the part you actually read. That is the entire reply.

5

When it cannot find a match

If nothing scores above the threshold it fails loudly rather than playing something random and pretending, and it hands her the closest filenames so she can offer you a choice.

no match
ERR no match for 'kraftwerk' (closest: Kraftwerk - Autobahn, Kraftwerk - The Mix, Radio-Activity)
The two failures you will actually hit

music folder ~/Music not found - set music-dir in chat-app.ini means she has no folder to search. no media player found - install mpv, vlc or ffmpeg means the files are there but nothing can play them. Both name the exact thing to fix.

6

Then control it

Ask her in words — “pause”, “stop that”, “what's playing?” — or use the transport strip in the chat window, which calls the very same media_control agent.

You“pause”
HerPaused.

And the same for video

play_movie is play_song's twin, line for line. It searches ~/Videos (override with video-dir) for .mp4 .mkv .webm .avi .mov .m4v .wmv .flv .mpg .mpeg, and it has one extra parameter.

queryMovie title or keywords; omit it for a random pick. Same fuzzy match, same 0.45 threshold, same “closest: …” failure.
fullscreenDefaults to true. Set it to false for a windowed player — “play X in a window” is enough to get there.
RUN line — fullscreen
RUN {"query":"the marathon"}
RUN line — windowed
RUN {"query":"the marathon","fullscreen":"false"}
One state directory, two agents

Both media agents write a small JSON session file recording the player they started, and media_control reads it to decide which process to drive. That is why “stop” works from her keyboard and stops the right thing, and why “what's playing?” gets a real answer.

Managing your agents

All four operations are modes of the binary you already have. There is no second tool to install.

1

List

What is installed, where it came from, and whether it is on. Disabled and broken agents are listed on purpose — a list built only from what discovery returns would make a disabled agent look uninstalled, which is the exact confusion this mode exists to end.

terminal
onidia-chat -agents-list
what it prints
ID               VERSION  STATE     LOCATION
play_song          1.0.0    enabled   ~/.config/chat-app/agents/play_song
pet_control        1.0.0    disabled  /opt/onidia/share/agents/pet_control
hello_world        1.0.0    broken    ~/.config/chat-app/agents/hello_world (exec not found)
2

Install

From a folder, a zip, a URL, or a registry id. It always installs into your directory and never the packaged one — /opt is root-owned, and a surprise sudo prompt because you wanted a buddy behaviour would be a bad afternoon.

terminal
onidia-chat -agents-install ~/agents/my_agent
onidia-chat -agents-install ~/downloads/weather.zip
onidia-chat -agents-install https://example.com/agents/tea.zip
onidia-chat -agents-install some_registry_id
what it prints
installed coffee -> /home/pi/.config/chat-app/agents/coffee
restart onidia-chat to load it (onidia-chat -agents-list shows it now)

A bare id only goes through a registry if you configured agents-registry. Anything containing a /, a ~ or ending in .zip is treated as a path, so relative paths are safe.

3

Disable, or delete

Disabling keeps the files and just stops offering the agent. Deleting removes your copy. For the bundled agents, disable is the only option.

terminal
onidia-chat -agents-disabled play_song,play_movie
onidia-chat -agents-remove coffee
what it prints
removed coffee from /home/pi/.config/chat-app/agents
restart onidia-chat to drop it from the catalog
Two things that catch everyone

A restart is always required. Discovery runs at startup only — there is no hot reload, and both commands say so. The disable flag is per-run. -agents-disabled on the command line applies to that one invocation and is not remembered; to make it stick, put agents-disabled = play_song in chat-app.ini.

The bundled agents refuse to be deleted

They belong to the package, so -agents-remove refuses and tells you the alternative. apt should be the only thing that removes those.

terminal
onidia-chat: pet_control belongs to the package (/opt/onidia/share/agents), not to you.
Switch it off instead - it keeps its files: onidia-chat -agents-disabled pet_control
4

The rest of the flags

-agents-dir <path>Scan and install somewhere other than your agents directory. An explicit choice is never quietly widened to include /opt.
-agents-offIgnore every external agent for this run. She keeps her built-in story agent and nothing else.
-agents-list / -install / -removeAll four modes print their result and exit. No window is ever opened.

Exit codes follow the same convention as the rest of the app: 0 success, 2 failed — no $HOME, unreadable registry, a bad agents-key, or a refused delete of a packaged agent.

Writing your own

A folder with an agent.json at its root. That is the entire requirement — the folder name is cosmetic, and any other files in there are yours to use.

1

The manifest

~/.config/chat-app/agents/tea/agent.json
{
  "id": "tea",
  "version": "1.0.0",
  "description": "Announce that the kettle is on. Use when the user mentions tea, kettle, or a hot drink.",
  "exec": ["./tea.sh"],
  "timeout_ms": 5000,
  "params": [
    { "name": "kind", "type": "string",
      "enum": ["tea", "coffee"], "default": "tea",
      "description": "which drink" }
  ]
}
Write the description for her, not for you

It is the only thing she sees. Say what the agent does and when to use it — “Control the desktop buddy's animation… not for playing music” is what stops her reaching for the wrong one. Vague descriptions produce an agent she never calls.

2

The body

Read one line, answer one line, exit. Params arrive as a JSON object on that line.

tea.sh
#!/bin/sh
# one RUN line in, one OK line out, then exit.
set -eu

IFS= read -r line || true
case "$line" in
  RUN *) payload="${line#RUN }" ;;
  *) echo "ERR expected a RUN line"; exit 1 ;;
esac

# pull "key":"value" out of the payload
kind=$(printf '%s' "$payload" \
  | sed -n 's/.*"kind":"\([^"]*\)".*/\1/p')
: "${kind:=tea}"

echo "PET action dance"            # optional: make her act it out
echo "OK The kettle is on - one $kind coming"

Remember chmod +x tea.sh. An agent whose exec cannot be resolved is listed as broken rather than silently skipped.

3

Manifest fields

idRequired. Lowercase, ^[a-z][a-z0-9_]{1,31}$. This is the name in the internal [AGENT: id] tag, so keep it short and stable.
descriptionRequired. Shown to her as the catalog entry. Say what it does and when to use it.
execRequired. An argv array. exec[0] resolves relative to the agent folder, as an absolute path, or as a bare name on PATH. The rest are static arguments.
versionFree-form, shown by -agents-list.
protocolMust be agent-line-v1, or omit it to get that default.
timeout_msPer-run deadline. Default 15000, maximum 600000. On expiry the process is killed and she reports agent tea: timed out after 15000.
disabledtrue skips it at discovery but keeps it on disk. Same effect as listing the id in agents-disabled.
paramsDeclared arguments: name, type (string, int, bool), required, enum, default, description.
4

Install it and try it

terminal
onidia-chat -agents-install ~/.config/chat-app/agents/tea
onidia-chat -agents-list
# restart her, then just ask:
# "put the kettle on"

Two rules worth knowing before you write anything clever. A run answers with OK or ERR and then exits — if your work takes longer than the reply, spawn it detached. And at most one PET line is allowed, before OK; a second one is a protocol error, because a run must not be able to quietly swap its command.

Where everything lives

~/.config/chat-app/agents/Your agents. First registration wins, so one of your own shadows a bundled agent with the same id.
/opt/onidia/share/agents/The bundled ones, on a package install. Only read, and only if your own directory does not already have that id.
chat-app.iniagents-dir, agents-off, agents-disabled, agents-registry, agents-require-sig, agents-key, plus music-dir and video-dir for the media agents.
CHAT_APP_*The environment handed to every agent, including CHAT_APP_MUSIC_DIR, CHAT_APP_VIDEO_DIR, CHAT_APP_PLAYER and CHAT_APP_STATE_DIR. Read these instead of guessing paths.
Taking only signed agents

Set agents-require-sig = true and an Ed25519 public key in agents-key, and unsigned installs are refused — useful when the agents folder is populated from somewhere you do not control. Registry installs can also be pinned by a SHA-256 in the index.

If it goes wrong

“broken” in the list

The folder was found but something in it is wrong: agent.json does not parse, the id is invalid, or exec does not resolve. The reason is printed next to the location. One broken agent never stops the others from loading.

She never calls it

Almost always the description. If it does not say when to use the agent, she has no reason to reach for it. Confirm it is in the catalog with -agents-list and not listed as disabled.

Installed, but not there

You did not restart. Discovery is startup-only, so a running window is working from the agent list it built when it launched.

“permission denied”

The exec target is not executable — chmod +x it. Note that a bare name in exec is looked up on PATH, while ./name is resolved against the agent's own folder: the dot is what makes it relative.

Music does not play

In order: is the folder right (music-dir), are there audio files in it, and is mpv, vlc or ffplay installed? She says which of the three it was, in the reply or the log.

She answers, but the buddy does nothing

A PET line only applies if she did not already pick an animation for that reply — the model's choice wins. It is also dropped when buddy forwarding is off (-pet-pipe off), and an unknown animation name is ignored on purpose.