Reference Cairnloop.Automation.DraftGenerator backed by Anthropic's Claude
(Messages API). Composes a customer-support reply only when grounding is strong,
using the canonical Knowledge Base evidence the retrieval layer already gathered.
Fail-closed by construction — it delegates to Cairnloop.Automation.ScoriaEngine
(the deterministic default) in every case where it must not let a model guess:
- grounding is
:clarificationor:escalation(not:strong) — ask for the missing detail / recommend handoff instead of composing a reply; - no API key is configured — degrade gracefully to the deterministic engine
(mirrors
Cairnloop.Embedder.ExternalApi's no-key behaviour); - the Anthropic call errors or returns an unparseable body — never crash the
DraftWorker; fall back so a draft still appears for the operator.
The proposal shape, grounding snapshot, evidence, and human-in-the-loop approval are
identical to the deterministic engine — only the :strong-grounding customer_reply
is model-composed (and still operator-reviewed before any send).
Configuration
# host config
config :cairnloop, :draft_generator, Cairnloop.Automation.DraftGenerator.Anthropic
# runtime.exs — secrets read at boot, never compiled in
config :cairnloop, :anthropic_api_key, System.fetch_env!("ANTHROPIC_API_KEY")Optional knobs (all have sensible defaults):
:anthropic_model— Claude model id (default"claude-sonnet-4-6"):anthropic_max_tokens— reply budget (default1024):anthropic_req_options— extraReqoptions merged into the request (the seam used by tests to inject a stubplug:):conversation_lookup— how to load the thread for prompt context (default&Cairnloop.Chat.get_conversation!/1; shared withDraftWorker)
The API key is also read from the ANTHROPIC_API_KEY env var when not set in config.