Skip to content

feat(ai): the outreach composer, and the checks that make it trustworthy - #10

Merged
ralyodio merged 1 commit into
mainfrom
feat/outreach-composer
Aug 11, 2026
Merged

ralyodio merged 1 commit into
mainfrom
feat/outreach-composer

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Adds packages/ai — the only package in the tree that talks to a model.

What makes it safe

Two properties are structural, not requested in a prompt:

  1. It can only see grounded inputs. The prompt is assembled from stored evidence, stored facts, and the customer's own offering. There is no path by which the model learns something it may not cite.
  2. Its output is checked, not trusted. Eight deterministic §14.2 gates run on every draft: grounding overlap, unsupported claims, identity confidence, flattery, spam patterns, cross-prospect duplication, sensitive topics, policy. A failing draft is rewritten once — naming the exact invented fragments — and if it still fails, it is withheld.

The undecidable question "is this claim true?" is inverted into a decidable one: does every specific assertion appear in stored evidence? Quoted phrases, numbers, and mid-sentence proper nouns are extracted and matched. That is what checks.ts answers.

Withholding is a working state, not an error. The card keeps the prospect, the evidence and the recommended action; the reviewer writes the message. A bad draft next to a caveat is still a bad draft someone might approve.

Surface

  • POST /recommendations/:id/draft — composes or recomposes on demand. Returns 200 {drafted: false, reason, unsupported} when withholding, and audits it. A user-edited draft is never discarded by a recompose.
  • The approval card gains Write draft / Rewrite, and states plainly why no draft exists instead of offering a retry that cannot help.
  • Prompt caching splits the stable offering/voice prefix from the per-prospect half, so a campaign pays for the prefix once.

Optional by design

Without ANTHROPIC_API_KEY the queue still runs end to end and drafting returns 503. Refusing to boot would take the whole system down for a feature that is designed to be allowed to produce nothing.

No model decides a merge, a score, or a policy outcome (PRD §1.1.8).

367 tests pass, typecheck clean.

🤖 Generated with Claude Code

Adds `packages/ai` — the only package in the tree that talks to a model. The
composer writes the message body for a recommendation, and everything around
it exists to make that output safe to show a reviewer.

Two properties are structural rather than requested:

  1. It can only see grounded inputs. The prompt is assembled from stored
     evidence, stored facts and the customer's own offering. There is no path
     by which the model learns something it may not cite.
  2. Its output is checked, not trusted. Eight deterministic §14.2 gates run
     on every draft — grounding overlap, unsupported claims, identity
     confidence, flattery, spam patterns, cross-prospect duplication,
     sensitive topics, policy. A draft that fails is rewritten once, naming
     the exact invented fragments; still failing, it is withheld.

Withheld is a working state, not an error. The card keeps the prospect, the
evidence and the recommended action, and the reviewer writes the message. A
bad draft next to a caveat is still a bad draft someone might approve.

The undecidable question "is this claim true?" is inverted into a decidable
one: does every specific assertion — quoted phrase, number, mid-sentence
proper noun — appear in stored evidence? That is what `checks.ts` answers,
and it is why no model output reaches a human unexamined.

Also:

- `POST /recommendations/:id/draft` composes or recomposes on demand, returns
  200 with `drafted: false` and a reason when withholding, and audits it.
  A user-edited draft is never discarded by a recompose.
- The approval card gains "Write draft" / "Rewrite", and states plainly why
  no draft exists rather than offering a retry that cannot help.
- Prompt caching splits the stable offering/voice prefix from the
  per-prospect half, so a campaign pays for the prefix once.
- The composer is optional infrastructure: without ANTHROPIC_API_KEY the
  queue still runs end to end and drafting returns 503. Refusing to boot
  would take the system down for a feature designed to produce nothing.

No model decides a merge, a score, or a policy outcome. 367 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​anthropic-ai/​sdk@​0.70.183100100100100

View full report

@ralyodio
ralyodio merged commit 0a216e5 into main Aug 11, 2026
4 checks passed
@ralyodio
ralyodio deleted the feat/outreach-composer branch August 16, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant