{"id":51325245,"url":"https://github.com/cumbof/team","last_synced_at":"2026-07-01T17:33:01.041Z","repository":{"id":357244166,"uuid":"1234281055","full_name":"cumbof/team","owner":"cumbof","description":"Orchestrate a cluster of containerized local LLMs — each with its own persona, role, and goal — that collaborate until the work is done.","archived":false,"fork":false,"pushed_at":"2026-06-22T15:55:48.000Z","size":4444,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T17:25:04.784Z","etag":null,"topics":["agent","agent-orchestration","agent-team","agentic-ai","ai","ai-agents","ai-team","ai-workflow","cli","containerized","docker","llm","multiagent","ollama"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cumbof.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-10T01:23:17.000Z","updated_at":"2026-06-22T15:56:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cumbof/team","commit_stats":null,"previous_names":["cumbof/team"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cumbof/team","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumbof%2Fteam","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumbof%2Fteam/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumbof%2Fteam/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumbof%2Fteam/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cumbof","download_url":"https://codeload.github.com/cumbof/team/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cumbof%2Fteam/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35017089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","agent-orchestration","agent-team","agentic-ai","ai","ai-agents","ai-team","ai-workflow","cli","containerized","docker","llm","multiagent","ollama"],"created_at":"2026-07-01T17:33:00.962Z","updated_at":"2026-07-01T17:33:01.024Z","avatar_url":"https://github.com/cumbof.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# team\n\nOrchestrate a cluster of containerized local LLMs — each with its own\npersona, role, and goal — that collaborate until the work is done.\n\n![PyPI - Version](https://img.shields.io/pypi/v/team-core)\n![Build Status](https://img.shields.io/github/actions/workflow/status/cumbof/team/tests.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/cumbof/team/blob/main/LICENSE)\n\n![team](https://raw.githubusercontent.com/cumbof/team/refs/heads/main/assets/logo.png)\n\n\u003cp align=\"center\"\u003e\u003cb\u003e⭐ Star this repository to stay updated with new releases ⭐\u003c/b\u003e\u003c/p\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n`team` lets you describe a small \"organisation\" of LLMs in a single YAML\nfile and then bring it to life: every member runs in **its own isolated\nDocker container** with its own [Ollama](https://ollama.com/) daemon and\nits own model, the orchestrator drives a turn-based conversation between\nthem, and the members produce real artifacts (code, manuscripts, reports,\n…) in a shared workspace.\n\nYou can mix and match model sizes per role — e.g. a 70B generalist as a\nPrincipal Investigator, a 7B coder as a Data Scientist, an 8B model as a\nReviewer — and pick a workflow that matches how the work should flow:\n**round-robin**, **manager-driven**, or **review-loop until consensus**.\n\n\u003e [!WARNING]  \n\u003e\n\u003e **Work in Progress:** This repository is currently under active development.\n\u003e While the core functionality is present, some features may be incomplete or\n\u003e not fully work as expected, and you may encounter unexpected bugs. Please\n\u003e test thoroughly before using this in any critical pipelines.\n\n\u003e [!NOTE]\n\u003e\n\u003e A significant portion of the code and documentation in this repository\n\u003e was written **with the assistance of a Large Language Model (LLM)**.\n\u003e All LLM-generated contributions have been reviewed, tested, and curated\n\u003e by the human maintainers, but — as with any software — bugs may exist.\n\u003e Please review the code critically, run the test suite, and open an issue\n\u003e if you find something unexpected.\n\u003e\n\u003e **Pull requests are very welcome**, including those written or\n\u003e co-authored with the help of an LLM.  We only ask that you review and\n\u003e test your changes before submitting, and disclose AI assistance in your\n\u003e PR description (e.g. *\"co-authored with GitHub Copilot\"*) so reviewers\n\u003e can calibrate their review accordingly.\n\n---\n\n## Feature overview\n\n| Feature | Description |\n| --- | --- |\n| **Containerised members** | Every LLM runs in its own Docker + Ollama container with configurable CPU, RAM, and GPU limits. |\n| **Flexible workflows** | `round_robin`, `manager`, `review_loop`, `sequential_chain`, `debate`, `parallel_review` — pick or combine. |\n| **Shared workspace** | Members read and write real files (code, reports, data) to a host directory. |\n| **Agent tool use** | 19 built-in tools (Python, Bash, web search, file I/O, memory, beliefs, decisions, delegation); `tool_mode: text` (fenced blocks) or `tool_mode: native` (OpenAI/Ollama function-calling API with JSON Schema); extend with custom skills. |\n| **Predefined persona library** | 16 ready-made personas (`@pi`, `@engineer`, `@reviewer` …) stored as individual YAML files in `personas/`; extend with your own via `TEAM_PERSONA_DIR`. |\n| **Per-agent persistent memory** | SQLite-backed memory that survives between runs; agents `remember` and `recall` across sessions. |\n| **Shared team belief board** | Structured collective knowledge with confidence scores, voting, and consensus tracking. |\n| **Cross-team federation (bridge)** | Two independent `team` clusters can delegate tasks to each other over HTTP — academic-lab-style collaboration. |\n| **Shared institutional context** | Drop a `context.md` in the workspace root and every member sees it on every turn — no per-member config needed. |\n| **Decision log** | Members call `log_decision` to append timestamped, rationale-rich entries to `decisions.md`; any member can `read_decisions` at any time. |\n| **Workspace time-travel** | `team rollback` restores the workspace to any past checkpoint and lets you resume from there. |\n| **Human-in-the-loop** | Interrupt a live run, read the transcript, inject a message, and let the team continue. |\n| **OpenAI-compatible backends** | Swap Ollama for any OpenAI-compatible API (GPT-4o, Mistral, Together AI, …) per member. |\n| **Context window management** | `sliding_window`, `truncate`, or `summarize` strategies keep long runs within token budgets. |\n| **Workspace checkpoints** | Automatic snapshots before every member turn; `team restore` rolls back to any point. |\n| **Run statistics \u0026 reports** | Per-member token usage, turn counts, elapsed time — exportable as a Markdown report. |\n| **Interactive wizard** | `team new` walks you through YAML creation. |\n| **Structured JSON output** | Force a member to reply with valid JSON; optionally validate against a JSON Schema with automatic retry. |\n| **Per-turn timeout** | Hard wall-clock deadline per member turn; raises `TurnTimeoutError` if the LLM doesn't respond in time. |\n| **`team test`** | Define assertions in the YAML and run them automatically after a team workflow to verify outputs in CI. |\n| **Parallel member execution** | `workflow: type: parallel` — all members run simultaneously in each round, bounded by the slowest rather than the sum. |\n| **`team replay`** | Step through a saved transcript turn-by-turn in an interactive terminal viewer; navigate, search by speaker, and view stats. |\n| **Token budget** | Hard-cap total tokens per member per run; gracefully stops with `TokenBudgetError` when exhausted. |\n| **Conditional routing** | Members declare the next speaker via simple YAML rules (`if_contains`, `if_match`, `default`), enabling dynamic branching and state-machine-like workflows. |\n| **LLM retry with backoff** | Automatic retry with exponential backoff on transient errors (5xx, connection refused, timeout); configurable per member. Raises `LLMRetryExhaustedError` when all attempts fail. |\n| **Cost estimation** | Estimated USD cost displayed in the token-usage table after every run (`team run`, `team stats`). Built-in pricing for OpenAI, Anthropic, Google, and Mistral; local Ollama models show `$0.00 (local)`. |\n| **Multi-team pipelines** | Chain multiple team runs with `team pipeline`; upstream artifacts and transcript summaries are automatically injected into downstream stages via `inject_files`, `inject_context`, and `goal_override` templates. |\n| **Team registry (service discovery)** | A lightweight HTTP directory where running team clusters advertise their capabilities (tags, models, tools). Other teams discover and delegate to specialist clusters via `query_registry` or the CLI. |\n| **Federated belief board** | Independent team clusters share their collective knowledge across the bridge. Pull accepted beliefs from a partner team (they arrive as pending for local consensus), push local beliefs outward, or bidirectional sync — via the `sync_beliefs` tool or `team beliefs-sync` CLI. |\n\n---\n\n## Table of contents\n\n- [Why?](#why)\n- [How it works](#how-it-works)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Defining a team](#defining-a-team)\n  - [Top-level fields](#top-level-fields)\n  - [`defaults`](#defaults)\n  - [`workflow`](#workflow)\n  - [`members`](#members)\n- [The collaboration protocol](#the-collaboration-protocol)\n- [Predefined persona library](#predefined-persona-library)\n  - [How personas are stored](#how-personas-are-stored)\n  - [Available personas](#available-personas)\n  - [Using a persona in YAML](#using-a-persona-in-yaml)\n  - [Adding your own personas](#adding-your-own-personas)\n- [Workflows](#workflows)\n- [Workspaces and artifacts](#workspaces-and-artifacts)\n- [Containers, isolation, and root](#containers-isolation-and-root)\n- [GPU support](#gpu-support)\n  - [Apple Silicon / no-Docker Ollama](#apple-silicon--no-docker-ollama)\n- [OpenAI-compatible backends](#openai-compatible-backends)\n- [Remote / no-Docker Ollama](#remote--no-docker-ollama)\n- [Custom Ollama image](#custom-ollama-image)\n- [Context window management](#context-window-management)\n- [Model retention (`keep_alive`)](#model-retention-keep_alive)\n- [CLI reference](#cli-reference)\n- [Interactive wizard](#interactive-wizard)\n- [Pre-flight checks](#pre-flight-checks)\n- [Streaming output](#streaming-output)\n- [Per-turn timeout](#per-turn-timeout)\n- [LLM retry with backoff](#llm-retry-with-backoff)\n- [Resuming an interrupted run](#resuming-an-interrupted-run)\n- [Human-in-the-loop intervention](#human-in-the-loop-intervention)\n- [Agent mode and tool use](#agent-mode-and-tool-use)\n  - [Available built-in tools](#available-built-in-tools)\n  - [Custom skill plugins](#custom-skill-plugins)\n- [Shared institutional context](#shared-institutional-context)\n- [Decision log](#decision-log)\n- [Structured JSON output](#structured-json-output)\n- [Conditional routing](#conditional-routing)\n- [Token budget](#token-budget)\n- [Per-agent persistent memory](#per-agent-persistent-memory)\n  - [Enabling memory](#enabling-memory)\n  - [Memory tools](#memory-tools)\n  - [Memory config reference](#memory-config-reference)\n- [Shared team belief board](#shared-team-belief-board)\n  - [Enabling the belief board](#enabling-the-belief-board)\n  - [Belief tools](#belief-tools)\n  - [Inspecting beliefs with team beliefs](#inspecting-beliefs-with-team-beliefs)\n  - [Belief config reference](#belief-config-reference)\n- [Workspace checkpoints](#workspace-checkpoints)\n- [Workspace time-travel (`team rollback`)](#workspace-time-travel-team-rollback)\n- [Token usage tracking](#token-usage-tracking)\n- [Cost estimation](#cost-estimation)\n- [Run statistics](#run-statistics)\n- [Exporting a run report](#exporting-a-run-report)\n- [`team replay` — interactive transcript browser](#team-replay--interactive-transcript-browser)\n- [Automated testing with `team test`](#automated-testing-with-team-test)\n- [Multi-team pipelines](#multi-team-pipelines)\n- [Cross-team collaboration (bridge)](#cross-team-collaboration-bridge)\n  - [How it works](#how-it-works-1)\n  - [Exposing a team as a bridge server](#exposing-a-team-as-a-bridge-server)\n  - [Delegating work from another team](#delegating-work-from-another-team)\n  - [Named peer registry](#named-peer-registry)\n  - [Broadcasting to multiple teams](#broadcasting-to-multiple-teams)\n  - [Cancelling a remote task](#cancelling-a-remote-task)\n  - [Server HTTP API reference](#server-http-api-reference)\n  - [Bridge config reference](#bridge-config-reference)\n  - [Security — HMAC-SHA256 shared secret](#security--hmac-sha256-shared-secret)\n  - [Additional security considerations](#additional-security-considerations)\n- [Team registry (service discovery)](#team-registry-service-discovery)\n- [Federated belief board](#federated-belief-board)\n- [Examples](#examples)\n- [Architecture overview](#architecture-overview)\n- [Development](#development)\n- [Troubleshooting](#troubleshooting)\n- [License](#license)\n\n---\n\n## Why?\n\nA single LLM is a generalist. Real work — research, engineering, writing —\nis usually done by **several specialists** that disagree, revise, and\nconverge.  `team` makes it easy to assemble such a group locally:\n\n* **Heterogeneous models, one per role.** Use a small, fast model for\n  routine tasks and a large model only where it matters.\n* **Strong isolation.** Every member is a separate `ollama serve`\n  process in a separate container, on a private Docker network, with its\n  own model cache.  A misbehaving member cannot reach into another's\n  filesystem, network namespace, or model store.\n* **Real deliverables.** Members write actual files (code, prose, data)\n  into a shared workspace; you keep them after the run.\n* **Pluggable workflows.** Pick how the team coordinates — and add your\n  own in a few lines of Python.\n\n---\n\n\n## How it works\n\n```\n                 ┌────────────────── orchestrator (host) ───────────────────┐\n                 │                                                          │\n                 │   transcript.jsonl     shared workspace (./runs/\u003cteam\u003e)  │\n                 │        ▲                       ▲                         │\n                 │        │ append every turn     │ files written by members│\n                 └────┬───┴────────────┬──────────┴─────────────┬───────────┘\n                      │                │                        │\n                      ▼                ▼                        ▼\n       ┌──────────────────┐  ┌───────────────────┐     ┌──────────────────┐\n       │ container: pi    │  │ container: postdoc│     │ container: ...   │\n       │ ollama serve     │  │ ollama serve      │     │                  │\n       │ model: 70B       │  │ model: 8B         │     │                  │\n       │ /workspace (ro+) │  │ /workspace (ro+)  │     │ /workspace (ro+) │\n       │ /private         │  │ /private          │     │ /private         │\n       └──────────────────┘  └───────────────────┘     └──────────────────┘\n                       \\\\              |                //\n                        \\\\             |               //\n                       team-\u003cname\u003e-net (private bridge network)\n```\n\nFor each member, the orchestrator:\n\n1. Starts a dedicated Ollama container, on a per-team Docker network, with\n   the team's shared workspace bind-mounted at `/workspace` and a\n   per-member private workspace at `/private`.\n2. Pulls the model the member is configured to use (cached in the\n   member's own named Docker volume).\n3. Builds a system prompt from the member's persona, the team goal, the\n   list of teammates, and the [collaboration protocol](#the-collaboration-protocol).\n4. Asks the chosen [workflow](#workflows) to drive the conversation.\n\nAt every turn the orchestrator hands the speaking member the **full\nshared transcript** plus a snapshot of the workspace; the member's reply\nis parsed for fenced `file:` blocks (which become real files on disk) and\nfor control tokens (`[[TEAM_DONE]]`, `NEXT: @\u003cmember\u003e`, `APPROVED`, …).\n\n---\n\n\n## Requirements\n\n* **Linux** host (tested) — macOS works if Docker Desktop has enough\n  resources for your models.\n* **Docker** (engine ≥ 20.10) reachable by the host user.\n* **Python 3.9+**.\n* For GPU acceleration: NVIDIA GPU + the\n  [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).\n* **Disk and RAM/VRAM** sized for your largest model — Ollama itself is\n  small but model weights aren't.\n\n---\n\n\n## Installation\n\nInstall from PyPI:\n\n```bash\npip install team-core\n```\n\nOr clone the repository for the latest development version:\n\n```bash\ngit clone https://github.com/cumbof/team.git\ncd team\npython -m venv .venv\n. .venv/bin/activate\npip install -e .\n```\n\nInstalls the `team` CLI into your virtualenv.  Verify:\n\n```bash\nteam --version\nteam --help\n```\n\nFor development extras (pytest):\n\n```bash\npip install -e \".[dev]\"\npytest -q\n```\n\n---\n\n\n## Quick start\n\n1. Generate a starter spec:\n\n   ```bash\n   team init my-team.yaml\n   ```\n\n2. Edit `my-team.yaml`: pick model names that exist in Ollama, write a\n   real `goal`, and tweak the personas.\n\n3. Run it end-to-end (containers come up, models get pulled if needed,\n   workflow runs, containers come down):\n\n   ```bash\n   team run my-team.yaml\n   ```\n\n4. Inspect the deliverables:\n\n   ```bash\n   ls runs/my-team/shared/\n   team transcript my-team.yaml\n   ```\n\n5. Or manage the lifecycle by hand:\n\n   ```bash\n   team up my-team.yaml          # start all member containers\n   team status my-team.yaml      # show container state\n   team logs my-team.yaml        # tail Ollama logs per member\n   team run my-team.yaml --no-up --keep-up   # run more rounds\n   team run my-team.yaml --resume            # resume after a crash\n   team down my-team.yaml --purge            # tear down + delete model caches\n   ```\n\n---\n\n\n## Defining a team\n\nA team is a single YAML file.  Annotated minimal example:\n\n```yaml\nname: my-team                # [a-z][a-z0-9_-]{0,30}\ngoal: |\n  Plain-English statement of what the team must accomplish.\n\nworkspace: ./runs/my-team    # host directory; created on demand\n\nworkflow:\n  type: round_robin          # round_robin | manager | review_loop\n  max_rounds: 6\n\ndefaults:\n  ollama_image: ollama/ollama:latest\n  context_window: 8192\n  temperature: 0.4\n  gpus: none                 # \"all\" | \"none\" | [0, 1, ...]\n  memory_limit: \"16g\"        # optional Docker memory cap per member\n  cpu_limit: 4               # optional Docker CPU cap per member (cores)\n  pull_timeout: 1800\n  request_timeout: 600\n\nmembers:\n  - name: lead\n    role: Project Lead\n    model: llama3.1:8b\n    persona: |\n      You coordinate the team.\n  - name: worker\n    role: Engineer\n    model: qwen2.5-coder:7b\n    persona: |\n      You implement code and produce concrete artifacts.\n```\n\n### Top-level fields\n\n| field | required | description |\n| --- | --- | --- |\n| `name` | yes | DNS-safe team name; used in container/volume/network names. |\n| `goal` | yes | The shared objective every member sees in its system prompt. |\n| `workspace` | no | Host directory for shared/private workspaces and the transcript.  Defaults to `./runs/\u003cname\u003e`. |\n| `workflow` | no | See below.  Defaults to `round_robin` with 6 rounds. |\n| `defaults` | no | Defaults inherited by every member that doesn't override them. |\n| `members` | yes | Non-empty list of member specs (see below). |\n\n### `defaults`\n\n| key | type | default | meaning |\n| --- | --- | --- | --- |\n| `ollama_image` | string | `ollama/ollama:latest` | Image used for member containers. |\n| `context_window` | int | `8192` | `num_ctx` passed to Ollama (`/api/chat` `options`). |\n| `temperature` | float | `0.4` | Sampling temperature. |\n| `top_p` | float | `0.9` | Top-p sampling. |\n| `memory_limit` | string | unset | Docker `mem_limit` per member (e.g. `\"12g\"`). |\n| `cpu_limit` | float | unset | Docker CPU cap per member (cores; e.g. `4`). |\n| `gpus` | str / list | `none` | `\"all\"`, `\"none\"`, or list of GPU indices. |\n| `pull_timeout` | int | `1800` | Seconds allowed for a model pull. |\n| `request_timeout` | int | `600` | HTTP timeout per chat call. |\n| `backend` | string | `ollama` | LLM backend: `\"ollama\"` or `\"openai_compat\"`. |\n| `api_key` | string | unset | API key for `openai_compat` backend; supports `\"env:VAR\"`. |\n| `context_strategy` | string | `none` | Context management: `\"none\"`, `\"sliding_window\"`, `\"truncate\"`, `\"summarize\"`. |\n| `context_budget` | int | `0` | Budget for context management: max turns (`sliding_window`) or approx token count (`truncate`/`summarize`). |\n| `tools` | list | `[]` | Built-in tools enabled for all members by default. |\n| `max_tool_rounds` | int | `10` | Maximum agentic tool-call rounds per member turn. |\n| `tool_timeout` | int | `300` | Seconds budget per individual tool execution (generous default to allow package installs). |\n| `tool_mode` | string | `\"text\"` | Tool invocation mode: `\"text\"` (fenced blocks) or `\"native\"` (LLM function-calling API). |\n| `skills` | list | `[]` | Skill plugin sources (local paths or remote URLs) available to all members. |\n| `ollama_url` | string | unset | Route **all** members to an existing Ollama instance at this URL instead of starting Docker containers. Per-member `ollama_url` overrides this. See [Apple Silicon / no-Docker](#apple-silicon--no-docker-ollama). |\n| `keep_alive` | string | `\"-1\"` | How long Ollama keeps a model loaded in RAM after a request. `\"-1\"` (default) means keep forever — models stay resident between turns. Accepts any Ollama duration string (`\"5m\"`, `\"1h\"`) or `\"0\"` to unload immediately after each call. |\n\n### `workflow`\n\n```yaml\nworkflow:\n  type: review_loop\n  max_rounds: 4\n  producer: postdoc\n  reviewer: reviewer\n  approve_token: APPROVED   # only review_loop; default \"APPROVED\"\n  manager: tech_lead        # only when type=manager\n  prompt_template: |        # only sequential_chain; {prev_speaker} and {prev_content} available\n    @{prev_speaker} produced the following. Refine it:\n    {prev_content}\n```\n\n| `type` | extra options |\n| --- | --- |\n| `round_robin` | none |\n| `manager` | `manager: \u003cmember name\u003e` |\n| `review_loop` | `producer: \u003cmember\u003e`, `reviewer: \u003cmember\u003e`, optional `approve_token` |\n| `sequential_chain` | optional `prompt_template` (supports `{prev_speaker}`, `{prev_content}`) |\n| `debate` | `pro: \u003cmember\u003e`, `con: \u003cmember\u003e`, `judge: \u003cmember\u003e`, optional `rounds` |\n| `parallel_review` | `producer: \u003cmember\u003e`, `reviewers: [m1, m2, …]` (≥2), `synthesizer: \u003cmember\u003e`, optional `approve_token` |\n\n### `members`\n\n| key | required | notes |\n| --- | --- | --- |\n| `name` | yes | DNS-safe; used as `@handle` in the protocol. |\n| `role` | yes | Free-text role label. |\n| `model` | yes | Any tag known to Ollama (`llama3.1:8b`, `qwen2.5-coder:7b`, …). |\n| `persona` | yes | Free-text persona prompt; quoted block. |\n| `temperature`, `top_p`, `context_window` | no | Per-member overrides of `defaults`. |\n| `memory_limit`, `cpu_limit`, `gpus` | no | Per-member resource overrides. |\n| `can_write_files` | no | Default `true`; set to `false` to forbid this member from creating files. |\n| `extra_system` | no | Free-form text appended to the rendered system prompt. |\n| `ollama_url` | no | Connect to an existing Ollama instance directly; skips Docker. |\n| `backend` | no | `\"ollama\"` (default) or `\"openai_compat\"` — overrides `defaults.backend`. |\n| `api_base` | no | Base URL for the OpenAI-compat API (required when `backend: openai_compat`). |\n| `api_key` | no | API key; supports `\"env:VAR\"` to read from an environment variable. |\n| `context_strategy` | no | Per-member override of context management strategy. |\n| `context_budget` | no | Per-member override of context budget. |\n| `tools` | no | List of tool names this member may use (e.g. `[web_search, run_python]`). |\n| `max_tool_rounds` | no | Per-member override of the tool-round limit. |\n| `tool_timeout` | no | Per-member override of the per-tool execution timeout (seconds, default 300). |\n| `tool_mode` | no | Per-member override: `\"text\"` or `\"native\"` (default inherits from `defaults.tool_mode`). |\n| `skills` | no | Member-specific skill sources merged with `defaults.skills`. |\n| `keep_alive` | no | Per-member override for Ollama model retention (e.g. `\"5m\"`, `\"-1\"`). Inherits from `defaults.keep_alive` when absent. |\n\n---\n\n\n## The collaboration protocol\n\nEvery member receives a system prompt that includes a small,\ndeterministic protocol so the orchestrator can parse replies reliably:\n\n* **Address a teammate**: prefix a section with `@\u003cmember\u003e:`.\n* **Write or overwrite a file in the shared workspace**: emit a fenced\n  block with an `file:` info-string, e.g.\n\n  ````\n  ```file:manuscript/manuscript.md\n  # Title\n  ...\n  ```\n  ````\n\n  The orchestrator atomically writes the body to that path under\n  `\u003cworkspace\u003e/shared/`.  Path-traversal attempts (`..`) are rejected.\n* **Private workspace**: each member has `/private` inside its container\n  (mapped to `runs/\u003cname\u003e/members/\u003cmember\u003e/` on the host) for personal\n  scratch files, drafts, and notes that are not shared with the team.\n  The list of files currently in `/private` is shown at the top of each\n  of the member's turn prompts.\n* **Declare the goal achieved**: end the reply with a line containing\n  exactly `[[TEAM_DONE]]`.  Workflows interpret this as \"stop now\".\n* **Manager workflow**: end the reply with `NEXT: @\u003cmember\u003e` to nominate\n  who speaks next.\n* **Review-loop workflow**: the reviewer emits `APPROVED` (configurable)\n  when the deliverable is ready.\n\n---\n\n\n## Predefined persona library\n\nWriting a good persona from scratch takes time.  `team` ships with\n**16 ready-made personas** spanning academic research, software engineering,\nand general-purpose roles.  Each persona lives in its own YAML file under\n`personas/` at the root of this repository — making them easy to read,\nedit, and contribute back to the project.\n\n### How personas are stored\n\n```\npersonas/\n├── pi.yaml            # Principal Investigator\n├── postdoc.yaml       # Postdoctoral Researcher\n├── phd.yaml           # PhD Student\n├── reviewer.yaml      # Critical Reviewer\n├── statistician.yaml  # Statistician\n├── bioinformatician.yaml\n├── ml_researcher.yaml\n├── architect.yaml\n├── engineer.yaml\n├── qa.yaml\n├── devops.yaml\n├── tech_writer.yaml\n├── analyst.yaml\n├── writer.yaml\n├── manager.yaml\n└── ethicist.yaml\n```\n\nEach file follows the same simple format:\n\n```yaml\nrole: Principal Investigator\ndescription: Lab director — sets research direction, evaluates results, writes grants.\npersona: |\n  You are a tenured Principal Investigator at a research university.\n  Your role is to set and guard the scientific direction of the project.\n  ...\n```\n\nThe filename stem (e.g. `pi` from `pi.yaml`) becomes the `@`-key used in team\nYAML files.\n\n### Available personas\n\n| Key | Role | Description |\n| --- | --- | --- |\n| `@pi` | Principal Investigator | Lab director — sets research direction, evaluates results, writes grants. |\n| `@postdoc` | Postdoctoral Researcher | Senior researcher — deep expertise, drives experiments and analysis. |\n| `@phd` | PhD Student | Junior researcher — literature review, baseline experiments, drafting. |\n| `@reviewer` | Critical Reviewer | Peer-review skeptic — challenges assumptions, finds weaknesses. |\n| `@statistician` | Statistician | Statistical methodologist — study design, power, inference correctness. |\n| `@bioinformatician` | Bioinformatician | Omics data specialist — pipelines, databases, variant/sequence analysis. |\n| `@ml_researcher` | Machine Learning Researcher | ML specialist — model design, training, evaluation, ablations. |\n| `@architect` | Software Architect | System designer — API contracts, scalability, tech decisions. |\n| `@engineer` | Software Engineer | Implementer — writes production-quality code, debugs, reviews PRs. |\n| `@qa` | QA Engineer | Quality assurance — test strategy, edge cases, regression detection. |\n| `@devops` | DevOps / SRE | Infrastructure and reliability — CI/CD, monitoring, deployment. |\n| `@tech_writer` | Technical Writer | Documentation specialist — clarity, structure, audience-appropriate prose. |\n| `@analyst` | Data Analyst | Data explorer — EDA, visualisation, dashboards, business insights. |\n| `@writer` | Science Writer | Communicator — translates technical findings into compelling narratives. |\n| `@manager` | Project Manager | Coordinator — milestones, blockers, stakeholder communication. |\n| `@ethicist` | AI / Research Ethicist | Ethics and compliance — bias, fairness, privacy, responsible use. |\n\nBrowse the library from the terminal:\n\n```bash\nteam personas              # list all personas with key, role, description\nteam personas pi           # print the full persona text for @pi\nteam personas engineer     # print the full persona text for @engineer\n```\n\n### Using a persona in YAML\n\nSet `persona` to `@\u003ckey\u003e` instead of writing a persona block:\n\n```yaml\nmembers:\n  - name: alice\n    model: llama3.1:70b\n    persona: \"@pi\"              # role is set to \"Principal Investigator\" automatically\n  - name: bob\n    model: llama3.1:8b\n    persona: \"@phd\"             # role is \"PhD Student\"\n  - name: carol\n    model: qwen2.5:7b\n    persona: \"@reviewer\"        # role is \"Critical Reviewer\"\n```\n\nYou can override the default role while keeping the library persona text:\n\n```yaml\n  - name: alice\n    model: llama3.1:70b\n    persona: \"@pi\"\n    role: \"Lab Director\"        # custom title; persona text stays the same\n```\n\nYou can also mix library personas with fully custom ones in the same team:\n\n```yaml\nmembers:\n  - name: alice\n    model: llama3.1:70b\n    persona: \"@pi\"\n  - name: custom\n    role: Domain Expert\n    model: llama3.1:8b\n    persona: |\n      You are a specialist in protein crystallography with 20 years of\n      experimental experience. You validate all structural claims against\n      PDB data.\n```\n\n### Adding your own personas\n\n**Option 1 — contribute to the built-in library** (share with everyone):\n\nDrop a `.yaml` file into the `personas/` directory at the repo root and submit\na pull request.  The file name becomes the `@`-key.\n\n**Option 2 — project-local personas** (private to your setup):\n\nPoint `TEAM_PERSONA_DIR` at any directory; files there are loaded *in addition\nto* the built-in library and take precedence over built-in keys with the same\nname:\n\n```bash\nexport TEAM_PERSONA_DIR=~/.team/personas\n```\n\nThen add files like `~/.team/personas/clinician.yaml`:\n\n```yaml\nrole: Clinical Research Collaborator\ndescription: Translates findings into clinical context and regulatory language.\npersona: |\n  You are a physician-scientist with expertise in clinical trial design.\n  You translate pre-clinical findings into clinical hypotheses, identify\n  regulatory hurdles (FDA, EMA) early, and ensure the team's outputs are\n  framed for a clinical audience.\n```\n\nAny team YAML can now use `persona: \"@clinician\"` once the env var is set.\n\n---\n\n\n## Workflows\n\n### `round_robin`\n\nEvery member speaks in declaration order.  Repeat for `max_rounds` full\nrounds, or until a member emits `[[TEAM_DONE]]`.  Useful for brainstorms\nand small symmetric teams.\n\n### `manager`\n\nA designated `manager` member opens the work, then after every other\nmember's turn the manager is asked again to evaluate progress and\nnominate the next speaker via `NEXT: @\u003cmember\u003e`.  The manager can also\ntake the floor itself, or end the run with `[[TEAM_DONE]]`.\n\n### `review_loop`\n\nA `producer` writes the first draft.  A `reviewer` critiques it; the\nproducer revises; repeat until the reviewer emits `APPROVED` (or\n`max_rounds` revisions are reached).  When approved, the producer is\ngiven one final turn to finalise and is expected to end with\n`[[TEAM_DONE]]`.  Ideal for any \"make a deliverable, then iterate until\nacceptable\" workflow (papers, design docs, code).\n\n### `sequential_chain`\n\nMembers form a **pipeline**: the first member runs with the default\nprompt, then each subsequent member receives the previous member's full\nreply as its explicit prompt.  At the end of a round the chain wraps\naround, so the first member of round N+1 receives the last member of\nround N's output.\n\nUse this when the work is a transformation series — for example:\n\n* drafter → editor → translator → formatter\n* researcher → summariser → chart-generator\n\nOptional `prompt_template` controls how the handoff is framed; it can\nuse the `{prev_speaker}` and `{prev_content}` placeholders:\n\n```yaml\nworkflow:\n  type: sequential_chain\n  max_rounds: 2\n  prompt_template: |\n    @{prev_speaker} produced the following output.\n    Your task is to refine and improve it:\n\n    {prev_content}\n```\n\n### `debate`\n\nTwo opposing members argue a proposition for N rounds, then a judge\nmember delivers a verdict.\n\n```yaml\nworkflow:\n  type: debate\n  rounds: 3          # pro/con exchange rounds before the judge speaks (default: 3)\n  pro: alice         # member arguing in favour\n  con: bob           # member arguing against\n  judge: carol       # member delivering the final verdict\n```\n\n1. The **pro** member makes an opening statement.\n2. The **con** member rebuts.\n3. Steps 1–2 repeat for `rounds` rounds.\n4. The **judge** receives the full exchange and delivers a verdict.\n5. Any member can end early by emitting `[[TEAM_DONE]]`.\n\n### `parallel_review`\n\nLike `review_loop` but all reviewers read the deliverable **at the same time**\n(using a thread pool), so the total review wall-time is bounded by the\n*slowest* reviewer, not the sum of all reviewers.  A designated **synthesizer**\nthen consolidates the parallel reviews into one prioritised verdict, and the\n**producer** revises.\n\n```yaml\nworkflow:\n  type: parallel_review\n  max_rounds: 4            # max revision cycles before stopping\n  producer: writer         # who creates and revises the deliverable\n  reviewers:               # 2 or more members who review in parallel\n    - methods_reviewer\n    - stats_reviewer\n    - clarity_reviewer\n  synthesizer: editor      # consolidates the parallel reviews (may equal producer)\n  approve_token: APPROVED  # optional; default is \"APPROVED\"\n```\n\n**Flow per revision cycle:**\n\n1. All reviewers are dispatched simultaneously; each receives the same\n   transcript snapshot and produces its review independently.\n2. Reviews are appended to the transcript in declaration order.\n3. The **synthesizer** reads all reviews and emits a consolidated verdict\n   (or `APPROVED` when no further changes are needed).\n4. If approved, the producer finalises and emits `[[TEAM_DONE]]`.\n5. Otherwise the producer addresses the feedback and the cycle repeats.\n\n\u003e **Thread-safety note:** Reviewer turns are truly parallel LLM calls.\n\u003e Each reviewer reads the transcript (read-only during the parallel window)\n\u003e and calls its own model.  Reviewers should not use file-writing tools\n\u003e during their review turns to avoid concurrent workspace writes.\n\n---\n\n### `parallel`\n\nAll members speak **simultaneously** in every round.  Unlike `parallel_review`\n(which has a fixed producer → reviewers → synthesizer structure), `parallel`\nis fully symmetric: every declared member runs at the same time, every round.\n\nEach member receives the same transcript snapshot at the start of the round —\nit cannot see what another member wrote *in the current round*, only in\nprevious rounds.  After all threads complete, turns are appended in member\ndeclaration order so the transcript is deterministic and `--resume` works.\n\n```yaml\nworkflow:\n  type: parallel\n  max_rounds: 4\n```\n\n**When to use `parallel`**\n\n- Independent expert panels — each member evaluates the problem from its own\n  perspective and writes its findings simultaneously.\n- Embarrassingly parallel tasks — member A generates candidate A, member B\n  generates candidate B; a later sequential step (or `sequential_chain`) picks\n  the best.\n- Speed-critical brainstorming where sequential dialogue would be too slow.\n\n**Rendering**\n\nThe CLI shows a `⚡ parallel` separator banner before the round starts, then\nrenders each member's completed panel (with full content, file-write list, and\ncolour) when the round finishes — no token-by-token streaming during the\nparallel window.\n\n\u003e **Thread-safety note:** Members read the transcript concurrently (safe) and\n\u003e write to the shared workspace.  Concurrent writes to the *same file path*\n\u003e are a race condition.  Design your team so that parallel members produce\n\u003e output in disjoint paths (e.g. `member_a/output.txt` vs `member_b/output.txt`).\n\n---\n\n\n## Workspaces and artifacts\n\nFor team `\u003cname\u003e` with `workspace: ./runs/\u003cname\u003e` you get:\n\n```\nruns/\u003cname\u003e/\n├── transcript.jsonl       # one JSON object per turn\n├── shared/                # mounted as /workspace inside every container\n│   └── \u003cfiles written by members\u003e\n├── checkpoints/           # automatic point-in-time snapshots (one per live turn)\n│   ├── 0001_alice_20240501T120000/\n│   ├── 0002_bob_20240501T120145/\n│   └── ...\n└── members/\n    ├── pi/                # mounted as /private inside the pi container\n    ├── postdoc/\n    └── ...\n```\n\n* `shared/` is the canonical place for deliverables and is visible to\n  every member at every turn.\n* `members/\u003cname\u003e/` is the **private workspace** for that member.  Its\n  contents are listed in the member's turn prompt under *\"Files in your\n  private workspace (/private)\"*, so the member can reference its own\n  previous work, intermediate files, or notes across turns.  Other members\n  cannot see these files.\n* `transcript.jsonl` is appended to as the run progresses; one record per\n  turn, with `speaker`, `role`, `content`, `files_written`, and\n  `timestamp` fields.\n\n`team transcript \u003cfile\u003e` renders the transcript human-readably.\n\n---\n\n\n## Containers, isolation, and root\n\nEach member runs in **its own container** with the following properties:\n\n| property | value | rationale |\n| --- | --- | --- |\n| Image | `ollama/ollama:latest` (overridable) | Standard Ollama runtime. |\n| User inside | **root** | Members have full root *inside their own filesystem*, satisfying \"root inside the container\" without granting host root. |\n| Network | per-team Docker bridge `team-\u003cname\u003e-net`, isolated from other teams and from your host services | Members can only reach each other through the orchestrator, not directly. |\n| Port exposure | `127.0.0.1:\u003crandom\u003e:11434` | Each member's Ollama API is reachable only from the host loopback by the orchestrator. |\n| Model cache | per-member named volume `team-\u003cname\u003e-\u003cmember\u003e-models` | Members do *not* share model storage. |\n| Mounts | shared workspace at `/workspace`, private workspace at `/private` | Conventional file-exchange surface. |\n| Restart policy | `unless-stopped` | Survives daemon restarts during long runs. |\n| Resource caps | `memory_limit`, `cpu_limit` honoured if set | Keep large models from starving the host. |\n\nContainers are **not** run with `--privileged` and do not get any host\ndevice access by default; root is confined to the container's mount and\nPID namespaces.  You can pass GPUs explicitly via `gpus` (see below).\n\n---\n\n\n## GPU support\n\nSet `gpus` either globally (under `defaults`) or per-member:\n\n```yaml\ndefaults:\n  gpus: all                # all visible GPUs\n\nmembers:\n  - name: pi\n    gpus: [0]              # only GPU 0\n  - name: postdoc\n    gpus: none             # CPU only\n```\n\nRequires the NVIDIA Container Toolkit on the host.  Passed through to\nDocker via device requests; non-NVIDIA setups can leave `gpus: none`.\n\n### Apple Silicon / no-Docker Ollama\n\nDocker Desktop on **macOS** runs a Linux VM that cannot access the host's\nGPU (neither NVIDIA nor Apple Metal).  Using `gpus: all` there produces:\n\n```\ncould not select device driver \"nvidia\" with capabilities [[gpu]]\n```\n\nThere are two escape hatches:\n\n#### Option A — CPU-only containers (`--no-gpu`)\n\nPass `--no-gpu` to `team up` or `team run`.  All containers are started\nwithout GPU device requests and fall back to CPU inference inside Docker.\nNo YAML change required, but inference will be slow on large models.\n\n```bash\nteam run myteam.yaml --no-gpu\nteam up  myteam.yaml --no-gpu\n```\n\n#### Option B — Native host Ollama with Metal (recommended for Apple Silicon)\n\nInstall [Ollama for macOS](https://ollama.com) natively.  The native app\nuses **Apple Metal** for GPU acceleration and is dramatically faster than\nCPU-only Docker containers.  Then tell `team` to bypass Docker entirely and\nconnect all members to it:\n\n**Via CLI flag** (no YAML change):\n\n```bash\n# Default URL is http://localhost:11434\nteam run myteam.yaml --host-ollama http://localhost:11434\nteam up  myteam.yaml --host-ollama http://localhost:11434\n```\n\n**Via YAML** (permanent):\n\n```yaml\ndefaults:\n  ollama_url: http://localhost:11434   # all members skip Docker\n```\n\nWhen `defaults.ollama_url` is set (or `--host-ollama` is passed), no Ollama\ncontainers are started; the orchestrator connects directly to the given URL.\nPer-member `ollama_url` overrides the default for individual members.\n\n\u003e **`team check` will report a `FAIL`** on macOS when GPU is requested\n\u003e without an `ollama_url` configured, and will guide you to one of the two\n\u003e options above.\n\n---\n\n\n## OpenAI-compatible backends\n\nBy default every member runs Ollama in a Docker container.  You can instead\npoint any member at any **OpenAI-compatible API** — LM Studio, vLLM, llama.cpp\nserver, the real OpenAI API, Anthropic (via a LiteLLM proxy), etc. — without\nDocker.\n\n```yaml\ndefaults:\n  backend: openai_compat\n  api_base: http://localhost:1234/v1   # LM Studio\n  api_key: env:OPENAI_API_KEY          # or a literal key\n\nmembers:\n  - name: lead\n    role: Tech Lead\n    model: gpt-4o                      # model name sent to the API\n    persona: ...\n  - name: worker\n    role: Engineer\n    model: llama-3.1-8b-instruct\n    backend: ollama                    # this member still uses Docker\n    persona: ...\n```\n\nThe `backend` and `api_base` fields can be set globally in `defaults` or\noverridden per-member.\n\n| field | meaning |\n| --- | --- |\n| `backend` | `\"ollama\"` (default) or `\"openai_compat\"` |\n| `api_base` | Base URL of the OpenAI-compat API (e.g. `https://api.openai.com/v1`) |\n| `api_key` | API key; use `\"env:VAR\"` to read from environment at runtime |\n\nWhen `backend: openai_compat` is set, no Docker container is started for\nthat member — the orchestrator calls the remote API directly.  The `model`\nfield is passed as-is to the API.\n\n---\n\n\n## Remote / no-Docker Ollama\n\nIf you already have an Ollama server running (locally or on a remote\nmachine), you can skip Docker for individual members by setting `ollama_url`:\n\n```yaml\nmembers:\n  - name: researcher\n    role: Researcher\n    model: llama3.1:70b\n    ollama_url: http://192.168.1.10:11434  # existing Ollama instance\n    persona: ...\n```\n\nTo route **all** members to the same Ollama instance, set it in `defaults`\nor pass `--host-ollama` on the command line (see\n[Apple Silicon / no-Docker](#apple-silicon--no-docker-ollama)):\n\n```yaml\ndefaults:\n  ollama_url: http://localhost:11434\n```\n\nNo container is started for any member that has an effective `ollama_url`\n(per-member or from `defaults`); the orchestrator connects directly to the\ngiven URL.  The model must already be pulled on that server (or Ollama's\nautomatic pull will fetch it on first use).\n\n---\n\n\n## Custom Ollama image\n\n`docker/Dockerfile.ollama` is an optional, slightly-augmented image that\nadds `python3`, `git`, `jq`, `curl`, and friends on top of\n`ollama/ollama:latest` for members that want richer in-container\ntooling.  Build it once and reference it from any team:\n\n```bash\ndocker build -f docker/Dockerfile.ollama -t team/ollama:latest docker/\n```\n\n```yaml\ndefaults:\n  ollama_image: team/ollama:latest\n```\n\nThe default `ollama/ollama:latest` is fine for most uses.\n\n---\n\n\n## Context window management\n\nBy default the orchestrator passes the full transcript to every member\nevery turn.  For long-running teams this can exceed a model's context\nwindow, causing silent truncation or errors.  Configure a strategy to\nkeep the context manageable:\n\n```yaml\ndefaults:\n  context_strategy: sliding_window   # none | sliding_window | truncate | summarize\n  context_budget: 20                 # max turns (sliding_window) or ~token budget (truncate/summarize)\n```\n\n| strategy | behaviour |\n| --- | --- |\n| `none` (default) | Full transcript always sent. |\n| `sliding_window` | Only the last `context_budget` turns are sent. |\n| `truncate` | Oldest turns are dropped until the estimated token count fits within `context_budget`. A note is prepended explaining that earlier turns were omitted. |\n| `summarize` | The oldest turns are compressed into a concise bullet-point digest by calling the member's own LLM (at temperature 0.2). The digest is prepended under a *\"Summary of N earlier turn(s)\"* heading; the most-recent turns are kept verbatim. 80 % of `context_budget` is reserved for recent turns, 20 % for the digest. Falls back to a plain omission notice if the summarization call fails. |\n\nOverride per member:\n\n```yaml\nmembers:\n  - name: reviewer\n    context_strategy: sliding_window\n    context_budget: 10    # this member sees only the last 10 turns\n```\n\n---\n\n\n## Model retention (`keep_alive`)\n\nBy default, `team` sets Ollama's `keep_alive` to `\"-1\"` on every chat request, which tells Ollama to keep the model loaded in RAM indefinitely.  Without this, Ollama's built-in default evicts a model after 5 minutes of inactivity — a problem for large models (tens of gigabytes) that must repeatedly load and unload between turns.\n\n```yaml\ndefaults:\n  keep_alive: \"-1\"   # keep every model loaded for the duration of the run (default)\n\nmembers:\n  - name: summarizer\n    model: llama3.2:3b\n    keep_alive: \"5m\"   # lightweight model — OK to evict after 5 minutes of idle\n    ...\n```\n\n| Value | Behaviour |\n| --- | --- |\n| `\"-1\"` | Keep the model loaded until Ollama stops or another model claim evicts it. **Recommended for team runs.** |\n| `\"5m\"`, `\"1h\"`, … | Evict after the given idle period (Ollama duration string). |\n| `\"0\"` | Unload immediately after each request (maximises GPU headroom at the cost of reload latency). |\n\n`keep_alive` is an Ollama-only parameter.  When the `openai_compat` backend is used it is silently ignored.\n\n---\n\n\n## CLI reference\n\n```text\nteam init        [PATH]               Write a starter team YAML.\nteam new         [PATH]               Interactive wizard to create a new team YAML.\nteam validate    \u003cteam.yaml\u003e          Parse and validate the YAML.\nteam check       \u003cteam.yaml\u003e          Run preflight checks (no Docker started).\nteam up          \u003cteam.yaml\u003e          Start containers, pull models.\n                 [--no-gpu] [--host-ollama URL]\nteam status      \u003cteam.yaml\u003e          Show container status per member.\nteam logs        \u003cteam.yaml\u003e          Tail per-member Ollama logs.\n                 [--member NAME] [--tail N]\nteam run         \u003cteam.yaml\u003e          Up + run workflow + (down).\n                 [--no-up] [--keep-up] [--resume] [--no-stream] [--interactive]\n                 [--no-gpu] [--host-ollama URL]\nteam transcript  \u003cteam.yaml\u003e          Render the persisted transcript.\nteam export      \u003cteam.yaml\u003e          Export transcript + artifacts to a report.\n                 [--format markdown|html|json] [--output PATH] [--no-artifacts]\nteam checkpoints \u003cteam.yaml\u003e          List all workspace checkpoints.\nteam restore     \u003cteam.yaml\u003e \u003cID\u003e     Restore the shared workspace to a checkpoint.\nteam down        \u003cteam.yaml\u003e          Stop \u0026 remove containers (and volumes).\n                 [--purge]\n```\n\nCommon flags:\n\n* `-v / --verbose` — debug-level logging.\n* `--prepare-timeout SECONDS` (on `up`/`run`) — how long to wait for each\n  member's Ollama daemon to become ready and its model to finish pulling\n  (default 600).\n\n---\n\n\n## Interactive wizard\n\n`team new` launches a guided wizard that asks you a series of questions\nand writes a validated YAML:\n\n```bash\nteam new my-team.yaml\n```\n\nThe wizard prompts for:\n\n* Team name and goal\n* Number of members, and for each: name, role, model, persona\n* Workflow type and max rounds\n* Workspace path\n\nThe output is a fully-formed, validated YAML ready to use with `team run`.\n\n---\n\n\n## Pre-flight checks\n\nBefore starting containers, verify that the environment is ready with\n`team check`:\n\n```bash\nteam check my-team.yaml\n```\n\nThe command checks:\n\n| Check | What it tests |\n|---|---|\n| Workspace writable | Can create the workspace directory and write files to it |\n| Disk space | Reports available GB; warns if below **5 GB** |\n| Docker daemon | Docker daemon reachable, version ≥ 20.10, Ollama image present |\n| GPU availability | Runs `nvidia-smi` when any member requests GPUs; warns if not found |\n\nExit code is `0` when all checks pass (warnings allowed), `1` when any\ncheck fails.  Failures are shown with a red ✗ and warnings with a yellow ⚠.\n\n---\n\n\n## Streaming output\n\nBy default `team run` streams each member's reply **token-by-token** to the\nterminal as it is generated.  You see a header like `@alice (Lead)` followed\nby the reply appearing live — no waiting for the full response.\n\nTo disable streaming (e.g. for CI or when redirecting output to a file):\n\n```bash\nteam run my-team.yaml --no-stream\n```\n\nWith `--no-stream` the full reply is printed at once after each turn\ncompletes.\n\n---\n\n\n## Per-turn timeout\n\nSet a hard wall-clock deadline (seconds) on how long any single member turn\nmay take.  If the LLM doesn't finish within the limit, a `TurnTimeoutError`\nis raised and the workflow stops.\n\n```yaml\ndefaults:\n  turn_timeout: 120     # 2 minutes for every member by default\n\nmembers:\n  - name: fast_reviewer\n    role: Reviewer\n    model: qwen2.5:3b\n    persona: You review code quickly.\n    turn_timeout: 30    # override — this member gets only 30 s\n```\n\nSet `turn_timeout: 0` (or leave it absent) to disable timeouts entirely.\n\n**Implementation details**\n\nThe member's `take_turn()` is executed in a `ThreadPoolExecutor` thread and\n`future.result(timeout=…)` enforces the deadline.  If the timeout fires the\nthread is abandoned (it will eventually finish and be garbage-collected), but\nthe calling workflow raises `TurnTimeoutError` immediately.\n\n---\n\n\n## LLM retry with backoff\n\n`team` automatically retries LLM calls that fail due to transient infrastructure errors — connection refused, timeouts, and HTTP 5xx responses from the server — using **exponential backoff**.\n\n```yaml\ndefaults:\n  max_retries: 3       # attempts per call (default: 3; 0 = no retries)\n  retry_backoff: 2.0   # backoff base in seconds (wait = backoff ** attempt)\n\nmembers:\n  - name: alice\n    max_retries: 5     # per-member override\n    retry_backoff: 1.5\n```\n\n### How it works\n\n| Scenario | Behaviour |\n| --- | --- |\n| Connection refused / timeout | Retried up to `max_retries` times. |\n| HTTP 5xx (server error) | Retried — the server never processed the request. |\n| HTTP 4xx (client error) | **Not retried** — a bad model name or malformed request won't self-heal. |\n| Partial streaming response | **Not retried** — the caller already received tokens; replaying would produce duplicates. |\n\nThe wait between attempts is `retry_backoff ** attempt` seconds (attempt 0 → 1 s, attempt 1 → 2 s, attempt 2 → 4 s for the default `retry_backoff=2.0`).\n\n### When all retries are exhausted\n\n`LLMRetryExhaustedError` (a subclass of `OllamaError`) is raised.  The CLI catches it and prints a red error panel instead of crashing, preserving any transcript written so far.\n\n---\n\n\n## Resuming an interrupted run\n\nIf a run is interrupted (crash, timeout, Ctrl-C) you can pick up exactly\nwhere it left off without re-running the turns that already completed:\n\n```bash\nteam run my-team.yaml --resume\n```\n\n`--resume` loads the existing `transcript.jsonl`, replays every already-\ncompleted turn instantly (no LLM call), and then continues the workflow\nlive from the first missing turn.\n\n* Containers are restarted (or re-used) as normal; models are not re-pulled\n  if their cache volumes still exist.\n* Combine with `--no-up` if your containers are already running from a\n  previous `team up`.\n* If the transcript doesn't exist or is empty, `--resume` is a no-op and\n  the run starts fresh.\n* If the previous run completed, resuming is a harmless no-op: the workflow\n  will detect `[[TEAM_DONE]]` in the first replayed turn and exit immediately.\n\n---\n\n\n## Human-in-the-loop intervention\n\nYou can inject new directives into a running team at any time without\nstopping or restarting.  Two mechanisms are available:\n\n### Interactive mode (foreground runs)\n\nPass `--interactive` to `team run`.  After every workflow round completes\nyou are prompted for an optional directive.  Press **Enter** with no text to\nlet the run continue, or type instructions and press **Enter** to have them\ninjected before the next round:\n\n```bash\nteam run my-team.yaml --interactive\n```\n\n```text\n── round 1/4 complete ──\nEnter a directive for the team (or press Enter to continue): Focus only on the auth module for now.\n↳ directive injected\n```\n\n### File-based injection (background / CI runs)\n\nAt any point during a run you can write a plain-text file called\n`inject.txt` into the workspace directory:\n\n```bash\necho \"Switch to Python 3.12 syntax only.\" \u003e ./runs/my-team/inject.txt\n```\n\nBefore the **next member turn** begins, the orchestrator checks for this\nfile.  If it exists, the content is read, the file is deleted, and the\ndirective is appended to the transcript as a `@human (director)` turn.\nAll members see it in their next turn's conversation context.\n\nThe file is consumed once and automatically removed.  Drop a new file to\ninject again at any later point.\n\n### What the team sees\n\nBoth mechanisms produce the same type of transcript entry:\n\n```text\n--- Turn N | @human | director ---\n\u003cyour directive here\u003e\n```\n\nThe entry is visible to every member in their next turn prompt, just like\nany other speaker's turn.\n\n---\n\n\n## Agent mode and tool use\n\nMembers can act as **agents**: they may call external tools, then receive\nthe tool's output and continue reasoning — all within the same logical turn.\nTwo invocation modes are supported:\n\n| Mode | How it works |\n| --- | --- |\n| `text` (default) | Member emits fenced `tool:` blocks in its reply; orchestrator parses and executes them. Works with any model. |\n| `native` | Uses the LLM's **function-calling API** (Ollama `tools` parameter / OpenAI function calling). Requires a compatible model (Llama 3.1+, Qwen 2.5, GPT-4 family, etc.). |\n\n### Enabling tools\n\n```yaml\ndefaults:\n  tools: [web_search, run_python]  # enable globally\n  max_tool_rounds: 10              # max tool-call rounds per turn (default: 10)\n  tool_timeout: 300                # seconds per tool execution (default: 300)\n  tool_mode: text                  # \"text\" (default) or \"native\"\n\nmembers:\n  - name: researcher\n    tools: [web_search, read_url]  # per-member override\n    tool_mode: native              # this member uses function-calling API\n  - name: data_scientist\n    tools: [run_python, run_bash, read_file, write_file, append_file, list_files]\n```\n\n### Tool invocation syntax — `text` mode\n\nA member invokes a tool by emitting a fenced block with a `tool:\u003cname\u003e`\ninfo-string:\n\n````\n```tool:web_search\nquery: IPCC AR6 key findings 2024\n```\n````\n\n### Tool invocation — `native` mode\n\nIn native mode the model receives **JSON Schema** definitions for all\nenabled tools and returns structured `tool_calls` objects (OpenAI/Ollama\nfunction-calling format) instead of text fenced blocks.  The orchestrator\nexecutes the tools and passes results back via `tool` role messages — no\ntext parsing required.\n\nEvery built-in tool has a corresponding JSON Schema automatically provided\nto the model.  Custom skill tools that lack a schema receive a minimal\n`input: string` schema.\n\n\u003e **Model requirements**: native mode requires a model that supports\n\u003e function calling.  For Ollama, use `llama3.1:8b` or newer, `qwen2.5:7b`,\n\u003e `mistral-nemo`, etc.  For OpenAI-compat backends, any GPT-4 / Claude\n\u003e model works.  If you pass native mode to a model that ignores the `tools`\n\u003e parameter, it will fall back to producing a text reply (no tool calls).\n\n````\n```tool:run_python\nimport pandas as pd\ndf = pd.read_csv('/workspace/shared/data.csv')\nprint(df.describe())\n```\n````\n\n````\n```tool:read_file\npath: analysis/results.json\n```\n````\n\n````\n```tool:write_file\npath: output/summary.md\n---\n# Summary\n\nThis file was written by the agent.\n```\n````\n\n````\n```tool:append_file\npath: logs/run.log\n---\n[step 3] analysis complete.\n```\n````\n\n````\n```tool:list_files\npattern: *.py\n```\n````\n\nAfter each tool block the orchestrator executes the tool, injects the result\nback into the conversation, and asks the member to continue.  Once the member\nproduces a reply with no tool blocks, that reply is recorded in the\ntranscript as usual.\n\n### Available built-in tools\n\n| tool | description |\n| --- | --- |\n| `run_python` | Execute Python code; cwd is the shared workspace directory. |\n| `run_bash` | Execute a bash command; cwd is the shared workspace directory. |\n| `web_search` | Search the web via the DuckDuckGo instant-answer API (no key required). |\n| `read_url` | Fetch and return the plain-text content of a URL. |\n| `read_file` | Read a file from the shared workspace by relative path. |\n| `write_file` | Write (create or overwrite) a file in the shared workspace. |\n| `append_file` | Append text to a file in the shared workspace. |\n| `list_files` | List files in the shared workspace with an optional glob filter. |\n| `remember` | Store a memory in the member's **persistent cross-session** memory store. |\n| `recall` | Search the member's persistent memory by keyword. |\n| `forget` | Delete a memory by key from the persistent store. |\n| `list_memories` | List stored memories (optionally filtered by tag). |\n| `assert_belief` | Add a claim to the team's **shared belief board** with confidence score. |\n| `contest_belief` | Contest an existing belief (moves it to contested status). |\n| `accept_belief` | Cast an accept vote for an existing belief. |\n| `list_beliefs` | List the shared belief board (optionally filtered by status). |\n| `delegate_task` | Delegate a sub-task to a remote bridge server and wait for results. Use `peer:` for named peers or `url:` for direct addressing. |\n| `list_peers` | List all configured peer teams and their live health status (pending/running counts). |\n| `broadcast_task` | Fan out the same goal to multiple peer teams concurrently and collect all results. |\n| `cancel_remote_task` | Cancel a queued or running task on a remote bridge server by task ID. |\n| `delegate_to_expert` | Send a prompt to an external cloud LLM (OpenAI, Anthropic, Google) for expert assistance when the task exceeds local capabilities. |\n| `log_decision` | Append a timestamped decision entry to `decisions.md` in the shared workspace. |\n| `read_decisions` | Read the full decision log (`decisions.md`) from the shared workspace. |\n| `query_registry` | Query a team registry to discover teams matching capability tags or a keyword; returns names, URLs, and tags. |\n| `sync_beliefs` | Synchronize the team belief board with a remote team cluster (pull, push, or both directions). |\n\n**`write_file` and `append_file` body format**\n\nBoth tools use a two-part body separated by a `---` line:\n\n```\npath: relative/path/to/file.txt\n---\nFile content goes here.\nMultiple lines are fine.\n```\n\nThe path is relative to the shared workspace root.  Parent directories are\ncreated automatically.  `write_file` replaces any existing content;\n`append_file` adds to the end of the file (creating it if it does not exist).\n\n**`list_files` body format**\n\nThe body is optional.  If omitted, all workspace files are listed.  Use a\n`pattern:` key to filter by glob pattern:\n\n```\npattern: **/*.py\n```\n\n### Security note\n\n`run_python` and `run_bash` execute code on the **host machine** with the\nprivileges of the `team` process.  Only enable these tools for members whose\nprompts you trust.\n\n### Expert delegation — `delegate_to_expert`\n\nWhen a task is too complex for the local model assigned to a member, that\nmember can **delegate the sub-problem** to a subscription-based cloud LLM\n(ChatGPT, Claude, Gemini) and receive the answer as a tool result.  The\nmember remains responsible for the turn — it incorporates the expert's reply\ninto its own response, so the team structure and role assignments are preserved.\n\nThe cloud model is **not** a team member.  It has no access to the\ntranscript, the shared workspace, or any other team state — only the prompt\ntext you explicitly send.\n\n#### Setup\n\nExport the API key for the provider(s) you want to use **on the host** before\nrunning `team`:\n\n```bash\nexport OPENAI_API_KEY=sk-…          # for provider: openai\nexport ANTHROPIC_API_KEY=sk-ant-…   # for provider: anthropic\nexport GOOGLE_API_KEY=AIza…         # for provider: google\n```\n\nEnable the tool for a member in the YAML:\n\n```yaml\nmembers:\n  - name: analyst\n    model: llama3.2:3b\n    tools: [delegate_to_expert, read_file, write_file]\n```\n\n#### Usage\n\n**Multi-line prompt (recommended for complex requests)**:\n\n````\n```tool:delegate_to_expert\nprovider: openai\nmodel: gpt-4o\nmax_tokens: 4096\ntemperature: 0.2\n---\nYou are a statistics expert.\nGiven the following regression output, identify any violations\nof linear regression assumptions and suggest remedies.\n\nResiduals: …\n```\n````\n\n**Single-line prompt**:\n\n````\n```tool:delegate_to_expert\nprovider: anthropic\nmodel: claude-opus-4-5\nprompt: What is the time complexity of Dijkstra's algorithm with a binary heap?\n```\n````\n\n| field | required | default | description |\n| --- | --- | --- | --- |\n| `provider` | ✓ | — | `openai`, `anthropic`, or `google` |\n| `model` | | provider default | Model name accepted by the provider API |\n| `prompt` | ✓* | — | Prompt text (single-line form; ignored when `---` body is present) |\n| `max_tokens` | | `2048` | Maximum tokens in the response |\n| `temperature` | | `0.2` | Sampling temperature 0–2 |\n\n\\* Required unless a `---` body separator is used.\n\n**Provider defaults**: `gpt-4o` (OpenAI), `claude-opus-4-5` (Anthropic),\n`gemini-1.5-pro` (Google).\n\n\u003e **Privacy**: the prompt text is sent to the external API.  Do not include\n\u003e sensitive data unless your data-handling agreement with the provider permits it.\n\u003e Only enable `delegate_to_expert` for members that may handle the data appropriately.\n\n### Full system access and package installation\n\nAgents have **full, unrestricted access to the host system** — the same\nprivileges as the user who runs the `team` process.  This is intentional:\nagents should be able to do anything a human researcher or engineer can do.\n\nIn particular, agents can install software at will:\n\n````\n```tool:run_bash\npip install scikit-learn seaborn --quiet\n```\n````\n\n````\n```tool:run_bash\napt-get install -y ffmpeg\n```\n````\n\n````\n```tool:run_python\nimport subprocess, sys\nsubprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"biopython\"], check=True)\nimport Bio\nprint(Bio.__version__)\n```\n````\n\nWhen a tool invocation takes longer than expected (e.g. downloading a large\npackage), increase the `tool_timeout` in your YAML:\n\n```yaml\ndefaults:\n  tool_timeout: 600   # 10 minutes — safe for most installs\n```\n\nThe default `tool_timeout` is **300 seconds** (5 minutes), which covers the\nvast majority of `pip install` and `apt-get` operations on a normal network\nconnection.\n\n### How it works\n\n**Text mode** (`tool_mode: text`):\n```\nmember turn:\n  1. LLM called with system prompt + conversation context\n  2. If reply contains tool: fenced blocks → execute each tool\n  3. Tool results injected as a follow-up user message\n  4. LLM called again (no streaming; repeats up to max_tool_rounds)\n  5. If no tool blocks in reply → reply recorded in transcript\n```\n\n**Native mode** (`tool_mode: native`):\n```\nmember turn:\n  1. LLM called with JSON Schema tool definitions in the \"tools\" parameter\n  2. If response contains tool_calls → execute each named tool using args_to_body()\n  3. Each result injected as a \"tool\" role message\n  4. LLM called again (repeats up to max_tool_rounds)\n  5. When LLM returns text (no tool_calls) → reply recorded in transcript\n```\n\nToken usage from all tool-call rounds is accumulated and reported in the\n[token usage summary](#token-usage-tracking).\n\n### Streaming display\n\nWhen streaming is enabled (`team run` without `--no-stream`), tool calls\nare displayed inline:\n\n```text\n@researcher (Research Lead)\nI'll search for recent data on this topic.\n\n  🔧 tool: web_search  query: climate change 2024 report\n     ↳ **Climate Change** A programming language. - Flooding in coastal…\nBased on the search, the key findings are…\n```\n\n---\n\n### Custom skill plugins\n\nThe built-in tool set is a starting point.  You can extend it with any\nPython file — local or fetched from a URL — and make those tools\navailable to any member.  This gives agents effectively **unlimited**\ncapabilities depending on what skills you provide.\n\n#### Skill file format\n\nA skill file must expose tools in one of two formats:\n\n**Single-tool format** (`TOOL_NAME` + `execute`):\n\n```python\n# skills/my_calculator.py\nTOOL_NAME = \"my_calculator\"\nTOOL_DESCRIPTION = \"Evaluate a Python arithmetic expression.\"\n\ndef execute(body, *, workspace_path=None, timeout=30, **kwargs):\n    try:\n        return str(eval(body.strip(), {\"__builtins__\": {}}, {}))\n    except Exception as exc:\n        return f\"ERROR: {exc}\"\n```\n\n**Multi-tool format** (`TOOLS` dict + optional `TOOL_DESCRIPTIONS`):\n\n```python\n# skills/db_tools.py\nimport sqlite3\n\ndef _query(body, *, workspace_path=None, **kwargs):\n    db_path = workspace_path / \"data.sqlite\"\n    conn = sqlite3.connect(db_path)\n    rows = conn.execute(body.strip()).fetchall()\n    conn.close()\n    return \"\\n\".join(str(r) for r in rows)\n\ndef _schema(body, *, workspace_path=None, **kwargs):\n    db_path = workspace_path / \"data.sqlite\"\n    conn = sqlite3.connect(db_path)\n    rows = conn.execute(\"SELECT name, sql FROM sqlite_master WHERE type='table'\").fetchall()\n    conn.close()\n    return \"\\n\".join(f\"{r[0]}: {r[1]}\" for r in rows)\n\nTOOLS = {\"sql_query\": _query, \"sql_schema\": _schema}\nTOOL_DESCRIPTIONS = {\n    \"sql_query\":  \"Run an SQL SELECT on the shared SQLite database.\",\n    \"sql_schema\": \"Return the schema of all tables in the shared SQLite database.\",\n}\n```\n\nBoth formats can coexist in the same file.\n\n#### Configuring skills\n\nAdd skill sources under `defaults.skills` (inherited by all members) or\n`members[*].skills` (member-specific, merged with defaults on top):\n\n```yaml\ndefaults:\n  skills:\n    - path: ./skills/my_calculator.py     # local path (relative to CWD)\n    - path: ./skills/db_tools.py\n    - url: https://example.com/skill.py   # remote URL (see security note below)\n      checksum: sha256:e3b0c44298fc…      # optional integrity check\n    - ./skills/shorthand.py               # plain string = auto-detect local/remote\n\n  tools: [web_search, my_calculator, sql_query, sql_schema]  # opt-in by name\n\nmembers:\n  - name: analyst\n    tools: [sql_query, sql_schema, run_python]   # member-specific tool set\n    skills:\n      - ./skills/analyst_helpers.py              # member-specific extra skill\n```\n\nTool names from skills are used exactly like built-in tool names everywhere\n(`tools:` lists, `tool:` fenced blocks, system prompts).\n\n#### Checksum verification\n\nFor any skill (local or remote) you can supply a checksum to verify\nintegrity before execution:\n\n```yaml\nskills:\n  - url: https://example.com/skill.py\n    checksum: sha256:\u003chex-digest\u003e\n  - path: ./skills/local.py\n    checksum: sha256:\u003chex-digest\u003e\n```\n\nSupported algorithms: any name accepted by Python's `hashlib` (e.g.\n`sha256`, `sha512`, `md5`).  `team` raises an error and refuses to load\nthe skill if the digest does not match.\n\n#### Markdown skills — context injection\n\nSkills do not have to be executable code.  A Markdown file (`.md`) loaded\nas a skill has its content injected verbatim into the member's **system\nprompt** at startup — no tool call required.  Use this for guidelines,\nchecklists, templates, and domain rules that should always be visible.\n\n```yaml\ndefaults:\n  skills:\n    - path: ./skills/review_checklist.md    # injected into system prompt\n    - path: ./skills/task_board.py          # callable tool as usual\n```\n\nA Python skill can also inject context by setting the `INJECT_INTO_CONTEXT`\nvariable to a non-empty string — the text is injected *and* the tool\nremains callable:\n\n```python\nTOOL_NAME = \"style_guide\"\nINJECT_INTO_CONTEXT = \"## Style guide\\n- Use snake_case for all variables.\\n...\"\n\ndef execute(body, **kwargs):\n    return INJECT_INTO_CONTEXT   # also callable on demand\n```\n\n#### Bundled team-specific skills\n\nThe `skills/` directory in this repository contains a set of skills designed\nfor multi-agent collaboration — things that have no use outside a team run\nand would never appear in a general-purpose skill library.\n\n| File | Type | Description |\n|---|---|---|\n| `review_checklist.md` | Markdown | Structured peer-review checklist injected into reviewer personas. |\n| `escalation_rules.md` | Markdown | When to proceed, flag a risk, or escalate to the manager. |\n| `decision_record_format.md` | Markdown | ADR-style template for writing `log_decision` entries. |\n| `task_board.py` | Python | `task_add` / `task_done` / `task_list` — shared TASKS.md board. |\n| `search_transcript.py` | Python | `search_transcript` — keyword search over the run transcript. |\n| `critique_request.py` | Python | `request_critique` / `pick_critique` / `list_critiques` — async peer-review queue. |\n| `progress_snapshot.py` | Python | `progress_snapshot` — write (or read) PROGRESS.md in the workspace. |\n\nReference them by path in your team YAML:\n\n```yaml\ndefaults:\n  skills:\n    - path: ./skills/review_checklist.md\n    - path: ./skills/escalation_rules.md\n    - path: ./skills/task_board.py\n    - path: ./skills/search_transcript.py\n  tools: [task_add, task_done, task_list, search_transcript]\n```\n\n## Shared institutional context\n\nWhen a workspace contains a `context.md` file at its root, `team` injects its\ncontent into **every** member's turn context automatically — no per-member\nconfiguration required.\n\nThis is the right place for knowledge that applies to all members equally:\nlab conventions, dataset descriptions, domain terminology, naming standards,\nrelevant prior work, or any background a new team member would need to read\non day one.\n\n**Creating the context file:**\n\n```bash\ncat \u003e ./runs/my-team/context.md \u003c\u003c 'EOF'\n# Lab context\n\nThis project analyses the TCGA-BRCA cohort (1,142 samples, 38 features).\n\n## Naming conventions\n- All feature files use `snake_case` column names.\n- Model outputs go in `results/`.\n\n## Domain notes\n- Use log2 CPM normalisation for expression data.\n- Primary endpoint is 5-year overall survival (OS5).\nEOF\n```\n\nThe file is read from disk **on every turn** so you can update it while a\nrun is in progress (e.g. to correct a mistake or add a new constraint).\nIf the file is absent, the section is silently omitted.\nThe content is truncated at 8 192 characters if the file is very large.\n\n---\n\n\n## Decision log\n\nMembers with the `log_decision` tool enabled can record structured, timestamped\ndecisions in a shared `decisions.md` file inside the workspace.  Any member\ncan later call `read_decisions` to review the accumulated rationale before\nmaking related choices.\n\n**Enabling the tools:**\n\n```yaml\ndefaults:\n  tools: [log_decision, read_decisions]   # add to any existing tool list\n```\n\n**Logging a decision:**\n\n````\n```tool:log_decision\ntitle: Chose pandas over polars for data wrangling\nrationale: Polars ecosystem is too immature; pandas is already a project dependency.\nalternatives: polars, dask, vaex\n```\n````\n\nThe entry is appended to `decisions.md` in the shared workspace:\n\n```markdown\n## Decision: Chose pandas over polars for data wrangling\n**Date:** 2024-07-15T10:32:44Z  \n**By:** @data_scientist  \n\n**Rationale:** Polars ecosystem is too immature; pandas is already a project dependency.\n\n**Alternatives considered:** polars, dask, vaex\n\n---\n```\n\n**Reading the decision log:**\n\n````\n```tool:read_decisions\n```\n````\n\nReturns the full `decisions.md` content so members can consult previous\ndecisions when facing related choices.\n\n---\n\n\n## Structured JSON output\n\nBy default members reply in free-form text.  When you need machine-readable\noutput — e.g. an extractor member whose results are consumed by downstream\ncode — set `output_format: json` on that member.\n\n```yaml\nmembers:\n  - name: extractor\n    role: Data extractor\n    model: llama3.1:8b\n    persona: You extract structured data from documents.\n    output_format: json\n    output_schema:                     # optional — validates the reply\n      type: object\n      required: [entities, summary]\n      properties:\n        entities:\n          type: array\n          items: {type: string}\n        summary:\n          type: string\n```\n\n**What happens**\n\n1. The system prompt gains an `## Output format` section instructing the model\n   to reply with valid JSON only.\n2. After the LLM replies, `team` calls `json.loads()` on the content.\n3. If parsing fails (or schema validation fails when `output_schema` is set),\n   the orchestrator sends a correction prompt and retries up to **3 times**.\n4. The parsed object is stored in `TurnResult.json_output` and is accessible\n   from custom workflows or post-run code.\n5. Schema validation requires `pip install jsonschema`; without it the schema\n   check is skipped silently.\n\n\u003e **Note:** `output_format` is per-member only — it is not available as a\n\u003e team-wide `defaults` key.\n\n---\n\n\n## Conditional routing\n\nEnable dynamic, branching conversations where each member's output determines who speaks next — building state-machine-like workflows without any code.\n\n```yaml\nworkflow:\n  type: conditional\n  start: writer       # optional; defaults to the first listed member\n  max_rounds: 20\n\nmembers:\n  - name: writer\n    model: llama3\n    persona: You are a technical writer.\n    role: Writer\n    routes:\n      - if_contains: \"NEEDS_REVISION\"\n        next: editor\n      - if_match: \"APPROVED|LGTM\"\n        next: publisher\n      - default: reviewer    # fallback when nothing else matches\n\n  - name: editor\n    model: llama3\n    persona: You are an editor.\n    role: Editor\n    routes:\n      - if_contains: \"DONE\"\n        next: publisher\n      - default: writer      # loop back for another draft\n\n  - name: reviewer\n    model: llama3\n    persona: You are a reviewer.\n    role: Reviewer\n    routes:\n      - default: writer\n\n  - name: publisher          # terminal node — no routes needed\n    model: llama3\n    persona: You are a publisher.\n    role: Publisher\n```\n\n### Route rules\n\nRules are evaluated **top-to-bottom**; the first match wins.\n\n| Key | Behaviour |\n| --- | --- |\n| `if_contains: \"TEXT\"` | Case-insensitive substring search in the member's last reply. |\n| `if_match: \"REGEX\"` | Case-insensitive `re.search` against the member's last reply. |\n| `default: member` | Unconditional fallback; fires when no other rule matches. |\n\nA member with **no `routes`** falls back to the standard round-robin next-speaker logic.\n\n### Workflow end conditions\n\nThe workflow stops when:\n* any member outputs `[[TEAM_DONE]]`, or\n* the total turn count reaches `max_rounds`.\n\n---\n\n\n## Token budget\n\nPrevent runaway costs by capping how many tokens a member may consume across all turns in a single run.\n\n```yaml\ndefaults:\n  token_budget: 5000   # max prompt+completion tokens per member per run\n\nmembers:\n  - name: alice\n    token_budget: 10000  # per-member override\n```\n\nWhen a member's cumulative token usage reaches the budget before their next turn, `TokenBudgetError` is raised and the run stops gracefully. The transcript and any workspace files written so far are preserved, and `team run --resume` with a higher budget can continue from where it left off.\n\n\u003e **Note:** Replayed turns (from `--resume`) do **not** count toward the budget.\n\n### Budget resolution\n\n| Setting | Effective budget |\n| --- | --- |\n| `token_budget` in `defaults` only | Applied to every member. |\n| `token_budget` in a specific member | Overrides the `defaults` value for that member only. |\n| Neither set | No limit — member runs until the workflow ends. |\n\n---\n\n\n## Per-agent persistent memory\n\nIn a real research lab, scientists remember what worked and what failed —\nacross months of experiments.  `team` gives each agent a **private,\npersistent memory store** backed by SQLite that survives between completely\nseparate `team run` invocations.\n\n```\nSession 1 (January): alice uses remember to store \"AlphaFold3 RMSD 1.2 Å\"\nSession 2 (February): alice uses recall to surface that result and build on it\n```\n\nThis is what separates `team` from all other orchestration frameworks: your\nagents actually **accumulate knowledge over time**.\n\n### Enabling memory\n\nAdd a `memory:` section to your team YAML:\n\n```yaml\nmemory:\n  enabled: true\n  inject_recent: 5    # memories injected into each turn's context (default: 5)\n  store: ~/.team/memory   # optional; defaults to \u003cworkspace\u003e/memory/\n```\n\nEnable memory tools for each member:\n\n```yaml\nmembers:\n  - name: alice\n    tools: [run_python, remember, recall, forget, list_memories]\n```\n\n### Memory tools\n\nAll memory tools use a `key:` / header + `---` / value body format:\n\n**`remember`** — store a cross-session memory:\n\n````\n```tool:remember\nkey: protein_folding_baseline_2025\ntags: results, methods\nimportance: 0.9\n---\nAlphaFold3 outperforms RoseTTAFold on monomers (RMSD 1.2 vs 2.1 Å, n=1 000).\nDataset: PDB validation set, tested January 2025.\n```\n````\n\n**`recall`** — full-text search across all memories:\n\n````\n```tool:recall\nquery: protein folding\nlimit: 5\n```\n````\n\nReturns a ranked list of matching memories (by importance then recency).\n\n**`forget`** — delete a memory by key:\n\n````\n```tool:forget\nkey: protein_folding_baseline_2025\n```\n````\n\n**`list_memories`** — browse all memories (optionally by tag):\n\n````\n```tool:list_memories\ntag: results\nlimit: 20\n```\n````\n\nAt the start of every turn, the *n* most recent memories are automatically\ninjected into the member's context under `## Your persistent memories`.\n\n### Memory config reference\n\n| key | type | default | description |\n| --- | --- | --- | --- |\n| `enabled` | bool | `false` | Enable persistent memory for all members. |\n| `inject_recent` | int | `5` | Number of recent memories to inject into each turn's context. |\n| `store` | path | `\u003cworkspace\u003e/memory` | Directory that holds the per-member SQLite databases. |\n\n---\n\n\n## Shared team belief board\n\nIn collaborative science, a team's most important output is not files — it is\n**what the team collectively knows**.  The `team` belief board formalises this\nas a living, structured record of claims with provenance, confidence scores,\nand consensus voting.\n\n```\nalice asserts: \"RNA Pol II is rate-limiting in elongation\" (confidence: 85%)\nbob accepts → 2/3 votes ≥ threshold → status: ACCEPTED\ncarol contests with reason: \"only tested in HEK293\" → status: CONTESTED\n```\n\nAfter a run: `team beliefs myteam.yaml` shows everything the team concluded.\n\n### Enabling the belief board\n\n```yaml\nbeliefs:\n  enabled: true\n  consensus_threshold: 0.5   # fraction of members required for acceptance\n  inject_limit: 10            # beliefs shown in each member's turn context\n```\n\nEnable belief tools for each member:\n\n```yaml\nmembers:\n  - name: alice\n    tools: [run_python, assert_belief, contest_belief, accept_belief, list_beliefs]\n```\n\n### Belief tools\n\n**`assert_belief`** — propose a claim with optional evidence:\n\n````\n```tool:assert_belief\nconfidence: 0.85\nevidence: RMSD analysis, PDB validation set, n=1 000, January 2025\n---\nAlphaFold3 is the best available method for monomer structure prediction.\n```\n````\n\nThe member who asserts a belief automatically casts an *accept* vote.  The\nreturned belief ID (e.g. `a3f2b1c9`) is used in subsequent votes.\n\n**`accept_belief`** — vote to accept:\n\n````\n```tool:accept_belief\nid: a3f2b1c9\n```\n````\n\n**`contest_belief`** — move a belief to `contested` status:\n\n````\n```tool:contest_belief\nid: a3f2b1c9\nreason: Dataset is limited to well-studied proteins; may not generalise.\n```\n````\n\n**`list_beliefs`** — browse the board:\n\n````\n```tool:list_beliefs\nstatus: contested\n```\n````\n\nValid status values: `pending`, `accepted`, `contested`, `rejected`.  Omit to\nlist all beliefs.\n\nBeliefs are injected into every member's turn context under\n`## Shared team belief board` so the whole team sees the current state before\neach turn.\n\n### Inspecting beliefs with team beliefs\n\n```bash\nteam beliefs myteam.yaml                    # all beliefs\nteam beliefs myteam.yaml --status accepted  # accepted only\nteam beliefs myteam.yaml --status contested # contested — needs attention\n```\n\nOutput example:\n\n```\n                  Belief board — team 'my-team'\n┏━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━━━━━┓\n┃ ID     ┃ Status      ┃ Claim                                                   ┃ Confidence ┃ By    ┃ For ┃ Against ┃\n┡━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━━━━━┩\n│ a3f2b1 │ ✓ accepted  │ AlphaFold3 is best for monomer structure prediction.    │       85%  │ @alice│   2 │       0 │\n│ 9c1d33 │ ⚡ contested│ The dataset generalises to all protein families.        │       60%  │ @bob  │   1 │       1 │\n└────────┴─────────────┴─────────────────────────────────────────────────────────┴────────────┴───────┴─────┴─────────┘\n⚡ Some beliefs are contested — review and resolve via accept_belief / contest_belief tools.\n```\n\n### Belief config reference\n\n| key | type | default | description |\n| --- | --- | --- | --- |\n| `enabled` | bool | `false` | Enable the shared belief board. |\n| `consensus_threshold` | float | `0.5` | Fraction of members who must accept a belief for it to become `accepted`. |\n| `inject_limit` | int | `10` | Maximum number of beliefs injected into each member's turn context. |\n\n---\n\n\n## Workspace checkpoints\n\nEvery time a live member turn is about to execute, the orchestrator\nautomatically snapshots the current state of the **shared workspace** before\nany files are written.  Snapshots are stored under\n`\u003cworkspace\u003e/checkpoints/` with names that encode the turn index, the\nmember about to speak, and the timestamp:\n\n```\ncheckpoints/\n├── 0001_alice_20240501T120000/   # state before alice's 1st turn\n├── 0003_bob_20240501T120145/     # state before bob's 2nd turn\n└── ...\n```\n\nIf the shared workspace is empty (no files have been produced yet), the\nsnapshot is silently skipped — there is nothing to back up.\n\n### Listing checkpoints\n\n```bash\nteam checkpoints my-team.yaml\n```\n\n```\n┌──────────────────────────────┬──────┬──────────────────────┬─────────────────────┬───────┐\n│ ID                           │ Turn │ Before member's turn │ Timestamp           │ Files │\n├──────────────────────────────┼──────┼──────────────────────┼─────────────────────┼───────┤\n│ 0001_alice_20240501T120000   │    1 │ @alice               │ 2024-05-01 12:00:00 │     3 │\n│ 0003_bob_20240501T120145     │    3 │ @bob                 │ 2024-05-01 12:01:45 │     5 │\n└──────────────────────────────┴──────┴──────────────────────┴─────────────────────┴───────┘\n```\n\n### Restoring a checkpoint\n\nCopy the checkpoint ID from the table and pass it to `team restore`:\n\n```bash\nteam restore my-team.yaml 0001_alice_20240501T120000\n```\n\n```\nrestored checkpoint 0001_alice_20240501T120000 — 3 file(s) now in the shared workspace.\n```\n\nThe current contents of `shared/` are replaced with the snapshot.\n**This cannot be undone** unless a later checkpoint already captured the\nstate you are overwriting, so check `team checkpoints` before restoring.\n\n### Use cases\n\n* **Undo a bad turn** — a member produced unwanted file changes; restore the\n  checkpoint taken just before that turn.\n* **Branch from a known-good state** — restore an earlier checkpoint, edit\n  `team.yaml` (e.g. change the goal or persona), and re-run from there.\n* **Audit the evolution of the workspace** — inspect any checkpoint\n  directory directly; it is a plain copy of `shared/` at that point in time.\n\n---\n\n\n## Workspace time-travel (`team rollback`)\n\nEvery live member turn is preceded by an automatic workspace snapshot (see\n[Workspace checkpoints](#workspace-checkpoints)).  When things go wrong you\ncan roll back the shared workspace to *any prior point in time* and resume\nfrom there — effectively forking the timeline:\n\n```bash\n# 1. List all available snapshots\nteam rollback myteam.yaml\n\n# 2. Restore to a specific checkpoint (with confirmation prompt)\nteam rollback myteam.yaml --to 0005_alice_20250510T183000\n\n# 3. Skip the confirmation prompt (useful in scripts)\nteam rollback myteam.yaml --to 0005_alice_20250510T183000 --yes\n```\n\nAfter rolling back, resume the run from the restored state:\n\n```bash\nteam run myteam.yaml --resume\n```\n\nBecause the transcript also persists, `--resume` skips all turns already\nrecorded in it.  To *re-run* from turn 5 with a different approach, truncate\nthe transcript manually (or delete it and rely entirely on the restored\nworkspace files).\n\n\u003e `team rollback` is a thin wrapper around the existing\n\u003e `CheckpointManager.restore()` logic.  The underlying `team restore`\n\u003e command (which requires an exact checkpoint ID argument) remains available\n\u003e for scripting.\n\n---\n\n\n## Token usage tracking\n\nAfter every `team run` a token usage summary is printed:\n\n```text\n┌────────────────────────────────────────────────────┐\n│              Token usage (live turns)              │\n├──────────┬─────────┬───────────┬───────────────────┤\n│ member   │  prompt │ completion│  total            │\n├──────────┼─────────┼───────────┼───────────────────┤\n│ @lead    │  12 450 │     3 210 │  15 660           │\n│ @worker  │   8 120 │     5 890 │  14 010           │\n├──────────┼─────────┼───────────┼───────────────────┤\n│ total    │  20 570 │     9 100 │  29 670           │\n└──────────┴─────────┴───────────┴───────────────────┘\n```\n\nToken counts come from the Ollama `/api/chat` `eval_count` /\n`prompt_eval_count` fields (for the `ollama` backend) or the OpenAI\n`usage` object (for `openai_compat`).  The summary is omitted when all\ncounts are zero (e.g. pure replay runs or backends that don't report\ntoken usage).\n\n---\n\n\n## Cost estimation\n\nAfter every `team run` and `team stats` command, the token-usage table includes an **Est. cost** column with a USD estimate based on the model used by each member.\n\nLocal Ollama models always show **$0.00 (local)** since they run on your hardware.  Cloud models (`backend: openai_compat`) are looked up in the built-in pricing table.\n\n### Built-in pricing table\n\n| Provider | Models |\n| --- | --- |\n| **OpenAI** | `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`, `gpt-4`, `gpt-3.5-turbo`, `o1`, `o1-mini`, `o3`, `o3-mini` |\n| **Anthropic** | `claude-opus-4`, `claude-sonnet-4`, `claude-3-5-sonnet`, `claude-3-5-haiku`, `claude-3-opus`, `claude-3-sonnet`, `claude-3-haiku` |\n| **Google** | `gemini-2.0-flash`, `gemini-1.5-pro`, `gemini-1.5-flash` |\n| **Mistral** | `mistral-large`, `mistral-medium`, `mistral-small`, `codestral` |\n| **Meta (cloud-hosted)** | `llama-3.1-405b`, `llama-3.1-70b`, `llama-3.1-8b`, `llama-3-70b`, `llama-3-8b` |\n\nModel names are matched by prefix/substring so versioned names like `gpt-4o-2024-08-06` automatically map to `gpt-4o` pricing.  If a model is not recognised, the cost column shows **?**.\n\n\u003e **Prices are estimates only.** Provider pricing changes over time — update `team/pricing.py` with the latest figures from your provider's pricing page.\n\n---\n\n\n## Run statistics\n\n`team stats` shows a detailed breakdown of a completed run — turn counts,\ntoken usage per speaker, total duration, and files written — without\nneeding to start any containers:\n\n```bash\nteam stats my-team.yaml\n```\n\nExample output:\n\n```text\nTeam: my-team  18 turns · 29 670 tokens · duration 142.3s · 5 file(s) written\n\n┌─────────────────────────────────────────────────────────────────────┐\n│               Turns \u0026 token usage by speaker                        │\n├──────────────┬───────┬───────────────┬──────────────────┬───────────┤\n│ Speaker      │ Turns │ Prompt tokens │ Completion tokens│    Total  │\n├──────────────┼───────┼───────────────┼──────────────────┼───────────┤\n│ @lead        │     5 │        12 450 │            3 210 │    15 660 │\n│ @orchestrator│     1 │             0 │                0 │         0 │\n│ @worker      │    12 │         8 120 │            5 890 │    14 010 │\n├──────────────┼───────┼───────────────┼──────────────────┼───────────┤\n│ total        │    18 │        20 570 │            9 100 │    29 670 │\n└──────────────┴───────┴───────────────┴──────────────────┴───────────┘\n```\n\nThe `Transcript.stats()` method in `team/bus.py` is also part of the\npublic Python API:\n\n```python\nfrom team.bus import Transcript\nfrom team.config import load_team\n\ncfg = load_team(\"my-team.yaml\")\nt = Transcript(persist_path=cfg.workspace / \"transcript.jsonl\", resume=True)\ns = t.stats()\nprint(s[\"total_turns\"], s[\"duration_seconds\"])\n```\n\n---\n\n\n## Exporting a run report\n\nAfter a run you can bundle the full transcript and every produced artifact\ninto a single shareable document:\n\n```bash\nteam export my-team.yaml                          # Markdown (default)\nteam export my-team.yaml --format html            # self-contained HTML (dark-mode aware)\nteam export my-team.yaml --format json            # machine-readable JSON\nteam export my-team.yaml --output ~/Desktop/run.md\nteam export my-team.yaml --no-artifacts           # omit workspace files (faster, smaller)\n```\n\nThe report includes:\n* Team name, goal, members, and workflow settings.\n* Every member turn with speaker, role, content, and files written.\n* **Token usage \u0026 estimated cost table** — per member and totals.\n* Full contents of all files produced in the shared workspace (omit with `--no-artifacts`).\n\nOutput path defaults to `\u003cworkspace\u003e/report.md` / `.html` / `.json`.\n\n**Format details:**\n\n| Format | Description |\n| --- | --- |\n| `markdown` | Single `.md` file with transcript, token table, and fenced artifact blocks. |\n| `html` | Self-contained `.html` — embedded CSS, no external deps, respects `prefers-color-scheme: dark`. |\n| `json` | Structured JSON (`format_version: 1`) with `team`, `stats`, `token_usage`, `turns`, and `artifacts` keys — suitable for post-processing. |\n\n---\n\n\n## `team replay` — interactive transcript browser\n\nAfter a run completes, `team replay` lets you step through the saved\ntranscript turn-by-turn in an interactive terminal viewer — like a\ndebugger for a past run.  No LLM calls, no Docker, no network — it\nworks entirely from the persisted `transcript.jsonl` file.\n\n```\nteam replay myteam.yaml                     # start at turn 0\nteam replay myteam.yaml --from 5            # start at turn 5\nteam replay myteam.yaml --speaker alice     # jump to alice's first turn\n```\n\n### Navigation keybindings\n\n| Key | Action |\n| --- | --- |\n| `→` / `n` / Space / Enter | Advance to the next turn |\n| `←` / `p` / `b` | Go back one turn |\n| `g` | Prompt for a turn number and jump directly to it |\n| `f` | Prompt for a speaker name and jump to their next turn |\n| `s` | Toggle the stats summary panel (token totals, turn counts) |\n| `q` / Esc | Quit |\n\n### Non-interactive mode\n\nWhen stdin is not a TTY (e.g. a CI pipeline or a pipe), `team replay`\nprints all turns sequentially — the same rich panel rendering used by\n`team transcript` — and exits immediately.  This makes it safe to use\nin scripts:\n\n```bash\nteam replay myteam.yaml | head -100\n```\n\n### Options\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `--from N` | `0` | Start at turn N (0-based). |\n| `--speaker NAME` | — | Jump to the first turn by NAME at startup. |\n\n---\n\n\n## Automated testing with `team test`\n\n`team test` runs the team and then validates a set of assertions defined in the\n`tests:` section of the team YAML.  This makes it easy to build a repeatable\ntest suite for your team in CI.\n\n```yaml\ntests:\n  - name: creates hello.py\n    type: file_exists\n    path: hello.py\n\n  - name: script contains print\n    type: file_contains\n    path: hello.py\n    text: \"print\"\n\n  - name: no error messages\n    type: file_not_contains\n    path: report.txt\n    text: \"ERROR\"\n\n  - name: results is valid JSON\n    type: json_valid\n    path: results.json\n\n  - name: results matches schema\n    type: json_schema\n    path: results.json\n    schema:\n      type: object\n      required: [entities, summary]\n\n  - name: any member mentioned Python\n    type: transcript_contains\n    text: \"Python\"\n\n  - name: developer specifically mentioned Python\n    type: transcript_contains\n    speaker: developer\n    text: \"Python\"\n\n  - name: exactly 4 member turns\n    type: transcript_count\n    count: 4\n```\n\n```\nteam test myteam.yaml               # run the team, then assert\nteam test myteam.yaml --no-run      # assert against an existing run\nteam test myteam.yaml --max-rounds 2 --goal \"quick smoke test\"\n```\n\nExits with code **0** if all assertions pass, **1** if any fail (suitable for\nCI gates).\n\n### Assertion reference\n\n| Type | Required fields | Description |\n| --- | --- | --- |\n| `file_exists` | `path` | File must exist in the shared workspace. |\n| `file_not_exists` | `path` | File must *not* exist. |\n| `file_contains` | `path`, `text` | File content must contain the substring. |\n| `file_not_contains` | `path`, `text` | File content must *not* contain the substring. |\n| `json_valid` | `path` | File must be parseable JSON. |\n| `json_schema` | `path`, `schema` | File must be valid JSON matching the JSON Schema. |\n| `transcript_contains` | `text` | At least one turn must contain the text. Add `speaker` to restrict to one member. |\n| `transcript_count` | `count` | Exact number of member turns (excludes `orchestrator`/`human`). |\n\nAll `path` values are relative to the **shared workspace** directory\n(`\u003cworkspace\u003e/shared/`).\n\n---\n\n\n## Multi-team pipelines\n\nA *pipeline* lets you chain multiple team runs together so that the output of one team — its shared workspace files and a transcript summary — is automatically injected into the next team's context.\n\n### Pipeline YAML\n\nCreate a `pipeline.yaml` alongside your team files:\n\n```yaml\nname: research-and-write\ndescription: Research a topic, then write a publication-ready paper.\nworkspace: ./runs/research-and-write   # optional; default is ./runs/\u003cname\u003e\n\nstages:\n  - id: research\n    team: ./teams/researcher.yaml\n\n  - id: writing\n    team: ./teams/writer.yaml\n    depends_on: [research]          # wait for research to complete\n    inject_files: true              # copy research's shared/ files here\n    inject_context: true            # write context.md from research output\n    goal_override: |                # {stage_id.summary} templates available\n      Write a publication-ready paper based on the research below.\n\n      {research.summary}\n```\n\n### Running a pipeline\n\n```bash\nteam pipeline pipeline.yaml\n```\n\nPreview the execution plan without running anything:\n\n```bash\nteam pipeline pipeline.yaml --dry-run\n```\n\n### Stage fields\n\n| Field | Type | Default | Description |\n| --- | --- | --- | --- |\n| `id` | string | *(required)* | Unique stage identifier used in `depends_on` and goal templates. |\n| `team` | path | *(required)* | Path to the team YAML file (relative to the pipeline file). |\n| `depends_on` | list of IDs | `[]` | Stages that must complete before this stage runs. |\n| `inject_files` | bool | `false` | Copy every file from upstream stages' `shared/` directories into this stage's `shared/` directory before the team starts. |\n| `inject_context` | bool | `false` | Write a `context.md` file into this stage's workspace summarising upstream stages' output. Members pick it up automatically. |\n| `goal_override` | string | — | Replace the team YAML's `goal` for this pipeline run. Supports `{stage_id.summary}` template substitution. |\n\n### How data flows\n\nEach stage runs inside its own sub-workspace: `\u003cpipeline.workspace\u003e/\u003cstage.id\u003e/`. At the end of every stage the runner extracts:\n\n- **Summary** — the last five member turns from the transcript, concatenated.\n- **Artifacts** — all files in `shared/`, keyed by relative path.\n\nWhen the next stage has `inject_files: true`, artifact files are copied verbatim into the destination stage's `shared/` directory before its team starts. When `inject_context: true`, a `context.md` is written at the stage workspace root with the summaries and file lists from all upstream stages.\n\n### Goal templates\n\n`goal_override` is a Python `str.format()` template. Each upstream stage result is available as `{stage_id.summary}`:\n\n```yaml\ngoal_override: |\n  Review the following research and identify gaps.\n\n  Research output:\n  {research.summary}\n\n  Initial draft:\n  {writing.summary}\n```\n\n---\n\n\n## Cross-team collaboration (bridge)\n\n`team` clusters running on **different machines**, operated by **different\npeople or organisations**, can collaborate on common goals through the bridge\nprotocol.  One cluster delegates a sub-task to a remote cluster; the remote\ncluster runs its full team workflow and returns the results — including all\nfiles it produced.  The exchange can repeat over multiple turns, just like a\nreal inter-laboratory collaboration.\n\n### How it works\n\n```\nLab A cluster (local)                       Lab B cluster (remote)\n┌─────────────────────────────────────┐     ┌──────────────────────────────────┐\n│  Orchestrator A                     │     │  team serve lab-b.yaml           │\n│  members: pi, analyst               │     │  BridgeServer (port 7001)        │\n│                                     │     │                                  │\n│  @pi uses delegate_task tool ───────┼─────┼──► POST /tasks                   │\n│                                     │     │    ┌──────────────────────────┐  │\n│                                     │     │    │ Orchestrator B","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcumbof%2Fteam","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcumbof%2Fteam","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcumbof%2Fteam/lists"}