Skip to content

Schema reference

Three TOML file types, all kebab-case on the wire. See DESIGN.md §2 for the full three-tier merge semantics.

The prefix before .portagenty.toml is required — portagenty.toml by itself is the per-project file, not a workspace file.

# Required
name = "Agentic stuff"
# Optional — stable identity across folder moves and environments.
# Auto-generated by pa init; survives git clone. See DESIGN §11.
id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# Optional — overrides the global default multiplexer
multiplexer = "tmux" # or "zellij"
# Optional — projects this workspace covers. Paths accept ~ and ${VAR}.
projects = ["~/code/portagenty", "./cyberbase"]
# Optional — tags for organizing the picker (filter / grouping).
# Committable; set them in-TUI with `t` or here by hand.
tags = ["rust", "agentic"]
# Optional (auto-maintained) — historical locations the workspace
# lived at before it was moved. pa auto-appends on walk-up
# re-registration; external tools (portaconv) read this to bridge
# state authored at the old path.
previous_paths = ["/home/cybersader/code/old-location"]
# Optional — session list. Name collisions resolve workspace-wins.
[[session]]
name = "claude" # required
cwd = "~/code/portagenty" # required, supports ~, ${VAR}, relative
command = "claude" # required
kind = "claude-code" # optional; see below
description = "main coding agent" # optional; shown dimmed in the TUI
env = { ANTHROPIC_LOG = "debug" } # optional; per-session env vars
[[session]]
name = "tests"
cwd = "."
command = "cargo nextest run"

Display name. Also the sanitized base for the mpx session name ([^a-zA-Z0-9_-] → _, clamp 50 chars).

Stable workspace identity (UUIDv4). Auto-generated by pa init and the onboarding wizard. Optional — old workspace files without one continue to work and retain ordinary Enter. Supervision-first Enter applies only to idle rows with a valid ID. S can explicitly confirm-add an ID to a writable legacy workspace while preserving comments and session blocks; an existing malformed ID never receives supervised Enter and is never silently replaced. The ID is designed to be committed alongside the file so external tools (like claudecode-project-sync) can track a workspace across folder moves and cross-environment access.

"tmux" or "zellij". "wezterm" parses but fails at launch time with a clear “use tmux or zellij” message; see roadmap rationale.

Paths to project roots. Resolved against the workspace file’s directory for relative entries; ~ expands to $HOME; ${VAR} expands to the env var.

Free-form strings for organizing the workspace picker. Committable — they travel with the file. Drives the picker’s #chips, the / filter, and the f tag-filter. Set them in-TUI (t on a picker row) or here by hand. At load time a workspace also inherits the tags of any of its projects that are registered with tags in the global [[project]] table (own tags first, then project tags, de-duplicated).

Historical on-disk locations this workspace lived at before. You don’t write this field by hand — pa auto-appends to it when walk-up discovery finds the workspace’s stable id previously registered at a different path (i.e. you moved the folder). The entries are parent directories, not workspace file paths, because external tools (portaconv) key conversation histories by the process cwd, not by the TOML file.

The field uses snake_case on the wire — a deliberate break with portagenty’s otherwise-kebab-case convention, so the contract with portaconv (and any other downstream tool reading this field) stays stable and agent-friendly. Safe to commit alongside id and projects.

Array-of-tables. Order is preserved and drives the default TUI selection order + pa claim’s default session.

  • name (required) — unique within the workspace.
  • cwd (required) — absolute, ~-prefixed, ${VAR}-templated, or relative to the workspace file’s directory.
  • command (required) — the shell command. Runs as-is under a shell, so pipes/redirections work. Caveat: multiplexers spawn commands under a non-interactive shell, which means ~/.bashrc / ~/.zshrc aliases and functions are NOT loaded. A command = "ccry" that resolves to claude --continue in your interactive shell will fail in a pa session (“command not found” → session immediately exits). Write the literal command ("claude --continue") or promote your alias to a real binary on PATH (see the session commands section below).
  • kind (optional) — one of claude-code, opencode, editor, dev-server, shell, other. Drives the TUI’s per-row glyph and documented kind-specific launch behavior. Exactly claude-code selects kind-aware resume plus the supervised Claude slice/default resource policy. Session names and command strings are never inspected to infer that policy; absent and other kinds remain generic.
  • description (optional) — a human-readable note about what the session is for. Display-only — never affects launch. Shown dimmed in the TUI: in the detail column on wide terminals (in place of the raw command) and on the indented second line in narrow / Termux mode. Set it with pa add --description "…" or pa edit <name> --description "…" (empty string clears it).
  • env (optional) — string-to-string map. Applied via tmux -e KEY=VAL (tmux) or env KEY=VAL ... bash -c "<cmd>" in the generated layout (zellij). Iteration order is alphabetical for deterministic diffs.

