Skip to main content

Local AI council review using Ollama -- no cloud API keys required

Project description

guardspine-local-council

Multi-model AI code review council -- local-first, cloud-optional.

Run multi-model code review councils on your machine using Ollama, or connect to cloud providers (OpenAI, Anthropic, OpenRouter). Reviews produce cryptographically chained evidence bundles compatible with the GuardSpine ecosystem.

Requirements

  • Python 3.10+
  • Ollama running locally (ollama serve) for local-only use
  • At least one model pulled (e.g. ollama pull llama3.1)

Install

pip install guardspine-local-council

Or from source:

git clone https://github.com/DNYoussef/guardspine-local-council.git
cd guardspine-local-council
pip install -e .

Dependencies

  • guardspine-kernel (>=0.2.0) -- Canonical JSON hashing (RFC 8785) and content hash computation. Ensures cross-language parity with the TypeScript @guardspine/kernel.
  • httpx (>=0.24.0) -- Async HTTP client for Ollama and cloud API calls.
  • wasmtime (>=16.0.0) -- WASM runtime for the built-in PII-Shield sanitizer.

Quick Start

import asyncio
from guardspine_local_council import LocalCouncil, OllamaProvider, ReviewRequest

async def main():
    providers = [
        OllamaProvider(model="llama3.1", reviewer_id="reviewer-a"),
        OllamaProvider(model="llama3.1", reviewer_id="reviewer-b"),
        OllamaProvider(model="llama3.1", reviewer_id="reviewer-c"),
    ]

    council = LocalCouncil(providers, quorum=2, consensus_threshold=0.66)

    request = ReviewRequest(
        artifact_id="my-function",
        artifact_type="python-function",
        content="def add(a, b): return a + b",
    )

    result = await council.review(request)
    print(f"Decision: {result.consensus_decision} ({result.consensus_confidence})")

asyncio.run(main())

How It Works

  1. You create provider instances (Ollama, OpenAI, Anthropic, or OpenRouter).
  2. LocalCouncil sanitizes the review prompt through the built-in PII-Shield WASM module.
  3. The sanitized prompt is sent to all providers in parallel.
  4. Each provider returns a structured vote (approve/reject/abstain + confidence + findings).
  5. SimpleAggregator computes a confidence-weighted majority decision.
  6. Quorum and consensus threshold checks determine the final result.
  7. An evidence bundle is produced with a SHA-256 hash chain for tamper detection.

Providers

Four providers are included. All implement the ReviewProvider protocol and return ReviewVote objects.

OllamaProvider (local, no API key)

from guardspine_local_council import OllamaProvider

provider = OllamaProvider(
    model="llama3.1",           # Any Ollama model
    base_url="http://localhost:11434",  # Ollama API endpoint
    reviewer_id="local-1",
)

Uses Ollama's /api/generate endpoint with JSON format mode. Falls back to abstain on parse failure.

OpenAIProvider

from guardspine_local_council import OpenAIProvider

provider = OpenAIProvider(
    model="gpt-4o",
    api_key="sk-...",  # or set OPENAI_API_KEY env var
)

AnthropicProvider

from guardspine_local_council import AnthropicProvider

provider = AnthropicProvider(
    model="claude-sonnet-4-5-20250929",
    api_key="sk-ant-...",  # or set ANTHROPIC_API_KEY env var
)

OpenRouterProvider

from guardspine_local_council import OpenRouterProvider

provider = OpenRouterProvider(
    model="openrouter/auto",  # or any model on OpenRouter
    api_key="sk-or-...",      # or set OPENROUTER_API_KEY env var
)

You can mix providers freely. A council of 1 Ollama + 1 OpenAI + 1 Anthropic model works.

Council Configuration

Parameter Default Description
providers (required) List of ReviewProvider instances
hooks [] Optional list of ReviewHook instances for pre/post processing
sanitizer None Optional external sanitizer (in addition to built-in WASM)
quorum 3 Minimum non-abstain votes required
consensus_threshold 0.66 Minimum weighted confidence for a decision
sanitization_salt_fingerprint sha256:00000000 Non-secret salt fingerprint for sanitization attestations

Review Modes

Single Review

council.review(request) sends one prompt to all providers in parallel and returns a CouncilResult with the aggregated decision.

Rubric Review

council.rubric_review(request, rubric) reviews code against a specific rubric. Providers run sequentially (VRAM constraint for local models). Returns a list of ReviewVote objects.

from guardspine_local_council import RubricContext

rubric = RubricContext(
    rubric_name="input-validation",
    description="All user input must be validated before use",
    violations=[  # from a deterministic scanner
        {"severity": "high", "rule_id": "IV-001", "file": "api.py", "line_number": 42,
         "description": "Unvalidated query parameter"}
    ],
)

votes = await council.rubric_review(request, rubric)

Full Audit

council.full_audit(request, rubrics) runs all providers against all rubrics and aggregates into an AuditResult. Each rubric gets a pass/fail/needs-review verdict via 2-of-3 majority. The overall decision rejects if any rubric with critical findings fails.

result = await council.full_audit(request, rubrics)
print(result.overall_decision)  # "approve" | "reject" | "needs-review"
print(result.summary)

