Aligned with the code. The pool currently holds 79 executors, split between handcrafted ones in /opt/metnos/executors/ and ones generated on the fly by Synt in ~/.local/share/metnos/executors/ (kept apart by design). Manifest signed with Ed25519, runtime fence built on the manifest itself, life cycle managed by the loader. See also agent_runtime (planner loop), sandbox (the fence) and synt (automatic generation).
← Documentation index Microdesign › executor

Metnos

executor — how it is made
Didactic introduction
For those who want to understand, not those who must implement.

Reading time: ten minutes.

Contents

  1. What an executor is (in thirty seconds)
  2. Anatomy: five things in the folder
  3. The manifest: the calling card
  4. The fence: what it can and cannot do
  5. An executor's life
  6. Four origins: handwritten, generated, imported, builtin
  7. Three concrete examples
  8. Going deeper

1. What an executor is (in thirty seconds)

An executor is a small specialised utility that does one thing only: reads mail, finds a file, takes a snapshot of the clock, sends a message. It lives on its own, in its own folder, and talks to Metnos through a precise contract.

Think of a toolbox. Every tool is simple and recognisable: the screwdriver tightens screws, the hammer drives nails. Nobody would ask the screwdriver to drive a nail. In Metnos it is the same: every executor is a tool with a clear job. When the user asks for something, the planner picks the right tool and uses it.

arguments {tz: "Europe/Rome"} get_now one thing only: returns date and time manifest signature result 2026-05-06 16:45 what I ask the tool that runs what I get back
An executor is a box with a precise job, an inbound contract, and an outbound contract.

Three things define it:

Why so small? The more a tool can do, the harder it is to trust. If an executor were to delete files and send mail, you could not safely use it inside an automated workflow. By cutting along the «one thing only» line, Metnos can compose them freely without fearing surprise side effects.

2. Anatomy: five things in the folder

An executor is a folder on disk. Inside there are at most five files, each with its own job. Nothing more.

get_processes/ manifest.toml the calling card: name, what it does, arguments, examples, output schema manifest.toml.sig the cryptographic signature of the calling card: it proves who wrote it get_processes.py the actual code: an invoke(args) function that performs the task manifest.lang_state.json translation state (multilingual): only when descriptions exist in more than one language five files, nothing more
An executor's folder: three required files (manifest, signature, code), one optional for translations, all flat inside the folder.

The three required files are the heart. The code does the work. The manifest describes the code so that the planner can decide whether to use it. The signature proves the manifest has not been tampered with since the author wrote it.

FileWhat it holdsWho reads it
manifest.tomlName, description, arguments, examples, output schema, sandbox profilethe planner (to choose), the loader (to load)
manifest.toml.sigEd25519 signature of the manifest bytesthe loader, when the catalog comes up
<name>.pyAn invoke(args) function that performs the workthe runtime, when the tool gets called
manifest.lang_state.jsonPer-language description hashes (used by the translation system)the nightly translation daemon
No subfolders, no Python packages with crossed imports, no requirements.txt. An executor lives in a flat folder. The libraries it uses are Python's standard ones or those already installed on the system. The more isolated it stays, the easier it is to sign, load, run, and move it to another machine. See sandbox for the details.

3. The manifest: the calling card

The manifest is a TOML file. You open it with a text editor and you can make sense of it even without being a programmer. It declares everything the planner needs to know: the executor's name, what it does, which arguments it takes, what the result looks like, a few examples for orientation.

name = "get_now" version = "0.1.0" affinity = ["time", "date", "now", "ora"] [description] en = "Returns the current date and time." [args.properties.tz] type = "string" default = "UTC" [output] schema_inline = "{ ok: bool,... }" [code] files = ["get_now.py"] digest = "sha256:e6e609f6..." [[capabilities]] name = "time:read" identity keywords helps the planner find this tool what it does a clear sentence, translated per language arguments type, default, description result shape what comes out of the tool, field by field code + fingerprint the digest changes if the code is touched permissions what it may touch
A real manifest, simplified. Every block plays a clear role: identity, keywords for retrieval, description, arguments, result shape, code with fingerprint, permissions.

What deserves a second look

Three things stand out, because the whole system rests on them.

The code fingerprint (digest): a cryptographic fingerprint computed over the bytes of the .py file. If anyone modifies even a single comma in the code without recomputing the fingerprint, the loader rejects the executor. The manifest and the code are bound like a certificate and the document it certifies.

The result shape (output.schema_inline): declares field by field what the executor returns. It serves whoever composes chains of executors (the planner does not fly blind: it reads the schema and knows what to expect at the next step) and whoever auto-generates code.

The permissions (capabilities): the manifest does not decide what the executor may do. The manifest declares what it would need in order to function; the system then decides whether to grant those permissions and under which constraints. See sandbox.

