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 (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
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. 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.

Historical on-disk locations this workspace lived at before. You don’t write this field by handpa 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 (display-only in v1.x).
  • 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"

"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 (eventually) a TUI home screen selector.

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.