Workspaces & isolation
A workspace is devflow’s unit of isolation: one materialized VCS directory plus its service instances, processes, hook-generated files, and registry state. Git uses the primary checkout for the default workspace and a linked worktree for every additional workspace. Jujutsu uses native workspaces.
For jj, the raw devflow workspace name is also a bookmark. Commit directly with jj, then use jj bookmark set <workspace> --revision @- when you need the bookmark to advance immediately. Workspace removal also refreshes the bookmark before forgetting the native workspace.
devflow treats jj’s native primary workspace (internal name default) as the stable project root. Keep that internal workspace registered under default; if it is renamed or forgotten, devflow fails closed instead of guessing that another workspace is the primary. Raw user-facing workspace identities remain bookmarks and are unaffected by this internal-name requirement.
Git worktree feature/auth ├─ directory ../myapp.feature_auth_fc659bd73585 ├─ service app-db postgres container or database (isolated) ├─ service cache redis DB index (isolated) └─ .env.local written by hooks (per-workspace values)Architecture
Section titled “Architecture”Three frontends (CLI, TUI, desktop GUI) drive one shared core:
┌────────────┐ ┌────────────┐ ┌────────────┐ │ Desktop GUI│ │ TUI │ │ CLI │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └──────────────┼──────────────┘ ▼ ┌──────────────────────────────────────────┐ │ devflow-core │ │ workspace lifecycle · hook engine · │ │ VCS layer (git/jj) · service providers ·│ │ config · state · reverse proxy │ └──────────────────────────────────────────┘The lifecycle (create → switch → remove) lives in core, so every frontend gets the same behavior: hooks fire, services follow, state stays consistent.
Two isolation models
Section titled “Two isolation models”Chosen per service in .devflow.yml; a project can mix both.
Physical isolation — type: local
Section titled “Physical isolation — type: local”One Copy-on-Write Docker container per workspace. Creating a workspace clones the parent’s entire data directory (APFS clone / ZFS snapshot / reflink — near-instant, near-zero extra disk). Strongest isolation: separate process, port, and data.
services: - name: app-db type: local service_type: postgres # postgres | clickhouse | mysql | generic | plugin local: image: postgres:17Logical isolation — type: shared
Section titled “Logical isolation — type: shared”One global container per engine; each workspace gets a logical boundary provisioned on the fly:
| Engine | Per-workspace unit | Branching semantics |
|---|---|---|
| PostgreSQL | database | CREATE DATABASE … TEMPLATE parent (branch-from-parent) |
| Redis (shared) | numbered DB index (1–15; 0 tracks allocations) | none — empty DB per workspace |
| RustFS (S3) | bucket ({project}-{workspace}) | none — empty bucket |
| ClickHouse | database | none — empty database |
services: - name: cache service_type: redis # shared DB allocation (or use local with a generic container config) - name: app-db type: shared service_type: postgres shared: port: 5432 # fixed well-known portShared engines use one fixed port, start faster, and consume less memory — at the cost of weaker isolation (shared process). The controller daemon keeps them running.
See Local containers and Shared engines for full configuration.
Workspace identity
Section titled “Workspace identity”devflow keeps two names with different jobs:
nameis the raw VCS identity, such asfeature/Auth-System. It is shown in every frontend, passed to VCS operations, and exposed to hooks as{{ workspace }}.service_keyis a deterministic, database/file-safe identity for services and generated paths. Already-safe names are preserved; names that need normalization receive a stable short hash, sofeature/auth,feature-auth, and case variants never collide. Hooks expose it as{{ workspace_key }}and as the{{ workspace_sanitized }}compatibility alias.
Never reconstruct a workspace name from its service_key; use the raw name field for switch, remove, and other VCS operations.
State & identity
Section titled “State & identity”Workspace metadata (creation parents, paths, service keys, executed commands) lives in ~/.config/devflow/local_state.yml — machine-local, never committed. Project identity is the canonical main-repo root: commands run from inside any worktree resolve to the same project as the primary checkout, so registries, hook approvals, and lookups agree.
devflow list reconciles this state with live Git worktrees or jj workspaces, services, and processes. Its JSON output is one versioned tree document for zero, one, or many services: schema_version, project/VCS metadata, context_workspace, default_workspace, roots, workspace nodes, and warnings. context_workspace is derived from the VCS workspace containing the project path used for the request; it does not imply other worktrees are inactive.
When upgrading from the older lossy naming scheme, devflow recovers raw names only where a live worktree gives an unambiguous match. That workspace keeps its persisted legacy service_key, so existing services and process state remain visible without risky renames. Ambiguous ownership is shown in inventory warnings and service/process operations fail before creating a parallel namespace or attaching another workspace’s data. Inventory nodes expose identity_status (canonical, legacy_adopted, or legacy_unresolved) plus canonical_service_key, so automation can handle recovery without parsing warning text.
Parent relationships
Section titled “Parent relationships”Every created workspace records its parent (--from <ws>, or the current context workspace). This is immutable creation/clone provenance, not inferred commit ancestry. Parents drive:
- service branching — the new database is cloned from the parent’s,
devflow list— the rendered parent tree in CLI, TUI, GUI, and JSON.
Deleting a parent does not rewrite its children. Inventory keeps the recorded relationship and marks that parent as missing/deleted, which preserves how service data was originally cloned.
Override the inferred context with DEVFLOW_CONTEXT_BRANCH=<ws> (useful in CI).