Instructions for

A workspace is the unit Shelbi dispatches issues to. It is capacity, not a role and not a process: a slot declared once in the project YAML, alive for the lifetime of the project, with an agent process cycled inside it per issue.

Each workspace is two pinned resources on one machine:

  • One Shelbi session, where the agent runs: a small detached process that owns the agent's PTY and outlives the connection, so the agent survives a dropped SSH link or a closed terminal.
  • One git worktree, its own checkout at <machine.work_dir>/.shelbi/wt/<workspace-name>. The path is fixed; not configurable.

A workspace handles one issue at a time. It does not spawn, it does not fan out, it does not multiplex. If the project declares five workspaces, you have five concurrent slots, no more, no less.

The pool lives in the sidebar, grouped by machine. Busy slots show the agent they're running; idle ones wait for work:

my-project
Orchestrator⌘O
Issues⌘I
Changelog⌘L
Workspaces
hub
alphaDeveloper⌘1
bravoDeveloper⌘2
charlieIdle
deltaIdle
devbox
echoDeveloper⌘3
foxtrotDeveloper⌘4
golfIdle
Ready for review3
Cold-start cache⌘5
you/t-011
CSV import fix⌘6
you/t-018
Nightly report⌘7
you/t-024
Queued for review2
Validate webhook payloads⌘8
you/t-023
Harden token refresh⌘9
you/t-025
Zen mode off
Filter:WorkflowsWorkspacesboard 2sSearchNew issue
BACKLOG10
Rework onboarding UX
task
Audit OSS licenses
task
Draft Q3 roadmap
task
Migrate CI to arm64
task
Sunset legacy v1 API
task
Add SSO for admins
task
Prune stale flags
task
Dedupe error reports
task
Archive S3 buckets
task
Refresh brand assets
task
TO DO9
Add API ratelimit
task
Fix mobile nav
task
Wire webhook retries
task
Split OTel spans
task
Sync i18n strings
task
Paginate search API
task
Cache user sessions
task
Add health probes
task
Debounce autosave
task
IN PROGRESS4
Deploy staging env
taskyou/t-009
Wire up OAuth flow
taskyou/t-010
Backfill order index
taskyou/t-017
Trim vendor bundle
taskyou/t-022
REVIEW5
Cold-start cache
taskyou/t-011
CSV import fix
taskyou/t-018
Nightly report
taskyou/t-024
Validate webhook payloads
taskyou/t-023
Harden token refresh
taskyou/t-025
DONE6
Migrate to PG 16
task
Ship dark mode
task
Retry dead-letters
task
Redis cache /profile
task
Add audit logging
task
Fix flaky CI tests
task
CANCELED0
h/l column k/j card ↵ open ⇧h/⇧l move ⇧k/⇧j reorder drag to move or reorder s start a assign e edit E editor n new

Workspace vs. agent: capacity vs. role

The single most important distinction on this page: a workspace is where work runs; an agent is who does it.

  • A workspace is a machine + Shelbi session + worktree. It's interchangeable capacity. bravo is just as good as alpha for any issue that fits its machine.
  • An agent is a system prompt + skill set: the developer, qa, or security role. It's loaded into a workspace when an issue is dispatched.

That split is what lets the same slot run a developer agent on one issue and a qa agent on the next, and it's why a workflow can say "review this status with the security agent" without caring which workspace is free to do it. The workspace supplies the compute; the issue's status supplies the agent.

The pool model

The pool is declared up front in the project YAML and stays fixed:

workspaces:
  - { name: alpha,   machine: hub }
  - { name: bravo,   machine: hub }
  - { name: charlie, machine: devbox }
  - { name: delta,   machine: devbox }

This is deliberate. Workspaces are not allocated on demand. They are named slots the orchestrator routes work to. That gives you:

  • Stable identities in the sidebar, the events log, and the kanban card's assigned_to field. "bravo is on the palette issue" means the same thing across sessions.
  • Pre-warmed worktrees: no git worktree add on the hot path. A new issue on bravo just switches branches in the worktree bravo already owns.
  • A real ceiling on concurrency. The number of declared workspaces is the parallelism cap; the orchestrator can't accidentally outrun your RAM by spawning more.

A project created with shelbi project add (or shelbi init -y) starts with an empty pool, and the orchestrator asks how many workspaces you want on its first boot. shelbi init without -y and the app's Add project form instead start you with two workspaces on hub: dev and review (tagged review). Add or remove workspaces later with shelbi workspace add <name> / shelbi workspace rm <name>, or in Settings ▸ Workspaces. (rm refuses a workspace with an active issue or unpushed work unless you pass --force.)

A slot can also carry tags: capability labels a workflow status can require, so an issue routes to a matching workspace. That routing is generic: review workspaces are just the canonical use of it (a slot tagged review that a review status routes to), but the same mechanism pins any capability to any pool.

Tags: capability labels

Every machine and every workspace can carry a free-form tags list. A workspace's effective tags are its own tags unioned with its machine's tags, so a tag declared once on a machine applies to all of its slots without repeating it per workspace.

