npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jmanuelcorral/openteam

v0.29.1

Published

Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.

Readme

openteam

🌐 Language / Idioma: English (this document) · Español

openteam — operator-controlled local, frontier, and mixed model execution for opencode, with cheapest-capable automatic selection and on-demand multi-agent orchestration.

CI npm License: MIT Runtime: Bun

📖 Documentation site: https://jmanuelcorral.github.io/openteam/ (also available in Español)

Table of contents

What it is and why it exists

openteam adds a routing and orchestration layer to opencode. The operator chooses a global local, frontier, or mixed execution domain; primary and worker policies can select an exact model or use cheapest-capable "auto" selection inside that domain.

openteam does not inspect prompt content to infer sensitivity before a frontier call. Frontier egress is controlled explicitly: use local globally, or narrow an agent to local under global mixed, when frontier is prohibited.

Features

  • Per-turn routing before inference: legacy chat.message or the native 2.x prompt/model boundary.
  • Registration of OpenAI-compatible local runtimes: Ollama, LM Studio, Lemonade Server, llama-swap and Foundry Local.
  • Capability profiles from Models.dev, with cache and a curated fallback when the catalog is unavailable.
  • Cheapest-capable automatic selection by cost, availability, tool support, and context requirements inside the allowed domain.
  • Budgets per session, per month and per frontier tokens per session.
  • Hash-only CostRecord JSONL telemetry: stores promptHash, cost/decision metrics — never raw prompts.
  • Per-agent exact pins or "auto", optional ordered fallbacks, and bounded cross-model handoff for an incomplete node.
  • Multi-agent orchestration with genuinely parented workers, explicit model selection and bounded completion/cancellation.
  • Lazy local/provider inventory before routing; session-event refreshes are awaited before selecting resident models.
  • A metadata-only agent timeline with durable coordinator/attempt history, historical cursor inspection, and live invalidation hints.
  • A local web Console aggregating every opencode session (cost, tokens, tool calls, routing, decisions) — redacted, loopback-only.
  • A read-only MCP server exposing aggregated team memory, plus opt-in semantic recall.

Requirements

  • opencode 1.18.19 (legacy) or stable >=2.0.5 <3.0.0 (native adapter). Other 2.x versions are not assumed compatible. See the host compatibility boundary.
  • Bun 1.3.14 for reproducible installs, development, and tests.
  • Node.js ^22.22.2 || ^24.15.0 || >=26.0.0 for the published CLI/plugin.
  • Optional local runtimes:
    • Ollama at http://localhost:11434/v1.
    • LM Studio at http://localhost:1234/v1.
    • Lemonade Server at http://localhost:13305/api/v1.
    • llama-swap at http://localhost:8080/v1.
    • Foundry Local with a dynamic port discovered via CLI on Windows.

Local runtimes are optional. You can run openteam frontier-only, or point it at an Ollama / LM Studio / Lemonade Server / llama-swap running on another machine on your LAN.

Installation

Option A: npm package

The package is published as @jmanuelcorral/openteam. These are the supported installation paths:

| Need | Supported method | | --- | --- | | Load the plugin in opencode | Add @jmanuelcorral/openteam to the plugin array in opencode.json. opencode resolves the package; no global CLI install is required. | | Run setup once | bunx --package @jmanuelcorral/openteam@latest openteam setup | | Keep the openteam CLI on PATH | bun add --global @jmanuelcorral/openteam@latest, then openteam setup |

Global installation with npm is not supported. Bun is the only supported package installer. Node.js runs the published CLI/plugin within the declared engine range; npm is limited to maintainer-only pack, Trusted Publishing, and link:local operations.

GitHub Packages route: releases are published to both npmjs and private GitHub Packages. See docs/guide/install-github-packages.md for the authenticated GitHub Packages install procedure, PATH setup on zsh, and Lemonade provider verification.

For direct opencode loading, reference the package name:

{
  "$schema": "https://opencode.ai/config.json",
  "model": "anthropic/claude-sonnet-4-5",
  "small_model": "ollama/qwen3:8b",
  "plugin": ["@jmanuelcorral/openteam"]
}

Those model identifiers must exist in your configured opencode providers; replace them with available equivalents when needed.

Keep operator routing policy in .opencode/openteam.json, not in the plugin declaration. The fastest way to create both files is the interactive openteam setup command below.

Option B: development path

During development you can load a local plugin from .opencode/plugins/openteam.ts:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["./.opencode/plugins/openteam.ts"]
}

Development-path loading does not change the configuration boundary: keep operator policy in .opencode/openteam.json.

Quickstart: openteam setup

