Projects
Sessions accumulate. A month in, the sidebar is a hundred rows across a dozen unrelated codebases, and "which of these was the API refactor" stops being answerable. Projects are the fix.
Projects are entities, not folder names
A project is a real record — {id, name, color, folders, archived, session_count} — managed through GET/POST/PATCH/DELETE /api/projects. It owns a set of folders, so one project can span several checkouts, which is what actually happens with worktrees and per-issue clones.
Assignment is a sidecar write: PATCH /api/sessions/{sid}/metadata with {project_id}, or null to clear it. As always, nothing is written to the engine's own store.
Each project carries a colour, and sessions can override it individually, so a glance at the sidebar tells you which world a row belongs to.
AI auto-sort
Assigning every session by hand is exactly the chore that stops people from using a system like this. Auto-sort does it: a background pass reads unassigned sessions and proposes a project for each, using the same OpenAI-compatible endpoint configuration as AI review.
It is deliberately conservative:
- A confidence floor (
confidence_min) — a proposal below it is discarded rather than guessed. - A cap per pass (
max_per_pass) so a bad configuration cannot relabel your whole sidebar in one sweep. - Call spacing of 1 second between model calls, so a local model is not stampeded.
- Near-misses are retained (up to 8) for inspection rather than silently dropped.
Auto-sort only ever touches sessions with no project. It does not second-guess an assignment you made.
Visibility: three settings that are easy to confuse
These do genuinely different things, and the difference matters:
| Setting | Effect |
|---|---|
projects_hidden | A launch-location control. The folder drops out of the new-session picker; its sessions stay in the sidebar. |
folder_exclusions | Blocks the folder outright — its sessions are not listed, and resume is blocked. |
projects_mode | all (default) or included — the only genuinely default-deny mode: with included, only explicitly listed projects appear. |
The first one surprises people, so it is worth restating: hiding a project hides where you start work, not the work itself. If you want a folder gone from the listing entirely, that is folder_exclusions.
Creating a project
New project opens a short wizard at /projects/new. It is on the dashboard, under the project picker in New session ("+ New project…"), and in Settings → Projects. The session list is hidden while it is open. It asks, one step at a time:
- Name: any name. If an active project already has it, the wizard warns you but lets you keep it.
- Folder: either a new folder (a parent,
~by default, and a folder name, which is suggested from the project name) or an existing folder you browse to. A new-folder name that is already there is labelled existing folder, reused: the project uses it as it is. A folder that overlaps one another project owns is flagged here, because a folder belongs to one project. - Colour: one of the sixteen colours Settings → Interface → Accent offers, or none, with a preview of the session-list row and the map cluster. The wizard preselects the first colour no other project uses. Settings → Projects uses the same picker; a project whose colour is not one of the sixteen (an older palette, or a hex set through the API) keeps it, shown first as Current colour.
- Agent (optional), Start sessions with: the agent, its model and reasoning effort, and whether permission prompts are skipped, for new sessions in this project. Each can stay on Global default (Settings → Agents). A model and effort belong to the project's own agent, so they are offered once you pick one; an agent that sets its model in its own configuration says so. Leaving everything on the global defaults stores nothing.
- Playbook: choose one of your playbooks (Library → Playbooks → Your playbooks: the bundled ones, the ones you created and the ones you added from the gallery) or No playbook. Each playbook is a card with its summary, its steps and how many files, inputs and agent steps it brings. A usable configured default is preselected; a missing default is explained. Local unsigned playbooks ask for your trust.
- Inputs, when a playbook is selected (Set up ‹playbook›): each input picks its source — Value, Global (the global variable of that name; a secret's value stays hidden) or Default — and each choice names what it resolves to, e.g. Default · staging, or No default. A required input that would be empty is marked in red on its card straight away, and Next waits until it is filled; the review stays the final check. For a format 5 playbook, Roles in this playbook lists what each agent step needs (its skills and minimum grade); there is nothing to assign, because a specialist fills each role when a mission runs. An older (format 4) playbook shows Agents per step instead: one table (step, agent, model, state) with the agent's own models and Set every step to … to assign one agent to all of them. Values stay in the wizard until the project exists and its inputs are bound.
- Review: the project details and, for a playbook, proposed file changes, rendered instructions, resolved inputs and targets, assignments and capability requests. Confirm each target derived from a default. Create project and apply remains unavailable while the review has conflicts or a required target is unconfirmed.
With No playbook, nothing is written until you press Create project. Then the new folder is made (only if it is absent, and only under your home directory), the project is created, and the default is set if you asked for it. If the server refuses (for example, the folder already belongs to another project), you stay on Review with its reason and a link to the step that fixes it. A folder that was made before the refusal is left where it is and named. A known refusal can be retried. If the creation response is lost or the server cannot establish its outcome, the wizard prevents another create: check Settings → Projects first.
With a playbook, the server rechecks the review before creating anything. A reviewed absent folder is created exclusively; even an empty folder that appeared in the meantime requires a new review. If the project is then refused, you can choose another folder and review it as usual; the folder already made stays where it is, and a replacement of it is never accepted as the one the wizard made. The project is then created, its inputs bound, the reviewed files applied, and the result verified. Assignment does not launch agents, start a workflow, run connection probes or schedule rituals.
The result distinguishes project creation, deployment and verification. If binding or applying fails, Retry binding or Retry apply keeps the existing project and the same operation. A lost response is reconciled, never treated as proof that nothing happened. After a completed apply, Re-verify checks the files and inputs again. A changed source or target requires an explicit new review when no operation is interrupted. Review removal of this playbook shows the inverse file changes and restored bindings before confirmation; the project and its folders stay.
Playbooks that write secret-reference files need an existing project ID before review. The playbook step explains that limitation and keeps No playbook usable. Shared collection selectors and saved API-profile controls are separate integrations; their absence does not prevent project creation.
When it is done you can start a session in the new project, plan a mission in it, see it on the map, or go back to where you started. From New session, you return with the new project and its folder selected, and your agent and permission choices kept. Cancel puts New session back exactly as you left it. If you have typed anything, leaving asks first.
The legacy folder-creation endpoint
POST /api/folders/mkdir creates a folder only inside a base directory you have allowed via AGENT_SESSIONS_PROJECT_ROOTS (an os.pathsep-separated list). If it is empty or unset the endpoint is off entirely, not merely hidden in the UI. When it is set, the target path is realpath-resolved and must land strictly under a listed root, so neither a symlink nor a .. can escape it. The app itself no longer calls it: the New project wizard and the folder picker use POST /api/fs/mkdir, which is bounded to your home directory the same way.
Verified against
Commit 218cf3a — docs/reference.md § Sessions & projects; src/agent_sessions/projects.py; src/agent_sessions/fsbrowse.py § make_dir; web/src/routes/NewProject.tsx (#1187); src/agent_sessions/autosort.py § NEAR_MISS_CAP, CALL_SPACING_S; src/agent_sessions/prefs.py § PROJECT_MODES.
Where a new session starts
A session starts in its project's default folder. New session has no folder control of its own: each project row shows its folder, and the line beside Start session names the folder the session will start in. A project with no default folder falls back to your global default folder, and that line says so. With neither, Start stays disabled and the line links to Settings → Projects, where you set the project's folder.
Session defaults
A project can say what its new sessions start with: an agent, that agent's model and reasoning effort, and whether permission prompts are skipped. Set them in the New project wizard's Agent step, or later with Session defaults on the project's row in Settings → Projects.
On New session, choosing the project preselects them, marked project, and a line under the project list says what the project starts as. What you pick on the form always wins for that session; anything the project leaves unset follows your global defaults. A project's skip prompts applies only once the project list has loaded, and never to an agent that cannot skip. The defaults are only a preselection: missions, automations and other launches don't read them.