somm explained · 05
Agents and Integrations
somm is designed to be grafted on, not built around. Two compatibility shims absorb existing call sites, a LangChain chat model absorbs agent frameworks, a 14-tool MCP server hands the accumulated telemetry to coding agents, and a packaged skill teaches those agents to instrument their own calls.
Every surface on this page converges on the same two primitives: SommLLM.generate() for execution and the project-local SQLite repository for memory. The shims and the LangChain adapter feed calls into that pipeline; the MCP server and the agent skill read intelligence back out of it. Nothing here adds a second data path — a call made through the OpenAI shim lands in the same immutable calls ledger as one made natively, and an MCP comparison run records ordinary telemetry like any other call (packages/somm-mcp/src/somm_mcp/server.py:663).
flowchart LR
subgraph adopt["Existing code plugs in"]
shim["OpenAI shim
openai_chat_completions()"]
compat["GenericLLMCompat
legacy-wrapper drop-in"]
lc["SommChatModel
LangChain / LangGraph"]
end
subgraph agents["Coding agents plug in"]
mcp["somm-mcp
14 stdio tools"]
skill["somm-skill
SKILL.md + SOMMELIER.md"]
end
gen["SommLLM.generate()
routing / budgets / hooks"]
db[("project SQLite
calls / evals / decisions")]
global[("global cross-project
decision repository")]
shim --> gen
compat --> gen
lc --> gen
mcp -->|"compare / replay"| gen
skill -. "teaches agents to call" .-> gen
gen --> db
mcp <--> db
mcp -. "best-effort mirror" .-> global
Drop-in adoption: the OpenAI shim and legacy-wrapper compat layer
The examples/ area exists to prove a claim: adding somm to an existing Python application should take minimal code changes (examples/README.md:3). It demonstrates two adoption paths, one for codebases with their own LLM wrapper class and one for codebases written against the OpenAI SDK shape.
For the wrapper case, GenericLLMCompat replaces the original LLM class at import time. Calls to .generate() keep the result fields the surrounding code already reads, while cost and provenance metadata arrive as additions rather than replacements (examples/drop_in_wrapper.py:33, examples/drop_in_wrapper.py:36). For batch work, .probe_providers(n) returns provider slots so a job can be striped across whichever providers are actually available (examples/drop_in_wrapper.py:62). Each client is explicitly closed in a try/finally block — a lifecycle pattern the examples apply consistently (examples/drop_in_wrapper.py:39).
The OpenAI shim, openai_chat_completions(...), accepts chat-completion arguments and preserves the access pattern existing code depends on — resp.choices[0].message.content still works (examples/openai_swap_in.py:21, examples/openai_swap_in.py:24). Somm-specific metadata is exposed as optional extra attributes on the response, so nothing downstream has to change to ignore it (examples/openai_swap_in.py:36).
The third example shows the privacy machinery working through a shim-shaped call site. A sensitive workload is registered once with PrivacyClass.PRIVATE; every subsequent call using that workload is restricted to local providers (examples/private_workload.py:18, examples/private_workload.py:27).
SommPrivacyViolation (examples/private_workload.py:27, examples/private_workload.py:36). The example layers a zero-dollar daily budget on top as a second, independent safeguard (examples/private_workload.py:24).
Once a shim is in place, the accumulated record is inspectable immediately: somm status and somm tail read the telemetry, and somm serve brings up the dashboard (examples/README.md:70, examples/README.md:78).
SommChatModel: LangChain and LangGraph without leaving somm
somm-langchain lets LangChain, LangGraph, and Deep Agents applications use somm as a standard chat model, preserving LangChain call sites while somm handles routing, telemetry, cost tracking, fallback, and model memory (packages/somm-langchain/README.md:3). SommChatModel subclasses LangChain's BaseChatModel; during generation it extracts and joins system messages, translates human, assistant, and tool messages into somm's provider-neutral format, and calls SommLLM.generate() with the workload, routing pins, sampling settings, and tools (packages/somm-langchain/src/somm_langchain/chat_model.py:79).
The translation is bidirectional and careful about tool calling. Going in, assistant tool calls become tool_use blocks and tool results become user-side tool_result blocks, with system prompts passed separately (packages/somm-langchain/src/somm_langchain/chat_model.py:173). Coming back, the somm result becomes an AIMessage inside a ChatGeneration, carrying tool calls, provider/model provenance, latency, cost, outcome, and token usage (packages/somm-langchain/src/somm_langchain/chat_model.py:118). bind_tools() accepts LangChain-compatible tool definitions, normalizes them through OpenAI's common schema, and unwraps them to somm's neutral schema immediately before generation (packages/somm-langchain/src/somm_langchain/chat_model.py:152).
Several small decisions reward attention:
- Failures raise by default, so LangChain retry or circuit-breaker middleware can react; callers can instead request an empty message carrying failure metadata (
packages/somm-langchain/src/somm_langchain/chat_model.py:63). - Pins are optional. Model and provider overrides exist, but absent them, routing stays with somm (
packages/somm-langchain/src/somm_langchain/chat_model.py:57). - Reasoning content is preserved across tool-calling turns, because some thinking-model providers reject subsequent requests without it (
packages/somm-langchain/src/somm_langchain/chat_model.py:122). - Text-only assistant turns collapse to plain strings for interoperability, while mixed text/tool turns keep structured blocks (
packages/somm-langchain/src/somm_langchain/chat_model.py:236). Unknown LangChain message subclasses are forwarded as user text on a best-effort basis rather than rejected (packages/somm-langchain/src/somm_langchain/chat_model.py:210).
SommLLM.stream().
somm-mcp: telemetry, advice, compare, and replay over stdio
The somm-mcp command loads project configuration, constructs the full configured provider chain, builds a FastMCP server, and starts its stdio transport (packages/somm-mcp/src/somm_mcp/cli.py:27). build_server opens the project's SQLite-backed Repository, indexes the supplied providers by name, and registers closures as MCP tools (packages/somm-mcp/src/somm_mcp/server.py:59). The result: an MCP-capable coding agent can inspect LLM usage, manage workloads and prompts, compare or replay model calls, and reuse routing decisions — without a commercial service anywhere in the hot path.
packages/somm-mcp/src/somm_mcp/server.py:1)
| Concern | Tools | Registered at |
|---|---|---|
| Telemetry | somm_stats, somm_search_calls — summarize or filter recorded calls | server.py:72 |
| Guidance | somm_recommend, somm_inbox — workload guidance and recommendation items | server.py:117 |
| Recommendation lifecycle | somm_apply_recommendation, somm_dismiss_recommendation | server.py:197 |
| Model advice | somm_advise — rank models under capability, provider, price, context, and modality constraints | server.py:232 |
| Decision memory | somm_record_decision, somm_search_decisions | server.py:310 |
| Registration | somm_register_workload, somm_register_prompt | server.py:436 |
| Evaluation | somm_eval_promote_call — promote a sampled call into a dataset | server.py:509 |
| Execution | somm_compare, somm_replay — side-by-side calls, or rerun a captured call | server.py:549 |
The intelligence-facing tools are layered on evidence, not vibes: somm_recommend combines open recommendations with model rankings from shadow evaluations, and when no evaluation data exists it falls back to model-intelligence candidates and prior decisions (packages/somm-mcp/src/somm_mcp/server.py:117). The execution tools instantiate a real SommLLM with explicit providers, record normal telemetry, and always close the client; replay additionally requires captured samples and refuses private workloads outright (packages/somm-mcp/src/somm_mcp/server.py:663).
packages/somm-mcp/src/somm_mcp/server.py:49). Provider-dependent tools stay discoverable even when no providers are configured, returning structured errors instead of vanishing from the catalog (packages/somm-mcp/src/somm_mcp/server.py:28). Compare enforces configurable fan-out and token ceilings, with elevated — but still bounded — caps available (packages/somm-mcp/src/somm_mcp/server.py:577).
packages/somm-mcp/README.md:7 vs. packages/somm-mcp/src/somm_mcp/server.py:1). Trust the server. The project's security worklist also flags MCP comparison limits among the surfaces still slated for hardening (notes/SOUNDCHECK-WORKLIST-2026-07-10.md:23).
Decision memory: record a model choice once, recall it across projects
Most telemetry in somm is per-project. Decision memory is the deliberate exception: when an agent (or a person driving one) commits to a model choice, somm_record_decision writes that judgment locally and best-effort mirrors it to a global cross-project repository, where somm_search_decisions can recall it from any other project on the machine (packages/somm-mcp/src/somm_mcp/server.py:360, packages/somm-mcp/src/somm_mcp/server.py:310).
sequenceDiagram
participant A as Coding agent
participant M as somm-mcp
participant L as Project repository
participant G as Global repository
A->>M: somm_search_decisions — recall related prior choices
A->>M: somm_advise — rank live candidates under constraints
M->>L: model intelligence + shadow-eval evidence
A->>M: somm_record_decision — committed choice + rationale
M->>L: write decision
M--)G: best-effort mirror (cross-project recall)
The three-step sequence — recall, advise, record — is codified as the model-selection lifecycle in SOMMELIER.md: first recall related cross-project decisions, then request ranked live candidates through somm_advise, then record the user's committed choice along with its rationale (packages/somm-skill/src/somm_skill/SOMMELIER.md:17, packages/somm-skill/src/somm_skill/SOMMELIER.md:40, packages/somm-skill/src/somm_skill/SOMMELIER.md:110).
Past model decisions inform recommendations but are deliberately non-authoritative — model intelligence changes.
— design note, packages/somm-skill/src/somm_skill/SOMMELIER.md:36
That stance matters. A decision recorded in April against last quarter's pricing and capability data should shape, not dictate, a choice made in July. The recall step surfaces precedent; the advise step re-ranks against current evidence; only then is a new decision recorded.
somm-skill: teaching coding agents to instrument their own calls
The last integration surface contains no runtime code at all. somm-skill packages two Markdown resources — onboarding instructions for coding agents working on Python projects that use somm (packages/somm-skill/README.md:3). The Python module is a docstring; consumers load or copy the bundled Markdown, including via importlib.resources in the documented Claude Code installation flow (packages/somm-skill/src/somm_skill/__init__.py:1, packages/somm-skill/README.md:19).
packages/somm-skill/pyproject.toml:4)skillopt/somm.md:35)SKILL.md is the operating manual: create a client with somm.llm(), tag every generation with a stable workload, register that workload with privacy and budget controls, and mark results with typed outcomes (packages/somm-skill/src/somm_skill/SKILL.md:24, packages/somm-skill/src/somm_skill/SKILL.md:44, packages/somm-skill/src/somm_skill/SKILL.md:103). It encodes operational judgment too — CLI subscription seats are pinned-only, preserving scarce quota for grading and low-volume work rather than exposing it to hot-loop routing (packages/somm-skill/src/somm_skill/SKILL.md:124). Because it ships as plain package resources, the same guidance travels to Claude, Codex, Cursor, Windsurf, and other agent packaging formats (packages/somm-skill/README.md:34).
The guidance itself is treated as a tested artifact, not prose. scripts/score_skill.py grades a candidate SKILL.md with deterministic behavioral string and regex checks, split into training and held-out cases; only the held-out aggregate is emitted for the optimizer (scripts/score_skill.py:39, scripts/score_skill.py:133). The most recent optimization run improved the skill from 4/5 to 5/5 held-out checks by converting safety-critical rules into protected uppercase NEVER/DO NOT guardrails, then stopped because no failing case remained (skillopt/somm.md:35, skillopt/somm.md:54).
Together, the skill and the MCP server close a loop the earlier pages set up: agents write instrumented calls (skill), those calls accumulate into evidence (substrate), workers turn evidence into recommendations (intelligence loop), and the same agents read the recommendations back and record their choices (MCP). Each surface is separable; none is required for the one before it to work.
Where to go next
The Life of a Call
What actually happens when a shim or SommChatModel call reaches SommLLM.generate(): workload resolution, hooks, routing, fallback, and the telemetry write.
The Data Substrate
The SQLite ledger every integration shares — and the privacy enforcement that makes replay refuse private workloads.
The Intelligence Loop
Where somm_recommend's evidence comes from: shadow evaluation, model-intelligence refresh, and the recommendation agent.
Engineering Reference
Package layout, CI gates, and the release machinery behind the six-package workspace these integrations ship in.