# Pivot findings from rubric-oriented to file-oriented
for filename, report in result.by_file().items():
    print(f"{filename}: {report.critical_count} critical findings")

Hooks

Hooks run deterministically around review calls. The models never call MCP tools themselves -- hooks enrich prompts before the model sees them and validate output after.

SequentialThinkingHook

Connects to @modelcontextprotocol/server-sequential-thinking via stdio. Decomposes each rubric into 5 structured reasoning steps and prepends a chain-of-thought scaffold to the prompt.

from guardspine_local_council import SequentialThinkingHook

hook = SequentialThinkingHook(num_steps=5)

council = LocalCouncil(providers, hooks=[hook])
await council.start_hooks()
result = await council.full_audit(request, rubrics)
await council.close_hooks()

MCPClientHook

Generic hook that calls any MCP server's tool to enrich prompts. Useful for injecting library docs, past findings, or external context.

from guardspine_local_council import MCPClientHook

hook = MCPClientHook(
    name="memory",
    server_command=["python", "-m", "memory_mcp"],
    tool_name="recall",
)

PII-Shield Integration

All review prompts pass through a built-in PII-Shield WASM module (lib/pii-shield.wasm) before reaching any model. This strips API keys, credentials, and PII from code submitted for review.

The WASM module runs via wasmtime using temporary files for stdin/stdout. The Engine and Module are cached as a singleton; only the Store is recreated per call.

Fail-closed by default: if the WASM module fails, the review raises RuntimeError rather than sending unsanitized content. Set GUARDSPINE_PII_FAIL_OPEN=1 to override (not recommended for production).

You can also provide an additional external sanitizer via the sanitizer parameter on LocalCouncil. Both stages are tracked in the evidence bundle's sanitization attestation block.

Evidence Bundle Output

Council reviews produce v0.2.x evidence bundles containing:

  • Individual reviewer votes with confidence scores
  • Consensus decision and rationale
  • SHA-256 hash chain with immutability proof (using guardspine-kernel for canonical JSON hashing)
  • Optional sanitization attestation metadata (v0.2.1 format when sanitization occurred)
result = await council.review(request)
bundle = result.evidence_bundle

print(f"Bundle ID: {bundle.bundle_id}")
print(f"Version: {bundle.version}")  # "0.2.0" or "0.2.1"
print(f"Root hash: {bundle.immutability_proof.root_hash}")

# Verify with guardspine-verify
import json
with open("council-evidence.json", "w") as f:
    json.dump(bundle.__dict__, f, default=str)
# $ guardspine-verify council-evidence.json

Bundles are unsigned by default. For signed bundles, use GuardSpine Enterprise or provide a signing key via configuration.

Ollama Setup

# Check Ollama is running
curl http://localhost:11434/api/tags

# Pull models
ollama pull llama3.1
ollama pull codellama

The council returns abstain votes if a model is unavailable.

Data Types

Type Purpose
ReviewRequest Input: artifact ID, type, content, optional context and risk tier hint
ReviewVote One reviewer's decision (approve/reject/abstain), confidence, rationale, findings
CouncilResult Aggregated result with votes, consensus, dissent, quorum status, evidence bundle
RubricContext Scanner-produced rubric with name, description, and violations
RubricVerdict Per-rubric result: pass/fail/needs-review with critical findings
AuditResult Full audit result across all rubrics with overall decision
FileFinding Single finding attributed to a specific file (for by_file() pivot)
FileReport All findings for one file, with critical_count property
EvidenceBundle v0.2.x bundle with items, hash chain, and optional sanitization

Related Projects

Project Description
guardspine-kernel-py Python kernel: canonical hashing, content hash (required dependency)
@guardspine/kernel TypeScript kernel (cross-language parity)
guardspine-verify Verify council evidence bundles offline
guardspine-spec Bundle specification (v0.2.1)
codeguard-action GitHub Action for automated code review

License

Business Source License 1.1 (source-available) -- see LICENSE. Free for non-commercial, evaluation, and small-organization use (annual revenue under USD 1,000,000); other production use requires a commercial license from GuardSpine, Inc. Each version converts to Apache-2.0 four years after its release.

Project details


Download files

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

Source Distribution

guardspine_local_council-2.0.0.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

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

guardspine_local_council-2.0.0-py3-none-any.whl (34.7 kB view details)

Uploaded Python 3

File details

Details for the file guardspine_local_council-2.0.0.tar.gz.

File metadata

File hashes

Hashes for guardspine_local_council-2.0.0.tar.gz
Algorithm Hash digest
SHA256 5a2e905b1836dc8395b0a20aac8dd21e3ea627fed628a83f2323b3a7c1750fb6
MD5 8045b4259ad7b5717de4623140041475
BLAKE2b-256 2e83be26c2d5a5fed0867ae5455ef6cf31e5aba80e3c095eadd0fa6f4c899320

See more details on using hashes here.

File details

Details for the file guardspine_local_council-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for guardspine_local_council-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 436dbe6b765d9fa84a38477fd95663279a4cad950d1ff1263f7ac2dfd0855c51
MD5 fbe2187986d5dcf28dee9b489403628e
BLAKE2b-256 21acf535fa9ef113c1ae66d6490359ff4835b1f685b14f042b2b509f1270df30

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