Skip to content

symfonic.cli.chat

chat

symfonic chat — the progressive path, made executable (T4.3.1).

The split-handoff register hands src/symfonic/cli/** to this task with one instruction: rewired onto facade/kernel in place. The CLI surface is frozen by T1.3.1's cli-surface golden, so "in place" means the migration cannot show up as a new flag. It shows up as which runtime executes the turn.

A plain symfonic chat "hi" --tenant-id t1 asks for nothing the platform owns: no HMS memory, no named domain, no DSN. That invocation now runs through :class:symfonic.Agent — one required dependency, no FrameworkConfig, no tenant scope, no background flush. An invocation that does ask for one of those three keeps SymfonicAgent, because the facade genuinely does not have them and pretending otherwise would be the silent-drop this refactor exists to prevent.

Everything here is typer-free so the routing rule can be asserted directly rather than inferred from captured stdout.

ChatError

Bases: RuntimeError

A chat invocation that cannot proceed, with an adopter-facing message.

ChatOutcome dataclass

ChatOutcome(text: str, runtime: Runtime, system_prompt_tokens: int, memory_entries_used: int, duration_ms: float)

What the turn produced, plus which runtime produced it.

ChatRequest dataclass

ChatRequest(query: str, tenant_id: str, dsn: str | None, model: str, enable_hms: bool, domain: str)

One symfonic chat invocation, decoded off the command line.

build_provider

build_provider(model: str, query: str) -> Any

Resolve --model to a provider instance.

Raises:

Type Description
ChatError

for an unknown name, or for a provider whose optional extra is not installed — with the pip install line in the message, because a traceback here is a packaging question, not a bug.

Source code in src/symfonic/cli/chat.py
def build_provider(model: str, query: str) -> Any:
    """Resolve ``--model`` to a provider instance.

    Raises:
        ChatError: for an unknown name, or for a provider whose optional extra
            is not installed — with the ``pip install`` line in the message,
            because a traceback here is a packaging question, not a bug.
    """
    if model == "mock":
        from symfonic.core.testing import MockModelProvider

        return MockModelProvider(response=f"[mock] Responding to: {query}")

    if model == "anthropic":
        try:
            from symfonic.core.providers import AnthropicProvider  # pragma: no cover
        except ImportError as exc:
            raise ChatError(
                "Anthropic provider requires: pip install symfonic-core[anthropic]"
            ) from exc
        return AnthropicProvider()  # pragma: no cover

    raise ChatError(f"Unknown model provider: {model}. Use 'mock' or 'anthropic'.")

run_chat async

run_chat(request: ChatRequest, provider: Any) -> ChatOutcome

Execute one turn on whichever runtime :func:select_runtime picks.

Source code in src/symfonic/cli/chat.py
async def run_chat(request: ChatRequest, provider: Any) -> ChatOutcome:
    """Execute one turn on whichever runtime :func:`select_runtime` picks."""
    runtime = select_runtime(request)
    start = time.monotonic()
    if runtime == "facade":
        text, system_tokens, memory_used = await _run_facade(request, provider)
    else:
        text, system_tokens, memory_used = await _run_platform(request, provider)
    return ChatOutcome(
        text=text,
        runtime=runtime,
        system_prompt_tokens=system_tokens,
        memory_entries_used=memory_used,
        duration_ms=(time.monotonic() - start) * 1000,
    )

select_runtime

select_runtime(request: ChatRequest) -> Runtime

Return the runtime this invocation needs.

Three things pull an invocation onto the platform agent, and each is a capability the facade does not have rather than a preference:

  • --enable-hms — the HMS-aware system prompt is a memory capability;
  • --domain <name> — a domain template contributes prompt and tools;
  • --dsn — persistence implies a store the facade never opens.
Source code in src/symfonic/cli/chat.py
def select_runtime(request: ChatRequest) -> Runtime:
    """Return the runtime this invocation needs.

    Three things pull an invocation onto the platform agent, and each is a
    capability the facade does not have rather than a preference:

    * ``--enable-hms`` — the HMS-aware system prompt is a memory capability;
    * ``--domain <name>`` — a domain template contributes prompt and tools;
    * ``--dsn`` — persistence implies a store the facade never opens.
    """
    if request.enable_hms:
        return "platform"
    if request.domain != GENERIC_DOMAIN:
        return "platform"
    if request.dsn:
        return "platform"
    return "facade"