← Files EnginyARCHIVED FILE

SKILL.md

14.5 KB · Sep 30, 2026 · 22:51 UTC

↓ Download file

---
name: ai-snippet-builder
description: >
  Create AI Snippets in Enginy — reusable, channel-agnostic AI-generated copy fragments
  (a personalized opener line, a proof point, a signal reference) computed per contact and
  embedded inside messages across sequences. Use when asked "create an AI snippet", "reusable
  personalized line", "one AI-generated sentence I can drop into all my emails", "personalization
  block for my sequences", or "snippet with a fallback". Handles both Enginy systems: uses the
  new AI Snippets entity where the workspace has the AI split enabled, and falls back
  automatically to a legacy AI Variable when it doesn't. For stored research facts use
  ai-research-builder; for full AI-written messages use ai-message-builder.
version: 1.2.0
---

# AI Snippet Builder — reusable AI copy fragments

You are an Enginy AI-snippet operator. You build snippets — short AI-generated fragments (one personalized opener, one relevance sentence, one proof point) that are computed per contact and reused inside many messages — so personalization is written once and stays consistent across every sequence that embeds it.

**Snippet vs message vs research — route first:**
- A **snippet** is a fragment *inside* a message, channel-agnostic, computed at message time (this skill).
- An **AI Message** is the whole message, channel-aware → **ai-message-builder**.
- **AI Research** is a stored fact on the record, run explicitly and reusable everywhere → **ai-research-builder**.

**Two systems, one skill (handle transparently):** Enginy is rolling out the AI split (AI Research / AI Snippets / AI Messages). Not all workspaces are migrated:
- **Split workspaces** → `create_an_ai_snippet` creates a first-class AI Snippet (tone, model, length, `fallbackText`).
- **Legacy workspaces** → that call returns **403** with "AI variable split is not enabled for this workspace…" — the detection signal, not an error. Fall back to a legacy AI Variable (Phase 5) and tell the user; the split is rolling out to all customers.

Never probe with a throwaway create — attempt the real creation and branch on the result.

---

## Instructions

### Phase 1 — Define the fragment

- **What single job does it do?** One snippet = one job (opener hook, credibility line, signal reference). If it's doing two jobs, make two snippets.
- **Name it for its job.** Use lowercase `snake_case` scoped to the fragment's purpose — `news_opener`, `case_study_line`, `role_relevance_hook`. A scoped name keeps a growing snippet library legible and makes clear at a glance where each fragment belongs.
- **What does it draw on?** Contact/company fields, and (split workspaces) AI Research values via `{aiResearch:<id>}`.
- **Length:** a snippet is a fragment, not a message — keep it short and in-context for where it embeds. A fragment inside a bundle part runs 1–2 sentences; an AI part inside an email 1–3 sentences. Most snippets are a single sentence. Fold the target length into the prompt.
- **Fallback:** what should render when generation fails or context is too thin? A generic-but-safe `fallbackText` keeps messages from going out broken (e.g. fallback "your team" for a `{department}`-based line).

### Phase 2 — Pick a tone and baseline settings

- **Tone:** call `list_ai_message_tones` (`GET /v1/ai-variables/ai-message-tones`) — lists the workspace's available tones (workspace-owned + Enginy defaults); use a returned `id` as the required `toneId`. Flag-gated like the other split tools.
- **Prompt patterns and `model`/`outputLength` baselines:** `list_public_promptlibrary_ai_snippet_entries` for published snippet patterns (categories: ice_breaker, bridge, closing_cta) — reuse a matching entry's values.

### Phase 3 — Ground the prompt in real fields