Per-project file (portagenty.toml at a project root)

Section titled “Per-project file (portagenty.toml at a project root)”

A lighter-weight file a project can ship to advertise its own sessions. Merged into any workspace that references the project via projects = [...].

[[session]]
name = "tests"
cwd = "."
command = "cargo nextest run"
# kind + env work here too, same as workspace sessions.

No top-level fields yet — the project’s identity is implicit from the file’s location. Per-project sessions lose to workspace-level sessions on name collision.

Global config ($XDG_CONFIG_HOME/portagenty/config.toml)

Section titled “Global config ($XDG_CONFIG_HOME/portagenty/config.toml)”

Machine-local. Not committed. Points at workspaces you open often and sets your default multiplexer.

default-multiplexer = "tmux"
[[project]] # optional
path = "~/code/portagenty"
tags = ["rust", "agentic"]
[[workspace]] # optional
path = "~/workspaces/agentic.portagenty.toml"
id = "a1b2c3d4-..." # optional, auto-mirrored
archived = true # optional, set by `a` in the picker

"tmux" or "zellij". Fallback when a workspace file doesn’t pin one. Defaults to "tmux" if unset.

Global project registry (optional). tags is recognized by the loader but the filter view that uses it is still on the roadmap.

Known workspace files — populates the TUI picker home screen.

  • path (required) — absolute path to the *.portagenty.toml.
  • id (optional) — mirror of the workspace file’s id, auto-written on registration. Lets pa detect folder moves even after the old file is gone (drives previous_paths).
  • archived (optional) — when true, the workspace is hidden from the picker’s default list and only shown in the archived view (A to toggle, a to archive/unarchive). Purely a per-machine display preference; omitted from the wire when false.

State file ($XDG_STATE_HOME/portagenty/state.toml)

Section titled “State file ($XDG_STATE_HOME/portagenty/state.toml)”

Written by pa on every launch. Machine-local, not committed. Not usually something you edit by hand; schema documented here for completeness.

[[recent]]
workspace-file = "/home/u/code/my.portagenty.toml"
session-name = "claude"
launched-at-unix = 1700000000

Bounded to 50 entries (most-recent first). Dedupes on (workspace, session) so the same session hitting the top twice doesn’t stack.

Session commands: aliases vs real binaries

Section titled “Session commands: aliases vs real binaries”

A session’s command = "..." is executed under a non-interactive shell spawned by the multiplexer (bash -c "<cmd>" under tmux, a similar path under zellij). Non-interactive shells do NOT source ~/.bashrc / ~/.zshrc — so shell aliases and shell functions defined there are invisible to pa sessions.

If you have alias ccry='claude --continue' in ~/.bashrc and write:

[[session]]
name = "claude"
command = "ccry" # ← WRONG — non-interactive shell can't find this
kind = "claude-code"

…the session will launch, the shell will error with ccry: command not found, the process will exit immediately, and the multiplexer will close the pane. From the user’s perspective: a flash of something, then they’re back in their parent shell.

1. Write the literal command in the TOML.

command = "claude --continue"

Simplest. Matches what ccry expands to. Downside: on a project with no prior Claude conversation, claude --continue errors on first launch. Works best for projects that have already been “seeded” with a session.

2. Use command = "claude" + pa’s --resume flag.

command = "claude"
kind = "claude-code"

Then launch with pa launch claude --resume (or the bundled alias plr claude via pa snippets install pa-aliases). pa’s kind-aware --resume transform appends --continue only at invocation time, so fresh launches work and continued launches work — one TOML handles both. Takeover-claim version: pa claim claude --resume (or pcr claude).

3. Promote the alias to a real binary on PATH.

mkdir -p ~/.local/bin
cat > ~/.local/bin/ccry <<'EOF2'
#!/usr/bin/env bash
exec claude --continue "$@"
EOF2
chmod +x ~/.local/bin/ccry

Now ccry is a real executable — interactive shells, pa sessions, cron, any process on PATH can run it. You can keep the shell alias too or delete it (the binary replaces the alias for interactive use once PATH prefers ~/.local/bin). Generally the cleanest approach for shortcuts you want available everywhere.

Tempting, but risky: sourcing user rc files at session spawn imports env vars (PATH mutations, custom PS1, etc.) that can fight the multiplexer’s own conventions, shadow the session’s declared env, or break reproducibility across devices. pa’s position: the workspace TOML is the single source of truth for what a session does; don’t let a user’s interactive rc file silently influence committed workspace behavior.