Skip to main content

Colleague is a swappable coder-agent harness that turns different models into repo workers behind one shared task contract.

Project description

colleague

Colleague is a swappable coder-agent harness that turns different model backends into repo workers behind one shared task runtime.

One runtime, many minds.

The problem it solves. AI coder backends are not interchangeable — a local vLLM model, an OpenAI-compatible endpoint, and a hosted assistant each bring their own SDK, controls, and output shape. Colleague is the harness around the model: you point it at a repo task, pick a backend, and it runs the work through that backend's bounded tool-loop and hands back the same typed result every time. Swap the backend and nothing else about your workflow changes — the caller never has to care which mind ran the task.

Quickstart (30 seconds)

No model and no network required — the mock backend runs the full loop deterministically, so you can see the shape of a task before wiring up a real model:

uv sync
uv run pytest -n auto                            # full suite, offline

# Run your first task through the deterministic mock backend:
uv run colleague work "add a CONTRIBUTING.md stub" --repo . --engine mock --no-pr

# Open the interactive cockpit (the session palette) at a terminal:
uv run colleague

To drive a real model (a local vLLM server, or any OpenAI-compatible endpoint), see Running a real model (vLLM) below.

When to reach for colleague

  • You want to swap coder backends without changing your workflow. The same work command and the same TaskResult come back whether a local 27B or a cloud endpoint ran it.
  • You want a different, independent mind on a task — a second opinion on a diff, a fresh read of an unfamiliar area, or a small change taken off your plate. The first-party ask-colleague skill is built for exactly this: diversity (a second mind), not a stronger model, is the point.
  • You want reproducible, auditable AI coding tasks. Every run writes a JSON result artifact with a full step trace, always-on stats, and opt-in OpenTelemetry traces — so a task's cost and behavior are inspectable after the fact.
  • You want operator controls around an autonomous loop — saved command templates, lifecycle hooks, and an approval gate that gates what the harness may execute.

Colleague is not a chat assistant and not an IDE plugin — it is a headless task runtime. For an interactive pair-programmer, use Claude Code or Codex; reach for colleague when you want a backend-agnostic worker you can script, gate, and audit.

Common commands

Command What it does
colleague work "<goal>" --repo . Run a task autonomously through a backend.
colleague (no args, at a terminal) Open the interactive cockpit / session palette.
colleague doctor --probe Check the install and ping the model endpoint.
colleague learn Print a prompt that teaches an agent how to drive colleague.
colleague learn-from claude Absorb another agent's skills into colleague's own (writes files; add --dry-run to preview).

learn and learn-from are different commands — see Two senses of "learn".

Architecture

Part In Colleague
Mind / backend the model/coder backend (a local vLLM model, an OpenAI-compatible endpoint, …)
Adapter the code that invokes and controls one backend (colleague/engines/)
Task runtime the shared task contract + lifecycle (TaskTaskResult)
Tool loop the bounded agentic loop the backend works the repo through
Plugins replaceable backend adapters, discovered via Python entry points
Run report the JSON result artifact + step trace each run writes
Telemetry opt-in OpenTelemetry traces + metrics (colleague/telemetry/)
Handoff branch/commit/push + gh pr create, gated for offline/CI (colleague/handoff.py)
Doctor colleague doctor — read-only configuration-readiness health check (colleague/oilcheck/)
Registry colleague backends list — the backend plugins installed in this environment
Approval gate colleague/policy.py — operator-declared .colleague/approvals.json that controls what the harness executes

What ships