openteam setup detects the installed local runtimes (Ollama, LM Studio, Lemonade Server, llama-swap, Foundry Local — all optional), lets you pick local models and the frontier baseline (sorted cheapest-capable first), and writes the project configuration and generated agent/command artifacts:

  • .opencode/opencode.json for a new project, or the existing .opencode/opencode.json / root opencode.json: declares the plugin, local providers, frontier model, and local small_model.
  • .opencode/openteam.json: openteam's validated runtime configuration (execution domain, local runtimes, primary/role model policies, budgets, and bounded retries/handoffs).
  • .opencode/agent/openteam.md: the primary openteam agent, visible in the Tab switcher.
  • .opencode/agent/<role>.md for the standard dispatch targets, plus generated .opencode/command/*.md entries and the local .opencode/.gitignore.
# One-off, without a global install
bunx --package @jmanuelcorral/openteam@latest openteam setup
# Or, after the supported global Bun install
openteam setup

The old openteam init command still works as a deprecated alias of openteam setup.

Lemonade uses active models only. Downloaded-but-unloaded, transitioning or unverified models are not selectable, and an empty active list does not allow a guessed model ID. openteam does not load or unload models to make a selection work. See the policy and server-side limits.

llama-swap also requires active models. The initial integration accepts canonical local model IDs with verified ready state and no active routing profile, on an unauthenticated, network-restricted server. Declared context and output ceilings feed setup's opencode compaction limits; missing tool support is not guessed. See the llama-swap guide for configuration and the non-atomic server-side boundary.

The wizard:

  1. Probes local runtimes, then first asks which ones to enable. Choosing None selects global frontier directly; it does not ask a redundant three-way mode question and proceeds to frontier model selection.
  2. For each enabled runtime, choose this machine or another LAN host, its default/thinking/coder models, and concurrency. Multiple instances retain distinct runtime and provider identities.
  3. With at least one local runtime, choose the team execution mode (default mixed). local pins the primary to the first enabled runtime's thinking model or default; frontier asks for a frontier model; mixed asks whether the primary uses that local model or the selected frontier model. Frontier choices come from Models.dev cheapest-capable ordering, with browse/custom and curated-offline fallbacks.
  4. Setup writes an exact primary policy with fallbacks: [], persists each worker as the compact partial policy { "model": "auto" }, and writes the 2/2 retry/model limits. Primary "auto" and primary fallbacks are supported by the schema and manual config, but setup does not prompt for them.
  5. Choose whether to enable YOLO mode. Existing config files require overwrite confirmation.

After setup, authenticate a frontier provider if the configured policies allow frontier, make sure each configured local runtime is reachable, and open opencode in the repo. Re-tune the baseline later with openteam baseline set <provider/model> or openteam baseline auto.

Upgrade note: privacyMode is deprecated, ignored metadata and no longer prevents frontier execution. Convert any required restriction to router.executionMode: "local" or an agent executionMode: "local" under global mixed.

The openteam agent

openteam setup generates a single primary agent named openteam. Press Tab in opencode to select it: you explicitly know you are in openteam mode. Its model follows the resolved global and primary-agent policy; the prompt does not select a domain by being judged sensitive or complex.

The generated primary frontmatter and opencode.json.model use the same exact bootstrap model. Generated workers intentionally omit frontmatter model; their policies in .opencode/openteam.json are authoritative. Guardian, scribe, and ralph are not forced local by name.

Setup creates no worker agents, generic or project-specific. Built-in role profiles are execution defaults, not additional .md agents. For each substantive request the primary compares the required capabilities with the roster and existing subagents. It reuses capable members and hires only missing specialists, preserving the existing cast. If a member merely lacks an execution profile, registration configures that same member with model: "auto" instead of hiring a duplicate or sending you to a terminal wizard. Existing policies remain unchanged; new automatic policies inherit the global execution domain. Model placement comes from these policies, not from automatic sensitivity classification. The unified router may still derive capability and complexity requirements inside the permitted domain. (This on-demand, themed-team model is inspired by Squad — see Acknowledgements.)

The team has a themed identity: the orchestrator picks (or asks for) a universe and names each agent after a character, recording the cast in .opencode/openteam/roster.md so names persist across sessions. Besides project-specific roles, it plans three standard roles created on demand:

  • scribe — silent memory: logs decisions and learnings to .opencode/openteam/decisions.md without touching code.
  • ralph — automation and triage: monitors the backlog, prioritizes, coordinates execution and escalates to the human on blockers or approvals.
  • guardian — security review and operator-policy advice. It is not an automatic pre-frontier content gate and has no built-in local requirement. Configure executionMode: "local" explicitly if the role must stay local.

Before delegating, the orchestrator plans dependencies and parallelizes: it splits the request into tasks and, since opencode runs all task calls emitted in a single turn in parallel (Vercel AI SDK), it launches independent tasks together and sequences only the dependent ones into waves. Work that collides on the same files is separated into distinct waves to avoid conflicts. In the todo list each entry shows the responsible agent via a name prefix, e.g. [@Basher] Implement the router.

Execute the whole plan

Use /execute for the agreed plan in the conversation, or /execute docs/mvp-plan.md for a project plan. The command prepares a local manifest and hands all dependency waves to a code-driven supervisor, including bounded retries, authorized model handoffs, independent acceptance and targeted corrections after a rejected review. It does not ask for another "continue" after each successful batch.

The existing limits still apply; execution is not unlimited. Progress is available through /execute status <planID>, and a safely stopped plan can continue through /execute resume <planID> in a new user turn. Crashed in-flight executions are not silently duplicated. See commands and stop/resume semantics. Existing installations need the updated plugin and setup-generated command, followed by an opencode reload.

Try it on another machine

To install and try openteam on another machine that already has opencode and Bun:

  1. Run or install the CLI. Pick one of the supported paths:

    # Global option (leaves the `openteam` command on the PATH)
    bun add --global @jmanuelcorral/openteam@latest
    
    # One-off option (no global install)
    bunx --package @jmanuelcorral/openteam@latest openteam setup
  2. Initialize the repository you will work in. It generates opencode.json, .opencode/openteam.json and .opencode/agent/openteam.md:

    cd C:\path\to\your\repo
    openteam setup          # global
    # The one-off option already ran setup in the previous step.

    The wizard detects Ollama / LM Studio / Lemonade Server / llama-swap / Foundry Local, lets you pick the local models (local or on another machine on the LAN) or none to run frontier-only, and the frontier baseline, then writes the configuration.

  3. Authenticate the frontier provider you chose:

    opencode auth login
  4. Start your local runtime if you enabled one and it was not already up (e.g. ollama serve, or open LM Studio / Foundry Local). If you pointed at a runtime on another machine on the LAN, make sure it is reachable from this machine (host exposed and port open); for Ollama this usually means starting it with OLLAMA_HOST=0.0.0.0 ollama serve on the remote machine. If you configured frontier-only, skip this step.

  5. Open opencode in the repo, press Tab and pick the openteam agent. Give it the first prompt: it creates the subagent team on demand and runs each agent within the configured execution domain.

Check the status with openteam doctor (runtimes + config) and the savings with openteam report.

Updating openteam

openteam is published to npm when a GitHub Release is published. To pull the latest version:

# Global install
bun add --global @jmanuelcorral/openteam@latest

Check versions:

bun pm ls --global                         # installed global packages

The most recent published version is shown at https://www.npmjs.com/package/@jmanuelcorral/openteam.

Notes when updating:

  • You do not need to re-run openteam setup to update the plugin: opencode resolves the @jmanuelcorral/openteam package declared in opencode.json to the installed version. Restart opencode so it reloads the plugin.
  • Re-run openteam setup to install new generated commands such as /create_roster or regenerate configuration (runtimes, models or baseline). Review its confirmation before overwriting opencode.json, .opencode/openteam.json or .opencode/agent/openteam.md; it does not overwrite or remove existing worker files, including generic files from older installations.
  • One-off adjustments without a re-init: openteam baseline set <provider/model> or openteam baseline auto.

Configuration

The canonical runtime configuration lives in .opencode/openteam.json; this repo ships .opencode/openteam.example.json as a complete minimal v2 example, not as a byte-for-byte contract for every setup choice. Canonical v2 serialization is compact: it omits default legacy heuristic values, retains non-default heuristic overrides for policy-less compatibility, and compacts resolved role profiles to partial policies relative to their built-in or synthesized baselines. The Zod-validated shape is in src/config/schema.ts. telemetry is a plugin option read by src/index.ts, not part of the persisted OpenTeamConfig.

Config files are strict: unknown keys fail closed in both the CLI and plugin instead of being silently ignored. Errors name each rejected path (for example router.executionMod) and include a nearest-key hint when available. This strict validation applies to the config file on both paths; programmatic plugin options are a separate surface and are not yet strictly validated (#102). Existing configs without configVersion remain valid because openteam migrates them to the current version before validation.

opencode config file resolution

openteam reads the opencode project config from two candidate paths and merges them — the same way opencode itself handles multiple config files:

| Priority | Path | | --- | --- | | Higher (overrides) | .opencode/opencode.json | | Base | opencode.json (project root) |

Non-conflicting keys from both files survive. When the same key appears in both, .opencode/opencode.json wins. This is verified against opencode 1.18.18: a provider declared in either location appears in opencode models --pure.

JSONC comments (// line comments and /* … */ block comments) are tolerated in both files — opencode itself accepts them, and openteam strips them before parsing, so a commented config file works identically to a plain JSON one.