Why TOML and not JSON? TOML is built to be read and written by hand, comments included. A manifest is a working document: the author writes it, somebody reviews it, every now and then it has to be updated. JSON would be stiffer and less readable.

4. The fence: what it can and cannot do

An executor does not roam free on the system. It lives inside a fence, built from the permissions its manifest declares. The fence sets: what it can read on disk, what it can write, which network hosts it can speak to, which system files are off limits even if the executor were to ask for them.

off limits for everyone (keys, /etc/shadow,...) read-only area granted by the manifest workspace: reads and writes here code invoke(args) the further you move from the centre, the less you may do
The fence as concentric rings. At the core, the code. Right around it, the working area (it reads and writes). Further out, things it may only read. At the outer edge, what is off limits to anyone, even to those with the right permissions.

The golden rule is simple: the fence is always tighter than the manifest. If a manifest asks to read /home, the fence may still cut the request down to /home/user/notes. If it asks to talk to any domain, the fence may restrict it to imap.example.com. The difference matters: the manifest is what the author hoped for, the fence is what the system decided after looking at who is calling, in which context, with what trust level.

WhatWhere it is written
What the executor would like to doin the manifest, capabilities block
What it actually may do in the current turnin the fence applied by the sandbox at invocation time
What no one may ever doin the «core rules», non-negotiable
A practical example. If an executor meant to read PDF invoices tried to open /etc/shadow (the system password file), here is what happens: the manifest does not request that permission, the sandbox catches the call and refuses it, the executor receives a «permission denied» error. Even if the code were briefly compromised, there is no way to step outside the fence.

5. An executor's life

An executor is not born active and does not stay active forever. It moves through a series of states the system manages on its own. Knowing these states helps you read the catalog: if a tool is not being picked anymore, it might be because it slipped into quarantine, not because it is broken.

proposed manifest only synthesised code + tests green active in pool, signed quarantine recoverable within 24h archived out of pool recovery only synthesised tools pass here they advance when the loader approves them
An executor's life cycle. Most seeds (handcrafted) start straight from active; those generated on the fly by Synt walk through proposed and synthesised first. From active a tool exits into quarantine (for example when another tool replaces it); after twenty-four hours without recovery, it is archived for good.
StateMeaningIn pool?
proposedonly the manifest exists, the code is missingno
synthesisedcode present and tests green, awaiting signatureno
activesigned and visible to the planneryes
quarantinereplaced by another or under suspicion, but recoverablevisible in audit, kept out of new picks
archivedout of the system; kept only for historical auditno
Why quarantine and not direct deletion? Because every now and then a new executor that was meant to replace an old one turns out imperfect. By keeping the old one on the bench for twenty-four hours, you can roll back without effort. It is a safety net analogous to the file manager's trash.

6. Four origins: handwritten, generated, imported, builtin

All executors share the same external anatomy, but they are born in four different ways. The distinction is not cosmetic: it changes who writes them, where on disk they live, and where the ideas behind them come from.

Handwritten

The author crafts them with care. They form the stable core, the seed from which everything else grows.

Folder: /opt/metnos/executors/

Examples: get_now, find_files, read_messages, send_messages.

Tight, robust, reviewed many times.

Generated on the fly

When the catalog does not cover a request, the Synt composes a new executor in five steps (name, contract, tests, description, code).

Folder: ~/.local/share/metnos/executors/

Examples: specialisations or generalisations of existing seeds.

Kept apart: they can never overshadow handwritten seeds.

Imported from an external skill

A public skill (e.g. from agentskills.io) describes in plain English how to use a third-party service. A deterministic translator turns it into one or more Metnos executors.

Folder: ~/.local/share/metnos/executors/skills/

Examples: read_events, set_events, delete_events (from a calendar skill).

Same checks as a generated one: no special treatment for coming from outside.

Builtin

Internal runtime services. They have no folder of their own; they live within Metnos.

Folder: runtime/system/

Examples: undo_last_turn, scheduler, admin.

System services, not regular tools.

One rule that saves a thousand headaches. An executor that is not handwritten — whether generated or imported — can never take the same name as a handwritten one. If it tries, the loader rejects it and moves it to a discard folder. The handcrafted core is never silently swallowed by an automatic composition or by a third-party skill.

Why the imported origin exists

A third-party library typically has its own textual documentation explaining how to use it: «to list the calendar, call gws calendar list; to create an event, use --summary and --start». The agentskills.io standard encodes this documentation in a precise format (a Markdown file with a structured front matter). Metnos's importer reads that format, translates it into the system's closed vocabulary, and generates the executor folder as if it had been handwritten.

The upside: any service already documented as a skill (Google calendar, mail, file storage,...) can be brought into Metnos without rewriting from scratch. The downside: you have to trust whoever wrote the skill (and its helper scripts). For this reason the importer does not install anything in /opt/metnos/executors/: imported executors live in a separate folder, under watch, and still pass through the vetting before every call.