machines:
  - name: devbox
    kind: ssh
    host: devbox
    work_dir: ~/Workspaces/myapp
    tags: [gpu]                 # every slot on devbox inherits `gpu`
 
workspaces:
  - { name: alpha, machine: devbox }                    # effective: {gpu}
  - { name: bravo, machine: devbox, tags: [review] }    # effective: {gpu, review}

Because machine tags are inherited, put review on individual slots, not on a machine: a machine-level review tag makes every slot on that machine a review slot.

Both fields accept a scalar tag: alias and a bare string as shorthand for a one-element list (tags: review ≡ tags: [review]). Both are elided from the on-disk form when empty, so existing project YAMLs round-trip unchanged.

Status-tag routing

A workflow status declares the tags an issue needs while it sits there:

statuses:
  - { id: review, owner: user, agent: review, tags: [review] }

When an issue enters that status, the orchestrator routes it to a free workspace whose effective tags are a superset of the status's required set (set-AND). Empty required tags (the default) match any free workspace, so any idle slot qualifies. tags: [review] on the status plus tags: [review] on a workspace is all it takes to pin that work to matching slots.

The routing query itself is generic. The review tag also carries built-in meaning: review-tagged slots refuse direct dev dispatch, are hidden from the sidebar's Workspaces section, are fed by the review auto-load queue, and receive the workflow's review: recipe. See Review workspaces.

If no declared workspace matches the required tags, the load fails loudly rather than silently running the work on a general slot.

Slots and $SLOT

Each workspace has a numeric slot. Set it explicitly with slot:, or let it default to the workspace's zero-based index among its machine's slots. The slot is exported to transition commands as $SLOT, which is how two workspaces on one machine avoid colliding on a port:

workspaces:
  - { name: alpha, machine: hub, slot: 0 }
  - { name: bravo, machine: hub, slot: 1 }

Alongside $SLOT, every transition command also gets $SHELBI_TASK, $SHELBI_BRANCH, $SHELBI_WORKTREE, and $SHELBI_MACHINE.

For a workflow's review: recipe the slot value is the port, and there is no index fallback: review slots need an explicit port-valued slot: (e.g. slot: 3000). See Review workspaces.

The sidebar: grouped by machine

Workspaces render in the sidebar grouped under the machine they're pinned to, so the layout mirrors where your compute actually lives. Each row shows the state badge, the name, and the loaded agent (or idle):

The desktop draws the same rows with its own glyphs: a green ▶ for working, a yellow ? for awaiting input, a dot for idle (the row reads Idle), plus ⚠ and ⏸. Busy rows are numbered ⌘1 to ⌘9 from the top, so a keystroke opens that workspace.

Machine headers only appear when the pool spans more than one machine. Idle slots carry no agent. They're capacity waiting to be filled. A busy slot shows which agent role it's running, which is how you tell at a glance that delta is doing a QA pass, not writing code.

Each machine group can be collapsed (click its machine row). The set of collapsed machine names persists to ~/.shelbi/state.json under sidebar.collapsed_machines (the SidebarPrefs struct), so the choice survives a sidebar respawn and follows you across projects that share a machine name. A collapsed machine shows a (total, N active) count on its header (e.g. hub (4, 2 active)) so capacity stays visible at a glance.

Review-tagged slots are the exception: they never render here. Their capacity surfaces under the sidebar's Ready for Review and Queued for Review sections instead.

Machine-aware routing

An issue can hint where it wants to run with prefers_machine:

shelbi issue add "Re-encode the marketing video assets" \
  --prefers-machine devbox

The orchestrator agent is instructed to honor the hint: it considers only workspaces on that machine, and if devbox is fully busy when the issue becomes ready, it leaves the card in its ready-category status rather than routing it to the wrong host. This is orchestrator prompt policy, not a hard rule enforced by Shelbi's code. The hint is stored as prefers_machine; clear it with shelbi issue edit --no-prefers-machine.

Workspace states

The hub polls each workspace's session every few seconds (see workspace_poll_interval_secs, default 5) and writes the observed state to ~/.shelbi/projects/<project>/workspaces/<name>/status.yaml. The sidebar reads from there.

BadgePersisted stateMeaning
⏵workingagent is mid-turn: actively typing, calling tools, running shells.
?awaiting_inputagent finished a turn and is sitting at the prompt.
⚠blockedagent paused on a permission dialog or other interactive gate.
⏸pausedthe workspace stalled on a usage limit and is waiting to resume.
⏵servinga review slot whose branch passed the review: ready probe.
·(no in-flight issue)the slot is idle and ready to be assigned.
…(not read yet)the workspace's status hasn't been read yet (e.g. right after a project switch).

The badges above are the TUI's. The desktop app draws the same states with its own glyphs: a green ▶ for working, a yellow ? for awaiting_input, ⚠ and ⏸ as above, a dot for idle, and … while loading; a serving review shows as a cyan check in the review sections.

A workspace that finishes an issue doesn't get its own "done" badge. The moment it writes the review-ready marker and the poller promotes the issue, the workspace closes its session and returns to idle (see How a task completes). Completion shows up in the sidebar's Ready for Review section, never as a lingering check on the workspace row.