Malformed config — if one of the candidate files is present but cannot be parsed (invalid JSON, schema violation), doctor and agents emit a warning that names the offending file and continue rather than aborting. A bad config file is not treated as a fatal error.

"Provider not configured" messages name the two paths that were searched, so you immediately know whether the file was not found or the provider is genuinely absent from both locations.

openteam setup write location — when setting up a fresh project (no existing opencode config file), setup now writes to .opencode/opencode.json. When an existing file is detected it is updated in place — only one file is written, never both. Upgrading users: a re-run of setup will land in .opencode/ if no root opencode.json is present.

No upward traversal — openteam reads only the two cwd-relative candidates above. It does not walk up to the git root. Commands are designed to be run from the project root; if you run them from a subdirectory you will see "provider not configured" and should cd to the project root first.

| Key | Type | Default | Description | | --- | --- | --- | --- | | configVersion | 2, optional input | canonical 2 | Missing means v1 and migrates before validation. Non-integer or below 1 is unknown; above 2 is unsupported. | | baseline.mode | "auto" \| "pinned" | "auto" | In auto, uses cheapest-capable frontier profiles when passed to the router; in pinned, uses baseline.pinnedModel if set. | | baseline.pinnedModel | ModelRef \| null | null | Explicitly pinned frontier model. ModelRef is { "providerID": string, "modelID": string }. | | baseline.hardDefault | ModelRef | { "providerID": "google", "modelID": "gemini-3.5-flash" } | Operator-configurable offline/curated fallback when no usable live or catalog profile drives automatic selection. It is not preferred over supplied profiles; setup may write the selected frontier model, and explicit per-agent pins remain allowed. | | router.executionMode | "local" \| "frontier" \| "mixed", optional | effective "mixed" | Hard global execution-domain upper bound. Canonical serialization writes "mixed" when omitted. Agent policy may narrow mixed, never broaden local or frontier. | | router.mode | "economy" \| "balanced" \| "quality" | "balanced" | Canonical legacy heuristic setting for callers with no v2 agent policy. It is not per-agent model selection and never overrides executionMode or adds sensitivity routing. | | router.trivialPromptMaxChars | positive integer | 280 | Legacy policy-less mixed-routing threshold. Retained for compatible callers that supply no v2 agent policy; generated v2 policies do not rely on it. | | router.frontierPromptMinChars | positive integer | 2000 | Legacy policy-less mixed-routing threshold. Retained for compatible callers that supply no v2 agent policy; generated v2 policies do not rely on it. | | router.localDefault | ModelRef \| null | null | Primary local model. Not set by default — no local model is routed until you configure one via openteam setup or by hand. | | local.runtimes | LocalRuntime[] | [] | Local runtimes to probe. Empty by default — no runtime is configured until you run openteam setup or add an entry by hand. | | local.runtimes[].id | string | required | Instance identity, unique within the array. Free-form (lmstudio-a, lemonade-lan) so two machines of the same kind are distinct; when kind is omitted it must itself be a runtime kind (back-compat). | | local.runtimes[].kind | "ollama" \| "lmstudio" \| "foundry-local" \| "lemonade" \| "llama-swap" optional | migrates from id | Runtime kind selecting the adapter. Optional: when absent, id is treated as the kind, so pre-existing configs keep working unchanged. | | local.runtimes[].enabled | boolean | true | Enable or disable that runtime. | | local.runtimes[].baseURL | optional URL | per runtime/config | OpenAI-compatible endpoint (/v1 for Ollama/LM Studio/llama-swap, /api/v1 for Lemonade); Foundry Local may omit it when using discovery. | | local.runtimes[].discovery | "cli" \| "sdk" \| "manual" optional | none per item | Foundry Local discovery; the registry uses CLI when configured and not manual. | | local.runtimes[].defaultModel | ModelRef | required | Default model for that runtime. | | local.runtimes[].maxConcurrency | positive integer, optional | 4 | Concurrent-generation cap for this instance (its real slot count). Applied per instance. | | local.runtimes[].modelConcurrency | Record<modelID, positive integer>, optional | none | Per-model slot counts, because slots belong to the loaded model rather than to the server: a single Lemonade host can serve one model at --parallel 3 and another at --parallel 1 simultaneously. Each declared model gets its own queue; every other model shares the instance-wide maxConcurrency pool. When left unset, openteam auto-detects each model's slots from the runtime's live health probe at dispatch time and applies them until you declare your own (explicit config always wins); openteam setup writes the map so the split survives restarts. | | budgets.sessionUSD | positive number, optional | unlimited | Frontier budget per session. | | budgets.monthlyUSD | positive number, optional | unlimited | Monthly frontier budget. | | budgets.frontierTokensPerSession | positive integer, optional | unlimited | Frontier token limit per session. | | budgets.hardStopOnBudgetExhaustion | boolean | false | Controls the configured budget action. It never authorizes leaving the execution domain. | | orchestrator | object, optional | absent | Holds primary/role policy and pass limits. | | orchestrator.maxRetries | non-negative integer, optional | effective 2 | Upper bound of extra openteam passes per authorized model; effective 2 permits up to three passes. The default is applied at runtime, not materialized by the schema. | | orchestrator.maxModelsPerNode | positive integer, optional | effective 2 | Total model candidates used by one node: the selected model plus at most one fallback by default. The runtime default is not materialized; with retry defaults it permits at most six attempts. | | orchestrator.taskBudget.maxBatches | integer 1..1000, optional | 8 | Cumulative batches for one trusted root user turn, including nested workers. | | orchestrator.taskBudget.maxAttempts | integer 1..10000, optional | 32 | Cumulative admitted attempts, including retries and model handoffs. | | orchestrator.taskBudget.maxDurationMs | integer 1000..86400000, optional | 7200000 | Task deadline from the root user turn; bounds queueing, creation and prompting, with separate bounded stop-confirmation grace. | | orchestrator.taskBudget.maxDepth | integer 0..32, optional | 3 | Maximum nested batch depth; root is depth zero. | | orchestrator.primary | partial ExecutionPolicy, optional | absent | Policy for the primary coordinator. Every field is optional. | | orchestrator.roles | raw Record<string, partial AgentRoleProfile> | {} when orchestrator exists | Raw entries may be partial. Loaded entries are fully merged and validated: built-in profile first, otherwise a synthesized profile, then the raw override. | | orchestrator.roles.<role>.opencodeAgent | non-empty string, optional | role ID | opencode agent name to dispatch for that role. | | orchestrator.primary.executionMode / orchestrator.roles.<role>.executionMode | "local" \| "frontier" \| "mixed", optional | inherited | May narrow global mixed; cannot contradict global local or frontier. | | orchestrator.primary.model / orchestrator.roles.<role>.model | ModelRef \| "auto", optional | omitted means "auto" | Exact selection or deterministic capable/available in-domain automatic selection. Mixed mode is local-first where capable, then cheapest-capable when catalog cost exists. Static unconfigured, out-of-domain, or known-incapable pins fail validation; transiently offline candidates are skipped only at runtime. Missing costs are not treated as zero. | | orchestrator.primary.fallbacks / orchestrator.roles.<role>.fallbacks | ModelRef[], optional | derived for auto; none for an exact pin | An explicit list replaces derived auto fallbacks and retains declared order after de-duplication and skipping the selected model. Every entry receives static identity/domain/capability validation before capping. A temporarily offline backup does not invalidate a healthy primary; it is skipped in favor of the next authorized candidate. | | privacyMode | legacy/deprecated | none | Accepted only as ignored compatibility metadata and removed on canonical persistence. Loading it emits one privacy-mode-ignored migration warning explaining frontier-egress risk and the executionMode: "local" remedy; the notice never changes routing. | | telemetry.enabled | boolean (plugin option) | true | Enable/disable JSONL writing. | | telemetry.path | string (plugin option) | .opencode/openteam-local/telemetry.jsonl | Path of the CostRecord JSONL log. |

Legacy router.localOnly and router.frontierOnly remain optional parse aliases so old files load; migration and canonical writes replace them with executionMode. TaskSignals.privacySensitive? likewise remains an optional historical caller field, but classification, scoring, rationale, and routing ignore it.

Example explicit coordinator and worker policies:

Exact policy models are always objects such as { "providerID": "anthropic", "modelID": "claude-sonnet-4-5" }, never a "provider/model" scalar. The slash/CLI baseline command and opencode's own model field use the scalar notation instead. Every example model must already exist in the operator's configured providers. A runtime instance ID such as lmstudio-b and a provider ID such as lmstudio are distinct concepts; use the provider identity configured for opencode.

{
  "configVersion": 2,
  "router": { "executionMode": "mixed" },
  "orchestrator": {
    "maxRetries": 2,
    "maxModelsPerNode": 2,
    "primary": {
      "executionMode": "frontier",
      "model": "auto"
    },
    "roles": {
      "scribe": {
        "executionMode": "local",
        "model": { "providerID": "ollama", "modelID": "qwen3:8b" }
      },
      "integration": {
        "executionMode": "frontier",
        "model": { "providerID": "anthropic", "modelID": "claude-sonnet-4-5" },
        "fallbacks": [
          { "providerID": "google", "modelID": "gemini-3.5-flash" }
        ]
      }
    }
  }
}

The resolved routeKind is always concrete local, frontier, or blocked, never mixed. Only the selected model and returned fallback chain may execute. A blocked decision does not dispatch; its selected identity is a cost/reference value only. Budget narrowing cannot substitute an unapproved local model: if no authorized candidate remains, routing blocks.

Usage

On each turn, openteam resolves the global and agent execution policy, filters available models by domain, tool support and context, ranks "auto" candidates by cost against the unified router's capability/complexity requirements, and assigns output.message.model. Prompt sensitivity and Graph privacy metadata are not routing inputs.

