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: 1 actions hash: sha256:daafe7a34b537dd4728c7a9bfa645c998265df5a93a2b1e11e114cb05bd744ba $ 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: 1 (1 medium) Requires: linux · journalctl ✓ Hash: sha256:daafe7a34b537dd4728c7a9bfa645c998265df5a93a2b1e11e114cb05bd744ba Docs: https://github.acme.internal/sre/my-pack … Verify skipped declares no verify action Signaled the runner to reload packs and re-advertise 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/
journal_tail.yaml # one declared action per file
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-pack.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.
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 environment variables your tool reads, and map each protected host
resource to the exact actions that need it. If the pack has a low-risk read with
usable defaults, you can name it as verify. This journal reader is
medium risk, so the example leaves that optional probe unset.
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-pack.journal_tail
title: Read recent system journal entries
kind: exec
risk: medium
description: >
Show the most recent system journal entries. Raw application messages can
contain personal or sensitive data that pattern redaction cannot fully detect.
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 with the same checks the runner uses, then install it.
The install writes to the configured pack directory and signals a running
daemon to reload without restarting or dropping runs. If you declared a
setup.verify
action, the install probes that action; otherwise it reports
the probe as skipped, as this example 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 Linux or macOS laptop, 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 emisar refuses dispatch for it 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. Editing the pack changes its hash and marks it pending again until you re-trust it, 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 signals a running daemon to reload. Config management, such as Ansible, Chef, or a base image, can place the directory itself instead and 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.
To distribute many packs to a large fleet without copying them, 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. Judge the action's real effect and output. Structured status and metadata can below; raw logs and application text are at leastmedium. Destructive changes and arbitrary secret-bearing dumps needhighorcritical. -
Resolvable binaries.
Declare required host binaries under
requires.binaries.