Skip to main content

star-chamber

Multi-LLM council protocol SDK. Fan out code reviews and design questions to multiple LLM providers, then classify findings by consensus.

Installation

pip install star-chamber

Or with uv:

uv add star-chamber

Otari extra

Routing through an Otari gateway needs the otari extra, because the gateway client ships outside the base install:

pip install 'star-chamber[otari]'
uv add 'star-chamber[otari]'
uvx --from 'star-chamber[otari]' star-chamber list-providers

Without the extra, an Otari-mode council still starts, but every non-local provider fails on its first call reporting that the Otari packages are missing. Direct-key councils never need the extra.

Configuration

Create ~/.config/star-chamber/providers.json:

{
  "providers": [
    {"provider": "openai", "model": "gpt-4o", "api_key": "${OPENAI_API_KEY}"},
    {"provider": "anthropic", "model": "claude-sonnet-4-20250514", "api_key": "${ANTHROPIC_API_KEY}"}
  ],
  "timeout_seconds": 90,
  "consensus_threshold": 2
}

API keys can be literal values or ${ENV_VAR} references that are resolved at runtime.

Response token budget

max_tokens is optional. When it is omitted, a member may spend 16384 tokens on its reply.

The field is a budget for the response, not a property of the model. The value is sent to the provider as given, and no limit is ever read back from one, so there is nothing to look up before adding a member.

A budget that is too low makes the member fail rather than return half a review, and the error names the budget that was reached, so raise it from that number or send fewer files. A budget above what the model permits is rejected by the provider. Omit the field until a member reports a cut-off reply.

Provider display names

When several council members share one provider — for example, multiple models behind an OpenAI-compatible gateway such as OpenRouter — give each entry a unique display_name. It becomes that member's identity in council output (consensus classification, flagged_by, quality ratings, individual issues) and is matched by --provider filters. Without it, same-provider members collapse into a single vote and unanimous consensus becomes unreachable:

{
  "providers": [
    {"provider": "openrouter", "display_name": "gpt-5.2", "model": "openai/gpt-5.2", "api_key": "${OPENROUTER_API_KEY}"},
    {"provider": "openrouter", "display_name": "gemini-3.1-pro", "model": "google/gemini-3.1-pro-preview", "api_key": "${OPENROUTER_API_KEY}"},
    {"provider": "openrouter", "display_name": "grok-4.3", "model": "x-ai/grok-4.3", "api_key": "${OPENROUTER_API_KEY}"}
  ],
  "consensus_threshold": 2
}

In the JSON output, each provider response carries both the underlying provider (the routing target) and this display_name, and the display name is the key used throughout the aggregated result — providers_used, providers_responded, quality_ratings, individual_issues, flagged_by, and failed_providers. A member with no display_name falls back to the provider name.

providers_used is the roster that was asked, including members that failed. providers_responded is the roster that answered, and it is the denominator the consensus and majority classifications were computed over. A consensus across two members when three were asked is a weaker signal than the same word across three, so report the answering roster rather than the roster asked.

Otari gateway

Instead of managing API keys per provider, you can route all non-local providers through Otari, Mozilla AI's OpenAI-compatible LLM gateway, by adding a top-level otari object. This mode requires the otari extra:

{
  "providers": [
    {"provider": "openai", "model": "openai:gpt-4o"},
    {"provider": "anthropic", "model": "anthropic:claude-sonnet-4-20250514"}
  ],
  "otari": {
    "api_base": "https://your-gateway.example",
    "api_key": "${OTARI_API_KEY}"
  },
  "timeout_seconds": 90,
  "consensus_threshold": 2
}

In Otari mode, Otari — not the SDK — picks the upstream provider, so the per-provider provider field becomes a label. Otari expects the model field to use a provider:model prefix such as "openai:gpt-4o"; consult Otari's documentation for its model-naming convention.

api_base is the gateway origin with no path, such as https://api.otari.ai, because the client adds the API path itself. Every command rejects a base that ends in an API path, such as /v1, before it calls any provider. The error names the setting that supplied the base, whether the config or OTARI_API_BASE/GATEWAY_API_BASE, and the corrected value to set.

api_base and api_key may be omitted from the config; when omitted, the SDK's OtariProvider auto-detects credentials from its own env vars: OTARI_API_KEY for the hosted otari.ai platform (Authorization: Bearer, matching otari.ai's docs), or GATEWAY_API_KEY for a self-hosted gateway (Otari-Key header). OTARI_AI_TOKEN is also accepted as a platform-token alias. Both fields also support ${ENV_VAR} references.

