Getting started
This page assumes you’ve already installed Seal and exported a CLAUDE_CODE_OAUTH_TOKEN (the least-friction setup — see Install: Set up an LLM provider or Provider backends for other options).
1. Scaffold a manifest
Section titled “1. Scaffold a manifest”cd ~/code/my-projectseal embarkseal embark is the first-run wizard. It confirms a credential is set, walks you through provider selection, and stamps a starter seal.toml into the project root. The starter is intentionally minimal — it grants read access to common source-file types, a handful of safe build / git commands, and the default Anthropic provider.
For subsequent projects (or to re-scaffold a manifest in a project where credentials are already configured), seal init does the same scaffold without the credential walk-through.
Open seal.toml and look at it. The shape is:
schema_version = 1
[model]provider = "anthropic"name = "claude-sonnet-4-6"
[capabilities.allow.read]default_files = ["*.rs", "*.toml", "*.md"]paths = [ "src/**", "tests/**", "crates/**", { path = ".", files = ["Cargo.toml", "Cargo.lock", "README.md"] },]
[capabilities.allow.write]default_files = ["*.rs"]paths = ["src/**", "tests/**", "crates/**"]
[capabilities.allow.commands]patterns = [ "cargo test:*", "cargo check:*", "cargo fmt:*", "cargo clippy:*",]
[capabilities.deny.read]default_files = ["*.key", "*.pem", "*.seed", ".env", ".env.*"]
[sandbox.os]command_fs = "system"command_tools = ["cargo"]The exact set of grants depends on the project type embark detected (Rust, Node, Go, Python, generic) — Rust shown above. Either way the starter is intentionally narrow: a few source-file types under the standard project paths, a small allowlist of read-only build commands, and an explicit deny on credential-ish files. Everything else surfaces as a prompt the first time the agent tries it.
2. Start a session
Section titled “2. Start a session”sealThis launches the TUI. The first time it runs in a project you’ll see a “manifest approval” prompt — Seal hashes the on-disk seal.toml and asks you to confirm. Press y to accept; the session starts.
If you’ve already approved the same seal.toml content before (the hash matches the last-signed value), the approval prompt is skipped and the session opens directly into the composer.
3. Your first prompt
Section titled “3. Your first prompt”Type into the composer at the bottom of the screen. A starter prompt to verify everything works:
Can you read README.md and tell me what this project does?What you’ll see:
- The agent calls
file_readagainstREADME.md. That matches the read grants — no prompt fires. - The agent issues a response based on the file content.
A more interesting prompt:
Add a TODO comment at the top of src/main.rs reminding me to add tests.- The agent calls
file_readagainstsrc/main.rs(read grant matches, silent). - The agent calls
file_edit— which writes — againstsrc/main.rs. The starter manifest’s write grant covers**/*.rs, so no prompt. - The edit lands; the diff renders inline.
4. Your first permission prompt
Section titled “4. Your first permission prompt”Now try something the starter doesn’t grant:
Run `cargo nextest run` and show me which tests are failing.- The agent calls
command_runwithcargo nextest run. - That pattern isn’t in
[capabilities.allow.commands.patterns]. - A permission prompt appears with the suggested pattern
cargo nextest run:*at the top and four choices:Allow/Allow always/Deny/Deny always.
The suggested pattern matters as much as the choice. The arrow keys walk it across scale levels:
cargo:* cargo nextest:* cargo nextest run:* cargo nextest run- Right narrows: more literal tokens, fewer matches.
- Left broadens: fewer literal tokens, more matches.
- Tab snaps back to the daemon’s recommended level.
Two distinct moves, picked deliberately:
- Narrow +
Allow always— for this specific call, pick the level you trust (cargo nextest run:*orcargo nextest:*). That lands a silent-allow pattern for the chosen scope; future matching invocations run without asking. - Broad +
Allow— broaden the pattern (cargo:*) and pickAllow(notAllow always). That writes the broad pattern asprompt = true— the call runs once now, and every future cargo subcommand prompts again at its own specific level, where you can narrow +Allow alwayson the subcommands you decide to silently allow.
The second move sets up the layered shape the next section covers: one broad prompt = true and several narrow silent allows on top. Don’t broaden + Allow always — that hands the whole tool family a silent pass, including subcommands you’ve never seen. cargo nextest:* is fine to silently allow; cargo:* covers cargo install and cargo publish too and is too wide for an unattended grant.
See Permission model for the full four-way semantics and Specificity scores for exactly how the matcher picks between layered patterns.
5. Layer a guardrail
Section titled “5. Layer a guardrail”After a few sessions of the step-4 flow, [capabilities.allow.commands] ends up shaped like one broad prompt = true per tool family with narrow silent allows on top:
[capabilities.allow.commands]patterns = [ { command = "git:*", prompt = true }, # ask before any git operation by default "git status", # except these read-only ones "git diff:*", "git log:*", "git branch:*",]Most-specific-wins matching handles the layering: git status matches both entries, the narrower one wins, the call runs silently. git reset --hard HEAD~3 only matches the broad prompt, so it prompts — same for git push --force, git clean -fdx, and anything else under git:* that you haven’t pre-cleared.
You can also anticipate this shape instead of discovering it call-by-call. Open seal.toml and write the broad prompt = true plus the narrow silent allows you already know are safe. The first session after the edit re-validates the manifest signature, you re-sign on the approval modal, and the layered shape is live from the next call. The /reload slash command does the same in-session without a relaunch.
The seal project’s own seal.toml — github.com/sealedsecurity/seal/blob/main/seal.toml — uses this pattern across git, cargo, gh, just, and jj. Read it for a real-world layered manifest after a few dozen sessions.
6. Read and write grants
Section titled “6. Read and write grants”Filesystem grants work differently from command grants — they’re a two-list combination, not a single pattern. Worth understanding because the prompt UX is shaped by it.
default_files × paths
Section titled “default_files × paths”[capabilities.allow.read] (and .write, .delete) takes a default_files glob list and a paths list. The two compose: each bare-string paths entry inherits default_files, producing the cartesian product. From the starter above:
[capabilities.allow.read]default_files = ["*.rs", "*.toml", "*.md"]paths = ["src/**", "tests/**", "crates/**"]This grants reads of src/**/*.rs, src/**/*.toml, src/**/*.md, tests/**/*.rs, etc. — every combination. To grant a path with a different file-type list, use the table form:
[capabilities.allow.read]default_files = ["*.rs", "*.toml", "*.md"]paths = [ "src/**", # inherits default_files { path = ".", files = ["Cargo.toml", "Cargo.lock"] }, # overrides default_files]The table form’s files list replaces default_files for that entry; it doesn’t extend it.
Globs use a standard set of operators:
*— anything except a path separator (matchesfoo.rs, notdir/foo.rs).**— anything including path separators (src/**matchessrc/foo.rsandsrc/a/b/c.rs).?— single character.{a,b}— alternation.[abc]— character class.
The same engine powers default_files, paths, the deny side, env-var passthroughs, and command-pattern colons — so the syntax is consistent across the manifest.
Deny precedence
Section titled “Deny precedence”[capabilities.deny.*] always wins. A bare default_files under a deny section means “these file types anywhere in the project” — the daemon injects an implicit ** path:
[capabilities.deny.read]default_files = ["*.key", "*.pem", ".env", ".env.*"]This blocks .env at the project root, app/.env.local, ~/.ssh/id_rsa (if ~/.ssh were otherwise readable), and so on. There’s no allow grant that can override a matching deny.
additional_directories
Section titled “additional_directories”To grant access to paths outside the project root — a sibling repo, a temp dir, ~/.config/<tool> — list them under [capabilities.allow] additional_directories:
[capabilities.allow]additional_directories = ["~/code/shared-lib", "/tmp/seal-scratch"]Two important things:
-
additional_directoriesdoes not grant any reads or writes on its own. It just expands the universe of paths yourpathsentries are allowed to reference. To actually grant access, a[capabilities.allow.read]or.writeentry has to name a path inside one of the listed directories. -
No automatic glob expansion. Listing
~/code/shared-libdoes not implicitly allow~/code/shared-lib/**. You still write the explicitpathsentry:[capabilities.allow]additional_directories = ["~/code/shared-lib"][capabilities.allow.read]default_files = ["*.rs"]paths = ["src/**", "~/code/shared-lib/src/**"]
additional_directories also widens the cwd allowlist for unscoped command grants — a bare "cargo build:*" matches when run inside any listed directory, not just inside project root.
Manifest-level reference: capabilities.allow. Real-world example: the seal project’s own seal.toml shows the layered grants, default_files × paths shape, and additional_directories usage on a non-trivial project.
- Permission model — what the four prompt options actually do.
- Manifest reference — every section of
seal.toml. - Sandbox — what the OS sandbox does that the manifest check doesn’t.
- Troubleshooting — common errors and fixes.