Concepts
The full architectural deep-dive lives in DESIGN.md in the repo. This page is the short version — one-line definitions for everything you’ll see in the TUI and the TOML.
● Live ○ Idle ? Untracked
pa drives it, never replaces itProject
Section titled “Project”A directory on disk with code or content you work on. Registered
with portagenty at any of three tiers (global, workspace, or
per-project portagenty.toml). Identified by its filesystem path.
Workspace
Section titled “Workspace”A named, curated view over one or more projects plus the sessions
you use to work on them. First-class file on disk
(*.portagenty.toml), designed to be committable. A workspace is
where “hierarchy on top of hierarchy” happens — same underlying
projects, many possible views (recency, tags, custom groups — the
latter two still on the roadmap).
Session
Section titled “Session”One unit of execution: a shell, a process, an agent. Core schema is
name + cwd + command, plus optional kind (display hint) and
env (key-value env vars). A session belongs to a workspace.
Session state (live / idle / untracked)
Section titled “Session state (live / idle / untracked)”Shown in the TUI as colored markers next to each row:
| Marker | State | Source | Enter does |
|---|---|---|---|
| ● (green) | Live | Workspace session, currently running in mpx | attach |
| ○ (dim) | Idle | Workspace session, not yet started | create_and_attach |
| ? (yellow) | Untracked | Running in mpx, not in workspace TOML | attach |
Untracked = the tmux/zellij session you started manually last week
that pa can see via list-sessions and let you re-attach to.
Sessions you started manually outside pa.
Visible via list-sessions.
Kind hint
Section titled “Kind hint”Optional kind: field on a session: claude-code, opencode,
editor, dev-server, shell, or other. The TUI shows a
one-letter colored glyph (C / O / E / D) next to the state marker.
For kind = "claude-code", pa launch --resume and
pa claim --resume append --continue before launch so Claude
picks up its prior conversation. Other kinds get a one-line hint.
Multiplexer / adapter
Section titled “Multiplexer / adapter”tmux or zellij. The thing that owns the terminal panes and keeps
them alive across detaches. portagenty drives it via its CLI — it
doesn’t replace it. Each adapter (TmuxAdapter, ZellijAdapter) is
a Rust implementation of the Multiplexer trait.
Attach mode (takeover / shared / fresh)
Section titled “Attach mode (takeover / shared / fresh)”Three shapes a pa attach can take:
- Takeover (default on tmux): detach any other clients on attach. Session keeps running; the other device’s client returns to its shell. Fixes the “screen size stuck to smaller client” multi-client issue.
- Shared (
--shared): attach without disturbing other clients. Pass--sharedtopa launch. - Fresh (
--fresh): kill any existing session with this name before launching, then create anew. Loses running state. Main use case: zellij takeover (see below).
pa claim is a short verb for takeover-attach; it defaults the
session name to the first one in the workspace and passes through
--fresh / --resume.
The tmux / zellij takeover split
Section titled “The tmux / zellij takeover split”The default takeover semantics work cleanly on tmux — the mpx
natively supports detach-client -a (kick others). On zellij, it’s
a different story:
| Default | --shared | --fresh | From inside (paclaim) | |
|---|---|---|---|---|
| tmux | real takeover, keeps state | explicit shared | overkill — wipes state | detach-client -a — instant |
| zellij | de-facto shared (no kick) | same as default | the only real takeover | prints limitation + --restart to kill |
Zellij doesn’t expose per-client disconnection in any form (checked
against 0.43.1 — no action disconnect-client, no equivalent). So
“takeover” on zellij means “kill the session + recreate” — you lose
running state but the other client drops because the session it was
attached to is gone.
If you need hard takeover without losing state, either use tmux for
that workspace (press m on the row in the session list to switch)
or accept shared clients (zellij is designed for them).
Three-tier config merge
Section titled “Three-tier config merge”Sessions + project registrations can be declared at:
- Global —
$XDG_CONFIG_HOME/portagenty/config.toml. Machine-local, not committed. - Workspace — any
*.portagenty.toml. Meant to commit. - Per-project —
portagenty.tomlat a project root. Meant to commit.
Merge rule on session-name collision: workspace > per-project > global. Closer to the user’s current intent wins.
State store
Section titled “State store”$XDG_STATE_HOME/portagenty/state.toml. Records a bounded LRU of
recent launches. Machine-local, not committed. Feeds the picker’s
recency sort and the session list’s “LAST” column.
Workspace picker (home screen)
Section titled “Workspace picker (home screen)”When you run bare pa from a directory with no walkable
*.portagenty.toml, pa shows a picker: a ratatui home screen
listing every workspace registered in your global config, sorted by
recency (most-recent on top, never-launched alphabetical below). A
bottom “live sessions on this machine” row gives you an ad-hoc browse
mode — attach to any live tmux/zellij session without authoring TOML.
Auto-registration: pa init and the onboarding wizard both append
the new workspace to [[workspace]] in the global config, so future
pa invocations see it from anywhere.
Navigation follows Android-back semantics:
- Esc from the session TUI → always returns to the picker.
- Esc / q from the picker → exit
pa. - q / Ctrl+C from anywhere → exit
padirectly.
The picker is also a “jump to another workspace” affordance for walk-up users: enter via walk-up, press Esc once, and you’re on the home screen with every other registered workspace one keypress away.
Visual differentiation in the TUI
Section titled “Visual differentiation in the TUI”The explorer encodes state with color, not just glyphs:
- Title bar shows a colored mpx badge — cyan
[tmux], magenta[zellij]— plus session count and an untracked-count badge in yellow when live sessions exist outside your workspace definition. - Session rows color the name itself, not just the marker:
green for
Live, dim forNotStarted, yellow forUntracked. - Kind glyphs get per-kind colors (blue
Cfor claude-code, cyanOfor opencode, magentaEfor editor, greenDfor dev-server). - Attached-client count on tmux live rows:
[live · 2 clients]/[live · 1 client]/[live · detached]. Zellij doesn’t expose per-session client counts, so those stay[live]. - Recency shows twice: the picker lists “X ago” per workspace,
the session list adds a
LASTcolumn on live rows at widths ≥ 80 cols.
Narrow-terminal layout
Section titled “Narrow-terminal layout”At widths below 60 columns, each session row renders as a two-line
card — marker + name + status on line 1, indented dim cmd · path
on line 2 — so the essentials stay readable on a phone keyboard in
portrait. The footer’s keybind hints shorten to fit
(Esc: back · q: quit at the narrowest). See
Termux for the full mobile story.
Session-name namespacing
Section titled “Session-name namespacing”Multiplexer session names are workspace-scoped: a session "shell"
in workspace "my-project" becomes my-project-shell in the mpx.
This prevents the collision where two workspaces both defining a
"shell" session would silently share the same tmux/zellij session.
The TUI display name stays unprefixed — you see shell, the mpx
sees my-project-shell. The mapping is handled by
workspace_session_name() in the sanitize module.
Find-folder overlay
Section titled “Find-folder overlay”Press n in the workspace picker to open a centered search overlay.
Type to fuzzy-search your filesystem for project folders. Tiered
backends fire in order:
- Recency — recent launches from
state.toml(instant). - Zoxide — frecency scores, if installed.
- plocate / locate / Everything CLI — pre-built indexes.
- fd — live walk respecting
.gitignore. - Stdlib walker — always-available fallback (depth-capped,
ignores
.git,node_modules,target, etc.).
Each tier is silently skipped when its tool isn’t installed. Results are deduped and ranked by nucleo (Helix’s pure-Rust fuzzy matcher). Enter on a candidate either opens an existing workspace there or scaffolds a new one with a confirm prompt.
dedup on canonical path → nucleo fuzzy rank → top N
Tree browser
Section titled “Tree browser”Press Ctrl+T inside the find overlay to switch to a filesystem tree
view. Directories expand on Enter (lazy read_dir, cached), and
collapse on ←/Backspace. Shift+Enter selects the highlighted
directory. A marquee breadcrumb shows the current path. Used by both
the n (new workspace) flow and the e → c (edit cwd) flow.
In-TUI session editing
Section titled “In-TUI session editing”Press e on any tracked session row to edit it without leaving the
TUI. A five-stage state machine walks you through: pick a field
(name, cwd, command, kind, env) → type a new value or pick from a
list. CWD editing opens the find/tree overlay for visual browse.
All writes are comment-preserving via toml_edit.
Auto-re-register on walk-up
Section titled “Auto-re-register on walk-up”When pa walks up from $PWD and finds a workspace file that isn’t
in the global [[workspace]] registry, it silently appends the new
path. This makes folder moves transparent: you don’t need to
manually re-register a workspace after moving its parent directory.
Agent context persistence
Section titled “Agent context persistence”Claude Code stores conversations at
~/.claude/projects/<path-encoded-cwd>/. Two scenarios break this:
- Folder moves — move a project and the encoded path changes.
--continuecan’t find old sessions. - WSL vs Windows — the same project gets different encoded paths in each environment. Context is siloed.
- Content poisoning — even when you bridge the storage dirs,
WSL-authored conversations have
/mnt/c/…paths baked into the content itself. A Windows Claude resuming that content hitsReaderrors on the first path reference.
What portagenty ships
Section titled “What portagenty ships”- Stable
idfield (UUIDv4) in every new workspace TOML. Committed, survives git clone, gives external tools a path-independent handle. previous_pathsauto-maintenance. When walk-up re-registers a workspace at a new location (i.e. the folder was moved),paappends the old directory to the TOML’sprevious_pathslist. The trail of past locations travels with the repo.pa convosshim. Forwards topconvwith the workspace’s TOML auto-injected, so lookups scope to this workspace’s projects +previous_paths.pa init --with-agent-hooksscaffolds.mcp.json+.claude/skills/so Claude Code agents self-discover portaconv when they enter the workspace.
What portaconv does
Section titled “What portaconv does”portaconv reads each agent CLI’s conversation storage
(Claude Code first) read-only and emits paste-ready markdown —
optionally with WSL↔Windows path rewriting (--rewrite wsl-to-win
/ win-to-wsl). Also ships as an MCP server (pconv mcp serve)
so any MCP-aware agent can query past conversations directly.
You don’t try to resume in place. You extract what you said, paste it into a fresh session on whatever host is in front of you, and the new session picks up the thread — with the paths already rewritten to the new OS.
# List this workspace's Claude Code sessions (scoped via pa)pa convos list
# Dump a specific session as paste-ready markdownpa convos dump <session-id>
# Same, with WSL paths rewritten to Windows before pastingpa convos dump <session-id> --rewrite wsl-to-winSee the dedicated Portaconv integration
concept page for the full move-recovery + paste-ready story, and
pa convos / pa init --with-agent-hooks command reference for
the surface.
If Claude Code adds native project-ID or path-migration support,
the id + previous_paths fields become redundant for that use
case but remain useful as general workspace-identity anchors.
Pre-launch banner
Section titled “Pre-launch banner”When you press Enter to attach, pa restores your terminal and
prints a one-line banner before the multiplexer takes over:
pa → zellij session "claude" detach: Ctrl+O then d · re-attach: pa claim claudeInformational only — pa never rebinds your mpx keys. The detach
chord shown is the multiplexer’s default; if you’ve customized it,
use your own chord. (Opinionated mpx-config belongs in a dev-
environment scaffold, not in the launcher.)