Kiln · Architecture

How Kiln works

The agent topology, the dispatch order, the data flow, and a minute-by-minute walkthrough of a real review run. Read top-to-bottom in about three minutes; jump in via the table of contents below.

1 · The 60-second loop

A reviewer (or a visitor) selects a deal. The orchestrator loads it from the SQLite CRM, fans out to gather context, dispatches the five sub-agents in parallel via Promise.all, routes the result through approval, hands the bundle to comms, and posts a structured summary to a real Slack channel. End-to-end wall-clock time is roughly 45–75 seconds.

The sub-agents are model-mixed and tightly scoped — each owns one domain and produces a Zod-validated JSON output. The orchestrator never streams free-text to the UI; it streams structured agent events. That distinction is the whole reason a deal-desk operator can audit what happened, replay it, or hand-edit the inputs.

On hero scenarios the agent outputs are served from a cache for deterministic playback. On visitor submissions, every call is live against Anthropic's API. Both paths run through the same orchestrator code — the only difference is whether the LLM step short-circuits to a recorded result.

sequenceDiagram
    autonumber
    participant V as Visitor
    participant O as Orchestrator (Opus 4.7)
    participant T as Tools (CRM · Vector · Exa)
    participant Subs as 5 Sub-agents (parallel)
    participant Slk as Slack #deal-desk
    participant Out as Artifacts (DOCX · PDF · XLSX · EML)

    V->>O: Run review on deal
    O->>T: Step 1 — fetch deal
    T-->>O: Deal record
    par Step 2 — gather context (parallel)
        O->>T: Vector k-NN over 40 deals
        T-->>O: 3 similar past deals
    and
        O->>T: Exa public signals
        T-->>O: 3-5 customer signals
    end
    par Step 3 — Promise.all dispatch
        O->>Subs: Pricing
        O->>Subs: ASC 606
        O->>Subs: Redline
    end
    Subs-->>O: 3 typed outputs
    O->>Subs: Step 4 — Approval routing (Haiku 4.5)
    Subs-->>O: ApprovalOutput
    O->>Subs: Step 5 — Comms drafts (Sonnet 4.6)
    Subs-->>O: CommsOutput
    O->>Slk: Block Kit post
    O->>Out: Generate 5 artifacts
    O-->>V: Synthesis (4-sentence verdict)
Step 2 (vector + Exa) and Step 3 (Pricing + ASC 606 + Redline) fan out in parallel. Approval and Comms are sequential — Approval needs the three upstream outputs, Comms needs Approval's routing decision.

2 · The agents

Five specialist sub-agents under one orchestrator. Each lives in its own file under lib/agents/ with a markdown system prompt at lib/prompts/.

AgentModelDomainKey inputsKey outputs
PricingSonnet 4.6Discount math, ramp/credit modelingDeal · guardrails · top-3 similar dealsEffective discount, margin estimate, 2-3 alternative structures
ASC 606Opus 4.7Revenue recognition reasoningDeal terms · clausesPerformance obligations, variable-consideration flags, monthly schedule
RedlineSonnet 4.6Non-standard clause detectionDeal · customer signalsFlagged clauses with counter + fallback positions
ApprovalHaiku 4.5Matrix evaluation + chain routingMatrix rules · all upstream outputsRequired approvers, approval chain, cycle-time estimate
CommsSonnet 4.6Internal + external draftsAll upstream outputs · approval chainSlack post, AE email, customer email, approval one-pager

Why these models. The orchestrator and ASC 606 use Opus 4.7 — the synthesis verdict and the rev-rec reasoning are the two highest-stakes outputs and both reward the strongest model. Pricing, Redline, and Comms run on Sonnet 4.6 for a balanced cost/quality trade on math, clause analysis, and tone-aware drafting. Approval is a deterministic table lookup over the matrix, so Haiku 4.5 handles it at a fraction of the cost.

3 · Data flow

Three data sources feed the orchestrator before the first sub-agent dispatches: the committed SQLite CRM, the sqlite-vec virtual table of deal embeddings, and Exa for public customer signals. A fourth path — the cached-output store — replays prerecorded agent results for hero scenarios.