Multi-agent orchestration uses static roles and opencode subsessions:

For coordinator-owned work, the role's defaultTier and capability minima are floors. Ordinary task capability/complexity classification may raise the effective requirement for harder work, but it cannot weaken the role requirement. The effective tier drives routing and telemetry. Sensitivity vocabulary and Graph privacy metadata have no routing effect.

| Role | opencode agent | Typical policy | | --- | --- | --- | --- | | rusty | architect | Configure "auto" or an exact in-domain model. | | livingston | integration | Usually requires tool support; domain is explicit. | | yen | local-runtime | May be narrowed to local under global mixed. | | basher | routing-cost | Auto selection may optimize cost inside the domain. | | scribe | scribe | No built-in local restriction; set one explicitly if required. | | linus | tester | Configure models with the required tools/context. |

runRoleTask runs a single subsession; runRoleTasks runs several with a batchID. Direct and batched attempts use the same stable logical report path and must prove a current, changed-when-required, substantive durable report before success. Permission and review policy remain separate from model placement.

SDK child dispatch uses a plugin-scoped, one-shot model authorization keyed by the exact child session, agent alias, and model. When the public SDK prompt re-enters chat.message, only that matching claim preserves the coordinator-authorized worker model; the authorization is released when the prompt settles. Unmatched and user-initiated messages still follow normal routing. This mechanism cannot broaden an execution domain or authorize a model outside selected plus fallbackChain.

To inspect actual model use, handoff reasons, and cost/savings estimates when available, read the telemetry JSONL. Key fields include promptHash, promptChars, routeKind, selected, rationale, estimatedCostUSD, baselineCostUSD, estimatedSavingsUSD, budgetAction, tokensIn, and tokensOut. Unknown model costs remain null/unknown rather than becoming free; savings are unknown when either required estimate is unavailable. For coordinator auto routing, the effective capability requirement—the role floor plus any upward task classification—determines both candidate capability and the capable frontier baseline used for cost comparison.

Runtime commands

openteam exposes commands to inspect and change routing without editing JSON by hand, over three equivalent surfaces.

1. In-conversation tool

The plugin registers the openteam tool. Ask the agent to use it, or install the /openteam command. Actions:

  • show — show the effective baseline.
  • set (with model as provider/model) — pin the baseline (pinned mode).
  • auto — return to cheapest-capable (auto mode).
  • doctor — diagnostics for execution policy, model availability, and telemetry.
  • report — available cost and savings estimates from telemetry; unknown values remain unknown.

set / auto changes are persisted to .opencode/openteam.json and apply on the next plugin start.

2. Slash command /openteam

openteam setup automatically generates .opencode/command/openteam.md, so /openteam appears in opencode with no manual steps:

/openteam baseline show
/openteam baseline set anthropic/claude-sonnet-4-5
/openteam baseline auto
/openteam doctor
/openteam agents
/openteam console
/openteam report

Don't see /openteam? Make sure .opencode/command/openteam.md exists (created by openteam setup) and restart/reload opencode to discover it.

Compose the team without starting work

/create_roster
/create_roster Build a booking API with a web dashboard and integration tests

Setup also installs .opencode/command/create_roster.md, explicitly bound to the openteam primary. With a description it composes from that goal; without one it inspects the existing codebase. If there is no meaningful source, it asks what you want to build and waits before writing anything.

The workflow reuses or extends the existing cast without overwriting customized agents, registers .opencode/openteam/roster.md through the validated tool, and creates missing .opencode/agent/<agentName>.md subagents. Registration also initializes missing custom worker profiles in .opencode/openteam.json with model: "auto"; it preserves existing profiles, the primary policy, machine-local overrides and permissions. Worker filenames follow the roster's aliases: scribe -> c3po needs c3po.md, not an additional scribe.md. It verifies the persisted team without starting implementation or distributing tasks. Ordinary work requests use the same preparation, then continue with the requested task. openteam roles init remains an optional compatibility command for legacy unprofiled workers, not a required onboarding step. Rerun setup after updating and reload opencode to discover the command; see the commands guide.

Every team creation or extension, including automatic creation, must show the registration tool's Agent / Role / Model table for the whole team before work starts. It includes the primary and guaranteed roles. Exact model policies show provider/model and any configured fallbacks; auto explicitly means the concrete model will be selected when a task is dispatched, not a guessed model.

After the successful /create_roster summary, the primary asks whether you want to reload opencode to discover the agents. Only an explicit affirmative answer requests a project-instance reload; declining or not answering leaves the prepared team in place without reloading. Host permissions still apply. The request has a 30-second limit, including permission approval, and waits for the confirming turn and other active sessions to finish. A new turn in any project session cancels it. It does not restart the process, delete saved conversation history, or authorize implementation work.

3. CLI (bin)

The package exposes the openteam binary. With a global install:

bun add --global @jmanuelcorral/openteam@latest
openteam baseline show
openteam baseline set anthropic/claude-sonnet-4-5
openteam doctor --config .opencode/openteam.json
openteam agents
openteam console
openteam report --telemetry .opencode/openteam-local/telemetry.jsonl
openteam yolo status
openteam local status
openteam local only
openteam local off
openteam local on
openteam upgrade
openteam upgrade --check
openteam upgrade --version <x.y.z>
openteam clear-cache
openteam clear-cache --delete

Flags: --config <path> and --telemetry <path> override the default paths; --opencode <path> points the yolo command at a specific opencode.json. An explicit --config may be relative or absolute and must be readable; only the default .opencode/openteam.json lookup falls back to built-in defaults when absent.

See each agent's LLM: openteam agents

Lists the agents in .opencode/agent/, showing their configured model and provider. Agents without their own model inherit the opencode.json default. That inherited/default model is not permission to escape the resolved execution domain; effective primary/role policies in .opencode/openteam.json remain authoritative.

YOLO mode (auto-approve permissions)

