Connection lost
Trying to reconnect…
Server didn't respond
Recovering…
Docs / Authoring packs
Author your own pack
A pack is a directory of YAML you write and maintain yourself — no SDK, no compiler, no signing keys, nothing uploaded to anyone. Drop it on the runners you control, trust it once, and your model can call it. Most packs stay private to your fleet, and that's by design: the public registry is curated, not a free-for-all (more on that below).
-
1 Lay out the pack directory
A pack is just a folder: a top-level
pack.yamlmanifest and one YAML per action inactions/. Optionalscripts/holds packaged shell scripts forkind: scriptactions. Keep the whole thing in your own git repo.my-pack/ pack.yaml # pack manifest (see step 2) actions/ do_thing.yaml # one declared action per file do_other_thing.yaml scripts/ # only when you have script actions do_thing.sh -
2 Write pack.yaml
The manifest declares pack-level metadata and which action YAMLs to load.
idis the pack slug (used in the install path and, for self-maintained packs, however you reference it internally).schema_version: 1 id: my-pack name: My ops pack version: 0.1.0 description: Short one-line summary shown on the runner + dashboard. vendor: acme homepage: https://github.acme.internal/sre/my-pack # your repo; optional requires: os: [linux] binaries: [my-cli] # optional — advertised to cloud; a missing one fails at run time actions: - actions/do_thing.yaml - actions/do_other_thing.yaml
-
3 Declare each action
One YAML per action — the runner refuses anything not in the catalog, so this file is the contract between your ops team and the LLM. This is where the safety lives: tight argument validation, an honest
side_effectslist, and the rightrisktier. See the full YAML schema reference for every field.schema_version: 1 id: my.do_thing title: Do the thing kind: exec risk: low description: > One-paragraph human description. Shown verbatim to the LLM — write it like a doc string, not a comment. side_effects: - Reads /var/log/myapp/*. - Touches nothing. args: - name: file type: path required: true validation: allowed_prefixes: ["/var/log/myapp/"] execution: command: binary: cat # bare name — resolved via PATH on the host argv: ["{{ args.file }}"] timeout: 10s output: parser: text max_stdout_bytes: 65536 # Optional. Renders on the dispatch form so operators (and LLMs) # see a real invocation before they fill in args. examples: - title: Tail myapp's error log args: file: /var/log/myapp/error.log -
4 Validate, install, and trust it
Validate locally first — the same checks the runner runs at load — then install the directory into the runner's packs dir and reload:
# on the runner host emisar pack validate ./my-pack sudo emisar pack install ./my-pack --dest /etc/emisar/packs sudo systemctl reload emisar
Because your pack isn't one emisar publishes, it has no baseline hash — so it lands in the dashboard's Packs page as pending, with dispatch held until you approve it. Open Packs, review the content hash, and click Trust. From then on, that exact byte-for-byte version is the only one authorized: edit the pack and its hash changes, which re-marks it pending until you re-trust. That's your drift guard — trust in bytes you reviewed, not a publisher you hope is honest.
Confirm it end-to-end: open the runner in the dashboard, check your actions under Advertised actions, and Run one.
-
5 Roll it out and maintain it
To put the pack on another runner, install it pinned to the hash you trusted, so every host runs identical bytes — or let your config management (Ansible, Chef, a base image) drop the directory and reload:
sudo emisar pack install ./my-pack --hash sha256:<the hash you trusted> --dest /etc/emisar/packs sudo systemctl reload emisar
When you change the pack, bump its
version, re-validate, re-deploy, and re-trust the new hash once in the dashboard — the same review you'd give any production change. The pack is yours: its lifecycle, its hosts, its risk tiers, on your schedule.
Keep it yours — or propose it to the registry
The public registry behind the Packs page is curated by us, deliberately. We only take packs that are genuinely generic and broadly useful — Postgres, Cassandra, Docker, Linux core, the AWS suite — the kind of thing many teams run unchanged. We don't accept every submission, because an open dumping ground of one-off, environment-specific packs would be a mess: hard to trust, hard to maintain, and low signal for everyone browsing it.
Most packs should stay yours. A pack that hardcodes your service names, your hostnames, or your internal CLIs is more useful private — and you keep control of its lifecycle. Self-maintained is the default here, not a fallback.
If your pack really is generic — useful to teams who've never heard of yours, with no
environment-specific assumptions — we'd love to ship it. Open a PR against the
repo
adding it under
runner/examples/packs/
plus an entry in apps/emisar_web/lib/emisar_web/packs_registry.ex, or email
support@emisar.dev
first if you're not sure it clears the bar. We review for:
- Genuinely generic — no hardcoded hosts, service names, or org-specific paths. If it only makes sense inside your company, it belongs in your repo, not ours.
-
Tight argument validation
— no free-form shell strings; paths use
allowed_prefixes, choice args usevalidation.enum, and numbers use min/max. -
Honest
side_effects— every file, network, and process side effect listed. The LLM reads these. -
Correct
risklevel — destructive actions (restart, delete, repair) arehighorcritical; reads arelow. -
Resolvable binaries
— declare what the runner needs on the host under
requires.binaries.