Docs navigation
Get started
AI agents
Connect
Operate
Day to day
When it breaks
Govern access
Team & account
Access
Identity concepts
Provider guides
Account
Author your own pack
A pack is a directory of YAML you write and maintain yourself — there is nothing to compile or upload. Put it on the runners you control, trust it once, and your model can call it. Most packs stay private to your fleet; the public registry is curated (more below).
$ emisar pack validate ./my-pack pack my-pack OK: 2 actions hash: sha256:e8edc3660cb2cf7a8f3fe0d9cea167d07565b8287f57bd089b4dd737eb0e3844 $ sudo emisar pack install ./my-pack installed my-pack → /etc/emisar/packs/my-pack my-pack — My ops pack (v0.1.0) Short one-line summary shown on the runner + console. Actions: 2 (2 low) Requires: linux · my-cli ✓ Hash: sha256:e8edc3660cb2cf7a8f3fe0d9cea167d07565b8287f57bd089b4dd737eb0e3844 Docs: https://github.acme.internal/sre/my-pack … Verify ok my.do_other_thing 5ms Reloaded the runner — it re-reads packs and re-advertises to the control plane.
Use your coding agent
The author-pack
skill walks your agent through writing, validating, testing, and trusting a pack of your own.
Write an emisar action pack for the tool I name, validate and test it, and get it trusted on my runner. Use the author-pack skill: https://raw.githubusercontent.com/AndrewDryga/emisar/main/skills/author-pack/SKILL.md
Works in Claude Code, Codex, or any agent that reads Markdown skills — read the skill first .
1. Lay out the pack directory#
A pack is a folder with a top-level pack.yaml
manifest and one YAML file per action in actions/. An optional
scripts/
directory holds packaged shell scripts for kind: script
actions. Keep the pack in your own Git repository.
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 metadata and the action YAML files to load. id
is the pack slug. It appears in the install path and your internal
references.
schema_version: 1
id: my-pack
name: My ops pack
version: 0.1.0
description: Short one-line summary shown on the runner + console.
vendor: acme
homepage: https://github.acme.internal/sre/my-pack # your repo; optional
requires:
os: [linux]
binaries: [journalctl] # optional — a missing one fails at run time
setup: # optional — powers pack info, verify, and the install probe
summary: >
Reads the local system journal.
host_access:
- actions: [my.journal_tail]
requirement: Read the system journal.
recipes:
- name: systemd Linux — default emisar service user
commands:
- sudo usermod -aG systemd-journal emisar
- sudo systemctl restart emisar
verify:
- sudo -u emisar journalctl -n 1 --no-pager
impact: The emisar service identity can read the complete system journal.
verify: my.journal_tail # a no-argument, low-risk connectivity check
actions:
- actions/journal_tail.yaml
The setup
block is what emisar pack info
prints and what pack verify
and the install probe run. Declare the env your tool reads, map each protected host
resource to the exact actions that need it, and name one low-risk read as verify. Host-access recipes are instructions for the operator; Emisar
displays and copies their commands but never runs them.
3. Declare each action#
Create one YAML file for each action. The runner refuses anything outside the catalog.
Each file defines the contract between your operations team and the LLM. Safety depends
on tight argument validation, an accurate side_effects
list, and the correct risk
tier. See the
Pack reference
for all action fields.
schema_version: 1
id: my.journal_tail
title: Read recent system journal entries
kind: exec
risk: low
description: >
Show the most recent system journal entries.
side_effects:
- Reads the system journal.
- Touches nothing.
args:
- name: lines
type: integer
default: 100
validation:
min: 1
max: 1000
execution:
command:
binary: journalctl # bare name — resolved via PATH on the host
argv: ["--no-pager", "-n", "{{ args.lines }}"]
timeout: 10s
output:
parser: text
max_stdout_bytes: 65536
max_stderr_bytes: 8192
# Optional. Renders on the dispatch form so operators (and LLMs)
# see a real invocation before they fill in args.
examples:
- title: Read the last 100 entries
args:
lines: 100
4. Validate, install, and trust it#
Validate the pack locally — it runs the same checks as the runner — then install
it. The install writes to the configured pack directory, reloads the runner
without a restart or dropped runs, and ends by probing your setup.verify
action, as the output at the top of this page shows:
# on the runner host emisar pack validate ./my-pack sudo emisar pack install ./my-pack
You can validate anywhere: pack validate
reads only the directory you point it at, and it does not need a config, a service,
or an account. On a laptop — Linux or macOS — or in CI, install just the binary:
# binary-only install — skips the service, needs no enrollment key $ curl -fsSL https://emisar.dev/install.sh | sudo bash -s -- --no-service $ emisar pack validate ./my-pack # in CI, pin the hash you will trust $ emisar pack validate ./my-pack --json | jq -r .hash
Your pack is not in the published catalog, so it is not trusted automatically: it shows as pending on the console's Packs page, and dispatch for it is refused until you review the content hash there and click Trust. Its actions are then callable, gated by your policy, and that exact version is the only one authorized. Edit the pack and its hash changes, re-marking it pending until you re-trust — so what runs is always the files you reviewed.
Then test it end to end: open the runner from Runners, 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 — every host then runs the exact same files, and the install reloads the runner for you. Config management — Ansible, Chef, a base image — can drop the directory itself instead, then reload the runner.
sudo emisar pack install ./my-pack --hash sha256:<the hash you trusted>
When you change the pack, increase its version. Validate and install it
again. Add --force
to replace an installed pack. Trust the new hash once in Packs. Give it the same review as any production change.
Distributing many packs to a large fleet? Skip the copying — host your own registry.
Keep it private, or propose it#
We deliberately curate the public registry behind the Packs page . We accept only generic packs that many teams can use unchanged, such as Postgres, Cassandra, Docker, Linux core, and AWS packs. We reject one-off, environment-specific packs: they are hard to trust and maintain, and they bury the packs everyone can use.
Most packs must stay yours. A pack that hardcodes your service names, your hostnames, or your internal CLIs is more useful as a private pack. You also keep control of its lifecycle. Self-maintained is the default.
A private pack can still serve a whole fleet —
packctl
builds a registry you host yourself, and your packs never enter ours.
Host your own registry
walks it end to end.
If your pack is useful to many teams and has no environment-specific assumptions, publish it in its own repository and open an issue suggesting we include it. We review for:
- Genuinely generic. Do not hardcode hosts, service names, or organization-specific paths.
-
Tight argument validation.
Reject free-form shell strings. Use
allowed_prefixesfor paths,validation.enumfor choices, and min/max for numbers. -
Honest
side_effects. List every file, network, and process side effect. The LLM reads this list. -
Correct
risklevel. Assignhighorcriticalto destructive actions. Assignlowto reads. -
Resolvable binaries.
Declare required host binaries under
requires.binaries.