graph LR
    CRM[("SQLite CRM<br/>40 deals · 30+ customers")]
    Vec[("sqlite-vec<br/>1536-dim embeddings")]
    Exa[("Exa API<br/>public signals")]
    Cache[("cached_outputs/<br/>5 hero scenarios")]
    Orc{{"Orchestrator<br/>Opus 4.7"}}
    Pri["Pricing<br/>Sonnet 4.6"]
    Asc["ASC 606<br/>Opus 4.7"]
    Red["Redline<br/>Sonnet 4.6"]
    Apv["Approval<br/>Haiku 4.5"]
    Com["Comms<br/>Sonnet 4.6"]
    Slk["Slack<br/>kiln-demo"]
    Doc["DOCX · PDF · XLSX · EML"]
    Aud[("audit_log")]

    CRM --> Orc
    Vec --> Orc
    Exa --> Orc
    Cache -.cached replay.-> Orc
    Orc --> Pri
    Orc --> Asc
    Orc --> Red
    Pri --> Apv
    Asc --> Apv
    Red --> Apv
    Apv --> Com
    Com --> Slk
    Com --> Doc
    Orc --> Aud

    classDef store fill:#f5f5f5,stroke:#737373,stroke-width:1px,color:#0a0a0a
    classDef agent fill:#fafaf9,stroke:#0a0a0a,stroke-width:2px,color:#0a0a0a
    classDef out fill:#fff7ed,stroke:#c2410c,stroke-width:1px,color:#0a0a0a
    class CRM,Vec,Exa,Cache,Aud store
    class Orc,Pri,Asc,Red,Apv,Com agent
    class Slk,Doc out
Solid edges are live calls; the dashed edge from cached_outputs is the deterministic-replay path used for the 5 hero scenarios. Visitor submissions never read from cache.

Step-by-step. Step 1 is a single-row CRM lookup (~20 ms). Step 2 fans out — vector k-NN returns in ~80 ms; Exa is the long pole at ~3 s. Step 3 dispatches Pricing, ASC 606, and Redline in parallel; ASC 606 is the longest agent at ~14 s because the rev-rec schedule is the most-token-heavy generation. Step 4 (Approval) takes ~3 s. Step 5 (Comms) is the heaviest single agent at ~12 s because it produces five distinct text artifacts. Slack post fires immediately on Comms completion (~300 ms). Total ~62 s wall-clock for a typical deal.

4 · MCP server architecture

Tools aren't inlined into the agent code. They're exposed through four internal MCP servers — running in-process inside the Next.js app — that the orchestrator and sub-agents call by name. Tool names are namespaced mcp__<server>__<tool>.

MCP serverTools exposedUsed by
crm-serverget_deal · get_pricing_guardrails · get_approval_matrixOrchestrator → Pricing, Approval
vector-serverfind_similar_deals (k-NN, top-3)Orchestrator
exa-servercustomer_signals (24h cache key)Orchestrator
slack-serverpost_deal_review (Block Kit)Comms (via Orchestrator)

MCP gives agents a clean tool boundary independent of where they run. The same agent code works inside Kiln's Node runtime, inside Claude Desktop, or against a future Clay-MCP integration — only the server registration changes. It also means a fork can swap the mock CRM for a real Salesforce backend by replacing one server file.

5 · Worked example — Anthropic Q1 expansion

What actually happens when the orchestrator runs on the Anthropic hero scenario — a $1.5M TCV strategic expansion with 5 non-standard clauses including an MFN. Timestamps are wall-clock from the live run captured during build verification.

  1. T+0.0sOrchestrator

    Load deal from CRM

    mcp__crm__get_deal('deal_anthropic_2026q1_expansion') → full deal record. $1.5M TCV, 36-month term, 12% headline discount, 5 non-standard clauses (MFN, exclusivity, custom data residency, rollover credits, termination-for-convenience).

  2. T+0.1sOrchestrator

    Step 2 — gather context (parallel)

    Fan-out to vector k-NN and Exa via Promise.all. Vector returns 3 similar deals (a 2025 Anthropic expansion, an OpenAI expansion, a Mistral mid-market deal). Exa returns 4 signals — recent funding, leadership change, new product launch, hiring signal. Step joins at ~3.2s.

  3. T+3.3sPricing · ASC 606 · Redline

    Step 3 — dispatch sub-agents (parallel)

    Pricing identifies the headline 12% discount as 28.4% effective once the ramp and credits are factored in; proposes three alternative structures. ASC 606 identifies 4 performance obligations and flags the ramp as variable consideration requiring the expected-value method. Redline flags 5 clauses with concrete counter + fallback positions for each. Step joins at ~15s (ASC 606 is the long pole).

  4. T+15.3sApproval

    Step 4 — approval routing

    Matrix evaluation triggers three rules: ACV > $500K → CFO; non-standard clause count > 3 → Legal; MFN clause present → CEO. Final chain: AE Manager → RevOps → CFO + Legal (parallel) → CEO. Estimated cycle time: 5 business days.

  5. T+18.4sComms

    Step 5 — communications drafts

    Block Kit Slack post, AE briefing email (collaborative tone, walks the AE through the 5 clause counters), customer-facing email (collaborative, leads with alternative structures), and approval one-pager. All drafts cite the upstream outputs with concrete numbers, not paraphrases.

  6. T+30.5sSlack + Artifact generators

    Slack post + 5 downloadable artifacts

    Block Kit message lands in #deal-desk in the kiln-demo workspace. Document generators emit redlined-msa.docx, order-form.pdf, approval-one-pager.pdf, ae-email.eml, and a 10-tab deal-summary.xlsx with live cross-tab formulas — open it in Excel and edit the discount cell to watch the rev-rec schedule recalculate.

  7. T+~62sOrchestrator

    Synthesis verdict

    Four-sentence executive summary citing each sub-agent. Audit log row written. Review persisted to deal_reviews. UI receives the final SSE event and renders the verdict card + agent output cards.

