How to extend the system without breaking it.
- Spec changes BEFORE code changes. Every new agent/skill/mart is added to
docs/gtm-context.mdbefore any implementation. The spec is the source of truth; code follows. - No agent ships without an eval. Promptfoo cases for the new prompt go in
evals/cases/before the n8n workflow goes live. - No metric is defined twice. New metric? Add it to the canonical metric-definitions Sheet first; surface it in BI second.
- Every change is reversible. Feature-flag new agents via Unleash; never enable for 100% on day one.
- No DIN, no send. Any new campaign type respects the DIN approval gate. Period.
Where it lives: skills/{skill-name}/SKILL.md
Skill structure:
---
name: skill-name
description: One-sentence what-this-skill-does (used by Claude to decide when to load it)
version: 0.1.0
owner: pmm-name@mothi.com
status: draft | stable | stale
depends_on: [other-skill, another-skill] # optional, for composition
tested_with: claude-haiku-4.5 # which model this is calibrated for
---
# Skill Title
## When to use this skill
(Plain English: under what conditions does this skill get loaded by an agent?)
## Inputs expected
(What variables / context the agent provides when invoking)
## Outputs
(What format the skill returns: text, structured, decision, etc.)
## Body
(The actual skill instructions — markdown, examples, dos/don'ts)
## Examples
### Good
(Example input → ideal output)
### Bad
(Example input → undesirable output to avoid)Required steps:
- Write the skill markdown
- Add 5+ Promptfoo eval cases at
evals/cases/{skill-name}/ - Run
npx promptfoo eval— must pass ≥90% of baseline cases - Update
skills/README.mdindex - PR with eval results screenshot + 2 reviewers
Where it lives: agents/{agent-name}.json (n8n workflow export)
Pattern every agent must follow:
1. Trigger (cron / webhook)
2. Pre-launch DIN gate ← rule #8 (skip-detection)
3. Fetch context (SF + Postgres + Drive + LLM-extracted properties)
4. Load skill from Shared Drive
5. Claude API call
6. Pydantic schema validation on output ← rule #2
7. Frequency cap check ← rule #4
8. HITL approval (if rule #9 applies)
9. Writeback (SF + MoEngage + Slack + Sheets)
10. Audit log entry to Postgres `agent_decisions` ← rule #11
11. UTM tagging on every link ← rule #9
Required steps:
- Add the agent to
docs/gtm-context.md§3 with: trigger, what-it-does, MCPs used - Build the workflow in n8n
- Test on staging Postgres + sandbox SF
- Add Promptfoo evals for any new Claude prompts
- Export workflow JSON to
agents/{agent-name}.json - Update
agents/README.mdindex with status (scaffolded/live/deprecated) - Wrap with Unleash feature flag — start at 10% rollout
Where it lives: sql/marts/{mart_name}.sql
Convention:
stg_*views = staging (cleanup, type-cast, dedupe)mart_*views = metric-ready aggregations- All marts are materialized views with nightly refresh
Required steps:
- Add the mart to
docs/gtm-context.md§8.4 with: purpose, columns, source tables - If introducing a new metric, add it to the
gtm.metric-definitionsSheet FIRST - Write the SQL view
- Add a
REFRESH MATERIALIZED VIEW CONCURRENTLYto the nightly cron - Test query latency (<3s for AE-facing marts)
- Update
sql/README.mdindex
Three-surface rule (anti-sprawl):
| Audience | Surface | Format |
|---|---|---|
| AE / SDR / CSM | Google Sheets | Sheet template URL + Apps Script source in dashboards/sheets/ |
| PMM / Demand Gen / RevOps | Metabase | Exported question/dashboard JSON in dashboards/metabase/ |
| VP Sales / VP Marketing / Founders | AWS QuickSight | Analysis definition in dashboards/quicksight/ |
Required steps:
- Identify the audience — pick exactly one surface
- Verify every metric on the dashboard is in
gtm.metric-definitions(the canonical list) - Build the dashboard
- Export the template/JSON to the appropriate
dashboards/subfolder - Add a row to the relevant README index
- Schedule monthly review — any chart not viewed in 30 days gets archived
The spec is canonical. Changes require:
- PR with the diff
- Reviewer must check: doesn't break a reliability rule, doesn't double-define a metric, doesn't bypass DIN gate
- Bump version in CHANGELOG.md
- If structural change (new section / renumbered): update README.md links + OPERATOR-QUICKSTART.md cross-refs
- ❌ Adding a tool just because it's available. Every tool earns its slot. See spec §4.
- ❌ Adding a metric without defining it in the canonical Sheet first.
- ❌ Building an agent that bypasses DIN gate, HITL, or frequency caps.
- ❌ Adding to v1 anything that's already in v2/v3 deferred list. Argue for re-prioritization in ROADMAP.md instead.
- ❌ Renaming the repo or breaking the *-ops pattern without team alignment.
- Python: Ruff lint + format.
make lintmust pass. - SQL: lowercase keywords, snake_case names, comment intent at top of view.
- Markdown: tight tables, sentence-case headings, link don't dump.
- n8n: descriptive node names, credentials never inline (use env), every workflow has a description block.
- [ ] Spec updated (docs/gtm-context.md)
- [ ] Promptfoo evals pass ≥90% baseline
- [ ] DIN gate respected (if launches a campaign)
- [ ] Audit log writes happen
- [ ] Unleash flag in place
- [ ] HITL on writes (if applicable)
- [ ] Frequency cap respected (if outbound)
- [ ] UTM tagging present (if outbound)
- [ ] CHANGELOG.md updated
- [ ] README.md status table updated
- [ ] Tested on staging Postgres + SF sandbox