> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enagrams.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Workspace Management

> Managing workspaces, members, and settings

## Workspaces

A workspace is the shared coordination context for a team working on one codebase. All agents connected to the same workspace share decisions, conventions, reservations, workstreams, tasks, and the symbol graph.

Every workspace is **bound to exactly one git repository**. Binding is what makes "which workspace is this?" an unambiguous question — any teammate who clones the repo gets routed to the same workspace without guessing at a name.

### Creating a Workspace

Pick whichever path matches how you're onboarding — they all produce the same workspace.

<Tabs>
  <Tab title="CLI (recommended for new repos)">
    From the root of the repo you want to coordinate:

    ```bash theme={null}
    npx enagrams init
    ```

    The CLI:

    1. Opens your browser to authenticate (first run only — subsequent runs reuse `~/.config/enagrams/config.json`).
    2. Computes the repo fingerprint (normalized `origin` URL + root commit SHA).
    3. Checks whether this repo is already bound to a workspace — if so, it routes you to it instead of creating a duplicate.
    4. Prompts for a name and slug, creates the workspace, binds it to this repo, and writes all IDE config files.

    See [Quickstart](/guides/quickstart) for what `init` writes to disk.
  </Tab>

  <Tab title="Dashboard + GitHub App">
    Best when the owner doesn't want to clone the repo locally first.

    1. Go to [enagrams.com/dashboard](https://enagrams.com/dashboard) and click **New Workspace**.
    2. Set a name and slug.
    3. In the new workspace's settings, click **Connect GitHub**. This launches the Enagrams GitHub App install flow (same pattern as Vercel/Netlify).
    4. Pick the account and repo. The App resolves the repo's root commit server-side and binds the workspace.

    After this, teammates who clone the repo and run `enagrams login` are recognized automatically — no `init` needed on their end unless they want the hooks and MCP configs.
  </Tab>

  <Tab title="Dashboard only (bind later)">
    You can create an unbound workspace and bind it from a clone later.

    1. **New Workspace** in the dashboard with name + slug.
    2. Later, whoever runs `npx enagrams init` in the matching repo will be offered a **one-keystroke "bind this repo to `<name>`"** shortcut — provided the workspace slug matches the repo's basename. This is the natural team flow: owner creates `chatgpt-wrapper` in the dashboard, invites the teammate, teammate runs `init` in their `chatgpt-wrapper/` clone, answers `y`, done.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    curl -X POST https://api.enagrams.com/workspaces \
      -H "Authorization: Bearer ek_your_key" \
      -H "Content-Type: application/json" \
      -d '{"name": "My Startup", "slug": "my-startup"}'
    ```

    Binding happens separately via `POST /workspaces/:id/repo-binding` with the fingerprint payload. The CLI and GitHub App are just wrappers around this endpoint.
  </Tab>
</Tabs>

### Repo Binding

A binding is the pair `(repo_url, repo_root_commit)`:

* **`repo_url`** — normalized form of `git remote get-url origin`. `git@github.com:Org/Repo.git`, `https://github.com/Org/Repo`, and `https://user:token@github.com/org/repo.git?ref=x` all fold to the same canonical URL. Host is lowercased; org+repo are lowercased on GitHub/GitLab/Bitbucket (case-insensitive hosts).
* **`repo_root_commit`** — SHA of the earliest commit (`git rev-list --max-parents=0 --all`, sorted). Stable across branches, clones, and forks of the same history.

The binding is **1:1**: any given repo can be bound to at most one workspace, and any given workspace can be bound to at most one repo. This is what the CLI uses to detect "teammate B just cloned the repo teammate A already bound."

<Note>
  Shallow clones (`git clone --depth=N`) don't carry the real root commit. The CLI detects this, skips the SHA, and binds on URL alone. Run `git fetch --unshallow` for the full fingerprint.
</Note>

### Workspace Slug

The slug is a URL-safe identifier (e.g. `my-startup`). It's what you supply for `ENAGRAMS_WORKSPACE` in each developer's `.env`. Slugs are unique across all workspaces.

When you create a workspace in the dashboard for a repo that doesn't exist locally yet, set the slug to the repo's basename (`github.com/org/chatgpt-wrapper` → slug `chatgpt-wrapper`). The CLI uses slug-matches-basename as the trigger for its one-keystroke bind shortcut.

## Sharing a Workspace

Enagrams is **invite-only**. A teammate can't join a workspace by guessing its slug or by running `enagrams init` in a bound repo — workspace owners and admins invite from the dashboard, and teammates accept.

### The flow

<Steps>
  <Step title="Teammate clones the repo and runs the CLI">
    ```bash theme={null}
    git clone git@github.com:org/chatgpt-wrapper.git
    cd chatgpt-wrapper
    npx enagrams login
    ```

    The CLI fingerprints the repo and asks the API whether any workspace already owns it.
  </Step>

  <Step title="CLI detects the binding and prints the invite hint">
    ```
    This repo is exactly already bound to an Enagrams workspace:
      ChatGPT Wrapper (chatgpt-wrapper)

      Ask one of its admins for an invite:
        • Aaron Siddiky <aaron@example.com>
        • Jimmy Charter <jimmy@example.com>

    Once you accept the invite, re-run `enagrams login`.
    ```

    The CLI exits cleanly — it won't create a duplicate workspace or offer to bind over the top of the existing one.
  </Step>

  <Step title="Admin invites from the dashboard">
    Owner or admin goes to **Dashboard → Team → Invite** and enters the teammate's email. The teammate gets an email with the invite link, clicks to accept, and is added as a `member`.
  </Step>

  <Step title="Teammate re-runs login">
    ```bash theme={null}
    npx enagrams login
    ```

    This time the CLI sees the teammate is a member of the bound workspace, fast-paths them to a one-keystroke **Continue with `<workspace>`** prompt, and writes the workspace into `.env`.
  </Step>

  <Step title="Run init to drop IDE configs (optional)">
    If the teammate wants Cursor / Claude Code / Codex hooks and MCP configs:

    ```bash theme={null}
    npx enagrams init
    ```

    `init` detects the already-bound workspace and skips straight to writing the IDE files — no bind prompts.
  </Step>
</Steps>

<Note>
  The "ask for an invite" hint is fired by an unauthenticated `POST /repo-lookup` probe, rate-limited to prevent enumeration. The endpoint only returns admin contacts when the fingerprint matches a real workspace — random probes get `{match: null}`.
</Note>

### Fingerprint edge cases

The CLI surfaces actionable messages for the five non-happy git states before showing any menu:

| Status                  | What the CLI says                           | Fix                                                       |
| ----------------------- | ------------------------------------------- | --------------------------------------------------------- |
| `no_git`                | This directory is not inside a git worktree | `cd` into your project or `git init`                      |
| `no_commits`            | This git repo has no commits yet            | `git add -A && git commit -m "first"`                     |
| `no_remote`             | No remote configured — binding on SHA alone | `git remote add origin <url>` (optional; SHA still works) |
| `shallow`               | Shallow clone; binding on URL alone         | `git fetch --unshallow` for the full fingerprint          |
| `no_remote_and_shallow` | No signal available                         | Fix one of the two above first                            |

## Members

### Roles

| Role     | Permissions                                                               |
| -------- | ------------------------------------------------------------------------- |
| `owner`  | Full access, delete workspace, bind/unbind repo, manage all members       |
| `admin`  | Manage members, bind/unbind repo, change settings                         |
| `member` | Create decisions, claim work packages, propose conventions, use MCP tools |

### Inviting Members

From the dashboard: **Workspace → Team → Invite** (enter email). The invitee gets an email; once they accept they're added as `member` by default (admins can change the role before or after).

You can also use the API:

```bash theme={null}
POST /workspaces/:id/members
Authorization: Bearer ek_...

{ "user_id": "usr_...", "role": "member" }
```

### Removing Members

```bash theme={null}
DELETE /workspaces/:id/members/:userId
Authorization: Bearer ek_...
```

When a member is removed their active sessions end, reservations release, and any workstreams they were the sole owner of are marked abandoned.

## API Keys

Each team member has their own API key:

1. `npx enagrams login` creates one automatically and saves it to `~/.config/enagrams/config.json`.
2. Or generate one manually at **Dashboard → API Keys**.

API keys have the prefix `ek_` and are tied to a user account. A key can access any workspace the user is a member of.

### Revoking Keys

Revoke from **Dashboard → API Keys → Revoke**. Active sessions using the key are terminated immediately.

## Workstreams

Workstreams are the primary unit of shared work within a workspace. Each one maps to a branch (`ena/<slug>`). See [Workstreams](/concepts/workstreams) for the full lifecycle.

List active workstreams:

```bash theme={null}
GET /workspaces/:id/workstreams?status=active
Authorization: Bearer ek_...
```

Or via MCP: [`workstream_list`](/mcp/workstream-list).

## Reservations

View current [reservations](/concepts/file-locking) (file and symbol level):

```bash theme={null}
GET /workspaces/:id/reservations
Authorization: Bearer ek_...
```

Reservations auto-expire after 10 minutes of inactivity and release on `sessionEnd`. Manual release: end the owning conversation.

## Conventions

[Conventions](/concepts/conventions) are workspace-scoped rules. `must`-tier conventions are enforced by the `preToolUse` hook. Manage them through:

* MCP: [`convention_propose`](/mcp/convention-propose), [`convention_list`](/mcp/convention-list).
* Dashboard: **Workspace → Conventions**.

## Moving a Workspace to a New Repo

If your team renames the repo or migrates hosts (GitHub → GitLab, etc.), the normalized URL usually still matches — the server silently backfills the new URL on the next bind. No action required.

If you're moving the workspace to a **different** repo (history and all), use:

```bash theme={null}
enagrams workspace migrate-repo
```

Owner/admin only. From within a clone of the *new* repo, this releases the workspace's current binding and rebinds it to the new repo's fingerprint. Atomic in practice: if the new repo is already claimed by another workspace, the migrate aborts and the workspace stays bound to the old repo.

<Warning>
  For workspaces bound through the **GitHub App** (Dashboard → Connect GitHub), `migrate-repo` is disabled — the CLI would leave the App's `installation_id` pointing at the old repo while webhooks fired against the new one. Disconnect from the dashboard and reconnect the new repo instead.
</Warning>

## Workspace Settings

| Setting                         | Description                                                                                             |
| ------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `name`                          | Display name                                                                                            |
| `slug`                          | URL-safe identifier — changing requires updating every `.env` and MCP config                            |
| `default_base_branch`           | Branch new workstreams fork from (defaults to `main`)                                                   |
| `repo_url` / `repo_root_commit` | The current binding. Read-only in `PATCH` — use `POST /workspaces/:id/repo-binding` or the CLI instead. |
| GitHub App binding              | Managed entirely from the dashboard. Disconnect releases the binding cleanly.                           |

```bash theme={null}
PATCH /workspaces/:id
Authorization: Bearer ek_...

{ "name": "New Name", "default_base_branch": "develop" }
```