Providers marked "local": true always bypass Otari and continue to use their own api_base.

Override the config path with the STAR_CHAMBER_CONFIG environment variable.

CLI

Code review

star-chamber review src/auth.py src/db.py

Design question

star-chamber ask "Should we use Redis or Memcached for session storage?"

Options

--provider, -p    Provider to include (repeatable)
--config          Path to providers.json
--timeout         Per-provider timeout in seconds
--context-file    File containing project context to include in the prompt
--council-context File containing prior council round feedback (debate mode)
--format          Output format: text or json
--output          Write JSON result to file

List providers

star-chamber list-providers

Protocol schemas

The SDK ships the council protocol specification as package data.

# List available schemas.
star-chamber schema list

# Print a specific schema.
star-chamber schema code-review-result

Python API

from star_chamber import run_council_sync, CouncilConfig, ProviderConfig

config = CouncilConfig(
    providers=(
        ProviderConfig(provider="openai", model="gpt-4o"),
        ProviderConfig(provider="anthropic", model="claude-sonnet-4-20250514"),
    ),
    timeout_seconds=90,
    consensus_threshold=2,
)

# Code review.
result = run_council_sync(
    files={"auth.py": open("auth.py").read()},
    config=config,
    mode="code-review",
)

print(result.summary)
for issue in result.consensus_issues:
    print(f"  [{issue.severity}] {issue.location}: {issue.description}")

# Design question.
result = run_council_sync(
    prompt="Should we use a monorepo or polyrepo?",
    config=config,
    mode="design-question",
)

print(result.consensus_recommendation)

Async

import asyncio
from star_chamber import run_council

result = asyncio.run(run_council(
    files={"auth.py": open("auth.py").read()},
    mode="code-review",
))

Schema access

from star_chamber import get_schema, list_schemas

# List available schema names.
names = list_schemas()

# Get a specific schema as a JSON string.
schema_json = get_schema("code-review-result")

Consensus classification

Issues from multiple providers are grouped by file, line proximity (within 5 lines), and category, then classified as:

  • Consensus -- all providers agree.
  • Majority -- two or more providers agree, but not all.
  • Individual -- flagged by a single provider.

Results are sorted by severity within each bucket.

Session summary

Every session ends with a short overall assessment in summary.synthesis. One member writes it, and it is not assembled from the members' own summaries. The author is the first member in config order that answered, so the same roster always asks the same member, and the material it reads carries no member name, so the assessment cannot attribute a point to a member.

summary.synthesized_by names the member asked, so you can check the author against the rule above. Set "synthesis": false in the config, or pass --no-synthesis, to skip it and spend no call on it. This costs one model call beyond the fan-out, so a council of three providers makes four calls per session. The call runs after the fan-out and uses the same timeout_seconds, so a session's worst-case wall time is two timeouts rather than one, and a debate pays the second one per round. When the author returns nothing usable, summary.synthesis is empty and summary.synthesis_error names the member asked and the reason.

License

Apache-2.0

Metadata

Release files for star-chamber 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for star-chamber 0.8.0
File Size Uploaded Provenance
star_chamber-0.8.0.tar.gz 145.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for star-chamber 0.8.0
File Interpreter ABI Platform
star_chamber-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 214.3 kB

Release files / star_chamber-0.8.0.tar.gz

Download URL star_chamber-0.8.0.tar.gz
Size 145.0 kB
Tags Source
SHA-256 checksum
How to use checksums
6709ac9883a7e757229924b0a41551c0bc4b3b358d9135470c92d7f88603ae65
BLAKE2b-256 checksum
How to use checksums
62f3741000b24ffbd26aadc9cc2f4af9f13b46b1f41d1d753b5387bfb01612c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / star_chamber-0.8.0-py3-none-any.whl

Download URL star_chamber-0.8.0-py3-none-any.whl
Size 69.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2d71a209bdc7ce905b745a98557f34dcaf6b4a2bb26876b18fe6bc66e5e6f918
BLAKE2b-256 checksum
How to use checksums
a4cc6197a63cff07c02bd559f547cb077842a9794112260c7d4a1f92a7e4ac28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for star-chamber 0.8.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for star_chamber-0.8.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page