Sky Yoo

Multi-agent systems · 2026 · completed

Skellington

A multi-agent coding orchestrator with a voting review panel.

Skellington — main view
SKELLINGTON · MAIN VIEW

Plan, route, delegate, synthesize — back to one voice. A request goes in; Jack plans it, routes each step to a specialist — a builder, a researcher, a codebase navigator, a three-way validation panel, a reporter that synthesizes the result — and every transition emits a typed event, streamed live over WebSockets. The specialists are named after The Nightmare Before Christmas, but the names are identifiers, not decoration: they map directly to classes in the codebase.

A personal learning project — not production-ready, not affiliated with any LLM provider. The point was never to ship an agent framework; it was to find out what actually breaks when you build one from scratch: routing, cost, consensus, and how to test something whose main dependency talks back.

The instrument

Skellington — A 2-of-3 panel, not a single gate
PLATE I — A 2-OF-3 PANEL, NOT A SINGLE GATE

A 2-of-3 panel, not a single gate

Lint, test, and security review run in parallel and vote. A crashing validator is a failed vote, not a panel-wide failure — the review survives one check breaking.

Skellington — Toolkit injection, not a global filesystem call
PLATE II — TOOLKIT INJECTION, NOT A GLOBAL FILESYSTEM CALL

Toolkit injection, not a global filesystem call

Agents take their tools as an argument — real ones in production, mocks in tests — so nothing in an agent body reaches around the interface to touch disk directly.

Skellington — LLM for judgement, Python for facts
PLATE III — LLM FOR JUDGEMENT, PYTHON FOR FACTS

LLM for judgement, Python for facts

Diffs come from `difflib`, counts from typed state — the model narrates what happened, it never computes what happened.

Skellington — 158 tests, zero live model calls
PLATE IV — 158 TESTS, ZERO LIVE MODEL CALLS

158 tests, zero live model calls

Every provider is mocked at the boundary, so the whole suite runs in under two seconds and never spends a token to tell you something broke.

The movement

The engineering underneath

The interesting parts aren't the agents — they're the seams: how work gets routed, checked, and paid for.

Capability flags, not model names

A model's quirks are declared once as flags on a registry entry; prompt fragments and request shaping key off the flag, never the model id. Swapping in a new model is one registry entry, not a new prompt file per model family.

models.py
class ModelCard(BaseModel):
    id: str
    provider: LLMProvider
    context_window: int
    supports_native_json: bool = False
    prefers_xml_tags: bool = False
    supports_thinking: bool = False
    supports_prompt_caching: bool = False
    supports_batch_api: bool = False

MODELS: dict[str, ModelCard] = {
    "claude-opus-4-7": ModelCard(
        id="claude-opus-4-7",
        provider=LLMProvider.ANTHROPIC,
        context_window=200_000,
        prefers_xml_tags=True,
        supports_thinking=True,
        supports_prompt_caching=True,
        supports_batch_api=True,
    ),
    "gpt-4o": ModelCard(
        id="gpt-4o",
        provider=LLMProvider.OPENAI,
        context_window=128_000,
        supports_native_json=True,
    ),
}

def get_model_card(model_id: str) -> ModelCard:
    card = MODELS.get(model_id)
    if card is not None:
        return card
    return _FALLBACK.model_copy(update={"id": model_id})

Graceful degradation is the default, not the exception

No search key configured — the research agent falls back to LLM-imagined results instead of erroring. An empty request short-circuits before spending an LLM call at all.

Specification
Language
Python — Pydantic v2, async-first
Interfaces
CLI and a FastAPI/WebSocket web UI, same orchestrator underneath
Tooling
Six in-tree MCP servers — filesystem, websearch, git, exec, database, docs
Testing
158 tests, mocked provider boundary — zero live model calls
Cost levers
Prompt caching, per-subagent model routing, keyword short-circuit, batch API