YOLO mode makes opencode auto-approve all permissions (like the native opencode --auto flag, but persistent). It writes the global rule permission: { "*": "allow" } to opencode.json and regenerates the openteam agent with the same wildcard:

openteam yolo status   # is it active?
openteam yolo on       # enable YOLO
openteam yolo off      # disable YOLO

The agent permission overrides the global one. opencode merges each agent's frontmatter permission after the global one and the last matching rule wins, so openteam yolo on/off regenerates .opencode/agent/openteam.md to keep its permission in sync. After changing mode, restart or reload opencode.

YOLO does not change the execution domain. It only affects opencode permission approvals, and it does not add a sensitivity screen. In frontier or mixed, prompt content may be sent to frontier. Use it only in trusted environments.

Routing mode: openteam local

Use openteam local to switch between the three global execution domains without re-running setup:

openteam local status   # mixed, local, or frontier? + configured runtimes
openteam local off      # frontier
openteam local only     # local-only (never escalate to frontier)
openteam local on       # mixed

The commands persist router.executionMode. local only is a hard upper bound: no primary/worker pin or fallback can reach frontier, and the command is rejected without an enabled local runtime. local off needs no local runtime. local on allows both domains, and each agent may narrow itself to one of them. Before any mode change is written, exact primary, role, and fallback policies are validated against the proposed domain; a conflict rejects the command and leaves config unchanged. Every write removes legacy localOnly, frontierOnly, and root privacyMode; local status shows the effective mode and enabled runtime identities.

Upgrading the plugin pin: openteam upgrade

openteam setup now writes an exact version pin derived from the installed CLI version (e.g. @jmanuelcorral/openteam@<version>). Because opencode's cache is keyed by the full spec string, each distinct pin creates a new cache directory — the next opencode restart installs fresh rather than serving a stale entry.

Use openteam upgrade to advance that pin without re-running setup:

openteam upgrade                           # update to latest published stable
openteam upgrade --check                   # read-only: show what would change
openteam upgrade --version <x.y.z>         # pin to a specific published version

After running, restart opencode for the new pin to take effect. Clearing stale cache entries with openteam clear-cache --delete is optional — new pins create a new cache entry and the old one is simply unused.

upgrade does not delete cache entries, install or update the global CLI, restart opencode, rewrite generated agent files, or change provider limits.

See docs/guide/update-plugin.md for the full workflow.

Updating the plugin and clearing the cache: openteam clear-cache

opencode caches each plugin installation and never refreshes an existing entry — there is no version comparison, TTL, or upstream eviction command. Publishing a new version of the plugin has no effect on a machine that already has a cache entry. Deleting the cache directory is the only eviction path.

openteam clear-cache                       # report only — prints the table, deletes nothing
openteam clear-cache --delete              # delete all matched cache entries
openteam clear-cache --cache-root <path>   # override the auto-resolved cache root

Slash command alias inside opencode: /openteam-clear-cache

The default (no flags) prints a table with columns spec dir, spec-pinned, installed and mtime for every @jmanuelcorral/openteam* cache entry found. Only entries in that scope are ever touched. Symlinks and Windows reparse points are listed as [SKIP — reparse point] and never deleted. On Windows, close opencode before running --delete to avoid [LOCKED] errors.

See docs/guide/update-plugin.md for the full explanation of the freeze mechanism and a step-by-step update procedure.

Iterative work under openteam-orchestrate 🎯

Unattended, backlog-style work is driven entirely from orchestration. The current model gets orchestrator.maxRetries extra passes (effective runtime default 2, so up to three passes per model). Persistent incompleteness uses that model's pass budget before handing the same node to the next authorized model. A recognized retry-safe prompt transport/capacity failure (prompt-timeout, prompt-rate-limited, prompt-connection, prompt-server, or prompt-context-length) is retried on that same model within its pass allowance; create-stage retry recognizes the same transport classes except context length. maxRetries is an upper bound, not a required repeat floor: after confirmed stop, an eligible transport failure may consume the remaining same-model allowance or hand off earlier to the next authorized candidate. Auth, content-filter, validation/invalid-model, generic rejection, and missing-session or unclassified failures are terminal. A candidate found unavailable before dispatch may be skipped because no attempt started. maxModelsPerNode has the effective runtime default 2; together the defaults cap a node at up to six openteam passes. Neither default is materialized by the schema. This is not a guaranteed pass/HTTP-call count or a statement about provider-internal SDK retries. The batch, objective, report path, and durable work state are preserved. An exact pin without fallbacks authorizes no second model.

Related and nested batches also share a persisted task allowance: by default 8 batches, 32 attempts, 2 hours and nesting depth 3. Configure the optional orchestrator.taskBudget block and restart opencode to change future-task limits. Another model-authored call or plugin reload cannot reset the task. A new root user turn starts a new task; old workers keep their old allowance. Exhaustion is explicit, not a successful completion.

Workers prefer openteam-submit-report with status and bounded, substantive findings. The coordinator assigns its only writable report path and preserves operator permission checks. Same-model report repair continues in the same worker session without another full implementation restart. Confirmed length-ended invalid native tool input requests smaller fresh calls within existing limits; malformed JSON is never repaired and executed. See the reliability decision.

Every pass, including a single pass with maxRetries: 0, needs a standalone openteam-status: done declaration and a durable report with at least 40 substantive body characters after actual declaration lines are removed. The last standalone declaration outside fenced code wins; quoted or fenced examples remain body text and cannot declare completion. A marker alone is not completion. The coordinator reads the report text only to parse that status and test substance; it does not copy the body into role results, routing telemetry, diagnostics, or the reference-only projection.

After a timed-out or otherwise thrown/uncertain prompt dispatch, the prior SDK request must settle and server stop must be confirmed before any same-model retry or model handoff. After a successful abort, bounded polling confirms stop when the exact session is idle or is absent from a valid sparse status snapshot; malformed/missing status payloads and sessions that remain busy or retry block continuation. Status evidence alone does not replace request settlement. Exhaustion is terminal failure/incomplete, and dependents do not run. See docs/guide/commands.md.

