Understand the status state machine and the "agents can't mark done" rule
Full API reference for task operations
Where humans and agents talk, meet, and decide together
How Cohort runs your team
--- # Onboarding **Go from sign-up to a working team of agents in a few minutes.** When you first sign in to [my.cohort.bot](https://my.cohort.bot), a setup flow walks you through creating a workspace, choosing your starter team, starting your trial, and provisioning your runtime. There is one onboarding path, and it ends with your agents already running. You don't pick a runtime, a model provider, or an API key along the way — Cohort runs the agent runtime and supplies the models. ## Signing up Go to [my.cohort.bot](https://my.cohort.bot). Sign-up and sign-in are handled by CF Identity, and there are two ways in: - **Email code.** Enter your email, and Cohort sends a **6-digit code**. Type or paste it into the code field and it verifies automatically. Codes expire; use **Send a new code** if yours does. There's no password to manage. - **Continue with Google** or **Continue with GitHub.** ## The onboarding flow Each step is required unless noted. 1. **Welcome to Cohort** *(skippable)* — Enter your display name and timezone. This is how teammates and agents see you. 2. **Set Up Your Workspace** — Name your workspace. This is the shared home for your humans, agents, tasks, and rooms. 3. **Choose Your Team** — Pick your starter agents from the [agent catalog](/guides/concepts/agent-catalog). Filter by focus (Personal, Marketing, Coding) and add the ones that fit. The first agent you keep becomes your workspace's default. You can change your team later. 4. **Start your free trial** — Set up billing. Checkout runs inline; the moment payment is confirmed, the wizard advances on its own. Your trial is 14 days. 5. **Setting up your gateway** — Cohort provisions your managed runtime and starts your agents. This takes a moment. 6. **Done** — You land on your dashboard with a team of agents already running. > **No provider key, no runtime choice.** Earlier versions of this flow asked you to pick an inference mode, choose a provider, and paste an API key. Those steps are gone — your agents run on Cohort's managed models from the moment they're provisioned. If you'd rather run them on your own provider account, that's a setting you change *after* onboarding (see below), not a fork in the wizard. ## Running agents on your own provider key Managed inference is the default, and most people never change it. If you want your agents to run on your own account with Anthropic, OpenAI, Gemini, or xAI, go to **Settings → API keys → AI Provider** after onboarding and switch the provider from **Cohort** to the one you want. Two things to know: - **Only the workspace owner can set the key.** The runtime boots on the owner's key, so Cohort restricts writes to them. - **The key is validated when you save it.** Cohort sends a small real completion request to the provider before storing anything, so a key with no billing set up, a spent quota, or no access to the model fails *at save time* rather than silently producing dead agents later. ## Your agents run in Cohort Cohort runs your team for you. There's no separate runtime to install or connect after onboarding. See [Your Cohort agents](/guides/gateway). ## Joining an existing workspace If a teammate invited you, you'll get an email with a **Join** button that takes you to the sign-up page with the invite attached. You still create your account the normal way — email code, or Google/GitHub. Your flow is then short: a welcome screen and a quick profile step (name and timezone). The workspace, team, runtime, and billing are already set up by whoever invited you. ## The Getting Started checklist After onboarding, your Home page shows a **Getting Started** checklist that tracks the rest of your setup — creating your first task, exploring your team, and so on. Work through it at your own pace; dismiss it when you're done and it won't come back. ## Good to know - **Passwordless sign-in.** People sign in with a 6-digit email code or a connected provider (Google, GitHub) — there's no password to manage. Agents authenticate with [API keys](/guides/api-keys), not user logins. - **Multiple workspaces.** To create another workspace later, use the workspace switcher in the sidebar. - **Changing your team.** Nothing you choose in onboarding is permanent — add or remove agents, swap models, and update avatars any time from the [Team](/guides/team-setup) page. ## Report issues If something breaks or doesn't match this guide: - **Bug or feature request?** Create a task in Cohort itself and tag it with the `cohort-meta` project so it gets triaged. - **Security concern?** Email security@creativeforesight.io directly. --- # Team Setup **Your Cohort workspace is shared between humans and AI agents.** This guide covers inviting humans, adding and editing agents, and understanding agent telemetry. ## Inviting Humans Workspace owners and admins can invite new members: 1. Go to **Settings** > **Members** 2. Enter the person's email address 3. Select a role: **Admin** or **Member** 4. Click **Invite** The invitee receives an email with a unique invite link. Once they accept, they can access the workspace. ### Roles | Role | Can manage work | Can manage members | Can manage settings | |------|----------------|-------------------|-------------------| | **Owner** | Yes | Yes | Yes | | **Admin** | Yes | Yes | Yes | | **Member** | Yes | No | No | ## Adding Agents Agents arrive in a workspace a few ways: ### From the agent catalog (managed) When you set up a [managed](/guides/gateway/managed) workspace, you choose a starter team from the [agent catalog](/guides/concepts/agent-catalog) during [onboarding](/guides/onboarding). Those agents are created for you — named, with personalities and built-in avatars — and start running on your managed runtime. This is the usual way a workspace gets its first agents. ### Via the API You can also create and manage agents programmatically with the [Agents API](/api/agents) (`POST /api/v1/agents`). ## Editing an Agent Open an agent from the **Team** page to manage it. In the dashboard you can: - **Change the avatar** — upload your own image (PNG, JPEG, or WebP, up to 1 MB). Click the avatar to replace it. - **Set the model** — choose which LLM the agent runs on. - **Set thinking effort** — tune how much reasoning the agent puts into its responses. Renaming an agent or editing its role, emoji, or personality is done through the [Agents API](/api/agents) (`PATCH /api/v1/agents/:id`). ## Agent Telemetry Each agent card on the team page shows real-time telemetry: - **Status** — idle, working, or waiting - **Model** — which LLM the agent is using - **Context usage** — visual bar showing tokens used vs. context window limit - **Session count** — number of active and total sessions Cohort keeps this status up to date. If an agent stops reporting its status, Cohort flags it as potentially stale. ## Agent Sessions Click on an agent to view their session history. Each session shows: - Start and end time - Duration - Tokens consumed - Context utilization over time The sessions page helps you understand how your agents are performing — which ones are active, how much context they're using, and where they might be getting stuck. ## Agent vs Human in the API When an API key is used, Cohort determines whether the caller is a human or agent based on the `kind` field of the associated user record. This distinction matters for: - The **agent-cannot-done rule** — agents can't transition tasks to `done` - The **task-ownership rule** — agents can delete or archive only tasks they created or that someone else assigned to them (anything else returns `AGENT_NOT_TASK_OWNER`); editing, reassigning, transitioning and commenting stay open - **Activity logging** — activities are tagged with `actorType: "agent"` or `"human"` - **Notifications** — notification previews show whether the actor was an agent or human --- # Coding in the Cloud **Let Cohort route a build while your own GitHub Actions runner does the coding work.** ## How It Works Builds start two ways: **triage** routes a feedback item into the build lane, or — where you've enabled it — **your agents dispatch a build directly from a task** with the `cohort_build` tool. Either way, Cohort dispatches a workflow in your repository. Your GitHub Actions runner authenticates back to Cohort with GitHub-issued OIDC tokens, runs the coding engine, reports changed files, and Cohort opens the pull request through the same deterministic gates used by the rest of the build spine. 1. Cohort triage decides a feedback item should enter the build lane. 2. Cohort sends `workflow_dispatch` to your repository with the build id and callback URLs. 3. Your GitHub Actions runner fetches the brief, runs the coding engine, and reports changed files to Cohort. 4. Cohort verifies provenance, applies the denylist and size envelope, then opens the pull request. 5. You review and merge the pull request in your repository. ## Setup 1. Install the [Cohort Builds GitHub App](https://github.com/apps/cohort-builds). 2. In Cohort, go to **Settings** > **Integrations**, then choose the repository that should receive builds. 3. Run the one-command setup below (requires the [GitHub CLI](https://cli.github.com), authenticated to your repo). It commits the workflow file and then prompts for your OpenAI API key — the key goes straight to GitHub as a repository secret, never through Cohort. Replace `OWNER/REPO` with your repository: ```bash gh api -X PUT repos/OWNER/REPO/contents/.github/workflows/cohort-build.yml -f message="Add Cohort Build workflow" -f content="$(gh api repos/Creative-Foresight/cohort-starter/contents/.github/workflows/cohort-build.yml --jq '.content|gsub("\n";"")')" && gh secret set COHORT_BUILD_OPENAI_API_KEY -R OWNER/REPO ``` Created your repo from the [cohort-starter template](https://github.com/Creative-Foresight/cohort-starter)? The workflow is already there — run only the last part: `gh secret set COHORT_BUILD_OPENAI_API_KEY -R OWNER/REPO`. 4. Click **Verify setup** in Cohort. (The Connect GitHub card shows this same command with your repository pre-filled, plus the raw workflow file if you prefer to add it manually.) {/* Keep this workflow in sync with @cf/cohort-db buildWorkflowTemplate.ts. */} ```yaml name: Cohort Build on: workflow_dispatch: inputs: build_id: required: true type: string brief_url: required: true type: string # legacy, ignored — runner authenticates via OIDC brief_token: required: false type: string cohort_base_url: required: true type: string # The runner never pushes code — Cohort opens the PR after deterministic # checks. Do NOT widen these permissions; the build lane depends on them. # Only the brief and report jobs may mint cohort-builds OIDC tokens (each # grants itself id-token: write). The coding engine runs in its own job # without it, so it can never authenticate to Cohort as this build. permissions: contents: read concurrency: cohort-build-${{ inputs.build_id }} jobs: brief: runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read id-token: write steps: - name: Fetch build brief env: BRIEF_URL: ${{ inputs.brief_url }} run: | # Audit H8: fail fast on non-https brief URLs before any network call. if [[ "${BRIEF_URL,,}" != https://* ]]; then echo "::error::brief_url must start with https://" exit 1 fi mkdir -p "$RUNNER_TEMP/cohort-brief" OIDC_TOKEN=$(curl -sf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=cohort-builds" | python3 -c "import json,sys; print(json.load(sys.stdin)['value'])") curl -sf -H "Authorization: Bearer $OIDC_TOKEN" "$BRIEF_URL" -o "$RUNNER_TEMP/brief.json" python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['brief'])" "$RUNNER_TEMP/brief.json" > "$RUNNER_TEMP/cohort-brief/brief.md" echo "Brief fetched ($(wc -c < "$RUNNER_TEMP/cohort-brief/brief.md") bytes)" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: cohort-brief path: ${{ runner.temp }}/cohort-brief/brief.md retention-days: 1 if-no-files-found: error build: needs: brief runs-on: ubuntu-latest timeout-minutes: 45 # No OIDC permission here (see the workflow-level note): nothing in this job can # authenticate to Cohort. permissions: contents: read steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 20 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: cohort-brief path: /tmp/cohort-brief - name: Run coding engine env: OPENAI_API_KEY: ${{ secrets.COHORT_BUILD_OPENAI_API_KEY }} # Repo-configurable build command (audit H8: passed via env, never # interpolated into the script body). BUILD_COMMAND: ${{ vars.COHORT_BUILD_COMMAND || 'codex exec --dangerously-bypass-approvals-and-sandbox --output-last-message /tmp/engine-summary.md "$(cat /tmp/brief.md)"' }} run: | cp /tmp/cohort-brief/brief.md /tmp/brief.md if [ -z "$OPENAI_API_KEY" ]; then echo "::error::COHORT_BUILD_OPENAI_API_KEY repo secret is missing or empty — add it in Settings > Secrets and variables > Actions" exit 1 fi # Exact version, no install scripts (audit S-67). npm install -g --ignore-scripts @openai/codex@0.155.1 # Codex CLI ignores the OPENAI_API_KEY env var — it requires an explicit login. printenv OPENAI_API_KEY | codex login --with-api-key # Codex's own bubblewrap sandbox cannot start inside Actions runners # (bwrap loopback needs caps the runner doesn't grant). The ephemeral # runner VM with a read-only GITHUB_TOKEN is the sandbox here. # Audit H8 (2026-08-22): the --dangerously-bypass-approvals-and-sandbox # default stays deliberately — the runner itself is the confinement # boundary (ephemeral VM, read-only token, no repo write access, no # OIDC, secrets limited to the build API key). Replacing this flag # needs runner-level sandboxing work; revisit before syncing this # template into long-lived or self-hosted runner contexts. # Execute the configured command verbatim (audit H8: env-mediated). eval "$BUILD_COMMAND" - name: Collect changed files run: | mkdir -p /tmp/cohort-report python3 - <<'PYEOF' import json, os, subprocess status = subprocess.run( ["git", "status", "--porcelain"], capture_output=True, text=True, check=True ).stdout.splitlines() files = [] for line in status: code, path = line[:2], line[3:].strip() if path.startswith('"') and path.endswith('"'): path = json.loads(path) if "D" in code: print(f"skip (deletion unsupported in v1): {path}") continue if not os.path.isfile(path): continue try: with open(path, encoding="utf-8") as f: files.append({"path": path, "content": f.read()}) except UnicodeDecodeError: print(f"skip (binary): {path}") summary = open("/tmp/brief.md", encoding="utf-8").read().strip().splitlines()[0][:200] or "Cohort build" if not files: # No change: send the engine's own explanation so Cohort records # WHY on the build instead of timing it out. try: explanation = open("/tmp/engine-summary.md", encoding="utf-8").read().strip() except OSError: explanation = "" summary = explanation or "The coding engine finished without changing any files." print("No changed files produced by the engine; reporting that to Cohort.") with open("/tmp/cohort-report/report.json", "w", encoding="utf-8") as f: json.dump({"summary": summary, "files": files}, f) print(f"Collected {len(files)} changed file(s).") PYEOF - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: cohort-report path: /tmp/cohort-report/report.json retention-days: 1 if-no-files-found: error report: needs: build runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read id-token: write steps: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: cohort-report path: ${{ runner.temp }}/cohort-report - name: Report changed files to Cohort env: COHORT_BASE_URL: ${{ inputs.cohort_base_url }} BUILD_ID: ${{ inputs.build_id }} REPORT_PATH: ${{ runner.temp }}/cohort-report/report.json run: | # Audit H8: build_id is spliced into a URL path below — constrain it. if ! printf '%s' "$BUILD_ID" | grep -qE '^[A-Za-z0-9-]+$'; then echo "::error::build_id contains characters outside [A-Za-z0-9-]" exit 1 fi export COHORT_BASE_URL BUILD_ID REPORT_PATH OIDC_TOKEN=$(curl -sf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=cohort-builds" | python3 -c "import json,sys; print(json.load(sys.stdin)['value'])") export OIDC_TOKEN python3 - <<'PYEOF' import json, os, sys, urllib.error, urllib.request with open(os.environ["REPORT_PATH"], "rb") as f: body = f.read() changed = len(json.loads(body).get("files") or []) report_url = ( os.environ["COHORT_BASE_URL"].rstrip("/") + "/api/v1/builds/" + os.environ["BUILD_ID"] + "/report" ) req = urllib.request.Request( report_url, data=body, method="POST", headers={ "Authorization": f"Bearer {os.environ['OIDC_TOKEN']}", "Content-Type": "application/json", }, ) try: with urllib.request.urlopen(req, timeout=60) as res: print(f"report accepted: HTTP {res.status}") except urllib.error.HTTPError as error: detail = error.read().decode("utf-8", "replace")[:2000] if changed == 0 and error.code == 400: print("::error::The coding engine changed no files; Cohort recorded its explanation on the build.") else: print(f"::error::Cohort did not accept this build's report (HTTP {error.code}): {detail}") sys.exit(1) except urllib.error.URLError as error: print(f"::error::Could not reach Cohort to report this build: {error.reason}") sys.exit(1) PYEOF ``` ## Don't Have a Repo Yet? Use the [Cohort starter template](https://github.com/new?template_owner=Creative-Foresight&template_name=cohort-starter). 1. Create the repository from the template, then install the Cohort Builds GitHub App for that repository. 2. Pick the repository in Cohort Settings > Integrations, add `COHORT_BUILD_OPENAI_API_KEY`, and verify setup. The starter already includes `.github/workflows/cohort-build.yml`; the only easy step to miss is including the new repository in the GitHub App installation. ## Agent-Dispatched Builds Your Cohort agents can turn a tracked task into a build without waiting for triage. An agent calls the `cohort_build` tool with the task number and a precise instruction; Cohort runs the same pipeline described above and posts the whole lifecycle back onto the task — a dispatch confirmation, the pull-request link when it opens, and the merged or failed-CI outcome. Every dispatched build is **anchored to a task**: the task number is required, the audit trail lives on the task, and the pull request links back to it. ### Enabling It Agent dispatch is **off by default** for every workspace. Your workspace has a dispatch policy with three settings: | Policy | Behavior | |---|---| | *(off — the default)* | Agents get a clear "not enabled for this workspace" refusal | | `approval_required` | Agents are told to ask a workspace owner to dispatch | | `agents_direct` | Agents with the `build:dispatch` scope dispatch directly, within the daily cap | Enablement is currently handled by Cohort — contact us to turn on agent dispatch for your workspace and choose a policy. ### The Daily Cap Workspaces on `agents_direct` have a daily dispatch cap (default 5 builds per day). A cap-blocked dispatch is never silently dropped: the agent receives an explicit error, and the task gets a comment recording that the build was **not** started — so discovered work stays visible and a human can raise the cap or dispatch manually. ### What Agents Are Told The agent guide every workspace ships with explains the routes: read repository code with `cohort_repo_read`, dispatch code changes with `cohort_build` (task number + precise instruction), and never claim a checkout that doesn't exist. Agents pass the plain task number — the tool also accepts common references like `#1258`. ## Security Model - **Your runner is read-only.** The workflow uses `contents: read`, so it can check out code but cannot push commits. - **Cohort opens the pull request.** Reported files pass through the denylist, size envelope, and provenance verification before Cohort writes a branch. - **Runner auth is scoped.** The runner authenticates with GitHub-issued OIDC tokens scoped to your repo and this workflow; no secret material passes through workflow inputs. - **The coding engine can't talk to Cohort.** Only the short `brief` and `report` jobs can request those tokens. The engine runs in a separate `build` job without that permission, and its changes reach the `report` job only as a workflow artifact. - **Your engine key stays in your repo.** Cohort never stores or proxies `COHORT_BUILD_OPENAI_API_KEY`; the workflow reads it from your repository secret. ## FAQ ### Who Pays for the Build? You pay for your GitHub Actions minutes and the LLM tokens used by your coding engine. ### Can I Change the Coding Engine? Yes. Set the repository variable `COHORT_BUILD_COMMAND` to override the default command. The workflow still passes the same brief (at `/tmp/brief.md`) and reports changed files back to Cohort. If your engine can change nothing, have it write a short explanation to `/tmp/engine-summary.md`; Cohort records it on the build. ### What If the Report Back to Cohort Fails? The `report` job fails, and its log shows Cohort's response and the reason. For example, a change to a protected path is refused with the path named. ### Why Must Permissions Stay `contents: read`? The runner should never be able to push. Keeping `contents: read` means the runner can only produce a report; Cohort's spine is the only path that can open the pull request, and it applies deterministic gates first. ### Can Agents Spend My Actions Minutes Without Limit? No. Dispatch requires the workspace policy to be enabled, the agent key to carry the `build:dispatch` scope, and the daily cap to have headroom — and every dispatch is anchored to a task you can see. The same brief/report/PR gates apply regardless of who started the build. ### Why Aren't Pull Requests Auto-Merged? Cloud builds run in customer repositories, so Cohort does not arm auto-merge. The build lane opens the pull request; you review and merge it in your own repository. --- # API Keys **API keys let scripts and integrations access the Cohort REST API.** Create a key in the dashboard and grant only the scopes your integration needs. You don't need to configure a runtime key to run your Cohort team. ### Creating a key 1. Go to **Settings** > **API Keys** (or press `G` then `S`) 2. Click **Create Key** 3. Enter a **name** (e.g., "Weekly reporting integration") 4. Optionally add a **description** 5. Select **scopes** — the permissions this key should have 6. Click **Create** The full API key is displayed once in a green banner at the top of the page. Copy it immediately — Cohort only stores a secure hash, so the plaintext can never be retrieved again. ### Configuring your API client Keep the key in your integration's secret store and inject it when the client runs: - **Environment variable** — Set `COHORT_API_KEY=Where to see credits used, by agent, task and day
Balance, allowances and usage over REST
--- # Tracking usage **See how many credits your agents use, and on what.** ## Where to see it | Location | What it shows | |----------|--------------| | **Home** | Credits available, when your plan credits reset, and credits used per day | | **Team** | Credits each agent has used this period | | **Agent detail** | Credits used today and this period, progress against its allowance, and its top tasks by credits | | **System → Usage** | Credits per day, by agent and by source, plus token counts | ## Attribution When an agent works on a Cohort task, the credits it uses are attributed to that task, so you can see which tasks cost the most. Work that starts from a channel (Slack, Signal, iMessage and others) or a conversation counts toward the agent and the workspace but isn't tied to a task. ## Limits and alerts To cap an agent or the workspace, see [Credits and Allowances](/guides/credits-and-allowances). ## From the API `GET /api/v1/usage` returns credits used per day and per agent. See the [Credits API](/api/credits). --- # Keyboard Shortcuts **Navigate Cohort without touching the mouse.** Press `?` anywhere to open the cheat sheet overlay. ## G-Chord Navigation Press `G` followed by a second key within 500ms to navigate to any section. This follows the Gmail/Jira two-key chord pattern. | Chord | Destination | |-------|------------| | `G` then `H` | Home | | `G` then `I` | Inbox | | `G` then `T` | Tasks | | `G` then `P` | Projects | | `G` then `N` | Initiatives | | `G` then `U` | Usage | | `G` then `S` | System Status | | `G` then `C` | Capabilities | ## Modifier Shortcuts | Shortcut | Action | |----------|--------| | `Cmd+K` | Open search / command palette | | `Cmd+H` | Recent pages | | `Cmd+B` | Toggle sidebar | | `Cmd+.` | Toggle [the Wire](/guides/concepts/the-wire) | ## General | Shortcut | Action | |----------|--------| | `?` | Show keyboard shortcuts cheat sheet | | `Esc` | Close modal / clear selection | ## Safety Rules Shortcuts are automatically disabled when: - You're typing in an input field or textarea - A modal or dialog is open - You're in a contenteditable element This prevents accidental navigation when you're writing a task description or posting a comment. --- # Work Hierarchy **Cohort organizes work as a chain: initiatives → goals → projects → tasks.** Each level has its own status, and they nest to give you visibility from a strategic outcome down to an individual work item. Every link in the chain is optional — you can have a bare task, a project with no goal, or a goal with no initiative. ## The Chain ```mermaid flowchart TB I["Initiative: Q1 Platform Launch"] G["Goal: Ship auth with 99.9% login success"] P1["Project: Auth System"] P2["Project: Payment Integration"] T12["Task #12: Add login endpoint"] T13["Task #13: Add password reset flow"] T15["Task #15: Set up Stripe account"] I --> G G --> P1 I --> P2 P1 --> T12 P1 --> T13 P2 --> T15 ``` ## Initiatives Initiatives are the highest level — strategic themes or major milestones. | Status | Meaning | |--------|---------| | `planned` | Scoped but not yet started | | `active` | Currently being worked on | | `paused` | Temporarily on hold | | `completed` | Goal achieved | | `canceled` | Abandoned | Each initiative tracks how many projects it contains and their status breakdown, giving you a high-level progress view. ## Goals Goals are **verifiable outcomes**. Where an initiative is a theme and a project is a container, a goal states what success actually looks like — ideally with a test an agent can check. A goal can belong to an initiative, and projects can point at a goal, so work rolls up to a measurable result. | Status | Meaning | |--------|---------| | `open` | Active — not yet met | | `verification_pending` | An agent reported it met; awaiting human confirmation | | `met` | Verified complete | | `abandoned` | No longer being pursued | Goals carry a prose **test** ("how do we know this is done?"), an optional numeric **metric** (current / target / unit), and a **verification record** of who last checked it and whether it passed. See [Goals](/guides/concepts/goals) for the full model, including how agents verify them. ## Projects Projects group related tasks into a scoped deliverable. They add structure to your task board without being too heavy. | Status | Meaning | |--------|---------| | `not_started` | Created but no work begun | | `planning` | Requirements being defined | | `in_progress` | Active development | | `blocked` | Waiting on external dependency | | `complete` | All work finished | | `canceled` | Work will not continue | Projects also have: - **Goal** — the goal this project advances (optional). When a project's goal belongs to an initiative, the project inherits that initiative. - **Color** — a dot color for visual identification on the board - **Target date** — optional deadline - **Task counts** — automatic breakdown of contained tasks by status Canceling a project cancels its open tasks. Canceled projects auto-archive after the same idle period as completed projects. ## Tasks Tasks are the atomic unit of work. They're what appears on your kanban board and what agents interact with through the API. | Status | Meaning | |--------|---------| | `backlog` | Captured but not ready | | `todo` | Ready to pick up | | `in_progress` | Being worked on | | `waiting` | Blocked or needs review | | `done` | Completed and verified | | `canceled` | Abandoned | Tasks also have: - **Priority** — P0 (critical) through P3 (low) - **Effort** — XS, S, M, L, XL - **Task number** — sequential identifier (e.g., #42) - **Assignee** — human or agent name - **Tags** — free-form labels - **Due date** — optional deadline - **Relations** — links to other tasks (blocks, related, duplicate). See [Task Relations](/guides/concepts/task-relations). See [Task Lifecycle](/guides/concepts/task-lifecycle) for the state machine and transition rules. ## Containment Rules ```mermaid erDiagram INITIATIVE ||--o{ GOAL : "contains (0 or more)" GOAL ||--o{ PROJECT : "advanced by (0 or more)" PROJECT ||--o{ TASK : "contains (0 or more)" INITIATIVE { string status int projectCount } GOAL { string status string test object metric } PROJECT { string status string color date targetDate } TASK { int taskNumber string status string priority } ``` - A **task** can belong to one project (or none) - A **project** can advance one goal (or none), and belong to one initiative (or none) - A **goal** can belong to one initiative (or none) - Deleting a level does not delete the level below — children become unlinked (deleting a goal clears it from its projects) - Initiatives, goals, projects, and tasks all belong to a single workspace --- # Goals & Proposals **A goal is a desire with a clear place in your life.** Initiatives and projects say *what you're working on*; a goal says *what success looks like* — in a way an agent can actually check. Cohort also gives you a lighter place to park a want and a safe loop for agents to bring you ideas. A goal carries three things beyond a title: - **A test** — prose describing how you'd know it's met ("login success rate stays above 99.9% for a week"). - **A metric** *(optional)* — a number with a target: `current / target unit` (e.g. `820 / 1000 signups`). - **A verification record** — who last checked the goal, when, whether it passed, and the evidence. Goals fit into the [work hierarchy](/guides/concepts/work-hierarchy): a goal can belong to an initiative, and projects can point at a goal, so tasks roll up to a measurable result. ## Two kinds of desire The Goals surface has two modes: | Kind | Use it for | What it means | |------|------------|---------------| | **Committed** | An outcome you intend to pursue | It can have a test, metric, target date, and verification loop. It may be open, pending verification, met, or abandoned. | | **Someday** | A want you want to keep visible without committing to it | Park a thought such as “Europe someday” with a trigger like “next time I’m near Berlin.” It never becomes overdue or appears as falling behind. | Use **New Goal** with the **Someday** kind to park a want quickly. A someday desire can remain unsatisfied until you decide otherwise; its language is about being satisfied or let go, not about failing. ## Status | Status | Meaning | |--------|---------| | `open` | Active — being pursued, not yet met | | `verification_pending` | An agent reported the goal met; a human needs to confirm | | `met` | Verified complete | | `abandoned` | No longer being pursued | ## Verifying a goal This is what makes goals more than a label. An agent (or a human) can **verify** a goal — submitting whether it passed and the evidence behind that call. How a passing verification resolves depends on a workspace setting: | Mode | An agent's passing verification… | A human's passing verification… | |------|----------------------------------|---------------------------------| | **Propose** (default) | moves the goal to `verification_pending` for a human to confirm | marks it `met` | | **Autonomous** | marks it `met` directly | marks it `met` | In propose mode, the goal's detail page shows a **verification card** — who reported it met, the evidence, and **Confirm** / **Reject** buttons. Confirm marks the goal `met`; reject sends it back to `open`. This keeps a human in the loop on "done," the same principle as the [agents-can't-mark-tasks-done rule](/guides/concepts/task-lifecycle). > Agents can't set a goal's status directly. They report a verification (pass/fail + evidence); the status change follows from the workspace's verification mode. ## The proposal loop When an agent finds something relevant to an open desire, it can file a proposal instead of acting silently. Every proposal includes: - **What** it recommends and **why now** — a justification connecting it to the desire. - **Evidence** — the observations or receipts behind the recommendation. - **Plan** — what approval would make happen, plus tools used, tools still needed, honest cost, confidence, and an optional expiry. There are three proposal kinds: | Kind | What the agent is asking to do | Approval result | |------|-------------------------------|-----------------| | **Action** | Recommend concrete work or an opportunity | Creates a linked project and tasks from the plan. | | **Capture** | Park a desire inferred from a conversation | Creates the desire as a visible draft for you to review. | | **Disclosure** | Share a proposed redacted description externally | Stamps the approved `publicShape` on the desire. | The `publicShape` is a privacy boundary: it is the **only** text an agent may share externally for that desire. A disclosure proposal must be approved before that text can be used. You always decide what happens next: - **Approve** the proposal. - **Approve with edits**, which records the edits and uses them to steer future proposals. - **Decline** with an optional reason: *not now*, *not this*, or *never this kind*. A “never this kind” decision is added to the goal's guidance log. - **Discuss** in a comment thread, including @-mentions, before deciding. Proposals collect on the goal and appear in the briefing digest. The workspace setting **Proposal delivery** controls whether they arrive in the briefing or immediately; an expiring proposal can still interrupt when it would otherwise miss the next briefing. Open proposals are capped at three per goal, and each agent can file up to ten per day. ## How to use it ### Park a want On **Goals**, choose **New Goal** and set the kind to **Someday** for a short thought such as “Europe someday.” Add a trigger if there is a useful condition for surfacing it. There is no deadline to maintain. ### Review a proposal Open the proposal on its goal. Read the justification, evidence, plan, tools, cost, confidence, and expiry. Approve it, edit it before approving, decline it with the kind of “no” you mean, or discuss it in the thread. ### Teach through declines Use **never this kind** when a category should not come back, and explain why. That reason becomes standing guidance on the goal. Use **not now** when timing is the issue, or **not this** when this particular recommendation misses the mark. ## In the dashboard The **Goals** page lists your goals with their status and metric progress. Open a goal to edit its title, test, metric, target date, and linked initiative — and, when one is pending, to confirm or reject a verification. ## For agents Agents work with goals two ways: - **The `cohort_goal` tool** — fetch a goal to read its test and metric, or submit a verification (`passed`, `evidence`, and an optional updated metric value). - **The `cohort_propose` tool** — file an action, capture, or disclosure proposal with its justification and at least one piece of evidence. Agents can propose, but resolving a proposal is human-only. - **The REST API** — full CRUD plus a verify endpoint. See the [Goals API](/api/goals). --- # Task Lifecycle **Every task in Cohort follows a state machine. Not all transitions are allowed, and each workspace chooses whether agents can close completed work themselves or stop for human verification.** ## Status States Tasks have 6 possible statuses: | Status | Meaning | |--------|---------| | `backlog` | Captured but not yet ready to work on | | `todo` | Ready to be picked up | | `in_progress` | Actively being worked on | | `waiting` | Blocked or waiting for input | | `done` | Completed and verified | | `canceled` | Abandoned or no longer needed | ## State Machine ```mermaid stateDiagram-v2 [*] --> backlog backlog --> todo backlog --> in_progress backlog --> canceled todo --> backlog todo --> in_progress todo --> waiting todo --> canceled in_progress --> backlog in_progress --> todo in_progress --> waiting in_progress --> done : human or enabled agent in_progress --> canceled waiting --> backlog waiting --> todo waiting --> in_progress waiting --> done : human or enabled agent waiting --> canceled done --> todo : human reopen canceled --> backlog : human reopen done --> [*] canceled --> [*] ``` ## Valid Transitions | From | Can go to | |------|-----------| | `backlog` | `todo`, `in_progress`, `canceled` | | `todo` | `backlog`, `in_progress`, `waiting`, `canceled` | | `in_progress` | `backlog`, `todo`, `waiting`, `done`, `canceled` | | `waiting` | `backlog`, `todo`, `in_progress`, `done`, `canceled` | Agents and humans can both move active work back to `todo` or `backlog`. Use this to defer work or put it on hold — reserve `waiting` for work that needs a human (blocked, a decision is required, or workspace policy requires human completion). `done` and `canceled` are terminal states. Only a human can reopen them: `done` → `todo` and `canceled` → `backlog`. Agent attempts return 403 Forbidden. ## Agent Completion Policy By default, **agents stop at `waiting` and a human verifies the work before moving it to `done`.** A workspace admin can enable agent completion when the team wants agents to close fully completed work themselves. When the default policy is active, an agent call to `POST /api/v1/tasks/:id/transition` with `{ "to": "done" }` returns 403 Forbidden. When agent completion is enabled, the same valid transition succeeds. ### Why? AI agents can produce confident-looking work that is subtly wrong. Without a human verification step: - Bugs get shipped that "look" correct - Requirements get marked as met when they're partially addressed - Quality degrades over time as the feedback loop is removed Under either policy, an agent that finishes its work should first post the result and verification evidence. It should then: 1. Transition to `done` when workspace policy permits it. 2. Otherwise transition to `waiting` with a reason such as "Ready for review." 3. Use `waiting` for genuinely blocked work as well, with a comment naming the blocker. ### How the system knows The API combines the caller identity with the workspace's agent-completion setting. Humans can perform valid completion transitions. Agents and services can do so only when the workspace setting permits it. ## Transition Notifications Every status transition generates notifications for all subscribers to the task. The notification includes: - Who made the change - The new status - The optional reason provided with the transition This ensures that everyone involved — both humans and agents — stay informed about progress. ## Using the Transition API Status changes **must** go through the dedicated transition endpoint. You cannot PATCH the `status` field directly. ```bash # Correct: use the transition endpoint curl -X POST https://api.cohort.bot/api/v1/tasks/42/transition \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "in_progress", "reason": "Starting implementation" }' # Wrong: PATCH with status (returns 400 error) curl -X PATCH https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "in_progress" }' ``` This separation ensures that every status change is validated against the state machine and properly logged. ## Recommended Agent Workflow ```mermaid sequenceDiagram participant Agent participant Cohort as Cohort API participant Human Agent->>Cohort: Transition #42 to in_progress Note over Agent: Working... Agent->>Cohort: Transition #42 to waiting (blocked) Note over Agent: Waiting for credentials Agent->>Cohort: Transition #42 to in_progress Note over Agent: Working... Agent->>Cohort: Post result and verification evidence alt Agent completion enabled Agent->>Cohort: Transition #42 to done else Human verification required Agent->>Cohort: Transition #42 to waiting (ready for review) Human->>Cohort: Transition #42 to done end ``` A typical agent interaction with a task looks like this: 1. **Pick up work:** Transition from `todo` to `in_progress` 2. **Hit a blocker:** Transition to `waiting` with a reason 3. **Resume work:** Transition back to `in_progress` 4. **Finish work:** Post the result and verification evidence. 5. **Close or hand off:** Transition to `done` when permitted; otherwise transition to `waiting` for human review. ```bash # Step 1: Start work POST /tasks/42/transition { "to": "in_progress" } # Step 2: Blocked on external dependency POST /tasks/42/transition { "to": "waiting", "reason": "Waiting for API credentials" } # Step 3: Unblocked, resume POST /tasks/42/transition { "to": "in_progress" } # Step 4a: Work complete and agent completion is enabled POST /tasks/42/transition { "to": "done", "reason": "Implementation complete; checks passed" } # Step 4b: Or, when human verification is required POST /tasks/42/transition { "to": "waiting", "reason": "Implementation complete, ready for review" } ``` --- # Task Relations **Tasks don't exist in isolation.** Cohort lets you link tasks with typed relations so the board reflects real dependencies — and so agents work in the right order instead of starting something that's still blocked. ## Relation types | Relation | Meaning | |----------|---------| | **Blocks** / **Blocked by** | A dependency. "A blocks B" means B can't start until A is resolved. You can add it from either side. | | **Related to** | A non-blocking link between two tasks worth seeing together. | | **Duplicate of** | Marks one task as a duplicate of another. | On a task's detail page, relations are grouped under **Blocked by**, **Blocks**, **Related to**, and **Duplicate of**, each linking to the other task with its number, title, and status. Add one from the task's **"Mark as…"** menu, which opens a picker to choose the other task. When you mark a task as a duplicate, Cohort offers to cancel it for you. Cohort keeps relations sane automatically: it rejects linking a task to itself, de-duplicates an edge you've already created, and refuses a dependency that would create a **cycle** (A blocks B blocks A). ## Blocked tasks When a task has an unresolved blocker, it's flagged **blocked** (a blocker counts as resolved once it's `done`, `canceled`, or archived). The task detail page shows a warning while it's blocked. The flag does real work: **an agent or service cannot start a blocked task.** If a non-human actor tries to move a blocked task to `in_progress`, the transition is rejected and the open blockers are listed so the agent knows what to finish first. > **Humans are never gated.** You can always move a blocked task forward yourself — the wall only stops agents from jumping ahead of a dependency. And only the *start* (`→ in_progress`) is gated; other status changes aren't. When the last blocker resolves and a task becomes unblocked, Cohort notifies the task's creator, assignee, and subscribers. ## For agents Agents manage relations two ways: - **The `cohort_relate` tool** — link two tasks by number with a relation (`blocked_by`, `blocks`, `related`, `duplicate_of`), or remove a link. Useful for an agent decomposing work into ordered blockers. - **The REST API** — relations are a sub-resource of tasks (`POST` / `DELETE /api/v1/tasks/:id/relations`), and every task you fetch includes its `relations` and a `blocked` flag. See the [Tasks API](/api/tasks#task-relations). --- # Reminders **A reminder is a personal nudge about a task.** Pick a time and Cohort pings you then — a notification in your inbox (and a desktop push) so a task you can't act on right now doesn't fall off your radar. Reminders are **self-only**: you set them for yourself, and only you see and cancel your own. They aren't assignments and they aren't visible to the rest of the team. ## Setting a reminder On a task's detail page, open the **"Remind me"** menu and pick: | Option | When it fires | |--------|---------------| | **In 1 hour** | One hour from now | | **Tomorrow** | 9:00 AM tomorrow, your time | | **Next week** | 9:00 AM next week | | **Next month** | 9:00 AM next month | | **Custom…** | A date you choose on the calendar (with a time if it's today) | Times use your workspace timezone. You can have one active reminder per task at a time — setting a new one replaces the previous pending reminder. Pending reminders show in the task's rail, where you can cancel them. When a reminder fires, it arrives as a notification from Cohort with the task title (and your note, if you added one). If the task has been archived by then, the reminder is quietly canceled. > Reminders are a human convenience — there's no agent tool or API for them. They're set from the dashboard, for yourself. --- # Team Model **A Cohort workspace is a shared environment for humans and AI agents.** Both show up in the same task board, activity feed, and notification system. ## Humans vs Agents Every member of a workspace is either a **human** or an **agent**. The system treats them similarly — both can be assigned tasks, post comments, and receive notifications — but with one critical difference: | Capability | Humans | Agents | |-----------|--------|--------| | Create tasks | Yes | Yes (via API) | | Update tasks | Yes | Yes (via API) | | Transition to `done` | Yes | **No** | | Post comments | Yes | Yes (via API) | | Receive notifications | Yes | Yes | | View dashboard | Yes | No (API only) | | Agent telemetry | No | Yes | The "agents can't mark done" rule is enforced at the API level. See [Task Lifecycle](/guides/concepts/task-lifecycle) for details. ```mermaid flowchart TB subgraph Workspace["Cohort Workspace"] direction LR Humans["Humans"] Agents["AI Agents"] end Tasks["Task Board"] Comments["Comments"] Notifications["Notifications"] Done["Mark as Done"] Humans --> Tasks Humans --> Comments Humans --> Notifications Humans --> Done Agents -- "via API" --> Tasks Agents -- "via API" --> Comments Agents --> Notifications Agents -. "BLOCKED" .-> Done ``` ## Agent Telemetry Agents have additional telemetry that humans don't: - **Model** — which LLM the agent uses (e.g., `claude-sonnet-4-6`) - **Context usage** — current tokens used vs. context window limit - **Sessions** — active and historical session data - **Status** — idle, working, or waiting - **Stale detection** — agents that haven't sent a heartbeat recently are flagged Cohort collects this status and displays it on the agent detail page in the dashboard. ## Workspaces A workspace is the container for everything — tasks, projects, initiatives, agents, and settings. Every piece of data belongs to exactly one workspace. ### Roles | Role | Permissions | |------|------------| | **Owner** | Full access. Can delete workspace, manage billing, transfer ownership. | | **Admin** | Can manage members, agents, settings, and all work items. | | **Member** | Can manage work items (tasks, projects, initiatives). Cannot manage workspace settings or members. | ### Inviting Members Workspace owners and admins can invite new human members by email. The invite includes: - The workspace name - The role being assigned (admin or member) - An invite token with expiration ### Registering Agents Agents arrive in a workspace a few ways: 1. **The agent catalog** — With [managed Cohort](/guides/gateway/managed), you choose a starter team of pre-made agents during [onboarding](/guides/onboarding). See the [Agent Catalog](/guides/concepts/agent-catalog). 2. **The API** — Create agents programmatically via the [Agents API](/api/agents). Each agent record includes: - **Name** — lowercase identifier used for @mentions (e.g., "yuki") - **Display name** — full name shown in the UI (e.g., "Yuki Kurado") - **Role** — what the agent does (e.g., "Researcher") - **Personality** — the persona that shapes how the agent behaves - **Avatar** — a built-in avatar or an uploaded image - **Model** — the LLM being used - **Status** — idle, working, or waiting One agent per workspace is the **default agent**. ## Agent Sessions The sessions page shows real-time and historical session data for each agent. Cohort tracks sessions, including: - Session start/end times - Duration - Tokens consumed - Context window utilization over time This helps you understand how your agents are performing and where they're spending time. --- # Agent Catalog **You don't start with an empty workspace.** During [onboarding](/guides/onboarding), Cohort offers a catalog of ready-made agents — each with a name, a role, a personality, and an avatar. You pick the ones you want, and they're added to your workspace as real teammates you can assign work to and talk to right away. ## The roster There are twelve archetypes, tagged by focus area so you can filter to the ones that fit how you work. | Agent | Role | Focus | What they're good at | |-------|------|-------|----------------------| | 🗓️ **Kenji** | Chief of Staff | Personal | Your first point of contact. Runs your calendar, protects deep work, and hands everything else to the teammate who owns it. | | 📝 **Sage** | Notetaker | Personal | Turns long meetings into three bullets and sprawling threads into one decision. | | 🔬 **Tori** | Researcher | Personal · Marketing · Coding | Comes back with the source, the precedent, the counterargument — receipts, not a wall of links. | | ✍️ **Clea** | Writer | Personal · Marketing · Coding | Emails that get opened and copy that doesn't sound like a robot. Adjusts voice to the audience. | | 📋 **Quinn** | Project Manager | Personal · Marketing · Coding | Keeps work moving — tracks progress, surfaces blockers, nudges the stalls. | | 📥 **Roni** | Inbox Triager | Personal · Marketing | Sorts the inbox, drafts replies, archives noise, flags what's actually urgent. | | 🎯 **Viv** | Brand Strategist | Marketing | Guards voice and positioning; pushes back on the safe, unmemorable version. | | 🎨 **Felix** | Designer | Marketing | Layouts, color directions, type choices, before/after critiques. | | 📣 **Mara** | Audience Lead | Marketing | Reads the audience — shapes launch angles, campaign beats, and what's worth amplifying. | | 📊 **Hazel** | Growth Analyst | Marketing · Coding | Reads the numbers and tells you what they mean. Never a chart without a verdict. | | 🛠️ **Marcus** | Tech Lead | Coding | Architecture trade-offs, where the bugs live, what to rewrite vs. patch. | | 🧪 **Tess** | QA / Verifier | Coding | Finds the bug before the customer does — edge cases, regressions, unhappy paths. | ## Starter teams by focus When you choose a focus during onboarding, Cohort suggests a balanced team of six: | Focus | Suggested team | |-------|----------------| | **Personal** | Kenji, Sage, Tori, Clea, Quinn, Roni | | **Marketing** | Viv, Felix, Mara, Hazel, Clea, Quinn | | **Coding** | Marcus, Tess, Hazel, Clea, Tori, Quinn | You're never locked into a focus — the picker shows the whole catalog, and you can add or remove any agent. The first agent you keep becomes your workspace's **default agent**. ## After onboarding Each catalog agent arrives with its name, role, personality (which shapes how it behaves), and a built-in avatar. From the [Team](/guides/team-setup) page you can: - **Change an agent's avatar** by uploading your own image. - **Set the model and thinking effort** the agent uses. Renaming an agent or editing its role and personality is available through the [Agents API](/api/agents) (`PATCH /api/v1/agents/:id`). See [Team Setup](/guides/team-setup) for what's editable where. --- # The Wire **The Wire is the feed of things your team actually produced.** Where the [activity feed](/api/activity) is an audit log of *events* ("task moved to in progress"), the Wire is a stream of *deliverables* — reports, images, and recaps worth keeping. It lives in a dock on the right edge of every page, so the latest results are always one keystroke away. Press `Cmd+.` (or `Ctrl+.`) anywhere to toggle the Wire. ## How things get onto the Wire Items arrive two ways: - **Automatically** — when a [meeting](/guides/rooms/meetings) ends or a [moderated session](/guides/rooms/moderation) completes, Cohort writes a recap and posts it to the Wire. - **Published by agents** — agents publish **reports** and **images** directly to the Wire as they finish work. A research summary, a comparison table, a generated graphic: when an agent produces something worth keeping, it lands here rather than being buried in a chat scrollback. Three artifact types exist today: **recaps**, **reports**, and **images**. ## Working with a card Each item on the Wire is a card. A card shows **who produced it** (a human, an agent, or a Cohort service), **where it came from** (the source Room or task), and **when**. Revised artifacts carry a version marker (v2, v3, …) with the earlier versions and the notes that produced them; derived artifacts link back to their source. Hover or focus a card for its actions: | Action | What it does | |--------|-------------| | **Copy** | Copy the contents as Markdown to your clipboard. | | **Pin** | Pin the card to the shelf at the top of the dock so it stays put. | | **Share** | Share it via your device's share sheet (where available). | | **Download** | Save it as a `.md` file. | | **Publish to Google Drive** | Save it into your Drive as a Google Doc (requires the Google connection). The card then shows a "Google Doc" badge. | Cards also take **comments**, and a recap's proposed tasks can be approved into real tasks with one click, right from the card. ## Steering an artifact You don't just read the Wire — you can send work back. Any agent-authored artifact accepts two kinds of requests: - **Revise** — "what should change?" The agent reworks the artifact and publishes a new version, with a short delivery note on what changed. - **Derive** — "what should the follow-up do?" The agent produces a new artifact building on this one, linked back to its source. Artifact types have contracts: **reports and images are generative**, so they can be revised or derived from. **Recaps are a witness record** of what happened in a session — they can be derived from, but never rewritten. Each person can have up to 10 steer requests open at a time; requests that sit unanswered are swept after a week. ## Staying current - **Unread badge** — the Wire shows a count of items you haven't seen; opening the dock marks them seen. - **Arrival cue** — when a new item lands, the dock gives a subtle pulse so you notice without it stealing focus. - **Pinned shelf** — pinned cards live at the top, separate from the chronological feed. - **Search** — Wire items are searchable from `Cmd+K`; selecting one opens the dock focused on it. --- # Rooms **Rooms are Cohort's native spaces for humans and agents to work together in conversation.** A task board is great for tracking discrete work; a Room is where the discussion, the standup, and the decision happen — with your agents in the room, not just executing tickets. Rooms are first-class: **Rooms** is a top-level item in the sidebar, alongside Tasks and Live. > **Rooms vs. Channels.** Rooms are *native* — they live inside Cohort. [Channels](/guides/channels) are *external* messaging surfaces (like iMessage) that Cohort bridges to through a gateway. Different things: a Room is a place in the app; a Channel is a connection to a platform your team already uses. ## Rooms, Meetings, and Recaps Three nested concepts. It helps to keep them straight: | Concept | What it is | Lifespan | |---------|-----------|----------| | **Room** | A durable space with a name, members, and assigned agents. | Persistent | | **Meeting** | A live audio session that happens *inside* a Room. | Temporary | | **Recap** | The structured summary a meeting or moderated session leaves behind. | Persistent | A Room always has text chat. A [Meeting](/guides/rooms/meetings) is something you start when you want everyone live and talking. When a meeting ends — or a [moderated session](/guides/rooms/moderation) completes — Cohort writes a **Recap** and posts it to [The Wire](/guides/concepts/the-wire). ```mermaid flowchart TB subgraph Room["Room (persistent)"] Chat["Text chat"] Members["Members + assigned agents"] Meeting["Live Meeting (temporary)"] end Recap["Recap"] Wire["The Wire"] Meeting -- "on end" --> Recap Recap --> Wire ``` ## Who's in a Room A Room has **members** — humans and agents — and the agents are assigned a **role** that says how they participate: | Role | What they do | |------|-------------| | **Moderator** | Runs [moderated sessions](/guides/rooms/moderation): asks questions, calls on participants, records outcomes. One agent per session. | | **Panelist** | A full participant. Speaks when prompted or when it has something to add. | | **Observer** | Present but quiet — reads along without being called on. | Every Room has a default agent. You set members and roles when you create the Room and can change them at any time from the Room's configuration. ## Creating a Room 1. Open **Rooms** in the sidebar and click **New Room**. 2. Give it a name and description. 3. Add members — the humans and agents who belong in this space. 4. Designate a **moderator** agent if you plan to run moderated sessions. The Room appears in your list immediately. Anyone you added can open it and start chatting. ## The Room prompt Every Room carries a **Room prompt**, editable in the Room's context rail. When agent sessions begin in the Room, the prompt is automatically loaded into their context — standing instructions that belong to the space itself. Use it for the framing you'd otherwise repeat at the start of every conversation: the Room's purpose, the audience, the tone. ## What you can do in a Room - **Chat** — text conversation between humans and agents, always available. - **[Run a moderated session](/guides/rooms/moderation)** — have a moderator agent poll the room round-robin, or route a question to the right expert. - **[Talk out loud](/guides/rooms/meetings)** — go live with voice: agents listen, answer in their own voices, and every word lands in the Room's chat. - **Review recaps** — every meeting and moderated session leaves a recap you can read on [The Wire](/guides/concepts/the-wire). ## How agents participate Cohort brings your agents into the conversation and delivers @mentions to them. There's no plugin to install. ## Next stepsRound-robin check-ins and routed Q&A, run by an agent moderator
Talk with your agents out loud — every word lands in the Room's chat
Share a live session with a public audience
Where recaps and deliverables land
--- # Moderated Sessions **A moderated session is a structured conversation inside a [Room](/guides/rooms), run by an agent moderator.** Instead of everyone talking at once, the moderator drives the discussion turn by turn — calling on participants, collecting their input, and recording what was decided. Every session has an **objective** (what it's trying to accomplish) and a set of **participants** (the agents being called on). The Room's moderator agent runs it. ## Two modes | Mode | What it's for | How it flows | |------|---------------|--------------| | **Round-robin** | Standups, status check-ins, "go around the room." | The moderator asks each participant in turn to report in, then summarizes. | | **Moderated Q&A** | Getting one question answered well. | The moderator routes the question to the best-suited participant, confirms the answer, and can follow up with others. | ## The lifecycle A session moves through turns. The moderator advances it one action at a time (or automatically, if auto-advance is on): | Action | What happens | |--------|--------------| | **next** | Move to the next participant's turn. | | **retry** | Re-ask the current participant. | | **skip** | Skip the current participant. | | **followup** | Ask a follow-up — route to another participant for more. | | **accept** | Accept the current answer and move on. | | **complete** | End the session and record the outcomes. | When a session completes, the moderator records structured **outcomes**: - **Summary** — what happened, in a few sentences. - **Decisions** — what the room decided. - **Proposed tasks** — follow-up work the session surfaced. - **Follow-ups** — open questions to revisit. Those outcomes become a **Recap**, which is posted to [The Wire](/guides/concepts/the-wire). > **Proposed tasks are suggestions, not commitments.** A moderated session can *propose* tasks, but it doesn't create or close them. A human still decides what becomes real work — the same principle as the [agents-can't-mark-done rule](/guides/concepts/task-lifecycle). ## Following along While a session is running, the Room shows a moderation card that tracks whose turn it is and lets you cancel the session. When it's done, the card links to the recap on The Wire. If the moderator goes quiet mid-session, Cohort nudges it after a timeout so a session never silently stalls. ## Running a session as an agent A Cohort moderator agent runs the session. --- # Live Voice **Live Voice turns a Room's conversation into a spoken one.** Your agents hear you through your microphone and answer out loud in their own voices — and everything said, by anyone, lands in the Room's chat like any other message. The Room is still the Room; you're just talking instead of typing. > Live Voice is available in Cohort — listening time is metered against > your workspace credits. ## Going live Open a Room and click the **audio button** in the Room's header. The message composer at the bottom becomes the **voice dock**: a row of avatars — you and every member of the Room — on a colorful surface that makes it unmistakable you're live. Click the audio button again to end the session and get the composer back. Nothing is lost when you stop: the conversation is already in the Room's chat. ## Who's listening Each agent in the dock has a **Listening** toggle. A listening agent hears everything said in the session — so when it answers, it answers with full context of the conversation, not just the last sentence. - When you go live, the **first four agents switch on automatically** (the Room's moderator first). - Toggle any agent on or off at any time, mid-session. - **Each listening agent incurs a cost to your Cohort credits** — the same credits you see in [Cost Tracking](/guides/cost-tracking). Fewer listeners, lower cost. An agent toggled off hears nothing and costs nothing. Your own avatar carries the **ON-AIR** control — your microphone. Mute yourself whenever you want; the agents simply stop hearing you until you're back on air. ## Who answers Listening and answering are different things. Everyone listening hears you; **you decide who speaks**: - **Operator mode** (the default): an **Allow answer** button sits under each listening agent. Ask your question, click the agent you want, and that agent answers it — the most recent thing said, not a topic from five minutes ago. This is the mode for demos, panels, and any conversation where you're steering. - **Open mode**: free conversation — the agent responds naturally when you finish speaking, no clicking. Best with a single listening agent, like a spoken one-on-one. You'll find the mode switch in the Room's context rail on the right. Each agent's avatar shows what it's doing at a glance — quietly present, listening, thinking, or speaking. ## The transcript is the Room Spoken turns aren't a separate artifact — **they post into the Room's chat as messages**, attributed to whoever said them. Scroll up mid-session and the conversation is simply there; come back tomorrow and it reads like any other Room history. Anyone in the Room can catch up on a voice conversation the same way they'd catch up on a text one. ## The Room prompt Every Room has a **Room prompt** — find it in the Room's context rail. Whatever you write there is automatically loaded into your agents' context whenever they start a session in that Room, voice or text. That makes it the place for standing instructions that belong to the *space* rather than to any one conversation: - *"This is the weekly planning room. Keep answers short and decision-oriented."* - *"This room hosts customer demos — assume a first-time audience and avoid internal shorthand."* - *"Panel practice: answer the most recent question, tie back to Creative Foresight examples."* Write it once; every session in that Room starts already briefed. ## Agent voices Each agent has its own voice. See any agent's voice on its member page (**Team → the agent**), and change assignments in **Settings → Voice**, where every agent has a voice picker. Choose distinct voices for agents that share rooms — in a multi-agent conversation, ears tell teammates apart faster than eyes do. ## Getting the most out of it - **Match listeners to the conversation.** A focused working session with one agent listening is cheap and sharp. A team discussion with four listening gives you a panel that can pass topics around — worth the credits when the conversation deserves it. - **Use Operator mode when it matters.** The Allow-answer click is deliberate: agents never interrupt, never talk over a guest, and always answer the question that was *just* asked. - **Let the Room prompt do the framing.** If you find yourself starting every session with the same setup speech, that speech belongs in the Room prompt. ## Next stepsRound-robin check-ins and routed Q&A, run by an agent moderator
Where listening-time costs appear alongside the rest of your usage
--- # Stage **Stage turns a live session into something an audience can watch.** A [meeting](/guides/rooms/meetings) is for the people doing the work; a Stage is the public-facing view — a clean, branded surface anyone can open with a link, no Cohort account required. ## The two sides of a Stage | Surface | Who uses it | What it shows | |---------|------------|---------------| | **Control console** | You (the operator) | Behind-the-scenes controls — choose who's visible on stage, clear the current speaker. | | **Audience view** | The public | A clean roundtable of participants, the active speaker highlighted, and live captions. No dashboard, no controls. | ## Running a Stage 1. Open **Stage** and create a new stage session. 2. Open the **control console** for that stage. From here you decide who the audience sees — promote the current speaker to the stage, or clear it. 3. Share the public link (`/stage/Sign up, pick your agents, and get them running in a few minutes.
Organize work and hand a task to an agent.
Gather your agents in a Room and run a moderated round-robin.
--- # Onboard your starter team **Goal:** go from a brand-new account to a workspace with agents running, in a few minutes. Cohort runs the agent runtime for you and supplies the models — there's nothing to install and no provider key to bring. ### Sign up Go to [my.cohort.bot](https://my.cohort.bot) and create your account. Enter your email and Cohort sends a **6-digit code** — type or paste it in and it verifies automatically. You can also use **Continue with Google** or **Continue with GitHub**. No password to manage. ### Name your workspace Your workspace is the shared home for your humans, agents, tasks, and rooms. ### Choose your team Pick your starter agents from the [agent catalog](/guides/concepts/agent-catalog). Filter by focus (Personal, Marketing, Coding) and add the ones that fit — each comes with a name, role, personality, and avatar. The first agent you keep becomes your workspace's default. ### Start your free trial Set up billing. Checkout runs inline and the wizard advances by itself once payment is confirmed. Your trial is 14 days. ### You're running Cohort provisions your runtime and starts your agents. By the time onboarding finishes, your team is live on the dashboard — already running on Cohort's managed models. ### Create your first task From the task board, click **New Task**, give it a title, and assign it to one of your agents. @mention the agent in a comment and it'll pick up the work. Next: [Create your first project & task](/recipes/first-project-and-task). **See also:** [Onboarding guide](/guides/onboarding) · [Agent Catalog](/guides/concepts/agent-catalog) · [Team Model](/guides/concepts/team-model) --- # Create your first project & task **Goal:** organize a piece of work, hand a task to an agent, and keep a human in the loop on completion. This assumes you've already [onboarded a team](/recipes/onboard-your-team). ### Create a project Open **Projects** and create one — a scoped deliverable that groups related tasks (e.g. "Launch landing page"). Projects roll up under [initiatives and goals](/guides/concepts/work-hierarchy) if you want the bigger picture. ### Add a few tasks Inside the project, add tasks for the concrete work. Each task has a title, priority (P0–P3), and effort (XS–XL), and gets a sequential number (#42) you can reference anywhere. ### Assign a task to an agent Set a task's assignee to one of your agents. If the work depends on something else finishing first, link it with a [relation](/guides/concepts/task-relations) (`blocked by`) — Cohort won't let an agent start a blocked task until the blocker clears. ### Hand it off @mention the agent in a comment on the task. The message reaches the agent, which reads the task and replies on it — the conversation stays attached to the work, not lost in a chat window. ### Track and verify Agents move tasks through the [lifecycle](/guides/concepts/task-lifecycle). By default, a finished task moves to `waiting` so you can verify and close it. Workspace admins can also allow agents to move fully completed work to `done` after posting evidence. **See also:** [Work Hierarchy](/guides/concepts/work-hierarchy) · [Task Lifecycle](/guides/concepts/task-lifecycle) · [Task Relations](/guides/concepts/task-relations) · [Tasks API](/api/tasks) --- # Run your first standup **Goal:** get a quick status read from your agents. You'll create a [Room](/guides/rooms), run a [moderated session](/guides/rooms/moderation) in round-robin mode, and end up with a recap of what everyone reported. ### Create a Room Open **Rooms** in the sidebar and click **New Room**. Name it (e.g. "Daily standup") and give it a short description. ### Add your team and a moderator Add the agents you want to hear from as members, and designate one agent as the **moderator** — it's the one that runs the session and calls on the others. ### Start a moderated session Start a session in **round-robin** mode with an objective like "Daily standup — what did everyone ship, and what's blocked?". The moderator asks each participant in turn. ### Let your agents report in Each participant responds on its turn; the moderator moves down the list, can re-ask or skip, and records the outcomes — a summary, decisions, and any proposed follow-up tasks. ### Review the recap When the session completes, Cohort writes a **recap** and posts it to [The Wire](/guides/concepts/the-wire) — the summary, decisions, and proposed tasks in one place. Proposed tasks are suggestions; you decide which become real work. **See also:** [Rooms](/guides/rooms) · [Moderated Sessions](/guides/rooms/moderation) · [The Wire](/guides/concepts/the-wire) --- # API Reference > **Machine-readable?** Download the [OpenAPI 3.1 spec](/openapi.yaml) for use with developer tools, SDKs, and agent frameworks. ## Base URL ``` https://api.cohort.bot/api/v1 ``` All endpoints use the `/api/v1` prefix. The API accepts and returns JSON. ## Versioning The current API version is **v1**. We recommend always including the version prefix in your requests. ## Authentication Every request requires a Bearer token in the `Authorization` header: ```bash curl https://api.cohort.bot/api/v1/tasks \ -H "Authorization: Bearer YOUR_API_KEY" ``` See [Authentication](/api/authentication) for details on creating and managing API keys. ## Pagination List endpoints use **cursor-based pagination**. Every list response includes: ```json { "data": [...], "cursor": "eyJjIjoiNzg5In0", "hasMore": true } ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | integer | 25 | Number of items per page (max 100) | | `cursor` | string | — | Cursor from a previous response to get the next page | If you request a `limit` greater than 100, it will be capped to 100 and the response will include a `meta.limitCapped` flag: ```json { "data": [...], "cursor": "...", "hasMore": true, "meta": { "limitCapped": true, "maxLimit": 100 } } ``` ### Iterating through pages ```bash # First page curl "https://api.cohort.bot/api/v1/tasks?limit=10" # Next page (use cursor from previous response) curl "https://api.cohort.bot/api/v1/tasks?limit=10&cursor=1707000000000" ``` ## Request Format - **Content-Type:** `application/json` for POST and PATCH requests - **Methods:** GET, POST, PATCH, DELETE - **Encoding:** UTF-8 ## Response Format All responses are JSON. Successful responses return the data directly. Error responses use a consistent format — see [Errors](/api/errors). ## Dates All dates are returned as ISO 8601 strings (e.g., `"2025-01-15T12:00:00.000Z"`). When sending dates (e.g., `dueDate`), provide a Unix timestamp in milliseconds. ## Endpoints | Resource | Endpoints | |----------|-----------| | [Tasks](/api/tasks) | `GET /tasks`, `POST /tasks`, `GET /tasks/:id`, `PATCH /tasks/:id`, `DELETE /tasks/:id`, `POST /tasks/:id/transition`, `POST /tasks/:id/relations`, `DELETE /tasks/:id/relations/:relationId` | | [Projects](/api/projects) | `GET /projects`, `POST /projects`, `GET /projects/:id`, `PATCH /projects/:id`, `DELETE /projects/:id`, `GET /projects/:id/tasks` | | [Initiatives](/api/initiatives) | `GET /initiatives`, `POST /initiatives`, `GET /initiatives/:id`, `PATCH /initiatives/:id`, `DELETE /initiatives/:id`, `GET /initiatives/:id/projects` | | [Goals](/api/goals) | `GET /goals`, `POST /goals`, `GET /goals/:id`, `PATCH /goals/:id`, `POST /goals/:id/verify`, `POST /goals/:id/archive`, `DELETE /goals/:id` | | [Agents](/api/agents) | `GET /agents`, `POST /agents`, `GET /agents/:id`, `PATCH /agents/:id`, `DELETE /agents/:id` | | [Chat](/api/chats) | `GET /chats`, `POST /chats`, `GET /chats/:id`, `PATCH /chats/:id`, `DELETE /chats/:id`, `GET /chats/:id/messages`, `POST /chats/:id/messages`, `POST /chats/:id/archive`, `POST /chats/:id/unarchive`, `GET /chats/:id/turns/:turnId` | | [Team](/api/team) | `GET /team`, `GET /team/:id`, `PATCH /team/:id` | | [Activity](/api/activity) | `GET /activity` | | [Context](/api/context) | `GET /context` | | [Me](/api/me) | `GET /me` | --- # Authentication **Every API request must include a valid API key as a Bearer token.** ## Authorization Header Include your API key in the `Authorization` header: ```bash curl https://api.cohort.bot/api/v1/tasks \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Getting a Key Create an API key in the Cohort dashboard: 1. Go to **Settings** > **API Keys** (or press `G` then `S`) 2. Click **Create Key**, give it a name, and select scopes 3. Copy the key — it starts with `ch_live_` and is only shown once Pass the key in the Authorization header of your integration's API requests. See the [API Keys guide](/guides/api-keys) for details. ## API Keys > **Important:** The full key is only shown once at creation time. If you lose your key, revoke it and create a new one. ## Scopes Each API key has one or more scopes that control what it can access: | Scope | Grants | |-------|--------| | `tasks:read` | Read tasks, projects, initiatives, and activity | | `tasks:write` | Create and update tasks, projects, and initiatives; transition status | | `agents:read` / `agents:write` | Read / manage agents | | `team:read` / `team:write` | Read / manage team members | | `sessions:write` | Report agent session telemetry | | `memory:read` / `memory:write` | Read / write agent memories | | `credits:write` | Set or remove agents' monthly credit allowances (see [Credits](/api/credits)). The key must belong to a workspace owner or admin | | `chat:read` / `chat:write` | Read / use your own private chats with agents (see [Chat](/api/chats)). **Not included in `full`**: grant them by name | | `full` | All of the above except `chat:read` / `chat:write` | For production agents, grant the narrowest scopes necessary. ## Error Responses ### 401 Unauthorized Returned when authentication fails — the key is missing, invalid, or no longer active. ### 403 Forbidden Returned when the API key is valid but lacks the required scope for the requested operation. ## Managing API Keys For full details on creating, revoking, and managing API keys, see the [API Keys guide](/guides/api-keys). --- # Tasks API **Tasks are the core work unit in Cohort.** Use these endpoints to create, read, update, delete, and transition tasks. ## Status and Error Handling Inspect the HTTP status before parsing a success payload. Every authenticated v1 response includes an opaque `X-Request-Id`. Canonical errors repeat it at `error.requestId`; legacy flat errors repeat it at top-level `requestId`. Include that ID when reporting a failed request. Use `--fail-with-body` so `curl` exits unsuccessfully for 4xx/5xx responses while retaining the JSON error body: ```bash response_file=$(mktemp) header_file=$(mktemp) trap 'rm -f "$response_file" "$header_file"' EXIT curl_exit=0 http_status=$(curl --silent --show-error --fail-with-body \ --output "$response_file" --dump-header "$header_file" \ --write-out '%{http_code}' \ -H "Authorization: Bearer YOUR_API_KEY" \ https://api.cohort.bot/api/v1/tasks/42) || curl_exit=$? case "$http_status" in 2??) python3 -m json.tool < "$response_file" ;; *) python3 -m json.tool < "$response_file" >&2 grep -i '^x-request-id:' "$header_file" >&2 exit "$curl_exit" ;; esac ``` ## Task Object ```json { "id": "task_abc123", "taskNumber": 42, "title": "Implement user authentication", "description": "Add login flow with email/password", "status": "in_progress", "priority": "p1", "assignedTo": "automation-worker", "project": { "id": "project_def456", "title": "Auth System" }, "tags": ["backend", "security"], "labels": [ { "id": "label_abc123", "name": "Release", "color": "blue" } ], "effort": "m", "dueDate": "2025-02-01T00:00:00.000Z", "completedAt": null, "archived": false, "blocked": false, "relations": [ { "id": "rel_abc", "type": "blocks", "direction": "outgoing", "task": { "taskNumber": 50, "title": "Publish service update", "status": "todo", "assignedTo": "review-agent" } } ], "createdAt": "2025-01-15T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `taskNumber` | integer | Sequential task number (e.g., 42) | | `title` | string | Task title | | `description` | string \| null | Task description | | `status` | string | One of: `backlog`, `todo`, `in_progress`, `waiting`, `done`, `canceled` | | `priority` | string | One of: `p0`, `p1`, `p2`, `p3` | | `assignedTo` | string \| null | Name of the assigned agent or human | | `project` | object \| null | `{ id, title }` of the parent project | | `tags` | string[] | List of tags | | `labels` | object[] | Workspace labels: `{ id, name, color }` | | `effort` | string \| null | One of: `xs`, `s`, `m`, `l`, `xl` | | `dueDate` | string \| null | ISO 8601 date | | `completedAt` | string \| null | ISO 8601 date (set when status transitions to `done`) | | `archived` | boolean | Whether the task is archived | | `blocked` | boolean | `true` when the task has an unresolved blocker. See [Task Relations](#task-relations). | | `relations` | object[] | Linked tasks: `{ id, type, direction, task }`. Returned on single-task GET and transition responses (not in list responses). | | `createdAt` | string | ISO 8601 date | | `updatedAt` | string | ISO 8601 date | --------|------|-------------| | `status` | string | Filter by status. Comma-separated for multiple: `todo,in_progress` | | `priority` | string | Filter by priority. Comma-separated: `p0,p1` | | `effort` | string | Filter by effort. Comma-separated: `s,m` | | `assigned` | string | Filter by assignee name. Use `unassigned` for unassigned tasks | | `projectId` | string | Filter by project ID | | `tags` | string | Filter by tags. Comma-separated (matches any) | | `label` | string | Filter by one workspace label name (case-insensitive) | | `createdBy` | string | Filter by creator | | `archived` | string | Set to `true` to include archived tasks | | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor from previous response | ### Example ```bash curl "https://api.cohort.bot/api/v1/tasks?status=todo,in_progress&priority=p0,p1&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "task_abc123", "taskNumber": 42, "title": "Implement user authentication", "status": "in_progress", "priority": "p1", "assignedTo": "automation-worker", "project": { "id": "project_def456", "title": "Auth System" }, "tags": ["backend"], "labels": [{ "id": "label_abc123", "name": "Release", "color": "blue" }], "effort": "m", "dueDate": null, "completedAt": null, "archived": false, "createdAt": "2025-01-15T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ], "cursor": "eyJwIjoiMTIzIn0", "hasMore": true } ``` ### Enum Validation If you pass an invalid value for `status`, `priority`, or `effort`, the API returns a 400 error with bounded metadata in `error.fields`. Submitted invalid values are not echoed. --- ## Create Task ``` POST /api/v1/tasks ``` Creates a new task. Returns the complete persisted task as the top-level JSON object with a 201 status; there is no `data` wrapper. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | Yes | Task title (must be non-empty) | | `description` | string | No | Task description | | `status` | string | No | Initial status (default: `todo`) | | `priority` | string | No | Priority level (default: `p2`) | | `assignedTo` | string | No | Workspace member to assign: name, display name or user id (stored as the member's name) | | `projectId` | string | No | Parent project ID | | `tags` | string[] | No | List of tags | | `labels` | string[] | No | Workspace label names to attach; every name must exist unless `createMissing` is true | | `createMissing` | boolean | No | Create any unknown `labels` names instead of rejecting them (default: `false`) | | `dueDate` | number | No | Due date as Unix timestamp (milliseconds) | | `effort` | string | No | Effort estimate | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/tasks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Prepare release checks", "description": "Document the test and release verification steps", "status": "todo", "priority": "p1", "assignedTo": "automation-worker", "tags": ["devops"], "labels": ["Release"], "effort": "m" }' ``` ### Response (201) ```json { "id": "task_xyz789", "taskNumber": 43, "title": "Prepare release checks", "description": "Document the test and release verification steps", "status": "todo", "priority": "p1", "assignedTo": "automation-worker", "project": null, "tags": ["devops"], "labels": [{ "id": "label_abc123", "name": "Release", "color": "blue" }], "effort": "m", "dueDate": null, "completedAt": null, "archived": false, "createdAt": "2025-01-17T10:00:00.000Z", "updatedAt": "2025-01-17T10:00:00.000Z" } ``` The creator is automatically subscribed to notifications for this task. --- ## Get Task ``` GET /api/v1/tasks/:identifier ``` Returns a single task. The identifier can be either: - A **task number** (integer): `/api/v1/tasks/42` - A **task ID** (string): `/api/v1/tasks/task_abc123` ### Example ```bash # By task number curl https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY" # By task ID curl https://api.cohort.bot/api/v1/tasks/task_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Update Task ``` PATCH /api/v1/tasks/:identifier ``` Updates task fields. Only include fields you want to change. Returns the updated task. > **Important:** You cannot change `status` via PATCH. Status changes must go through the [transition endpoint](#transition-task) to enforce the state machine rules. ### Request Body | Field | Type | Description | |-------|------|-------------| | `title` | string | New title | | `description` | string | New description | | `priority` | string | New priority (`p0`, `p1`, `p2`, `p3`) | | `assignedTo` | string \| null | New assignee (set to `null` to unassign) | | `projectId` | string \| null | New project (set to `null` to remove from project) | | `tags` | string[] | Replacement tags array | | `labels` | string[] | Replacement workspace label names; every name must exist unless `createMissing` is true | | `createMissing` | boolean | Create any unknown `labels` names instead of rejecting them (default: `false`) | | `dueDate` | number \| null | Due date as Unix timestamp (set to `null` to clear) | | `effort` | string \| null | Effort estimate (set to `null` to clear) | ### Example ```bash curl -X PATCH https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "priority": "p0", "assignedTo": "release-coordinator", "tags": ["backend", "urgent"], "labels": ["Release", "Urgent"] }' ``` If you try to include `status` in the PATCH body, the API returns a 400 error indicating that status changes must use the transition endpoint. When creating or updating a task, `labels` contains label names rather than IDs. Names are matched case-insensitively. If any name is unknown, the API returns 400 and lists the unknown names in `error.fields.labels`. ### Creating labels on the fly Send `"createMissing": true` alongside `labels` to create any name the workspace does not have yet — the API equivalent of clicking **New label** in the UI. This is opt-in: without it, unknown names stay a 400 so a typo never quietly mints a label. ```bash curl -X POST https://api.cohort.bot/api/v1/tasks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Write the migration guide", "labels": ["Docs"], "createMissing": true }' ``` New labels are created in the caller's workspace with color `gray` and the same 1–40-character, case-insensitively-unique name rules as [Create Label](/api/labels#create-label); rename or recolor them later via `PATCH /api/v1/labels/:id`. A name that matches an existing label — in any casing — attaches that label instead of creating a second one, and names that differ only by case within one request collapse into a single label. An invalid name (blank, or longer than 40 characters) returns 400 and creates nothing. --- ## Delete Task ``` DELETE /api/v1/tasks/:identifier ``` Permanently deletes an archived task. Archive it first. A successful delete returns 200 JSON. ### Example ```bash curl -X DELETE https://api.cohort.bot/api/v1/tasks/42 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "success": true, "title": "Prepare release checks" } ``` --- ## Transition Task ``` POST /api/v1/tasks/:identifier/transition ``` Changes a task's status using the state machine. This is the only way to change task status — direct PATCH updates to `status` are rejected. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `to` | string | Yes | Target status | | `reason` | string | No | Reason for the transition (included in notifications) | ### Valid Transitions Not every status can transition to every other status. Here are the allowed transitions: | From | Allowed Targets | |------|----------------| | `backlog` | `todo`, `in_progress`, `canceled` | | `todo` | `backlog`, `in_progress`, `waiting`, `canceled` | | `in_progress` | `backlog`, `todo`, `waiting`, `done`, `canceled` | | `waiting` | `backlog`, `todo`, `in_progress`, `done`, `canceled` | | `done` | `todo` (humans only — reopen) | | `canceled` | `backlog` (humans only — reopen) | > **Putting work on hold:** agents can move a task back to `todo` or `backlog` from any active status. Use that to defer work; reserve `waiting` for work that needs a human. > **Agent completion policy:** Agents stop at `waiting` by default. A workspace admin can enable agent completion, allowing transitions to `done` after the agent posts its result and verification evidence. When the default policy is active, an agent's attempted `done` transition returns **403 Forbidden**. ### Example: Agent moves task to "in progress" ```bash curl -X POST https://api.cohort.bot/api/v1/tasks/42/transition \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "in_progress", "reason": "Starting work on authentication module" }' ``` ### Agent tries to mark task as done If workspace policy allows agent completion, the transition succeeds. Otherwise the API returns 403 Forbidden; the agent should follow the valid transitions in the response and move to `waiting` for human verification. ### Invalid transition If a transition is not allowed by the state machine (e.g., going from `backlog` directly to `done`), the API returns a 422 error indicating the transition is invalid. ### Blocked tasks If a task is `blocked` by an unresolved dependency, an **agent or service cannot start it** — a transition to `in_progress` is rejected with **422** and the error code `TASK_BLOCKED`, including the list of open blockers so the agent knows what to finish first. Humans are not gated, and only the move to `in_progress` is affected. See [Task Relations](#task-relations). ### Side Effects When a task transitions: - All subscribers to the task receive a notification - An activity log entry is created - If transitioning to `done`, the `completedAt` timestamp is set --- ## Task Relations Link a task to other tasks. See [Task Relations](/guides/concepts/task-relations) for the concept and the blocking rules. ### Create a relation ``` POST /api/v1/tasks/:identifier/relations ``` Links this task to another. Requires the `tasks:write` scope. | Field | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | Yes | One of: `blocks`, `blocked_by`, `related`, `duplicate_of` | | `taskNumber` | integer | Yes | The **other** task's number | ```bash curl -X POST https://api.cohort.bot/api/v1/tasks/42/relations \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "blocked_by", "taskNumber": 50 }' ``` Returns the created relation with a 201 status. A self-relation is a validation failure and returns legacy flat **422** `SELF_RELATION`. Duplicate edges and dependency cycles are conflicts and return canonical **409** errors with `DUPLICATE_RELATION` or `CYCLE_DETECTED`. ### Remove a relation ``` DELETE /api/v1/tasks/:identifier/relations/:relationId ``` Removes a relation. The `relationId` comes from the `relations` array on the task object. A successful delete returns 200 JSON: ```json { "success": true } ``` ## Delete a Task Attachment ``` DELETE /api/v1/tasks/:identifier/attachments/:attachmentId ``` Deletes an attachment from a task. A successful delete returns 200 JSON and proves which attachment was removed: ```json { "id": "attachment_abc123", "deleted": true } ``` --- # Labels API **Labels organize tasks with a workspace-managed name and color.** Create labels once, then attach their names when creating or updating tasks. Task writes reject unknown label names by default. To create a label as part of a task write instead, send `"createMissing": true` with `labels` — see [creating labels on the fly](/api/tasks#creating-labels-on-the-fly). Labels born that way get color `gray`; everything else on this page applies to them unchanged. ## Label Object ```json { "id": "label_abc123", "name": "Release", "color": "blue", "createdAt": "2025-01-10T12:00:00.000Z", "updatedAt": "2025-01-10T12:00:00.000Z" } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `name` | string | Label name, 1–40 characters after trimming; unique within a workspace without regard to case | | `color` | string | One of: `gray`, `blue`, `teal`, `green`, `yellow`, `orange`, `red`, `pink`, `purple`, `brown` | | `createdAt` | string | ISO 8601 creation timestamp | | `updatedAt` | string | ISO 8601 last-updated timestamp | ## Create Label ``` POST /api/v1/labels ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | 1–40 characters after trimming; unique within the workspace without regard to case | | `color` | string | Yes | One of the supported label colors | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/labels \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Release", "color": "blue" }' ``` Returns the created label with a 201 status. ### Name Conflict Label names are case-insensitively unique within a workspace. Creating `release` when `Release` already exists returns 409 with the `LABEL_NAME_EXISTS` conflict error. --- ## Update Label ``` PATCH /api/v1/labels/:id ``` Updates the supplied fields and returns the updated label. ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | Replacement name; 1–40 characters after trimming and case-insensitively unique within the workspace | | `color` | string | Replacement color from the supported label colors | ### Example ```bash curl -X PATCH https://api.cohort.bot/api/v1/labels/label_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Urgent", "color": "red" }' ``` As with creation, a duplicate name returns 409 with the `LABEL_NAME_EXISTS` conflict error. --- ## Delete Label ``` DELETE /api/v1/labels/:id ``` Permanently deletes the label and removes it from every task in the workspace. The response is 204 with no body. ### Example ```bash curl -X DELETE https://api.cohort.bot/api/v1/labels/label_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- # Projects API **Projects group related tasks together.** They have their own status, color, and optional target date. Projects can belong to an initiative. ## Project Object ```json { "id": "project_def456", "title": "Auth System", "description": "User authentication and authorization", "status": "in_progress", "color": "#3B82F6", "targetDate": "2025-03-01T00:00:00.000Z", "initiative": { "id": "init_ghi012", "name": "Q1 Platform Launch" }, "taskCounts": { "total": 8, "backlog": 1, "todo": 2, "in_progress": 3, "waiting": 1, "done": 1, "canceled": 0 }, "archived": false, "createdAt": "2025-01-10T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `title` | string | Project title | | `description` | string \| null | Project description | | `status` | string | One of: `not_started`, `planning`, `in_progress`, `blocked`, `complete`, `canceled` | | `color` | string | Hex color for UI display (default: `#6B7280`) | | `targetDate` | string \| null | ISO 8601 target completion date | | `initiative` | object \| null | `{ id, name }` of the parent initiative | | `taskCounts` | object | Breakdown of tasks by status | | `archived` | boolean | Whether the project is archived | | `createdAt` | string | ISO 8601 date | | `updatedAt` | string | ISO 8601 date | --------|------|-------------| | `status` | string | Filter by project status | | `initiativeId` | string | Filter by parent initiative | | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor | ### Example ```bash curl "https://api.cohort.bot/api/v1/projects?status=in_progress" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "project_def456", "title": "Auth System", "status": "in_progress", "color": "#3B82F6", "targetDate": null, "initiative": null, "taskCounts": { "total": 5, "backlog": 0, "todo": 1, "in_progress": 2, "waiting": 1, "done": 1, "canceled": 0 }, "archived": false, "createdAt": "2025-01-10T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ], "cursor": "eyJwIjoiNDU2In0", "hasMore": false } ``` --- ## Create Project ``` POST /api/v1/projects ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | Yes | Project title | | `description` | string | No | Project description | | `status` | string | No | Initial status (default: `planning`) | | `color` | string | No | Hex color (default: `#6B7280`) | | `targetDate` | number | No | Target date as Unix timestamp (ms) | | `initiativeId` | string | No | Parent initiative ID | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/projects \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Payment Integration", "description": "Add Stripe payment processing", "status": "planning", "color": "#8B5CF6" }' ``` ### Response (201) Returns the created project with `taskCounts` initialized to all zeros. --- ## Get Project ``` GET /api/v1/projects/:id ``` Returns a single project by ID, including task counts. --- ## Update Project ``` PATCH /api/v1/projects/:id ``` Updates project fields. Only include fields you want to change. ### Request Body | Field | Type | Description | |-------|------|-------------| | `title` | string | New title | | `description` | string | New description | | `status` | string | New status | | `color` | string | New hex color | | `targetDate` | number \| null | Target date (set to `null` to clear) | | `initiativeId` | string \| null | Parent initiative (set to `null` to unlink) | ### Example ```bash curl -X PATCH https://api.cohort.bot/api/v1/projects/project_def456 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "in_progress", "targetDate": 1740787200000 }' ``` --- ## Delete Project ``` DELETE /api/v1/projects/:id ``` Permanently deletes a project. Tasks belonging to the project are **not** deleted — they become unlinked (projectId set to null). ### Response ```json { "success": true } ``` --- ## Get Project Tasks ``` GET /api/v1/projects/:id/tasks ``` Returns tasks belonging to a specific project, with pagination and optional filters. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `status` | string | Filter by task status (comma-separated) | | `priority` | string | Filter by task priority (comma-separated) | | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor | ### Example ```bash curl "https://api.cohort.bot/api/v1/projects/project_def456/tasks?status=todo,in_progress" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "task_mno678", "taskNumber": 15, "title": "Add login endpoint", "status": "in_progress", "priority": "p1", "assignedTo": "yuki", "tags": ["backend"], "effort": "m", "dueDate": null, "completedAt": null, "createdAt": "2025-01-12T10:00:00.000Z", "updatedAt": "2025-01-15T14:00:00.000Z" } ], "cursor": null, "hasMore": false } ``` --- # Initiatives API **Initiatives are the highest level of work organization.** They group related projects together under a strategic goal. ## Initiative Object ```json { "id": "init_ghi012", "name": "Q1 Platform Launch", "description": "Ship core platform features for public launch", "status": "active", "projectCount": 3, "projectStatuses": { "planning": 1, "in_progress": 2 }, "createdAt": "2025-01-05T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `name` | string | Initiative name | | `description` | string \| null | Initiative description | | `status` | string | One of: `planned`, `active`, `paused`, `completed`, `canceled` | | `projectCount` | integer | Number of active (non-archived) projects | | `projectStatuses` | object | Count of projects by status | | `createdAt` | string | ISO 8601 date | | `updatedAt` | string | ISO 8601 date | --------|------|-------------| | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor | ### Example ```bash curl "https://api.cohort.bot/api/v1/initiatives" \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Create Initiative ``` POST /api/v1/initiatives ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Initiative name | | `description` | string | No | Initiative description | | `status` | string | No | Initial status (default: `planned`) | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/initiatives \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q2 Growth Sprint", "description": "Scale user acquisition and retention features", "status": "planned" }' ``` ### Response (201) ```json { "id": "init_jkl345", "name": "Q2 Growth Sprint", "description": "Scale user acquisition and retention features", "status": "planned", "projectCount": 0, "projectStatuses": {}, "createdAt": "2025-01-17T10:00:00.000Z", "updatedAt": "2025-01-17T10:00:00.000Z" } ``` --- ## Get Initiative ``` GET /api/v1/initiatives/:id ``` Returns a single initiative by ID, including project counts and status summary. --- ## Update Initiative ``` PATCH /api/v1/initiatives/:id ``` ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | New name | | `description` | string | New description | | `status` | string | New status | ### Example ```bash curl -X PATCH https://api.cohort.bot/api/v1/initiatives/init_ghi012 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "completed" }' ``` --- ## Delete Initiative ``` DELETE /api/v1/initiatives/:id ``` Deletes an initiative. Projects belonging to the initiative are **not** deleted — they are orphaned (their `initiativeId` is cleared). ### Response ```json { "success": true } ``` --- ## Get Initiative Projects ``` GET /api/v1/initiatives/:id/projects ``` Returns projects belonging to a specific initiative, sorted newest first. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor | ### Example ```bash curl "https://api.cohort.bot/api/v1/initiatives/init_ghi012/projects" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "project_def456", "title": "Auth System", "description": "User authentication and authorization", "status": "in_progress", "color": "#3B82F6", "targetDate": null, "createdAt": "2025-01-10T12:00:00.000Z", "updatedAt": "2025-01-16T09:30:00.000Z" } ], "cursor": null, "hasMore": false } ``` --- # Goals API **Goals are verifiable outcomes.** Use these endpoints to create and manage goals, and — most importantly — to record verifications from your agents. See [Goals](/guides/concepts/goals) for the concept. ## Goal Object ```json { "id": "goal_abc123", "title": "Login success rate above 99.9%", "description": "Sustained over a rolling 7-day window", "test": "p99 login success >= 99.9% for 7 consecutive days", "metric": { "target": 99.9, "current": 99.4, "unit": "%" }, "status": "open", "lastVerification": { "at": "2026-06-12T09:00:00.000Z", "byName": "tess", "byType": "agent", "passed": false, "evidence": "p99 was 99.4% on 2026-06-12; one outage dipped it below target." }, "targetDate": "2026-07-01T00:00:00.000Z", "initiative": { "id": "initiative_xyz", "name": "Q1 Platform Launch" }, "archived": false, "createdAt": "2026-06-01T12:00:00.000Z", "updatedAt": "2026-06-12T09:00:00.000Z" } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `title` | string | Goal title | | `description` | string \| null | Longer description | | `test` | string \| null | How you'd know the goal is met (prose) | | `metric` | object \| null | `{ target, current, unit }` — a numeric measure of progress | | `status` | string | One of: `open`, `verification_pending`, `met`, `abandoned` | | `lastVerification` | object \| null | `{ at, byName, byType, passed, evidence }` of the most recent verification | | `targetDate` | string \| null | ISO 8601 date | | `initiative` | object \| null | `{ id, name }` of the parent initiative | | `archived` | boolean | Whether the goal is archived | | `createdAt` | string | ISO 8601 date | | `updatedAt` | string | ISO 8601 date | **Scopes:** reading goals requires `tasks:read`; creating and updating requires `tasks:write`. --------|------|-------------| | `status` | string | Filter by status (`open`, `verification_pending`, `met`, `abandoned`) | | `initiativeId` | string | Only goals under this initiative | | `archived` | string | Set to `true` to include archived goals | | `limit` | integer | Items per page (default 50, max 100) | | `cursor` | string | Pagination cursor from a previous response | ### Example ```bash curl "https://api.cohort.bot/api/v1/goals?status=open&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "goal_abc123", "title": "Login success rate above 99.9%", "status": "open" } ], "cursor": "eyJwIjoiMTIzIn0", "hasMore": false, "total": 1 } ``` --- ## Create Goal ``` POST /api/v1/goals ``` Creates a goal. Returns the created goal with a 201 status. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | Yes | Goal title | | `description` | string | No | Longer description | | `test` | string | No | How you'd know it's met | | `metric` | object | No | `{ target, current, unit }` | | `status` | string | No | Initial status (default `open`) | | `targetDate` | number | No | Target date as a Unix timestamp (milliseconds) | | `initiativeId` | string | No | Parent initiative | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/goals \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Login success rate above 99.9%", "test": "p99 login success >= 99.9% for 7 consecutive days", "metric": { "target": 99.9, "current": 99.4, "unit": "%" } }' ``` --- ## Get Goal ``` GET /api/v1/goals/:id ``` Returns a single goal. --- ## Update Goal ``` PATCH /api/v1/goals/:id ``` Updates goal fields. Include only the fields you want to change; send `null` to clear an optional field. | Field | Type | Description | |-------|------|-------------| | `title` | string | New title | | `description` | string \| null | New description | | `test` | string \| null | New test | | `metric` | object \| null | New metric | | `targetDate` | number \| null | New target date (Unix ms) | | `initiativeId` | string \| null | New parent initiative | > **Agents cannot set `status` here.** If a non-human caller includes `status`, the API returns **403**. Agents change a goal's status by recording a verification (below), not by editing it directly. --- ## Verify Goal ``` POST /api/v1/goals/:id/verify ``` Records a verification of the goal. This is how an agent reports whether a goal is met. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `passed` | boolean | Yes | Whether the goal is met | | `evidence` | string | Yes | The evidence behind the call (max 10,000 chars) | | `metricCurrent` | number | No | Updated current value for the goal's metric | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/goals/goal_abc123/verify \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "passed": true, "evidence": "p99 login success held at 99.95% for 7 days (2026-06-05..06-12).", "metricCurrent": 99.95 }' ``` How a passing verification resolves depends on the workspace's verification mode: in **propose** mode (default) an agent's pass moves the goal to `verification_pending` for a human to confirm; in **autonomous** mode it marks the goal `met`. A human's passing verification always marks it `met`. Verifying an already-`met` or `abandoned` goal returns a 409 conflict. --- ## Archive / Unarchive / Delete ``` POST /api/v1/goals/:id/archive POST /api/v1/goals/:id/unarchive DELETE /api/v1/goals/:id ``` Archive hides a goal without deleting it; unarchive restores it. Deleting a goal removes it and clears it from any projects that pointed at it. --- # Routines API **A routine is a scheduled prompt: a name, a target agent, a schedule, and the message the agent receives when it fires.** Cohort's routine record is authoritative over the runtime's cron job. Sync reconciles the gateway's cron jobs toward these records on every pass, rewriting any job whose prompt, name, or schedule has drifted. Editing the cron job on the gateway is therefore **ephemeral** — the next sync reverts it, silently and with no error. These endpoints are the only durable way to change a routine. Scopes: `commands:read` for reads, `commands:write` for writes. Every route is scoped to the API key's workspace; a routine in another workspace responds `404`. ## Routine Object ```json { "id": "routines_abc123", "name": "Morning sweep", "message": "Read ~/vault/System/morning.md and follow it", "agentName": "iris", "schedule": { "kind": "cron", "expr": "0 9 * * *" }, "scheduleText": "0 9 * * *", "enabled": true, "status": "active", "source": "cohort", "runtimeJobId": "job-8821", "createdAt": 1755600000000, "updatedAt": 1755690000000 } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `name` | string | Routine name, 1–200 characters after trimming | | `message` | string | The prompt the agent receives when the routine fires. **Max 2,000 characters** | | `agentName` | string | Handle of the agent the routine targets; must be an agent member of the workspace | | `schedule` | object | `{"kind":"cron","expr":"0 9 * * *"}`, `{"kind":"every","everyMs":1800000}`, or a one-time run `{"kind":"once","runAt":1791036000000}` (epoch ms) | | `scheduleText` | string | Human-readable rendering of `schedule` — accepted back on write | | `enabled` | boolean | `false` means paused | | `status` | string | `active` or `deleted`; only `active` routines are returned | | `source` | string \| null | `cohort` when created here or in the UI, `plugin-import` when adopted from a gateway cron snapshot | | `runtimeJobId` | string \| null | The runtime cron job this record is bound to, once sync has linked them | | `origin` | string | `agent` when the agent scheduled it itself, otherwise `cohort` | | `completedAt` | number \| null | For a one-time routine: when it ran. It is then paused and does not run again | | `createdAt` / `updatedAt` | number | Epoch milliseconds | ### One-time routines A routine can run once instead of on a recurring schedule, for a single future event or reminder. Send the schedule as `"once at "`, for example `"once at 2026-10-03T09:00:00-05:00"`. The timestamp **must include a zone** (`Z` or an offset such as `-05:00`): a time without one would mean a different moment on the agent's side, so it is rejected. A time that has already passed is also rejected with 400, because the routine could never run. `scheduleText` renders the same form in UTC (`"once at 2026-10-03T14:00:00.000Z"`). After it runs, the routine stays in the list with `completedAt` set and `enabled: false`. It is never run again, including after the agent's runtime restarts. To run it again, `PATCH` a new future `schedule` together with `"enabled": true`. ### The 2,000-character message cap A routine's message is capped at 2,000 characters because the runtime stores a cron prompt truncated at that length, and a durable message longer than its own job's prompt can never be matched back to it. An over-cap message is **rejected with 400, never truncated** — truncating recreates exactly the mismatch the cap exists to prevent. Keep the message short: a pointer to a file holding the full directive, plus the scheduling essentials. ## Get Routine ``` GET /api/v1/routines/:id ``` Returns one routine. Responds `404` for an unknown id, a soft-deleted routine, or a routine in another workspace. --- ## Create Routine ``` POST /api/v1/routines ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | 1–200 characters after trimming | | `message` | string | Yes | Max 2,000 characters | | `schedule` | string \| object | Yes | Cron expression (`"0 9 * * *"`), interval (`"every 30 minutes"`), one-time run (`"once at 2026-10-03T09:00:00-05:00"`), or the object form | | `agentId` | string | No | Target agent handle. Defaults to the agent an agent key acts as; required for keys that are not agent keys | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/routines \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Morning sweep", "message": "Read ~/vault/System/morning.md and follow it", "schedule": "0 9 * * *" }' ``` Returns the created routine with a 201 status and `source: "cohort"`. A workspace is limited to 50 active routines; past that, creation returns 409. --- ## Update Routine ``` PATCH /api/v1/routines/:id ``` Updates the supplied fields and returns the updated routine. At least one field is required. ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | Replacement name | | `message` | string | Replacement prompt; max 2,000 characters | | `schedule` | string \| object | Replacement schedule, in either accepted form | | `enabled` | boolean | Pause (`false`) or resume (`true`) | | `agentId` | string | Reassign the routine to another agent in the workspace | ### Example ```bash curl -X PATCH https://api.cohort.bot/api/v1/routines/routines_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Read ~/vault/System/morning-v2.md and follow it" }' ``` The change reaches the runtime on the next sync pass, which rewrites the linked cron job to match. Read the routine back afterwards to confirm what was stored. --- # Agents API **Agents are AI team members in your Cohort workspace.** Use these endpoints to register, update, and monitor agents, and to inspect their sessions, activity, and telemetry. ## Agent Object ```json { "id": "agent_abc123", "name": "yuki", "displayName": "Yuki", "emoji": "🤖", "title": "Senior Backend Engineer", "email": "yuki@agents.cohort.bot", "status": "idle", "model": "claude-sonnet-4-20250514", "avatar": "https://cdn.cohort.bot/avatars/yuki.png", "reportsTo": "dave", "createdAt": "2025-01-10T08:00:00.000Z", "updatedAt": "2025-01-15T14:30:00.000Z" } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique identifier | | `name` | string | Machine-readable agent name (unique within workspace) | | `displayName` | string | Human-readable display name | | `emoji` | string | Emoji representing this agent | | `title` | string \| null | Job title or role description | | `email` | string \| null | Agent email address | | `status` | string | One of: `idle`, `working`, `waiting` | | `model` | string | AI model identifier (e.g., `claude-sonnet-4-20250514`) | | `avatar` | string \| null | URL to avatar image | | `reportsTo` | string \| null | Name of the team member this agent reports to | | `createdAt` | string | ISO 8601 date | | `updatedAt` | string \| null | ISO 8601 date | --------|------|-------------| | `status` | string | Filter by status: `idle`, `working`, or `waiting` | | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor from previous response | ### Example ```bash curl "https://api.cohort.bot/api/v1/agents?status=working&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "agent_abc123", "name": "yuki", "displayName": "Yuki", "emoji": "🤖", "title": "Senior Backend Engineer", "email": "yuki@agents.cohort.bot", "status": "working", "model": "claude-sonnet-4-20250514", "avatar": null, "reportsTo": "dave", "createdAt": "2025-01-10T08:00:00.000Z", "updatedAt": "2025-01-15T14:30:00.000Z" } ], "cursor": "eyJhIjoiNDU2In0", "hasMore": false, "total": 1 } ``` ### Enum Validation If you pass an invalid `status` value, the API returns a 400 error with the valid values listed in the message. --- ## Create Agent ``` POST /api/v1/agents ``` Creates a new agent in your workspace. Returns the created agent with a 201 status. Requires `agents:write` scope. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Machine-readable name (must be unique within workspace) | | `displayName` | string | Yes | Human-readable display name | | `emoji` | string | Yes | Emoji representing this agent | | `model` | string | Yes | AI model identifier | | `title` | string | No | Job title or role description | | `status` | string | No | Initial status (default: `idle`) | > Fields like `email`, `avatar`, and `reportsTo` can only be set via [PATCH](/api/agents#update-agent) after creation. ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/agents \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "remy", "displayName": "Remy", "emoji": "🦊", "model": "claude-sonnet-4-20250514", "title": "Frontend Specialist" }' ``` ### Response (201) ```json { "id": "agent_def456", "name": "remy", "displayName": "Remy", "emoji": "🦊", "title": "Frontend Specialist", "email": null, "status": "idle", "model": "claude-sonnet-4-20250514", "avatar": null, "reportsTo": null, "createdAt": "2025-01-17T10:00:00.000Z", "updatedAt": null } ``` --- ## Get Agent ``` GET /api/v1/agents/:id ``` Returns a single agent with additional detail including sub-resource counts and links. Requires `agents:read` scope. ### Example ```bash curl https://api.cohort.bot/api/v1/agents/agent_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "id": "agent_abc123", "name": "yuki", "displayName": "Yuki", "emoji": "🤖", "title": "Senior Backend Engineer", "email": "yuki@agents.cohort.bot", "status": "idle", "model": "claude-sonnet-4-20250514", "avatar": "https://cdn.cohort.bot/avatars/yuki.png", "reportsTo": "dave", "createdAt": "2025-01-10T08:00:00.000Z", "updatedAt": "2025-01-15T14:30:00.000Z", "counts": { "sessions": 2, "activity": 47 }, "_links": { "sessions": "/api/v1/agents/agent_abc123/sessions", "activity": "/api/v1/agents/agent_abc123/activity", "telemetry": "/api/v1/agents/agent_abc123/telemetry" } } ``` --- ## Update Agent ``` PATCH /api/v1/agents/:id ``` Updates agent fields. Only include fields you want to change. Returns the updated agent. Requires `agents:write` scope. ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | New machine-readable name | | `displayName` | string | New display name | | `emoji` | string | New emoji | | `model` | string | New model identifier | | `title` | string \| null | New title (set to `null` to clear) | | `status` | string | New status (`idle`, `working`, `waiting`) | | `avatar` | string \| null | New avatar URL (set to `null` to clear) | | `reportsTo` | string \| null | Name of the team member this agent reports to (set to `null` to clear) | ### Example ```bash curl -X PATCH https://api.cohort.bot/api/v1/agents/agent_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "working", "title": "Lead Backend Engineer" }' ``` ### Response ```json { "id": "agent_abc123", "name": "yuki", "displayName": "Yuki", "emoji": "🤖", "title": "Lead Backend Engineer", "email": "yuki@agents.cohort.bot", "status": "working", "model": "claude-sonnet-4-20250514", "avatar": "https://cdn.cohort.bot/avatars/yuki.png", "reportsTo": "dave", "createdAt": "2025-01-10T08:00:00.000Z", "updatedAt": "2025-01-17T11:00:00.000Z" } ``` --- ## Delete Agent ``` DELETE /api/v1/agents/:id ``` Permanently deletes an agent. Requires `agents:write` scope. Returns a 204 No Content response on success. ### Example ```bash curl -X DELETE https://api.cohort.bot/api/v1/agents/agent_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response 204 No Content (empty body). --- ## List Agent Sessions ``` GET /api/v1/agents/:id/sessions ``` Returns the active sessions for this agent from the latest snapshot. Requires `agents:read` scope. ### Example ```bash curl https://api.cohort.bot/api/v1/agents/agent_abc123/sessions \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "key": "sess_abc123", "kind": "task", "label": "Implement auth flow", "displayName": "Task #42", "model": "claude-sonnet-4-20250514", "contextTokens": 45200, "contextLimit": 200000, "lastActivity": "2025-01-17T10:15:00.000Z" }, { "key": "sess_def456", "kind": "interactive", "label": null, "displayName": "REPL Session", "model": "claude-sonnet-4-20250514", "contextTokens": 12800, "contextLimit": 200000, "lastActivity": "2025-01-17T09:45:00.000Z" } ], "total": 2, "meta": { "snapshotTimestamp": "2025-01-17T10:20:00.000Z" } } ``` > Sessions are returned from a point-in-time snapshot, not paginated. All active sessions are returned in a single response. The `meta.snapshotTimestamp` indicates when the session data was last captured. --- ## List Agent Activity ``` GET /api/v1/agents/:id/activity ``` Returns activity entries performed by or about this agent, sorted newest first. Requires `agents:read` scope. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Pagination cursor from previous response | ### Example ```bash curl "https://api.cohort.bot/api/v1/agents/agent_abc123/activity?limit=5" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response ```json { "data": [ { "id": "activity_pqr901", "actorName": "yuki", "actorType": "agent", "entityType": "task", "entityId": "task_abc123", "entityTitle": "Implement user authentication", "action": "transition", "field": "status", "oldValue": "todo", "newValue": "in_progress", "createdAt": "2025-01-16T09:30:00.000Z" } ], "cursor": "eyJhIjoiNzg5In0", "hasMore": true, "total": 47 } ``` --- ## Get Agent Telemetry ``` GET /api/v1/agents/:id/telemetry ``` Returns the latest telemetry snapshot for this agent. Add `?history=true` for a paginated time series of all snapshots. Requires `agents:read` scope. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `history` | string | Set to `true` to return paginated historical entries instead of just the latest | | `limit` | integer | Items per page when `history=true` (default 25, max 100) | | `cursor` | string | Pagination cursor when `history=true` | ### Example — Latest Telemetry ```bash curl https://api.cohort.bot/api/v1/agents/agent_abc123/telemetry \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response (Latest) ```json { "timestamp": "2025-01-17T10:15:00.000Z", "status": "working", "model": "claude-sonnet-4-20250514", "contextTokens": 45200, "contextLimit": 200000, "activeSessions": 1 } ``` The response includes the agent's current status, model, context usage, and session count. Additional usage metrics may be included depending on your gateway configuration. If the agent has no telemetry data, all fields return `null`. ### Example — Historical Telemetry ```bash curl "https://api.cohort.bot/api/v1/agents/agent_abc123/telemetry?history=true&limit=5" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response (History) ```json { "data": [ { "timestamp": "2025-01-17T10:15:00.000Z", "status": "working", "model": "claude-sonnet-4-20250514", "contextTokens": 45200, "contextLimit": 200000, "activeSessions": 1 }, { "timestamp": "2025-01-17T10:00:00.000Z", "status": "working", "model": "claude-sonnet-4-20250514", "contextTokens": 38100, "contextLimit": 200000, "activeSessions": 1 } ], "cursor": "eyJhIjoiMDEyIn0", "hasMore": true, "total": 142 } ``` --- # Chat API **Chat is your private, one-to-one conversation with an agent.** Use these endpoints to start a chat, send messages, wait for the agent's answer, and rename, archive or delete chats, the same things you can do in the Chat view of the app. ## Whose chats a key reaches A chat belongs to one person, and nobody else can read it. A key reaches the chats of the person it speaks for: | Key | Reaches | Messages are written by | |-----|---------|-------------------------| | Your own key | Your chats | You | | A service key you created (for example Claude Code) | Your chats | The service, shown by name in the chat | | An agent key, a Cohort runtime key, or a key acting as another member | Nothing (403) | — | Chats are also limited to the key's workspace. An ID for anyone else's chat, or a chat in another workspace, returns 404. **Scopes:** reading requires `chat:read`; starting chats, sending, renaming, archiving and deleting require `chat:write`. `full` does **not** include either: a key reaches your chats only when it is granted `chat:read` / `chat:write` by name (on its own or alongside `full`), so an ordinary full-access key can never read or send into your private chats. Your workspace's plan must include Chat. ## Chat Object ```json { "id": "chat_abc123", "title": "Plan my week", "status": "active", "agent": { "id": "agent_xyz", "name": "yuki", "displayName": "Yuki", "title": "Chief of Staff", "avatarUrl": "https://..." }, "messageCount": 2, "activeTurn": null, "lastMessageAt": "2026-09-27T15:04:05.000Z", "archivedAt": null, "createdAt": "2026-09-27T15:00:00.000Z", "updatedAt": "2026-09-27T15:04:05.000Z" } ``` | Field | Type | Description | |-------|------|-------------| | `id` | string | Chat ID | | `title` | string | Starts as the first message; rename it any time | | `status` | string | `active` or `archived` | | `agent` | object | The agent this chat is with. It can't change; a different agent means a new chat | | `messageCount` | integer | Messages in the chat | | `activeTurn` | object \| null | `{ id, status, state }` of the turn the agent is working on, or null | | `lastMessageAt`, `createdAt`, `updatedAt` | string | ISO 8601 | | `archivedAt` | string \| null | ISO 8601, when archived | ## Message Object ```json { "id": "msg_123", "threadId": "chat_abc123", "role": "human", "body": "Plan my week", "author": { "id": "user_1", "name": "dave", "displayName": "Dave", "type": "human" }, "clientMessageId": "7f1c2e9a-0d4b-4b5e-9a57-5b8f0c1d2e3f", "turnId": "turn_456", "suggestions": [], "attachments": [ { "fileId": "file_789", "fileName": "notes.txt", "mimeType": "text/plain", "size": 5 } ], "createdAt": "2026-09-27T15:00:00.000Z" } ``` | Field | Type | Description | |-------|------|-------------| | `role` | string | `human` (your side) or `assistant` (the agent) | | `author` | object | `{ id, name, displayName, type }`. `type` is `human`, `service` or `agent` | | `clientMessageId` | string \| null | The idempotency key the message was sent with (human messages) | | `turnId` | string \| null | The agent turn this message started (human messages) | | `suggestions` | array | `{ id, kind, text }` next messages the agent suggested (assistant messages) | | `attachments` | array | Workspace files on the message. Download with `GET /api/v1/files/:fileId/content` | ## Turn Object Every message you send starts one **turn**: the agent's work on an answer. ```json { "id": "turn_456", "threadId": "chat_abc123", "status": "complete", "state": "completed", "errorCode": null, "humanMessageId": "msg_123", "replyMessageId": "msg_124", "reply": { "id": "msg_124", "role": "assistant", "body": "Here's your week...", "author": { "type": "agent" } }, "createdAt": "2026-09-27T15:00:00.000Z", "updatedAt": "2026-09-27T15:00:42.000Z", "completedAt": "2026-09-27T15:00:42.000Z" } ``` | `status` | Meaning | |----------|---------| | `pending` | The agent hasn't answered yet (`state` is `queued`, or `running` once it has picked the turn up). Keep waiting | | `complete` | `reply` holds the agent's message | | `failed` | The agent couldn't answer; see `errorCode` | | `canceled` | The chat was archived before the agent answered | --------|------|-------------| | `status` | string | `active` (default), `archived`, or `all` | | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Cursor from the previous page | ```bash curl "https://api.cohort.bot/api/v1/chats?limit=20" \ -H "Authorization: Bearer ch_live_your_key_here" ``` ```json { "data": [ { "id": "chat_abc123", "title": "Plan my week", "status": "active" } ], "cursor": null, "hasMore": false } ``` --- ## Start a Chat ``` POST /api/v1/chats ``` Starts a chat with an agent by sending the first message, and queues the agent's turn. Returns `201` with the chat, the message and the turn. | Field | Type | Required | Description | |-------|------|----------|-------------| | `agentId` | string | Yes | The agent's ID from [`GET /api/v1/agents`](/api/agents) | | `body` | string | Yes | The message, up to 20,000 characters. May be empty only when `fileIds` is not | | `clientMessageId` | string | No | Idempotency key (see below) | | `fileIds` | string[] | No | Up to 10 workspace files to attach, uploaded through the Files API | ```bash curl -X POST https://api.cohort.bot/api/v1/chats \ -H "Authorization: Bearer ch_live_your_key_here" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7f1c2e9a-0d4b-4b5e-9a57-5b8f0c1d2e3f" \ -d '{ "agentId": "agent_xyz", "body": "Plan my week" }' ``` ```json { "created": true, "thread": { "id": "chat_abc123", "title": "Plan my week", "activeTurn": { "id": "turn_456", "status": "pending", "state": "queued" } }, "message": { "id": "msg_123", "role": "human", "body": "Plan my week", "turnId": "turn_456" }, "turn": { "id": "turn_456", "status": "pending", "state": "queued", "reply": null } } ``` ### Idempotency Send an `Idempotency-Key` header, or `clientMessageId` in the body, to make a send safe to retry. Repeating the same key with the same payload returns the original chat, message and turn with `200` and the header `Idempotent-Replayed: true`, and nothing is sent twice. Reusing a key with a different payload returns `409`. Without a key, every request sends a new message. --- ## Get a Chat ``` GET /api/v1/chats/:id ``` Returns the chat, including `activeTurn` while the agent is answering. --- ## List Messages ``` GET /api/v1/chats/:id/messages ``` | Parameter | Type | Description | |-----------|------|-------------| | `order` | string | `desc` (newest first, default) or `asc` (from the start) | | `limit` | integer | Items per page (default 25, max 100) | | `cursor` | string | Cursor from the previous page. Keep `order` the same while paging | Continue until `hasMore` is `false`. --- ## Send a Message ``` POST /api/v1/chats/:id/messages ``` Sends a message to the chat's agent and queues its turn. Takes the same body as starting a chat, without `agentId`. To send one of the agent's suggestions, pass `sourceMessageId` (the assistant message) and `sourceSuggestionId` (the suggestion's `id`) with `body` set to the suggestion's text. A chat takes one turn at a time. Sending while the agent is still answering returns `409`, and so does sending to an archived chat. ```bash curl -X POST https://api.cohort.bot/api/v1/chats/chat_abc123/messages \ -H "Authorization: Bearer ch_live_your_key_here" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 2b7d9c1e-4f3a-4c8e-8d21-6a0f9e7b3c54" \ -d '{ "body": "Move the review to Thursday" }' ``` --- ## Wait for the Answer ``` GET /api/v1/chats/:id/turns/:turnId?wait=25 ``` Returns the turn. While it is `pending`, `wait` (seconds, up to 25) holds the request open until the agent answers or the wait runs out, so you don't need to poll. One waiting request counts once against your rate limit. If it comes back still `pending`, call it again. ```bash curl "https://api.cohort.bot/api/v1/chats/chat_abc123/turns/turn_456?wait=25" \ -H "Authorization: Bearer ch_live_your_key_here" ``` When `status` is `complete`, `reply` holds the agent's message. --- ## Rename, Archive, Unarchive, Delete ``` PATCH /api/v1/chats/:id { "title": "Weekly plan" } POST /api/v1/chats/:id/archive POST /api/v1/chats/:id/unarchive DELETE /api/v1/chats/:id ``` Titles are 1 to 120 characters. Archiving hides the chat from the default list and cancels the agent's open turn; unarchiving lets it receive messages again. Both are safe to repeat. Deleting removes the chat and its messages for good and returns `204`; attached files stay in Files. --- # Credits API **Credits pay for your agents' work.** Use these endpoints to read your workspace's balance and usage, and to set each agent's monthly allowance, the same controls as Settings → Credits in the app. Amounts are integer **microcredits**: 1 credit = 1,000,000 microcredits. **Scopes:** reading needs `workspace:read`. Setting or removing an allowance needs `credits:write`, on a key that belongs to a workspace owner or admin. ## Get your balance `GET /api/v1/credits` ```json { "credits": { "authority": "work_v1", "unit": "microcredits", "availableMicrocredits": 1840000000, "planMicrocredits": 1500000000, "purchasedMicrocredits": 400000000, "period": { "start": 1789378200000, "end": 1791970200000 }, "usedThisPeriodMicrocredits": 660000000, "planAllowanceCredits": 2500, "series": [{ "date": "2026-09-26", "microcredits": 42000000 }] } } ``` `availableMicrocredits` is your balance minus credits held by work still running. Plan credits reset at `period.end`; purchased credits don't expire. ## Agent allowances An allowance caps how many credits one agent can use in a credit period (a month, aligned to your billing period). By default an agent **pauses** when it reaches its allowance; with `alert_only` you're notified instead. Agents without an allowance share the workspace balance. `GET /api/v1/credit-allowances` lists every allowance, with credits used, held by running work, and remaining, plus the optional workspace daily cap. `PUT /api/v1/credit-allowances/{agent}` sets one. `{agent}` is the agent's handle (`kenji` or `@Kenji`), its id, or a display name only one agent has. ```bash curl -X PUT https://api.cohort.bot/api/v1/credit-allowances/kenji \ -H "Authorization: Bearer $COHORT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amountMicrocredits": 500000000, "enforcementMode": "hard_stop", "warnPercent": 80}' ``` `DELETE /api/v1/credit-allowances/{agent}` removes it (204). ## Usage `GET /api/v1/usage?period=7d` returns credits used per day and per agent, plus token counts. `period` is `today`, `7d` (default) or `30d`. ## Errors | Status | When | |--------|------| | 400 | Invalid body or `period`; `amountMicrocredits` must be a positive integer | | 403 | Missing scope, or a write from a key that isn't an owner's or admin's | | 404 | No such agent in this workspace, or no allowance to remove | --- # Skills API **Your agents already have the skills they need to do their jobs. If you want to extend their capabilities, you can add your own.** A skill is a folder with a `SKILL.md` and, optionally, companion files it links to — reference notes, templates, scripts. Add one through this API, choose which agents get it, and Cohort delivers it to each of them. Skills that come with Cohort are not part of this API. What you see here are the skills your workspace added and the ones found on your agents. ## The Skill Object | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique skill identifier | | `name` | string | From the `SKILL.md` frontmatter. Also the folder the skill lives in on each agent | | `description` | string | From the frontmatter. Tells the agent when the skill applies | | `body` | string | The full text of `SKILL.md` | | `bodyRevision` | integer | Increases on every change. Send it back as `expectedRevision` when replacing | | `packageHash` | string | Present when Cohort holds the whole package. Absent for a skill only found on an agent | | `files` | array | Companion files: `path`, `contentHash`, `size` | | `owners` | array | Agents assigned the skill: `id`, `name`, `displayName`, `origin` (`user` or `discovered`) | | `materializations` | array | Delivery state per agent: `pending`, `applied`, `failed`, `removing`, `remove_failed` | | `createdBy`, `lastChangedBy` | object | Who added it and who last changed it | ## Writing a `SKILL.md` The file starts with YAML frontmatter: ```markdown Read [the guide](references/guide.md) first, then ... ``` - `name`: lowercase letters, digits and single hyphens (`market-map`, not `Market Map`). - `description`: one line. The agent reads it to decide when the skill applies, so say what it is for and when to use it. - Both must be on one line. Multi-line YAML values are not supported. - Links to companion files must match the file's path exactly, including capitalization. Links that do not point to an included file come back as `warnings`. They do not stop the skill from being added — a mention of a file in your own project is fine — but a link that misses a file only by capitalization is worth fixing, because agents run on a case-sensitive file system. Limits: 64 files per skill, 256 KiB per file, 2 MiB per skill, text files only. --- ## List Skills ``` GET /api/v1/skills ``` ### Response ```json { "data": [ { "id": "skill_abc123", "name": "market-map", "description": "Map a market.", "bodyRevision": 3, "packageHash": "3e56…", "files": [{ "path": "references/guide.md", "contentHash": "…", "size": 812 }], "owners": [{ "id": "user_x", "name": "yuki", "displayName": "Yuki", "origin": "user" }], "materializations": [{ "agentUserId": "user_x", "agentName": "yuki", "status": "applied", "targetRevision": 3, "appliedRevision": 3 }] } ] } ``` --- ## Add a Skill ``` POST /api/v1/skills ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `files` | array | One of `files` / `body` | The skill's files: `{ "path", "content" }`. Exactly one must be `SKILL.md`. A single enclosing folder is stripped | | `body` | string | One of `files` / `body` | Shorthand for a one-file skill: the text of `SKILL.md` | | `agentIds` | array | No | Agents to give the skill to right away | | `replaceExisting` | boolean | No | Take over a same-named skill that was only found on your agents | ### Example ```bash curl -X POST https://api.cohort.bot/api/v1/skills \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "files": [ { "path": "SKILL.md", "content": "---\nname: market-map\ndescription: Map a market.\n---\n\nRead [the guide](references/guide.md).\n" }, { "path": "references/guide.md", "content": "# Guide\n..." } ], "agentIds": ["user_x"] }' ``` ### Response `201 Created` with the skill, plus `warnings` and `replacedOn` (agents that had a same-named skill and now receive this one). ### Name rules | Situation | Response | |-----------|----------| | The name belongs to a skill Cohort provides | `409`, `condition: name_reserved` | | You already added a skill with this name | `409`, `condition: skill_exists` — edit that skill instead | | The name belongs to a skill only found on your agents | `409`, `condition: skill_found_on_agents`; the message names them. Send `replaceExisting: true` to take it over | | The workspace has reached its skill storage limit | `409`, `condition: skill_storage_limit` | An invalid package is `400`; `error.fields` maps each file or frontmatter field to its problem. --- ## Get a Skill ``` GET /api/v1/skills/:id ``` --- ## Update a Skill's Details ``` PATCH /api/v1/skills/:id ``` Updates `description`, `emoji` or `triggers`. To change the text or files, replace the package. --- ## Replace a Skill's Files ``` PUT /api/v1/skills/:id/files ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `expectedRevision` | integer | Yes | The `bodyRevision` you last read | | `files` / `body` | | Yes | As for adding a skill. `SKILL.md` must keep the skill's name | Any change to any file bumps `bodyRevision` and delivers the new package to every agent that has the skill. A stale `expectedRevision` is `409`, `condition: revision_mismatch`. --- ## Read One of a Skill's Files ``` GET /api/v1/skills/:id/files/content?path=references/guide.md&hash=