Colleague has graduated from v0 to v1 (see CHANGELOG.md for the per-version history and pyproject.toml for the current release). The runtime ships:

  • A shared task contract — a typed Task and TaskResult that every backend consumes and produces identically.
  • A bounded agentic tool-loop — the backend calls read_file, write_file, edit_file (partial-edit; cost scales with the change, not the file), list_dir, run_command, culture (AgentCulture CLIs), and finish, confined to the target repo, until it finishes or hits the step budget.
  • Two backends, both registered through the same colleague.engines entry-point group an out-of-tree plugin would use:
    • mock — deterministic and networkless; the CI workhorse.
    • vllm-openai — drives any OpenAI-compatible /v1/chat/completions endpoint with tool calling. The built-in default model is sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP (an NVFP4 Qwen3 checkpoint on a vLLM server — what doctor checks for); any tool-calling model works.
  • Git/PR handoff — branch → commit → push → gh pr create, gated so --no-pr (or no remote) stays a local commit and CI never pushes.
  • A result artifact (.colleague/<task-id>.json) for handoff back to Guildmaster / Taskmaster / Steward.
  • Command templates — reusable, parameterized task recipes stored under .colleague/commands/*.md, invoked with work --command <name> [args…] or selected in the interactive palette.
  • Lifecycle hooks — operator-authored shell commands that fire at task_start, pre_tool, post_tool, and finish events; a pre_tool hook can allow, deny, or rewrite tool calls before the backend executes them. A per-model hooks overlay (.colleague/<model>/hooks.json) layers model-specific fixes ahead of the base hooks, giving the operator a precision tool for recurring model biases — applied only for the targeted model, a strict no-op for all others; no new runtime dep, socket, or daemon.
  • Interactive palettecolleague session is the agent-native entry point: type a free-text goal and it routes to work or plan on colleague's own backend automatically — no subcommand needed. Template numbers and names run directly; / opens slash commands. Override the session backend via COLLEAGUE_SESSION_ENGINE without touching the global COLLEAGUE_ENGINE.
  • Layered per-model config — AGENTS instructions (AGENTS.mdAGENTS.colleague.mdAGENTS.colleague.<model>.md) and skills (.colleague/skills/*.md.colleague/<model>/skills/*.md) compose into a model-specific system prompt, with strict per-model isolation; inspect them with colleague agents list / colleague skills list.
  • Telemetry: OpenTelemetry observability — opt-in traces + metrics over OTLP, emitted identically by every backend; off by default and a strict no-op, with the SDK as an optional [otel] extra so the base install stays dep-free.
  • doctor — a read-only configuration-readiness health check across identity, provider, engines, otel-readiness, and environment; emits a rubric-shaped report and exits non-zero when unhealthy.
  • Mesh-member integration — a work item resolves a process-level identity (the repo's culture.yaml nick or .colleague/identity.json) and propagates it to subcommands via COLLEAGUE_IDENTITY. The loop exposes one curated culture tool (allow-list: agtag, devex) that shells out to the operator-installed CLIs with the identity injected. Operators opt into read-only ephemeral neighbour clones via .colleague/neighbours.json (defaults to empty; no new runtime dependency).
  • Destination — colleague's sibling to telemetry. When a task warrants it, a backend can set a curated devague loop tool to open and converge a goal-frame before driving the repo, and declare the announcement on arrival. The destination (frame slug + announcement) is recorded in the JSON artifact; the curated allow-list excludes confirm/reject (user-only) and export (operator-only), and convergence is advisory — only human-confirmed claims are authoritative. Setting a destination is optional and backend-judged.
  • Approval gate — an operator-declared .colleague/approvals.json that gates what the harness executes. Approval is tamper-protection, not just a name list: approve records a file's content checksum; if the file changes later the approval is void. Three categories are gated, each opt-in by presence of its section: run_command CLIs by program token (allow/deny lists), lifecycle hook scripts by checksum, and command templates by checksum. Skills and AGENTS instructions load freely — they are never gated. See the Approval gate section below for the full config shape and usage.
  • Startup bannercolleague work and colleague session greet an interactive terminal with an ASCII banner. It's decorative chrome: written to stderr, shown only on a TTY, and suppressed under --json, so it never pollutes the stdout result stream or agent-parsed output.

Out of scope (by design — each needs a re-spec before it lands): a multi-backend router / routing policy, an execution sandbox, a daemon mode, Codex/Claude/Gemini adapters, a --no-hooks escape hatch (there is no such flag; the approval gate is the landed hook-trust increment — a policy gate, not a sandbox), and a live MCP runtime (no mcp.json, no mcp verb; the curated culture tool shells out to operator CLIs — no socket, no MCP transport). The runtime package has no third-party dependencies — the vLLM adapter speaks the OpenAI wire format over the standard library.

Feature docs

Each shipped feature has a focused page under docs/features/ — start at the feature index. For the per-version list of what shipped (and when), see CHANGELOG.md.

Feature Doc
Work & the tool-loop work-and-loop.md
Context budget / graceful degradation graceful-degradation.md
Capacity standard / fill-line decision (v1) spec
Backends & plugins engines.md
Model & endpoint selection model-selection.md
Git/PR handoff handoff.md
Result artifact artifact.md
Command templates command-templates.md
Lifecycle hooks hooks.md
Interactive palette session.md
Cockpit views (tui / TAUI) tui.md
Layered per-model config layered-config.md
Learn skills from a peer learn-from.md
Telemetry: OpenTelemetry telemetry.md
Work stats & feedback (ROI) stats-and-feedback.md
doctor (health check) doctor.md
Agent-first CLI agent-cli.md
Mesh-member integration mesh-member.md
Resident promote (Culture member) resident-promote.md
Destination destination.md
Subagents subagents.md
Parallel subagents parallel-subagents.md
Auto-split (too-large assignment) auto-split.md
Audit fan-out audit-fanout.md
Per-model configuration per-model-configuration.md
Approval gate See Approval gate section below
Ask colleague (a different mind) ask-colleague.md
Escalation (agtag continuation) escalation.md
Memory (eidetic recall/remember) memory.md
Background one-shot (work --background) background.md
Resident appserver (mesh work requests) resident.md
Deepthink (dual-model escalation) deepthink.md
Mode profiles mode-profiles.md
Adaptive backpressure backpressure.md
Tier visibility tier-visibility.md
Skill curation (budget-aware) skill-curation.md
Rig budget (cross-process slots) rig-budget.md
Task goals & acceptance task-goals.md
Session modes (shift-tab / /mode) session-modes.md
Plan mode plan-mode.md
Subagent roles (typed workforce) subagent-roles.md
Write isolation (throwaway worktrees) write-isolation.md
Lint pre-finish gate lint-gate.md
Test-integrity gate test-integrity.md
Affected-tests gate affected-tests.md
Capacity standard / fill-line capacity-standard.md
Explore never wastes a run explore-never-wastes.md
Continue-working (stall recovery) continue-working.md
Flight (pilot a run mid-air) flight.md
Cleanup / reap (clean) cleanup-reap.md
Config resolution (.colleague/config.json) config-resolution.md
Approval gate (detail) approval-gate.md
CLI on agentfront (rendered surface) cli-on-agentfront.md
ask-colleague trustworthy reflex ask-colleague-trustworthy-reflex.md

The detailed sections below remain the canonical reference; the feature pages add per-feature source pointers and cross-links.

Before → after: the extensibility layer

Before this layer, colleague work accepted one raw instruction string and ran the tool-loop with no operator gate and no saved recipes: run_command and write_file executed unconditionally, and every task had to be typed from scratch.

After, operators drop files into .colleague/ and gain three things that work identically across every backend (the all-engines rule):

  1. Command templates — author a recipe once, invoke it by name with positional arguments; work --command <name> [args…] expands it into the same Task shape a raw work "…" produces.
  2. Lifecycle hookspre_tool hooks can allow, deny (reason fed back to the model), or rewrite tool arguments before they execute; post_tool hooks run formatters or linters after; task_start and finish hooks bracket the whole work item. Every firing is recorded in the result artifact.
  3. Interactive palettecolleague session lists discovered templates, accepts a selection (by number or name) plus optional arguments, and runs the chosen task through the same work path, loop, hooks, and artifact — no parallel code path.

This extensibility lives in the runtime (colleague/loop.py), not in any one backend, so it binds equally to mock, vllm-openai, and any future plugin.

Running a real model (vLLM)

To go beyond the deterministic mock backend, point colleague at a real OpenAI-compatible model server. (Discover what's installed first with uv run colleague backends list.)

Start an OpenAI-compatible vLLM server with tool calling enabled:

vllm serve Qwen/Qwen3-32B \
  --port 8001 \
  --enable-auto-tool-choice \
  --tool-call-parser hermes

The right --tool-call-parser depends on the model and the vLLM build: hermes works for many models (including Qwen/Qwen3-32B above), while other builds need a different one — e.g. an NVFP4 Qwen3 checkpoint served via vLLM may want qwen3_coder. The backend itself is parser-agnostic — any parser that makes the server emit OpenAI-format tool calls works.

Tip (anecdotal). With an NVFP4 Qwen3 checkpoint, qwen3_coder handled tool-argument escaping more reliably than hermes in our testing: a hermes run over-escaped the triple-quotes in a generated docstring (writing \"\"\" instead of """), producing a SyntaxError, where qwen3_coder wrote the same file cleanly. This is a single observation, not a benchmark — but if a parser garbles quote-heavy edits, trying the other one is worth a shot.

Then point Colleague at it (defaults already target localhost:8001):

uv run colleague work "fix the typo in the README title" \
  --repo /path/to/target/repo \
  --engine vllm-openai \
  --base-url http://localhost:8001/v1 \
  --model Qwen/Qwen3-32B

Configuration resolves in the order: explicit flag → COLLEAGUE_* env → OPENAI_* env → default. The built-in default --model is sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP; the example above overrides it with --model to match the server you started. Because the adapter only touches the OpenAI surface, pointing --base-url at any compatible server (llama.cpp, an OpenAI proxy) needs no code change.

The opt-in live end-to-end test proves this against a real server:

COLLEAGUE_VLLM_E2E=1 uv run pytest tests/test_vllm_live.py -v

Command templates

Operators save reusable task recipes as Markdown files under .colleague/commands/<name>.md (repo-level or ~/.colleague/commands/ for user-level; repo-level shadows user-level by stem).

.colleague/commands/ is committable — it is the one part of the otherwise gitignored .colleague/ dir that git tracks, so a team can share recipes in-repo (run artifacts, hooks.json, and approvals.json stay local). The committed doc-review recipe is a worked example.

Template file format

A template may open with an optional --- metadata block:

---
description: Fix lint errors under a path
engine: mock
constraints: keep diffs minimal, run the formatter
arg-hint: <path>
---
Fix all lint errors under $1. Then run the formatter. $ARGUMENTS

Supported metadata keys:

Key Meaning
description One-line description shown in listings
engine Engine to use when running this command (overridden by --engine)
constraints Comma-separated constraints added to the Task
arg-hint Short argument hint shown in commands list

If no --- block is present, the entire file content is the body.

Argument substitution

Placeholder Expands to
$ARGUMENTS All arguments joined by a space
$1, $2, … The N-th positional argument (empty string if not supplied)

Running a command template

# One-shot via work:
uv run colleague work --command fix-lint src/ --repo /path/to/repo --engine mock --no-pr

# List all discovered templates:
uv run colleague commands list --repo .

# Surface overview:
uv run colleague commands overview

The --command flag and a positional instruction are mutually exclusive; any tokens after --command <name> are passed as template arguments ($1, $2, $ARGUMENTS).

Lifecycle hooks

Hooks are operator-authored shell commands registered in .colleague/hooks.json (repo-level or ~/.colleague/hooks.json for user-level; repo-level wins).

Config format

{
  "hooks": {
    "pre_tool":  [{ "matcher": "run_command", "command": "my-policy-gate.sh" }],
    "post_tool": [{ "matcher": "write_file",  "command": "black $file 2>/dev/null; true" }],
    "task_start":[{ "command": "echo task starting" }],
    "finish":    [{ "command": "echo done" }]
  }
}

Each entry has:

Field Meaning
matcher Regex (re.fullmatch) tested against the tool name. Absent or empty matches every tool. Ignored for task_start / finish events.
command Shell command run in the target repo directory.

Lifecycle events

Event When it fires Pre/post effect
task_start Before the first tool call Observe only
pre_tool Before each tool call Can allow, deny, or rewrite
post_tool After each tool call Observe only (side-effects OK)
finish After the loop ends Observe only

Hook I/O contract

The hook receives a JSON payload on stdin:

{
  "event": "pre_tool",
  "tool": "run_command",
  "arguments": { "command": "pytest" },
  "task_id": "<uuid>",
  "repo_path": "/path/to/repo"
}

The hook signals its decision via exit code and optional structured stdout:

Exit code Stdout Decision
non-zero any deny — stderr (fallback: stdout) is fed back to the model as the tool result
0 empty or non-JSON allow — tool runs as-is
0 {"decision":"allow", ...} allow
0 {"decision":"deny", "reason":"..."} deny — reason fed back to model
0 {"decision":"rewrite","arguments":{...}} rewrite — tool runs with the supplied replacement arguments

Any response may carry an "additionalContext" string. Every firing (event, matched command, decision, exit code) is recorded in TaskResult.hook_firings and appears in the result artifact JSON.

post_tool, task_start, and finish hooks are observe-only: a deny from these events is recorded but does not halt the loop.

Inspecting hooks

uv run colleague hooks list --repo .
uv run colleague hooks overview

Interactive cockpit (session)

colleague session is the agent-native entry point to colleague: type a free-text goal and it routes automatically to work or plan on colleague's own backend — no subcommand needed.

uv run colleague session --repo /path/to/repo
# or, relying on COLLEAGUE_SESSION_ENGINE / COLLEAGUE_ENGINE:
colleague session --repo /path/to/repo

Agent-native by default (#234). Free-text input is classified by colleague/session_intent.py (classify_intent) and routed to work (the default) or plan without naming a subcommand. A → work: / → plan: line confirms the dispatch. Numbers and template names run a work template directly and are never reclassified. The session runs on colleague's own served backend: precedence is --engine > COLLEAGUE_SESSION_ENGINE (a session-only env-var override, new) > COLLEAGUE_ENGINE > vllm-openai. To point the conversational session at a different backend than bare colleague work, set COLLEAGUE_SESSION_ENGINE — without touching COLLEAGUE_ENGINE.

Legible action feed (#233). Consecutive identical feed entries collapse into <entry> ×N instead of stacking (e.g. four back-to-back culture calls become [culture] agtag issues ×4); the culture/devague tools now surface as <cli> <args> rather than a bare [culture] sentinel; and step hints are capped at 120 characters (up from 48).

AgentFront probe reflex (#235). Before the first real use of an unfamiliar CLI in a run, the backend is instructed to check its learn/explain/--help surface first. This is advisory and read-only — it never installs or approves anything.

Input is line-based. Plain text runs a work item — a number (palette entry), a template name, or a free-text instruction (intent-routed to work or plan). A line starting with / is a slash command:

  • Introspection (surface an existing noun in the cockpit): /help, /commands, /skills, /agents, /config (the doctor readiness view), /engines, /telemetry, /feedback.
  • Live config: /engine <name>, /model <name>, /base <branch>, /pr (toggle push + PR) — change the session without restarting it.
  • /quit (or q / empty line) ends the session.

Three render tiers of the one state, chosen automatically:

  • Interactive (a colour TTY) — the dynamic ANSI cockpit: redraw-in-place, and popups on real events (an error popup when a work item step fails).
  • Non-interactive (piped / captured)Markdown menus (the static but full agent-readable view), the default off a TTY. --no-tui forces it on a TTY.
  • --json — stdout carries only the work item TaskResult (one JSON object each, preserving the machine contract); the cockpit renders to stderr as chrome.

Running colleague with no arguments at a terminal opens this same cockpit (backend resolved as above: --engine > COLLEAGUE_SESSION_ENGINE > COLLEAGUE_ENGINE > vllm-openai, never a silent mock). By default it is a "talk + iterate" loop — each work item commits locally but does not push or open a PR; /pr or --pr opts in.

Cockpit views (tui)

colleague tui exposes a headless, stdlib-only cockpit in three views of one CockpitState: a JSON/TAUI mirror (the agent-readable, selector-addressed source of truth — tui state), an ANSI frame (the visual render — tui render, the default), and a Markdown view (the agent-facing readable render — tui render --format markdown). TAUI (Textual Agentic UI) lets an agent read and operate the UI without screen-scraping, an LLM, or any colleague import.

The cockpit is a pure reducer (event → reduce(state, event) → CockpitState) and its state carries a popup model (skill_suggestion / confirmation / error / progress / diff / help). tui snapshot captures a moment; tui diagnose classifies cross-view disagreements (no LLM, no network); tui live opens the foreground TTY cockpit.

uv run colleague tui state                          # the TAUI JSON mirror
uv run colleague tui render --state <file>          # the ANSI frame
uv run colleague tui render --format markdown --state <file>
uv run colleague tui overview

Watching a live work item. A real work feeds the cockpit (#74):

uv run colleague work "<task>" --engine mock       # auto: live cockpit on a TTY
uv run colleague work "<task>" --engine mock --no-tui   # force the plain step lines
uv run colleague work "<task>" --engine mock --tui-events run.jsonl   # live event stream
uv run colleague tui replay --trace .colleague/<id>.trace.jsonl      # replay a finished work item
  • Live cockpit (A1) — on an interactive terminal a work item renders the cockpit as it runs (conversation per step, popups on real events — e.g. an error popup when a tool step fails). Auto-on a TTY; --tui / --no-tui force it. Off a TTY (pipes/agents/CI) it falls back to the plain step N: <tool> [ok|err] stderr lines, byte-for-byte unchanged.
  • Live event stream (A3)--tui-events <path> appends one WorkStep JSONL line per step as the work item runs, so an agent can follow it turn-by-turn or tui replay it. (A stream written into the driven repo is treated as harness telemetry — never swept into the work branch.)
  • Replay a real work item (A4)tui replay --trace <id>.trace.jsonl folds a finished work item's loop-step trace into the cockpit (live and replayed steps read identically — one shared converter).
  • Interactive cockpit (A2)colleague session is now cockpit-rendered with slash commands; see Interactive cockpit (session).

A mid-loop failure still writes a partial artifact (status=error) with the steps, usage, and changed files accumulated so far.

See docs/features/tui.md for the full surface.

Telemetry: OpenTelemetry observability

A work item can emit OpenTelemetry traces + metrics so it's observable against an OTLP collector — not just the per-run JSON artifact. Telemetry lives in the runtime (the loop + the shared work path), so every backend emits it identically, exactly like lifecycle hooks.

It is off by default and a strict no-op when off (no spans, no SDK import, the result artifact unchanged). The OpenTelemetry SDK is an optional extra — the base install keeps zero runtime dependencies:

pip install 'colleague[otel]'                 # or: uv sync --extra otel
export COLLEAGUE_OTEL_ENABLED=1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318   # OTLP/HTTP collector
uv run colleague work "<task>" --repo . --engine mock --no-pr
#   -> stderr prints "trace: <id>"; the collector receives the spans + metrics

Requested without the extra installed, colleague degrades to a no-op with a one-line stderr notice — it never fails the work item.

Signals. Spans: colleague.work (root) → colleague.tool.* (per tool call) → colleague.handoff. Metrics: colleague.steps, colleague.tokens, colleague.tool.latency, colleague.tool.calls, colleague.hook.denials, colleague.work.duration.

Config (precedence: explicit > COLLEAGUE_OTEL_* > standard OTEL_* > default): COLLEAGUE_OTEL_ENABLED, COLLEAGUE_OTEL_ENDPOINT / OTEL_EXPORTER_OTLP_ENDPOINT, COLLEAGUE_OTEL_SERVICE_NAME / OTEL_SERVICE_NAME. OTEL_SDK_DISABLED=true is honored as a kill-switch.

uv run colleague telemetry status      # resolved config + whether the SDK is installed
uv run colleague telemetry overview    # describe the surface

Work stats & the feedback loop (ROI)

Together these let a caller compute the ROI of outsourcing a task: the stats say what a work item cost, a feedback record says how good it was.

Always-on stats. Every TaskResult carries a stats block (WorkStats), written into the artifact on every work item — no flag, no opt-in. It records the request, ISO start + wall-clock duration, model turns, step count, per-tool counts, files changed, exact UTF-8 bytes_written, and reasoning-vs-answer char/byte sizes. Exact token counts stay on usage, verbatim from the model response (never estimated). Like hooks and telemetry, stats are runtime-owned — identical for mock and vllm-openai.

Honest token limit. Colleague has no tokenizer (zero deps), and the served model reports no reasoning-token breakdown — so "thought vs written" is measured as chars/bytes, not tokens.

Feedback. A single record per work item (re-grading overwrites) lives beside the artifact at .colleague/<task_id>.feedback.json; a per-repo last_work pointer lets you grade the most recent work item without quoting its id. An ungraded work item reads back as a clean "no feedback yet" state, never an error.

uv run colleague feedback record last --rating 4 --notes "correct but verbose"
uv run colleague feedback show last --repo .
uv run colleague feedback overview

The agent-facing entry is the ask-colleague feedback skill verb. See docs/features/stats-and-feedback.md.

Configuration readiness: doctor

Before you hand colleague work, colleague doctor answers "is this install actually ready to work?" It is colleague's read-only, diagnose-only health check (no --fix, zero new runtime deps) that emits a rubric-shaped {healthy, checks[]} report across five ordered check-groups:

Group Checks (severity)
identity prompt_file_present / backend_consistency (error), skills_present (warning)
provider resolved base_url/model with redacted api_key (info); credentials + budget advisories (warning) on a non-default provider
engines backends discovered + both bundled backends present + each plugin loads (error; all-engines rule)
otel telemetry enabled / SDK importable / endpoint configured (info; error only when enabled but the [otel] extra is missing)
environment .colleague/ config, hooks.json validity, command-template parsing, AGENTS/skills layering, git (error) + gh (warning) on PATH, CLI integrity

Only a failed error check flips the report unhealthy; warnings and info are advisory. doctor exits 1 when unhealthy, else 0. The diagnostic logic lives in the runtime-level colleague/oilcheck/ package (like telemetry); the verb is a thin renderer. Add a check-group by appending a read-only checks() callable to CHECK_GROUPS — see colleague explain doctor and docs/features/doctor.md.

uv run colleague doctor          # human-readable rubric; exit 1 if unhealthy
uv run colleague doctor --json   # structured {healthy, checks[]}
uv run colleague doctor --probe  # + a live provider ping (the one networked check)

--probe adds two opt-in checks that open a network connection — provider_reachable (can colleague reach the endpoint?) and provider_model_available (is the configured model actually served at that endpoint?) — gated behind the flag so the default doctor stays network-free.

Two senses of "learn": self-teaching vs. learning from a peer

Colleague has two verbs with "learn" in the name. They do different things — this is the one place to keep them straight:

Command Direction What it does Who it's for
colleague learn colleague → reader Prints a structured self-teaching prompt (purpose, command map, exit codes, --json, the explain pointer). Read-only — nothing is written. An AI agent onboarding into a repo that needs to drive colleague. (Humans usually want colleague explain <path> or colleague overview instead.)
colleague learn-from <source> peer → colleague Colleague absorbs another agent's skills: it reads e.g. Claude Code's .claude/skills/<name>/SKILL.md, adapts each into colleague's own .colleague/skills/<name>.md, and (optionally) has the backend rewrite Claude-isms for colleague's tool surface. Writes files. An operator growing colleague's skill set from a peer mind.

Mnemonic: learn teaches someone else how to use colleague; learn-from teaches colleague by absorbing a peer's skills. See docs/features/learn-from.md for the full two-stage flow (a deterministic copy, then an optional LLM review-and-adapt pass), the safety rules, and the honest "load, not execute" limit.

uv run colleague learn                       # self-teaching prompt (read-only)
uv run colleague learn-from claude --repo .  # absorb Claude's skills (writes .colleague/skills/)
uv run colleague learn-from claude --copy-only --dry-run   # preview the copy, write nothing

Per-model instructions & skills

Colleague composes a model-specific system prompt for every work item from two layered families, resolved relative to the model currently driving. Strict per-model isolation: driving model X reads only X's overlay plus the shared base — it never even opens model Y's files (isolation is structural, built from exact paths, not filtered).

AGENTS instructions cascade from the repo root (the cross-tool standard location — sibling agent tools read AGENTS.md there too), general → specific, with a ~/.colleague/ user-level fallback:

AGENTS.md                       # shared base
AGENTS.colleague.md           # colleague overlay
AGENTS.colleague.<model>.md   # model overlay

Skills are markdown capability docs under .colleague/, folded into the prompt as a compact name + one-line-summary catalog (a skill is instructional text only — colleague loads skills as instructions, it does not execute them):

.colleague/skills/*.md            # base
.colleague/<model>/skills/*.md    # model overlay (shadows base by stem)

<model> is sanitized to a filename-safe token (e.g. Qwen/Qwen3-32BQwen-Qwen3-32B). Inspect what resolves for a model:

uv run colleague agents list --model Qwen/Qwen3-32B --repo .
uv run colleague skills list --model Qwen/Qwen3-32B --repo .

MCP layering is not built yet. Colleague does not read mcp.json or connect to any MCP server today; a live MCP client needs its own spec. There is no mcp verb — don't rely on a non-existent surface.

Subagents

Mid-work, a backend may delegate scoped sub-tasks via two loop tools: subagent (a single child) or subagents (a batch that runs concurrently). Each child runs the same bounded tool-loop as a nested in-process call, isolated in its own throwaway git worktree on a sub/<id> branch; its result is returned to the parent and folded into TaskResult.sub_results (omitted when empty). A sequential merge-subagent integrates the branches afterward, surfacing (never force-merging) unresolvable conflicts. An optional engine/model parameter lets a child run on a different backend or model, resolved through the existing registry.load + EngineConfig inheritance (a config-level switch, no backend code change).

Concurrency is opt-in: COLLEAGUE_SUBAGENT_CONCURRENCY (default 1 = byte-identical sequential behavior); with width > 1, up to MIN(width, MAX_SUBAGENT_FANOUT-1) children run in parallel via concurrent.futures (threads confined to colleague/subagents.py), reserving one slot for the merge child. Delegation is backend-judged and optional (like the devague destination tool), never a forced gate. Termination is structural: MAX_SUBAGENT_DEPTH=2 (checked before any child work) and MAX_SUBAGENT_FANOUT=4 (per-work-item, including the merge child). Only the top-level work item hands off — sub-drives never branch, commit, or open a PR. This is runtime-owned (the tools fire identically for every backend) and is explicitly not the out-of-scope multi-backend router / routing policy: there is no automatic task→backend routing.

Honest limit: real wall-clock speedup requires the served model to handle concurrent requests; on a serializing server, gain is bounded by overlapped I/O wait, not model compute.

uv run colleague explain subagent   # the loop tool's contract (not a CLI verb)

Ask colleague (a different mind)

ask-colleague is colleague's one first-party Claude Code skill — the inverse of the vendored skills. It lets another agent hand a scoped task to colleague: a different backend/model (e.g. a local vLLM Qwen), not a stronger one — diversity is the point. Four verbs over colleague work:

Verb What it does
ask-colleague explore Read-only investigation of an area (worktree-isolated).
ask-colleague review A diverse second opinion on the committed <base>...HEAD diff (the headline verb).
ask-colleague write Delegate a small change — previews by default; --apply lands a work branch, --pr opens a PR.
ask-colleague feedback Grade a finished work item (close the ROI loop).

explore/review run in a throwaway git worktree (no working-tree side effects); write previews in one too unless --apply/--pr. (Renamed from outsource; "outsource this" still triggers it.) See docs/features/ask-colleague.md.

Approval gate

The approval gate is an operator-declared allow-list that controls what the harness executes — not just what it discovers. Approval is tamper-protection: approve records the file's current content checksum; if the file changes after approval, the checksum no longer matches and the approval is void.

Config shape

{
  "run_command": { "allow": ["git", "pytest", "uv"], "deny": [] },
  "hooks":       { ".colleague/lint.sh": "sha256:<hex>" },
  "commands":    { "fix-lint": "sha256:<hex>" }
}

Place this file at .colleague/approvals.json in the target repo (or ~/.colleague/approvals.json for user-level defaults; repo-level wins). A per-model overlay at .colleague/<sanitized-model>/approvals.json is composed ahead — per-model keys replace base keys for the same section; no sibling model is ever read.

What is gated (and what is not)

Category Gated by Absent section
run_command Program token (shlex first token): allow/deny lists No-op (all commands allowed)
hooks Content checksum of the referenced hook script file No-op (all hooks run)
commands Content checksum of the template .md file (checked at expansion) No-op (all templates expand)
Skills / AGENTS Never gated — declarative, load freely

A section is gated only when it is present in approvals.json. An absent section is a strict no-op: byte-identical to behavior before the gate existed. When a section is present, allow-list semantics apply: anything unlisted, unapproved, or tampered is denied.

Approving files

# Approve a command template by checksum (default: sha256):
uv run colleague commands approve fix-lint --repo .

# Approve a hook script by repo-relative path:
uv run colleague hooks approve .colleague/lint.sh --repo .

# Use md5 instead (drift detection; not recommended for integrity):
uv run colleague commands approve fix-lint --repo . --algo md5

# Both commands support --json for machine-readable output.

Inspecting approval status

# commands list shows: approved | drifted | unapproved | ungated
uv run colleague commands list --repo .

# hooks list shows approval status per entry + the run_command policy if present
uv run colleague hooks list --repo .

# skills list always shows: accessible (never gated)
uv run colleague skills list --repo .

Status values:

Status Meaning
approved Entry present, checksum matches current file content
drifted Entry present, but file changed since approval — approval void
unapproved Section present but no entry for this name
ungated Section absent from approvals.json — gate not active
accessible Skills / AGENTS — never gated, always accessible

Honest limits

This is a policy gate, not a sandbox.

  • The run_command check inspects the first shell token only. It is trivially bypassable by sh -c '...', shell pipelines, command substitution, shell expansion, or an absolute path to a renamed binary. The gate encodes operator intent; it does not contain a hostile process. An airtight execution sandbox is explicitly out of scope (it would need its own re-spec).
  • md5 detects accidental drift (file edited by mistake), not a deliberate attacker who can recompute a hash. Use sha256 when integrity matters.
  • Checksum-only (current release). There is no version-based pinning. Approvals are recorded and verified by content hash only. Version pinning is a documented follow-up that is not yet built — do not rely on it.
  • This is the landed increment of the tracked "per-repo hook trust gate" from the security section below. There is still no --no-hooks flag — that remains a future follow-up.

⚠ Security: repo-shipped hooks run by default

This is a code-execution risk. Read before driving an untrusted repo.

When you run colleague work (or colleague session) against a repo that contains a .colleague/hooks.json, those hooks execute automatically with your operating-system privileges. There is no confirmation prompt and no sandboxing. Cloning a malicious repository and pointing Colleague at it will run whatever shell commands that repository's hooks.json specifies.

This behavior is intentional under Colleague's trusted-operator-env model (D2): the same design tradeoff Claude Code and Codex make for their .claude/ and .codex/ hook configs. You are expected to trust (or audit) the repos you work in.

What is implemented: the approval gate lets you gate hook scripts by checksum — an unapproved or tampered hook script is skipped (not a hard deny of the tool call; it fires a skipped firing in the artifact). The run_command allow/deny list gates which CLI programs the loop may invoke.

What is NOT yet implemented: a --no-hooks escape hatch or any other mechanism to disable repo-shipped hooks without editing .colleague/hooks.json yourself. The approval gate is a policy gate, not a sandbox — see its honest limits above. A further hardening increment is tracked but has not shipped in the current version. Do not rely on a non-existent flag.

Safe practices until the trust gate ships:

  • Only run colleague on repos you own or have audited.
  • Review .colleague/hooks.json before running work in an unfamiliar repo.
  • Use user-level (~/.colleague/hooks.json) hooks as an allow-list approach if you want hooks without trusting any repo's config.

CLI

Verb What it does
work <goal> Work toward a goal/instruction: work autonomously through a coder backend; write the artifact; hand off.
work --command <name> [args…] Expand a saved command template and run it.
commands list List discovered command templates for a repo (shows approval status).
commands approve <name> Record a checksum approval for a command template.
commands overview Describe the commands surface.
hooks list List configured hook entries for a repo (shows approval status + run_command policy).
hooks approve <script> Record a checksum approval for a hook script file (repo-relative path).
hooks overview Describe the hooks surface.
agents list List resolved AGENTS instruction layers for a model.
agents overview Describe the agents surface.
skills list List resolved skill docs for a model.
skills overview Describe the skills surface.
telemetry status Show the resolved telemetry / OpenTelemetry config + whether the SDK is installed.
telemetry overview Describe the telemetry surface.
session Open a foreground interactive palette.
backends list List discovered backend plugins (the registry; wheels is a deprecated alias).
whoami Report nick, version, mesh backend, and the live work engine + model (the delegate an ask-colleague would actually run).
learn Print a structured self-teaching prompt — teaches an agent how to drive colleague (read-only; nothing is written).
learn-from <source> Absorb a peer agent's skills into colleague's own .colleague/skills/ (e.g. learn-from claude); writes files. Not to be confused with learn — see Two senses of "learn".
explain <path> Markdown docs for any noun/verb path.
overview Read-only descriptive snapshot of the agent.
doctor Configuration-readiness health check: identity, provider, engines, otel, environment.
cli overview Describe the CLI surface itself.

Every command supports --json. Results go to stdout, errors/diagnostics to stderr (never mixed). Exit codes: 0 success, 1 user error, 2 environment error, 3+ reserved.

Writing your own backend plugin

A backend is a class implementing colleague.engine.Engine (one method: work(task, config) -> TaskResult). Advertise it under the entry-point group and colleague backends list discovers it — no change to Colleague core:

[project.entry-points."colleague.engines"]
my-engine = "my_package.engine:MyEngine"

Most backends never re-implement the loop — they delegate to colleague.loop.run and only supply how the model is called. Because the loop owns hook firing, a custom backend inherits the full lifecycle extensibility layer for free.

License

Apache 2.0 — see LICENSE.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

colleague-1.51.0.tar.gz (3.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

colleague-1.51.0-py3-none-any.whl (821.1 kB view details)

Uploaded Python 3

File details

Details for the file colleague-1.51.0.tar.gz.

File metadata

  • Download URL: colleague-1.51.0.tar.gz
  • Upload date:
  • Size: 3.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for colleague-1.51.0.tar.gz
Algorithm Hash digest
SHA256 24a1beaf7edd02932dfe632c18ebaa8b9f7e716ae9cd77a082a4368d32edfd36
MD5 24a48178111613255d52e3a6d11eb5af
BLAKE2b-256 4552cf2e3c4bbc1e4752fc72fa80c674ce93621d58cc72ff37d7e23576d78cd5

See more details on using hashes here.

File details

Details for the file colleague-1.51.0-py3-none-any.whl.

File metadata

  • Download URL: colleague-1.51.0-py3-none-any.whl
  • Upload date:
  • Size: 821.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for colleague-1.51.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ea494a082bd585ca3f1b6c3a45926dc9c73413dcc2380f0483c33260e135dc4
MD5 a0989c4c6dc6fdede389f4583fdbf3f0
BLAKE2b-256 6108d134bf16e78ee97890387f8514a88ec32f3a154d2b1d7fbd8e9a4021dc29

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page