awaiting_input is the right state for "agent done with this turn, waiting for the next prompt." It's what fires when claude's Stop hook runs at end of turn. The agent has not finished the issue; it just finished one round of work. The actual completion signal is the review-ready marker (see below).

State changes are also appended to ~/.shelbi/events.log:

2026-06-22T14:22:11+00:00 project=myapp workspace=bravo none -> working
2026-06-22T14:24:03+00:00 project=myapp workspace=bravo working -> awaiting_input

That feed is what the orchestrator tails to know when to dispatch more work. See the events log.

Switching issues clears context

When a workspace picks up a new issue its session is ended and re-created from scratch. The branch switch happens first, while the old session is still intact; the session is ended and restarted only after the worktree and settings are in place:

  1. The worktree is switched to the issue's branch, creating the branch off the project's base_branch() (the git.base_branch setting when present, otherwise default_branch, and a workflow can override it further) if it doesn't exist, and refusing to switch if there are uncommitted changes.
  2. .claude/settings.local.json is re-wired under the worktree: an additive, self-healing merge, not a clobber (Shelbi no longer overwrites the committed settings.json on every dispatch).
  3. The workspace's agent session is ended.
  4. A new Shelbi session is started (on the remote machine, for remote workspaces) and the agent CLI is launched in it, loaded with the agent the issue's status calls for.
  5. The startup prompt (the agent's charter plus the issue context) is passed to the agent at launch. Shelbi then confirms the agent went busy, falling back to typing the prompt once the input box is ready (Claude only).

This is intentional. The previous issue's conversation history, scratchpad files, and any agent-local state are gone. Each issue starts the agent with a clean context, with no leakage between issues on the same workspace.

Uncommitted work blocks the switch

Step 1 refuses to switch branches if the worktree has uncommitted changes. An agent that leaves work uncommitted will stall its own next dispatch. The completion protocol is a commit plus the review-ready marker, never a dirty tree.

The worktree itself persists. Files committed on the previous issue's branch are still there on disk; only the branch checkout changes. This keeps the on-machine cost of an issue switch small (one branch checkout, not a whole clone).

How a task completes

A workspace reports issue completion by writing its issue id into a marker file in the worktree:

<worktree>/.shelbi/shelbi-ready

The hub poller cats this file on each tick (locally or over SSH), and when it finds a non-empty value:

  1. Confirms the named issue is in-progress and assigned to this workspace.
  2. Rebases the branch onto the base branch, pushes it, and moves the issue forward: to an agent-owned gate status if the workflow declares one, otherwise to the first handoff status (review in the default workflow), firing that edge's actions (in the default workflow, opening the PR).
  3. Clears the marker.
  4. Appends the issue transition to the events log, e.g. project=<name> task=<id> workflow=<name> in_progress -> review reason=workspace:ready-marker from_category=active to_category=handoff.

Completion is signalled only by the marker file; the agent doesn't call shelbi to finish. (Agents also report state through Claude hooks and can ask for clarification through the hub socket.) A remote workspace needs only a compatible shelbi binary, git, and the agent CLI on its machine.

Once the issue is safely promoted, the finishing dev workspace closes its own session and frees its slot for the next issue. No lingering "done" session. If the project declares review workspaces, the hub's review auto-loader then loads the promoted branch onto one so a human can run the change, not just read the diff.

Local vs. remote workspaces

A workspace's session runs on its machine, local or remote:

                 ┌─ alpha ──────────┐  ┌─ bravo ──────────┐
hub workspaces → │  agent (session) │  │  agent (session) │   sessions on the hub
                 └──────────────────┘  └──────────────────┘
 
                 ┌─ charlie ────────┐
remote         → │  agent (session) │   one session per workspace,
workspaces       └──────────────────┘   on the remote machine

Every workspace, hub or remote, runs its agent in its own Shelbi session: a small detached process that owns the agent's PTY, so it survives a dropped SSH connection or a closed terminal. Attach to any of them with shelbi attach <workspace>. For a remote machine the hub bridges every session over a single SSH channel (see shelbi relay), and shelbi machine makes sure a compatible shelbi binary is there to run them.

The hub's own SSH (dispatch, polling) never prompts, so a remote machine should take your key or agent. If it wants a password, opening one of its workspaces from the sidebar asks for it once, in a macOS dialog, and the hub then shares that connection until it closes. Cancel shows "Couldn't sign in to host" and stops; open the workspace again to retry.

See also

  • Project config: the workspaces:, machines:, and agent_runners: field reference.
  • Review workspaces: the tag-routed slot that loads and serves a finished branch for human review.
  • Agents: the role that runs inside a workspace, and the role/slot split.
  • Workflows: what the default workflow's statuses mean, and where workspaces fit in the issue lifecycle.
  • The events log: the shape of every workspace transition line.
  • Orchestrator: how it picks which workspace to dispatch an issue to.
  • shelbi workspace: add, remove, list, inspect, and stop workspaces.