6 · Caching strategy

Two paths through the same orchestrator. Hero scenarios serve cached output from db/seed/cached_outputs/ so the demo is snappy and deterministic — five recorded JSON files, one per hero deal, replayed at SSE-streaming speed so the timeline feels live. Visitor submissions trigger a real Anthropic API call and run live.

The split is deliberate. Hero scenarios optimize for determinism + cost — the recruiter sees the same well-tuned output every time, and the demo costs nothing to operate. Visitor submissions optimize for authenticity — when somebody puts their own deal in, the agents must actually reason about it. Approximate cost per live submission: a few cents.

Visitor deals live in an in-memory store keyed by a signed session cookie (lib/visitor-submit/store.ts) and never touch the committed SQLite file. Other visitors can't see them, the dashboard's pipeline KPIs ignore them, and they evaporate on the next deploy. That isolation is the privacy contract.

7 · Output schemas

Every agent emits JSON conforming to a Zod schema declared in lib/agents/schemas.ts. Structured outputs are the reason the UI is renderable, the audit log is queryable, and the eval harness can grade runs at all.

Output schema
// lib/agents/schemas.ts
export const PricingOutputSchema = z.object({
  list_price: z.number(),
  proposed_price: z.number(),
  effective_discount_pct: z.number(),     // real discount after ramp + free months
  margin_pct_estimate: z.number(),         // assumes 40% gross margin at list
  guardrail_evaluations: z.array(z.object({
    rule_name: z.string(),
    passed: z.boolean(),
    severity: z.enum(["info", "warn",
                      "block_without_approval", "block_absolute"]),
    actual_value: z.number(),
    threshold_value: z.number(),
    explanation: z.string(),
  })),
  alternative_structures: z.array(z.object({
    label: z.string(),                     // "Trade discount for term length"
    description: z.string(),
    proposed_price: z.number(),
    effective_discount_pct: z.number(),
    expected_acv_impact: z.number(),
    margin_pct_estimate: z.number(),
    rationale: z.string(),
  })).min(2).max(3),
  ltv_estimate_under_usage_assumptions: z.number().nullable(),
  similar_deal_references: z.array(z.string()),
  confidence: z.enum(["low", "medium", "high"]),
  reasoning_summary: z.string(),
});
Example output (truncated)
{
  "list_price": 1800000,
  "proposed_price": 1530000,
  "effective_discount_pct": 28.4,
  "margin_pct_estimate": 22.1,
  "guardrail_evaluations": [
    { "rule_name": "max_discount_enterprise",
      "passed": false, "severity": "block_without_approval",
      "actual_value": 28.4, "threshold_value": 20,
      "explanation": "Effective discount exceeds 20% threshold once ramp + credits factored in." }
  ],
  "alternative_structures": [
    { "label": "Trade discount for 48-month term",
      "proposed_price": 1620000, "effective_discount_pct": 18.0,
      "rationale": "12 extra months reduce CAC payback to 14 months." },
    /* …2 more */
  ],
  "confidence": "high",
  "reasoning_summary": "Headline 15% becomes 28.4% effective…"
}

8 · Tech surfaces

The stack at a glance, for the technical reader who skimmed past the diagrams.

  • Streaming·Server-Sent Events from app/api/run-review/[dealId]/route.ts; one event per agent step.
  • Vector search·sqlite-vec extension on the same SQLite file. k-NN over 1536-dim embeddings.
  • Embeddings·OpenAI text-embedding-3-small. Generated once at seed time; persisted as BLOB on the deal_embeddings virtual table.
  • Document generation·docx for redlined MSA · pdfkit for order form + one-pager · exceljs for the 10-tab live-formula workbook.
  • Slack·@slack/web-api with Block Kit JSON. Bot posts to #deal-desk in a real kiln-demo workspace; invite link surfaces in the sidebar.
  • Agent framework·@anthropic-ai/claude-agent-sdk via query() per agent. Bounded reasoning (effort: low, maxTurns: 2) for predictable latency.
Source: github.com/fbalenko/kiln · Architecture decisions live in docs/01-architecture.md, docs/03-agents.md, and docs/13-clay-integration-plan.md.