One operational detail: imported executors that need credentials (an API key for an external service, the session file of an account) do not make them up. The first time you call one, it opens a dialog window, asks you for the missing data, encrypts it in a local vault, and proceeds. Subsequent calls find the credential already in place. No passwords in code, no configuration files to edit by hand.

7. Three concrete examples

Three executors actually in use, told from the outside. No source code: just what you ask for and what you get.

7.1 get_now — «what time is it?»

The simplest tool in the catalog. No required arguments. Returns a dictionary with the current date and time.

call: get_now(timezone="Europe/Rome")
response: { ok: true,
 content: "2026-05-06T16:45:23+02:00",
 metadata: { timezone: "Europe/Rome", iso8601: "...", epoch:... } }

No network, no file read, no write. Permissions: time:read. It is one of those tools that look superfluous until you see why they matter: the planner must never invent the date from training memory. When it needs to compute «yesterday's mail», it calls get_now first, then subtracts a day. That way «yesterday» is always today's yesterday, not the yesterday from when the model was trained.

7.2 find_files — «find the photos»

Searches files by name or pattern (the classic «extensions»). Returns the list together with basic metadata: path, name, size, last modified, type.

call: find_files(base_path="/home/user/images", pattern="*.jpg")
response: { ok: true,
 entries: [
 {path: "/home/.../foto1.jpg", size: 2458123,...},
 {path: "/home/.../foto2.jpg", size: 1923456,...},...
 ],
 metadata: { count: 247,... } }

The planner uses it when it has to hand the list off to another tool: for example to keep only the most recent photos, compute total size, compress the older ones. Note that filtering does not live in find_files: the tool returns the files and that is it. If you want a subset, you ask via the pattern, or you pipe the result into a tool that filters. One thing at a time.

7.3 filter_lists — «find overlapping events»

A tool that works on two lists rather than one. Useful when a user question intersects two sets: "which HLT appointments overlap with MNM ones in the next three months?", or "which files are present in both folders?".

call: filter_lists(op="overlap",
 from_step=2, # list A (HLT events)
 with_step=3) # list B (MNM events)
reply: { ok: true,
 op: "overlap",
 entries: [...entries of A that overlap with at least one of B... ],
 metadata: { count_a: 4, count_b: 5, count_out: 0 } }

Available operations: intersect (entries common to both lists, match on a key), union (entries from either, deduped), difference (entries of A not in B), symdiff (symmetric difference), overlap (temporal AND: entries of A whose time window overlaps with at least one in B; auto-detect start/end).

The taxonomy of list operators is closed and readable: filter_entries reduces a single list (predicates: where_starts_with, where_contains, where_glob, where_regex), filter_lists combines two lists with set ops, compute_entries computes a scalar (sum, average, min, max, count). The three primitives together cover almost every list manipulation without inventing new verbs.

7.4 send_messages — «send a message to Lucia»

Sends one or more messages over Telegram (today) or email (coming soon). Main argument: a list of messages, each with recipient and body.

call: send_messages(messages=[
 {to_user: "lucia", body: "Out for a bit, back at seven."}
 ])
response: { ok: true, ok_count: 1, fail_count: 0,
 results: [{to_user: "lucia", channel: "telegram", message_id: "abc123"}] }

A «transformative» tool: it changes the world, it really sends a message. For this reason, executors that change the world get treated with more care: their manifest declares broader permissions (net:write), they go through a check before execution, and their effect stays tracked so it can be undone. See vaglio for pre-execution checks and approval_ux for how the user is asked to confirm.

8. Going deeper

This document is an introduction. If you want to understand the mechanisms beneath — how a manifest gets signed, how the fence is applied, how the Synt generates code, how the planner picks a tool — the documents below should be read one at a time.

To understand…Read
the planner that picks the right executoragent_runtime
the fence in detail (forbidden paths, exceptions, «sort the photos»)sandbox
how the Synt composes new executorssynt
how an external skill is imported as an executorskill importer
the check that precedes the execution of risky actionsvaglio
how the user sees and approves actionsapproval_ux
the memory that executions leave behindmnest and mnestome
the dialogue with the world (Telegram, web, voice)channel
observability (what happened, why, when)observability

For design decisions (why this choice and not another) there is a registry in /opt/metnos/decisions/. Examples: why generated executors live in a different folder from handwritten ones; how to ask for a password without ending up with it in plain text in the logs; how to index thirty thousand photos without blocking everything else.

The advice. Do not read these documents in order. Start from the concrete question that interests you, follow the links, and stop when you have understood what you needed. The corpus is built for browsing, not for linear reading.

Metnos — executor, didactic introduction