Multi-session web Console: openteam console

openteam exposes a local web Console you launch from the CLI with openteam console. It aggregates all opencode sessions at once (one tab per session, plus "All"), because each plugin writes redacted events to per-session JSONL logs that the Console watches (fs.watch) and aggregates.

It shows live: recorded token usage and cost estimates per message (input/output/reasoning/cache), tool calls with duration and ok/fail, recent routing (cost/savings and chosen model — prompt hashes only, never text), agent decisions and meetings, orchestration progress, the team, and commits. These controls and estimates are finite/auditable, but they are not provider invoice accounting.

Agent tree includes the operation graph, real chronology/replay, filters, canvas controls and a default-readable inspector. Missing/unverified usage is shown as unavailable / partial, not confirmed zero. Unknown tool duration stays unknown, and routing estimates are distinct from measured usage.

Conversations are visible by default. Active Agent tree and Logs views automatically show operator prompts, visible agent responses and recorded tool interactions, with live updates. Reads use opencode's current session history, not prompt copies in telemetry. No diagnostic flag or per-agent reveal is required; explicit console.sessionHistory.contentEnabled: false keeps a metadata-only view. Content access does not enable terminal input or a PTY.

Each session tab also includes a "Session" view: a console where you can watch the live output (SSE) and send prompts or answer permissions for that opencode session, authenticated with an ephemeral token per launch. Optionally, with the terminal.pty flag, a "Terminal" view opens a real shell in the workspace via opencode's PTY API (no node-pty).

openteam console          # print the URL and serve the multi-session Console
openteam console --open   # also open the browser
openteam console --status # print the config only (don't launch the server)

See the Console guide for the full reference.

Sessions are read from:

.opencode/openteam-local/sessions/*.jsonl

You can tune host/port and the terminal in .opencode/openteam.json (console block):

{
  "console": {
    "host": "127.0.0.1",
    "port": 4599,
    "autoPortFallback": true,
    "refreshMs": 2000,
    "recentRoutes": 50,
    "openBrowser": false,
    "terminal": { "enabled": true, "pty": false }
  }
}

Agent tree and timeline. console.graphView.enabled defaults to true. Native lifecycle history is composed independently of Graph execution authority; the separate Graph DAG follows the effective graph.mode. See Agent timeline and Graph observability web.

Security: the server listens on loopback only (127.0.0.1/localhost, Zod-validated). Conversation reads require the ephemeral token and exact-workspace validation, mask known credential patterns and use no-store. Telemetry/lifecycle/state remain reference-only; conversation text is not copied into them. Session input and PTY retain their separate permissions. If the port is busy and autoPortFallback is true, the Console tries the next free one. Remote access via openteam tunnel was removed from the MVP (2026-08-31); there is no built-in remote-exposure path.

Interface stability. The Console's web surface is pre-1.0: its UI, endpoints, console config block and event format may still change. The routing, budgets, telemetry and memory core are stable.

Team memory over MCP (read-only): openteam mcp

openteam mcp starts a read-only MCP server (JSON-RPC 2.0 over stdio, no dependencies) that exposes the team memory aggregated from per-session events. It uses the same StorageProvider as the Console and never exposes raw prompts. Tools: list_decisions (filterable by agent/tag), list_meetings, list_sessions and cost_summary (recorded usage and available cost/savings estimates against the all-frontier baseline, with unknown costs retained as unknown; not an invoice).

Register it in opencode.json like any local MCP server:

{
  "mcp": {
    "openteam-memory": { "type": "local", "command": ["openteam", "mcp"] }
  }
}

The plugin's openteam-memory write tool is separate from this read-only MCP server and from the general scribe role's model policy. Its extraction transport is explicitly local-only and requires a reachable configured local runtime. Global frontier execution refuses the memory-model call; the tool does not add a frontier memory path or silently fall back to frontier.

Semantic recall (opt-in): recall_facts, recall_preferences, similar_tasks

Beyond team memory, openteam mcp can expose semantic recall of the long-term knowledge memory (facts, preferences, similar tasks). It is disabled by default; enable it with the memory.semantic block in .opencode/openteam.json:

{
  "memory": {
    "semantic": { "enabled": true }
  }
}

When enabled, and if there is a local runtime with a baseURL, the server adds three tools: recall_facts, recall_preferences and similar_tasks. The query is embedded locally (never leaves for frontier) and records are redacted at the source (storeContent=false by default), so the tools never return raw prompts. If bun:sqlite is missing or the index cannot be opened, openteam mcp degrades safely and keeps exposing team memory only.

Consolidation / forgetting: openteam memory consolidate

Long-term memory forgets by decay: each record's confidence decays over time and, once it falls below the threshold, it is marked soft-invalidated (without deleting: the history is kept for "as-of" auditing). Run the forgetting pass with:

openteam memory consolidate

It appends tombstones to the memory log and reports how many records were forgotten. It is idempotent and non-destructive.

Automatic push to context (opt-in)

With injection enabled, openteam prepends relevant memory to the prompt on each turn (chat.message), respecting a token budget:

{
  "memory": {
    "semantic": {
      "enabled": true,
      "injection": { "enabled": true, "maxChars": 1200, "maxItems": 8 }
    }
  }
}

It is disabled by default. Injection happens after the model is chosen, so it does not alter routing or telemetry, and it never breaks the turn if recall fails. It only injects what is already in the records (redacted at the source if storeContent=false).

Ollama

ollama serve
ollama pull qwen3:8b
Invoke-RestMethod -Uri "http://localhost:11434/v1/models" -Method Get

LM Studio

  1. Open LM Studio.
  2. Load a compatible model.
  3. Enable the local OpenAI-compatible server on port 1234.
Invoke-RestMethod -Uri "http://localhost:1234/v1/models" -Method Get

Lemonade Server

Invoke-RestMethod -Uri "http://localhost:13305/api/v1/models" -Method Get

Foundry Local

foundry service status

openteam extracts the port from the CLI output and normalizes the endpoint to http://localhost:<PORT>/v1.

llama-swap

Prepare the intended model with llama-swap before setup. These calls only read the catalog, readiness and profile state:

Invoke-RestMethod -Uri "http://localhost:8080/v1/models" -Method Get
Invoke-RestMethod -Uri "http://localhost:8080/running" -Method Get
Invoke-RestMethod -Uri "http://localhost:8080/api/profiles" -Method Get

See the llama-swap guide for ready-only selection, declared context/output limits and supported transport boundaries.

Telemetry

openteam writes a hash-only CostRecord JSONL log (never raw prompts). Tail the most recent entries:

Get-Content -Path ".opencode/openteam-local/telemetry.jsonl" -Tail 5

Local verification

bun test
bun run typecheck
bun run lint
bun run format:check
bun run build

Local development in dev mode (no registry)

To make the global openteam CLI and opencode use this repo's local build instead of the published version (without going through any registry), the package is linked with a global symlink:

bun run link:local   # = bun run build && npm link

npm link creates a global link (junction/symlink) to this repo, so after each bun run build the global CLI reflects the local code.

A versioned pre-push hook (.githooks/pre-push) can run bun run link:local before each git push, keeping the local install current. Installing dependencies does not modify Git. Enable the hook explicitly, once per clone:

bun run hooks:install

The hook warns if the build or the link fails, but it does not block the push.

Safe publishing

The release workflow builds and verifies a single tarball with no write permissions and no OIDC. Another job publishes exactly that artifact, after validating its integrity, via npm Trusted Publishing; a final job with only contents: write attaches it to the GitHub Release. There is no fallback to persistent credentials, and normal CI needs no npm configuration.

Before the first real release, the owner must register an npm Trusted Publisher for @jmanuelcorral/openteam with owner jmanuelcorral, repository openteam, workflow release.yml, allowed action npm publish and no environment. Until that one-time registration is complete, publishing fails closed. After the first successful OIDC publish, enforce token-less 2FA, revoke the old automation token and remove its inherited GitHub Actions secret.

Privacy

openteam does not detect prompt sensitivity or promise to intercept sensitive content before a frontier call. privacyMode is deprecated and has no routing authority. frontier and mixed deliberately permit frontier egress under the configured agent policy. To prohibit it, configure router.executionMode: "local" globally, or narrow the relevant agent to local under global mixed.

Credential redaction, prompt-hash telemetry, reference-only shared storage, loopback/access controls, and finite budget/model/pass limits remain in force. Telemetry uses hashPrompt (currently fnv1a32:<hash>) and never serializes the full prompt. See SECURITY.md for vulnerability reporting.

Quality

Non-negotiable gates (see docs/testing-and-quality.md):

  • bun test
  • Global coverage: lines/functions >= 85%.
  • Coverage of src/router/** and src/memory/**: 100% lines and functions (enforced by bun run coverage:check).
  • bun run typecheck
  • biome check . (or bun run lint + bun run format:check)
  • Router determinism test.
  • Contract tests when opencode hooks, agents, commands, providers or the SDK change.

Compatibility

See docs/compatibility.md. Verified summary as of 2026-09-22:

| Surface | Version / contract | | --- | --- | | openteam | 0.29.1; current package version | | opencode host | 1.18.19 legacy; stable >=2.0.5 <3.0.0 native support | | @opencode-ai/plugin | 1.18.19 | | @opencode-ai/sdk | 1.18.19 | | Routing hook | Legacy chat.message; native prompt + session.switchModel; never chat.params | | Child sessions | Legacy SDK or native scoped Session service; actual parenting and explicit model |

Contributing

Contributions are welcome. Please read CONTRIBUTING.md for the development setup (Bun), the non-negotiable quality gates, the module boundaries (router/ is pure — no I/O, clock, randomness or logging) and the pull-request process. By participating you agree to abide by our Code of Conduct.

Roadmap

| Phase | Status | Deliverables | | --- | --- | --- | | 0. Scaffold / PoC | Done | TS/Bun, minimal plugin, chat.message hook. | | 1. Local registry + router | Done | Ollama/LM Studio/Lemonade/Foundry, probes and deterministic chooseModel. | | 2. Profiles + budget + telemetry | Done | ModelCapabilityProfile, budgets and CostRecord. | | 3. Multi-agent orchestration | Done | Coordinator, roles, child sessions and permissions. | | 4. npm packaging + docs | Done | Exportable package, README, Windows examples and compatibility matrix. | | 5. Runtime commands | Done | baseline show/set/auto, doctor and report via tool, /openteam and CLI bin. | | 6. Web Console + MCP memory | Done | The multi-session Console is loopback-only and gate-covered; read-only MCP server and semantic recall are stable. | | Agent-stack MVP | In progress | Config v2 execution domains, per-agent model policies, bounded model handoff, removal of sensitivity routing, and retirement of duplicate Graph-local policy resolution. Unified router capability/complexity routing remains. LangGraph migration issues remain separate and pending. |

License

MIT © Jose Manuel Corral (@jmanuelcorral).

Acknowledgements

openteam stands on the shoulders of prior work. These projects shaped its design — we took inspiration from them, adapted the ideas to opencode and reimplemented them from scratch in TypeScript:

  • Squad (bradygaster/squad) — the on-demand, themed multi-agent team model: a single orchestrator that casts specialist subagents as the work requires them. It inspired openteam's openteam orchestrator agent and the team/roster convention.
  • Superpowers by Jesse Vincent — a composable, skill-driven methodology for coding agents. It inspired openteam's skill-based agent conventions and the spec-first / subagent-driven workflow.
  • agent-memory-dotnet by José Luis Latorre — a semantic long-term memory model for agents. It inspired openteam's memory subsystem (extract → rank → consolidate → recall) with local-first, hash-redacted storage.

All trademarks and project names belong to their respective owners; the links above are provided for attribution and are not endorsements.