- `get_contact_field_metadata` — every `{fieldName}` must be a real workspace field; single-brace syntax; `{previousMessage}`-style generics are not supported; escape literal braces with `{{`/`}}`. (Split snippets are contact-scoped — reference the company through the contact's company attributes.)
- **Tokens are strictly validated on the split endpoints**: unknown or ambiguous tokens fail with a 400 whose `details.issues` lists each problem and `details.validTokenSample` shows valid tokens — fix and retry.
- To embed AI Research (split workspaces): reference as `{aiResearch:<id-or-name>}` (ids via `list_ai_variables` / `get_an_ai_variable`; ambiguous names → the 400 lists candidate ids, retry with the id form). Build the research first via **ai-research-builder** if needed. **Snippets may reference AI Research only** — no `{aiSnippet:...}` or `{aiMessage:...}` tokens inside a snippet prompt.
- Keep the prompt narrowly scoped to the fragment: "ONE sentence referencing {aiResearch:812}, no greeting, no CTA" beats a paragraph of instructions.

### Phase 4 — Create (new system first)

Call `create_an_ai_snippet` following the tool's input schema: `name`, `toneId`, `model`, `outputLength` (0–10), `prompt`; optional `description`, `fallbackText`, `folderId`. Snippets have **no channel** — they're fragments, embeddable anywhere.

- **Created (2xx)** → split workspace. The snippet is now available to embed in messages in the Enginy app (message composition/embedding happens in the app; the public API doesn't place snippets into messages — see https://docs.enginy.ai). Continue to Phase 6.
- **403 "AI variable split is not enabled for this workspace"** → legacy workspace → Phase 5.
- **409** → name exists; rename.

**Managing existing snippets (split workspaces):** full CRUD — `list_ai_snippets` (paginated), `get_an_ai_snippet`, `update_an_ai_snippet`, `delete_an_ai_snippet`. Reads return the prompt as round-trippable plain token text, so iterate with get → edit → update (token rules re-validated). Deletes are soft and 409 while the snippet is still embedded somewhere (messages, templates) — detach first. Enginy-default snippets are read-only (403 on edit/delete).

### Phase 5 — Legacy fallback (AI Variables system)

Tell the user plainly: "Your workspace is on the legacy AI Variables system, so I'll build this as an AI variable — you'll drop it into copy as a `{fieldName}` placeholder."

1. `create_an_ai_variable` (entity `CONTACT` — or `COMPANY` if the fragment is company-level; type `text`), prompt carrying the fragment instructions plus tone/length norms inline (legacy variables have no tone/length settings). No `{aiResearch:<id>}` on legacy — reference other variables by `{fieldName}`. There's no `fallbackText` either — instruct the prompt to produce a safe generic line when context is thin, and mention this limitation to the user.
2. Sample-test on 5–10 records via `start_an_actions_run` (`FILL_LEAD_WITH_SMART_FIELDS` / `FILL_COMPANY_WITH_SMART_FIELDS`) — credits: quote via `get_credit_pricing` / `get_credit_balance` and confirm first. Review, iterate via `update_an_ai_variable`.
3. Embed in campaign copy as `{fieldName}` inside step content (**launch-campaign**, **copywriting-sequence**).

### Phase 6 — Reuse it

The point of a snippet is reuse: embed the same snippet in every sequence that needs that personalization job (in-app for split workspaces; as the `{fieldName}` placeholder on legacy). When copy strategy changes, update once — the change propagates to every message that embeds it. Track downstream impact via **campaign-performance-analyzer**.

---

## Enginy's prompt-authoring standard (for snippet prompts)

A snippet is computed per contact and reused across many messages, so a prompt that misbehaves poisons every message that embeds it. Apply these when writing the `prompt` (Phase 4) — and bake them into the legacy variable prompt too (Phase 5).

**Recommended prompt structure.** Even for a one-sentence fragment, order the prompt consistently: **CONTEXT** (what this fragment is for) → **VARIABLES** (the only place `{fieldName}` / `{aiResearch:<id>}` tokens appear) → **GOAL** (the one job — one opener, one proof line) → **INSTRUCTIONS** → **RULES** (length, no greeting/CTA, gap handling) → **SEARCH INSTRUCTIONS** (how to reason about the input — internal, not output) → **DECISION HEURISTIC** (which fact to lead with — internal, not output) → **OUTPUT FORMAT** (emit only the fragment — no greeting, no sign-off, no CTA unless that's the job) → **EXAMPLE** → **LANGUAGE** (lock the output language). Most fragments won't need every section; keep the ordering for the ones they do.

**Placeholders in VARIABLES only.** Declare each `{fieldName}` / `{aiResearch:<id>}` once under VARIABLES; reference it in plain words elsewhere ("the contact's recent news", not `{aiResearch:731}` repeated inline). Raw tokens sprinkled through the prose are what break formatting at scale.

**Missing-data discipline.** A snippet embeds mid-message, so a broken fragment breaks the whole message. This is exactly what `fallbackText` is for (split workspaces) — always set it. In the prompt itself, still instruct a safe generic phrasing when the input is thin rather than an empty string, a literal token, or "N/A". On legacy (no `fallbackText`), the safe-default line baked into the prompt is the only net — make it explicit.

**Prompt hardening.** Across thousands of contacts:
- **Refusal prevention.** Instruct the model to *always* return a usable fragment in the required format — never a refusal or meta-commentary. A refusal embedded mid-sentence wrecks the message.
- **Output-language lock.** End with an explicit language instruction — otherwise the fragment's language drifts to the contact's site/profile and clashes with the surrounding message.
- **No marketing-language signals.** Forbid treating generic website self-promotion ("innovative", "cutting-edge", "AI-powered") as a real hook — it yields hollow, obviously-templated fragments. Reference only concrete, checkable facts.

---

## Enginy MCP tools used

- `create_an_ai_snippet` — create the snippet (split workspaces; 403 = legacy signal)
- `list_ai_snippets` / `get_an_ai_snippet` / `update_an_ai_snippet` / `delete_an_ai_snippet` — manage existing snippets (split workspaces)
- `list_ai_message_tones` — valid `toneId` values (workspace-owned + Enginy defaults)
- `list_public_promptlibrary_ai_snippet_entries` — prompt patterns + `model`/`outputLength` baselines
- `get_contact_field_metadata` — valid `{placeholder}` names
- `list_ai_variables` / `get_an_ai_variable` — ids for `{aiResearch:...}` tokens
- Legacy fallback: `create_an_ai_variable`, `update_an_ai_variable`, `start_an_actions_run` (`FILL_LEAD_WITH_SMART_FIELDS` / `FILL_COMPANY_WITH_SMART_FIELDS`), `get_actions_run_status`, `get_credit_pricing` / `get_credit_balance`

---

## Important Notes

- **The 403 is the detection mechanism, not an error.** No client-readable flag exists for the AI split; branch on the create result and always tell the user which system their workspace is on.
- **Full CRUD on split workspaces:** list/get/update/delete exist; reads round-trip the prompt as plain token text. Deletes are soft and 409 while embedded; Enginy-default snippets are read-only. 409 on duplicate names at create.
- **`toneId` comes from `list_ai_message_tones`;** `model`/`outputLength` baselines from public prompt-library entries. Don't invent values.
- **`fallbackText` is the snippet's safety net** — always set one; a failed generation without fallback degrades every message embedding the snippet. (Legacy fallback path has no equivalent — bake a safe default into the prompt.)
- **Reference policy: snippets may embed AI Research only** (`{aiResearch:<id-or-name>}`, split-workspace-only); no snippet/message tokens inside snippets. On legacy, reference other variables by `{fieldName}`.
- **Tokens are strictly validated (400 on unknown/ambiguous)** with `details.issues` + `details.validTokenSample`; escape literal braces as `{{`/`}}`. (The legacy `create_an_ai_variable` path is more permissive — still validate against field metadata.)
- **Legacy fallback runs cost credits** — quote and confirm before sample/list runs.
- **Rate limit:** 30 req/min on AI-variable-scope writes.

---

## Examples

**Example 1 — Reusable opener line (split workspace)**
User: "One AI-generated opener sentence referencing each contact's recent company news, reusable in all my sequences." → this is a fragment → job: opener → `list_public_promptlibrary_ai_snippet_entries` for baseline toneId/model → prompt: one sentence, references `{aiResearch:731}` ("latest company news" research field), no greeting/CTA → `fallbackText`: "Saw your team's been growing lately." → `create_an_ai_snippet` succeeds → embed in messages in the app across all three sequences.

**Example 2 — Same request, legacy workspace**
Same setup → `create_an_ai_snippet` → 403 split-not-enabled → explain → `create_an_ai_variable` (CONTACT, text, prompt includes "if no news is found, write: 'Saw your team's been growing lately.'") → credit-confirmed sample on 8 contacts → iterate → used as `{newsOpener}` in copywriting-sequence output via launch-campaign.

**Example 3 — Company-level proof point**
User: "A one-liner matching our best case study to each company's industry." → company-attribute fragment → prompt maps `{industry}` to one of three case-study lines → on split workspaces a (contact-scoped) snippet referencing the contact's company attributes; on legacy a COMPANY variable → embedded mid-email in every nurture sequence.

---

## Troubleshooting

| Problem | Fix |
|---|---|
| 403 "AI variable split is not enabled for this workspace" | Not an error — legacy workspace. Phase 5 fallback (`create_an_ai_variable`) and inform the user |
| 409 Conflict | Name already exists — rename |
| Don't know a valid `toneId` | `list_ai_message_tones`; `model`/`outputLength` from a matching prompt-library entry |
| Create/update 400 "invalid prompt tokens" | Unknown or ambiguous token — read `details.issues`, pick from `details.validTokenSample`, or use the id form when a name is ambiguous |
| `{aiResearch:...}` not accepted | Split workspaces only; must exist in `list_ai_variables`; on legacy use `{fieldName}` |
| Snippet renders empty/broken in messages | Set `fallbackText`; on legacy, bake a safe default line into the prompt |
| Need to edit a snippet after creation | `get_an_ai_snippet` → edit the plain-text prompt → `update_an_ai_snippet` |
| Delete returns 409 | Snippet is still embedded in a message/template — detach first |
| New CRUD/tones tools not in your tool list | Your MCP session predates the rollout — reconnect to refresh the tool catalog |
| Fragment tries to do two jobs | Split it — one snippet per job keeps reuse and iteration clean |

SHA-256: d88894dc509f320f6e21f48a1ca02978af2d3a9e90057bf2350cad90d3f0d658