Skip to content

Embed realize-toolkit, align with Apr 2026 brand guardrails#2

Merged
yanush88 merged 23 commits into
taboola:mainfrom
amitl-levi:fix/embed-toolkit-and-brand-guardrails
Jun 18, 2026
Merged

Embed realize-toolkit, align with Apr 2026 brand guardrails#2
yanush88 merged 23 commits into
taboola:mainfrom
amitl-levi:fix/embed-toolkit-and-brand-guardrails

Conversation

@amitl-levi

@amitl-levi amitl-levi commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Embeds the realize-toolkit knowledge layer into the plugin:
    os/guardrails.md (consolidated system-prompt), knowledge/ (10 topic MDs +
    manifest.json), scripts/brand-check.sh (linter).
  • Applies the Apr 2026 PMM brand guardrails across all plugin content.
  • Aligns operational thresholds with the toolkit (toolkit treated as
    authoritative source).
  • Consolidates the os/ layer from 4 files (guardrails, tone,
    orchestration, routing) into 1 (guardrails) — Claude Code skill-routing
    handles intent matching natively.
  • Removes redundancy: duplicate attribution rules folded into the global
    guardrails layer, and backlog.md removed (content lives in
    docs/realize-best-practices-gap.md Part 3).

Why

  • Brand: PMM published an updated brand-guardrails doc (Apr 2026) with
    feature renames and tightened banned positioning. Plugin now follows it.
  • Knowledge alignment: plugin had thresholds taken from public Taboola
    help articles that contradicted the internal realize-toolkit best practices.
    The toolkit is now the authoritative source.
  • Architecture: skills already handle intent routing natively, so a
    separate routing.md was redundant. Single source of truth =
    os/guardrails.md.

Key changes

Area Before After
Feature naming Realize Pixel; Marketplace Audiences; Realize 1P
Taboola Pixel; Taboola First Party Audiences
UI naming Realize console Realize UI
Brand naming "Taboola Realize" "Realize" alone
Click threshold (item-level diagnosis) 500–1000 clicks per item **100+
clicks per item**
Spend threshold (CPA/CVR diagnosis) $50/day flat **daily spend ≥ 8× CPA
goal**
Learning phase 7–10 days (mixed elsewhere) **7–10 days, consistent
everywhere**
Ads per campaign 3–10 4–6 (never more than 10)
Creative variations 5–10 per URL **3 distinct titles + 3 unique images
per campaign**
Fixed Bid budget 5× CPA daily / 150× monthly per client requirements
(manual)

Test plan

  • bash scripts/brand-check.sh passes (0 FAIL, 1 known false-positive
    WARN)
  • Open the plugin in Claude Code; verify the realize-analyst agent reads
    os/guardrails.md at session start
  • Ask "Which bid strategy for a new conversion campaign?" — should consult
    knowledge/bidding.md
  • Ask "CPA is 3× goal" — should activate optimize-campaign skill with
    the new toolkit-aligned thresholds
  • Verify Taboola Realize, Realize console, Marketplace Audiences no
    longer appear in any output

Notes for review

  • backlog.md deleted — content (with more detail, including UI paths) is in
    docs/realize-best-practices-gap.md Part 3.
  • Three pre-existing TODO: markers remain in CLAUDE.md, CONTRIBUTING.md,
    SECURITY.md — not introduced by this PR.
  • scripts/brand-check.sh allows item_id as a public MCP parameter (it
    remains banned as a schema column in the realize-toolkit's separate linter
    copy).
  • 26 files changed (13 new, 13 modified), +2467 / -63 lines.

Amit comment:

Update (Jun 16 2026 — commit a0d6176)

Addresses all 13 review threads from @chrishall-taboola + bonus work for block-list wiring and guardrails v4 alignment.

MCP write-surface API fixes

skills/manage-campaigns/references/mcp-write-surface.md

  • Readback tool names → list_campaigns / list_items / get_item
  • spending_limit_model="DAILY""NONE" + daily_cap + daily_ad_delivery_model="STRICT"
  • conversion_rules shape → {"rules":[{"id":<int>}]}
  • target_roas removed; roasGoal documented as update-only / DCO-only / not MCP-wired
  • publisher_bid_modifier shape → {"values":[{"target":<name>,"cpc_modification":<multiplier>}]}
  • cpc_cap narrowed to MAX_CONVERSIONS only

SKILL ↔ knowledge contradictions

  • TARGET_CPA min: 5×/150× → 10× CPA/day
  • SMART (Enhanced CPC) split out with 5×/150× min
  • cpc bid-lever: SMART + FIXED (was FIXED only)
  • Bundling threshold: per-day, strategy-specific
  • Canonical bid-lever matrix in bidding.md: dropped Target ROAS row, fixed cpc_cap row, added editing-note guard

Codex + permissions safety

  • Write-gate ported into os/guardrails.md (covers Codex without skills)
  • .claude/settings.json write-tool allowlist removed; per-user opt-in template at .claude/settings.local.json.example
  • INSTALL.md adds Codex install section + opt-in permissions doc
  • CONTRIBUTING.md checklist rewritten to match enabled writes

Manifest + Campaign Groups + closed-gap restructure

  • manifest.json creative entry trimmed (phantom Wilson / 1P/3P tags removed)
  • Pointer added in creative.md to targeting.md for the Display item shape
  • Campaign Groups: MCP-status callout (UI-only today)
  • docs/realize-best-practices-gap.md: closed-gap rows moved out of "Future ability" tables

Block-list wiring (from Chris's Jun 15 upstream work)

Publisher block-list edits routed through update_campaign(publisher_targeting=...):

  • agent triage updated (was UI-only)
  • new manage-campaigns recipe: search_publishers → get_campaign → merge → top-N guard → preview → write → verify
  • new W4b test scenario with full-replace gotcha + cleanup
  • gap-doc closed-gap notes + CHANGELOG entry

Guardrails v4 brand spec alignment

  • Backstage added to Banned brand variations
  • Banned company-vs-platform framing section removed
  • Internal codename → external name mapping section removed; runtime echo guard added
  • Banned competitor terminology → Banned industry terms

Verification: scripts/brand-check.sh passes (0 FAIL, 5 WARN — all pre-existing false-positives). 19 files changed, 342 insertions, 108 deletions.

Amit Levi and others added 17 commits April 30, 2026 00:25
- Embed toolkit knowledge layer: os/guardrails.md (consolidated system-prompt),
  knowledge/ (10 topic MDs + manifest.json), scripts/brand-check.sh.
- Apply PMM brand guardrails: feature renames (Realize Pixel → Taboola Pixel;
  Marketplace Audiences → Taboola First Party Audiences; first-party segments →
  Taboola First Party Audiences), UI naming (Realize console → Realize UI),
  brand naming (Taboola Realize → Realize).
- Align operational thresholds with toolkit: 100+ clicks per item (was 500–1000),
  daily spend >= 8x CPA goal (was \$50/day flat), 7–10 day learning phase,
  4–6 ads per campaign (was 3–10), 3 titles + 3 images per campaign
  (was 5–10 variations), Fixed Bid per client requirements.
- Enforce attribution + timeframe rule globally via os/guardrails.md (single
  source of truth) — every CPA/CVR/Leads/ROAS figure must specify both
  attribution basis (CT/VT/Total) and timeframe.
- Consolidate os/ from 4 files (guardrails, tone, orchestration, routing) to 1
  (guardrails) — Claude Code skill-routing handles intent matching natively.
- Adapt brand-check.sh: allow item_id as public MCP parameter; tighten
  console/dashboard regex; drop bare \bhip\b alternative.
- Remove redundant backlog.md — content now lives in
  docs/realize-best-practices-gap.md Part 3 as single source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… to private layer

- Remove competitor comparison bullet ("No bias towards owned and operated
  inventory (Taboola does not own inventory like Meta and Google)") — names
  competitors, can't appear publicly.
- Move four sections from os/guardrails.md (public) to guardrails-private.md
  (internal layer), out of the plugin repo:
  - Preferred messaging direction (full section)
  - Framing rules (Tone subsection)
  - Emotional tone matching (Tone subsection)
  - What the assistant is not (Tone subsection)

These are internal directional rules — the assistant should follow them
silently, but they must not be visible in the public guardrails file shipped
with the plugin (Maayan flagged them as marketing-playbook content that
shouldn't be exposed externally).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three renames in upstream realize-mcp (../realize-mcp):
- get_all_campaigns  → list_campaigns
- get_campaign_items → list_items
- get_campaign_item  → get_item

Pure token swaps across 8 files. No new tools added, no skill files
created, no other content touched. Scope intentionally minimal —
follow-up needed for stale "11 tools" claim in
docs/realize-best-practices-gap.md and the write-tool framing in
agents/realize-analyst.md Tool Reference.

Verified: zero old tool-name hits via grep; JSON + YAML frontmatter
parse cleanly; scripts/brand-check.sh passes (0 FAIL, 1 pre-existing
WARN); 13/16 manual test scenarios PASS against live MCP, 3 require a
Claude Code session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add new skills/discovery/SKILL.md wrapping the 9 read-only "look it up"
tools the MCP exposes (search_geos, search_techno, search_audiences,
search_lookalike_audiences, search_contextual_segments,
search_publishers, search_conversion_rules, list_time_zones,
list_cta_types). Read-only - designed for inventory queries
("what audiences are configured?") and forward name -> opaque-code
resolution before campaign work in the Realize UI.

Agent Tool Reference grew from 9 to 18 read tools. Grouped:
Accounts / Campaigns / Items / Discovery / Reports / Auth.
Added discovery routing example + Core Responsibility #2 line.
Tool-existence-boundary updated to acknowledge upstream's 4 write tools
exist but are intentionally not wired in this revision.

Updated CLAUDE.md architecture diagram (added discovery row, 18 reads,
4 upstream writes noted), docs/realize-best-practices-gap.md capability
baseline (folded Discovery), README skills table (new discovery row),
tests/test-scenarios.md (scenarios 12-16 verified live against MCP).

Version 0.1.0 -> 0.2.0. CHANGELOG 0.2.0 section consolidates prior
[Unreleased] entries (toolkit embed, brand alignment, threshold
updates, optimize-campaign skill) with this Phase 1 wiring.

Content for the discovery skill + scenarios 12-16 reused verbatim from
upstream c8e4a5b ("Align skills, agent, and tests with latest
realize-mcp tool set") since no toolkit/brand conflicts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add new skills/discovery/SKILL.md wrapping the 9 read-only "look it up"
tools the MCP exposes (search_geos, search_techno, search_audiences,
search_lookalike_audiences, search_contextual_segments,
search_publishers, search_conversion_rules, list_time_zones,
list_cta_types). Read-only - designed for inventory queries
("what audiences are configured?") and forward name -> opaque-code
resolution before campaign work in the Realize UI.

Agent Tool Reference grew from 9 to 18 read tools. Grouped:
Accounts / Campaigns / Items / Discovery / Reports / Auth.
Added discovery routing example + Core Responsibility #2 line.
Tool-existence-boundary updated to acknowledge upstream's 4 write tools
exist but are intentionally not wired in this revision.

Updated CLAUDE.md architecture diagram (added discovery row, 18 reads,
4 upstream writes noted), docs/realize-best-practices-gap.md capability
baseline (folded Discovery), README skills table (new discovery row),
tests/test-scenarios.md (scenarios 12-16 verified live against MCP).

Version 0.1.0 -> 0.2.0. CHANGELOG 0.2.0 section consolidates prior
[Unreleased] entries (toolkit embed, brand alignment, threshold
updates, optimize-campaign skill) with this Phase 1 wiring.

Content for the discovery skill + scenarios 12-16 reused verbatim from
upstream c8e4a5b ("Align skills, agent, and tests with latest
realize-mcp tool set") since no toolkit/brand conflicts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nage-campaigns restructure

Replays the realize-buyer-agent port on top of fork's 3 prior commits
(MCP-tool-ref rename + discovery/campaigns/manage-campaigns skill split).
The original single-commit port (preserved on backup/port-2026-06-08
and as inputs/0001-Port-realize-buyer-agent-Steps-1-7-...patch) targeted
the pre-restructure layout; this commit re-applies it cleanly on the new
architecture without losing fork's read/write split.

Knowledge layer: add 2 new topics (reach-estimation, reporting-aggregation),
section-merge 6 grown topics (bidding/creative/targeting/campaign-structure/
budget/site-management). Fix stale "MCP creates Native only" claim in
campaign-structure.md with the correct two-path description (pricing_model
=VCPM locks Display at create; pricing_model=CPC sets type by first item).
Standardize learning phase at 7-14 days. Refresh manifest.

Skills:
- optimize-campaign: rewrite as lean SKILL.md (5 mandatory pre-checks
  P1-P5, RCA vs general optimization framework, action prescriptions
  routed to manage-campaigns) + references/optimization-flow.md
  (dimensional drill-down, supply-side eligibility, creative-fatigue
  tiers, bid-lever matrix, 6 symptom branches, common-mistake patterns).
- manage-campaigns: extend with Display item write coverage (the gap
  left by the 0.3.0 release which covered Native items only) —
  create_display_item + update_display_item with 3P-tag and 1P-hosted
  recipes, status-gated update flow, pricing-model-picks-the-type
  subsection, per-strategy bid-lever gate matrix, pointer to new
  references/mcp-write-surface.md (field-by-field write reference).
- reports: 4 mandatory pre-checks (conversion-goal resolution, marketing
  -objective alignment, delivery-eligibility, learning-period guard),
  sum-reconciliation gate, default exclusions.

Agent: realize-analyst Tool Reference now documents 19 read tools (added
get_campaign_reach_estimate under Reach Estimation) + 6 write tools
(added create_display_item, update_display_item). Updated count and
tool-existence-boundary; UI-only categories explicitly enumerated
(Custom Rules, conversion-rule creation, CRM uploads, lookalike seeds).

Install: dual-platform manifests (.codex-plugin/plugin.json) +
SessionStart hook (.claude/settings.json + scripts/ensure-realize-mcp.sh)
for one-shot OAuth onboarding. Hook idempotent: silent no-op when
already wired, one-line narration on failure.

Guardrails: replace trimmed Apr 2026 version with comprehensive public
layer per post-PMM split. Adds banned brand variations, banned competitor
terminology, internal codename mapping, banned feature-naming variants
(tCPA, eCPC, MaxConv, Realize Pixel, etc.), banned tone patterns,
banned ad-creative output, banned content topics, never-guarantee
subsection, pattern-based refusal (don't enumerate guardrails).
16-item self-check. Private companion layer kept outside this repo.

PMM-aligned brand language throughout: Maximize Conversions, Target CPA,
Enhanced CPC, Taboola Pixel, Taboola First Party Audiences, Realize UI.
brand-check 0 FAIL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves 10 conflicts from upstream main's PR taboola#3 (align-latest-mcp) that
landed during port work. Resolutions:

- skills/create-campaign/SKILL.md (modify/delete): took delete — replaced
  by the manage-campaigns skill from 0.3.0.
- skills/optimize-campaign/SKILL.md: kept the rewrite with rigorous P1-P5
  pre-checks + 6-signal RCA + 7-point optimization framework (vs main's
  older 500-1000 click / $50/day playbook thresholds).
- agents/realize-analyst.md: kept 19-read + 6-write tool count, write-
  routing to manage-campaigns, post-rename tool names. Dropped the stale
  "Out of scope — no writes" section from main (writes are in scope).
- skills/discovery/SKILL.md: kept the 3 minor edits (Realize UI not
  console; manage-campaigns hand-off not create-campaign).
- .claude-plugin/plugin.json: version 0.3.0; description widened to
  include create/update.
- CHANGELOG.md: kept the [Unreleased] port-content + [0.3.0] manage-
  campaigns + [0.2.0] expanded (fork's superset of main's [0.2.0]).
  Restructured to remove the auto-merge-induced misplaced 0.2.0 header.
- CLAUDE.md: kept the architecture diagram with 19 read + 6 write tools
  routed through manage-campaigns (vs main's 18-read-only diagram).
- README.md: kept the writes-in-scope blurb + manage-campaigns skill row;
  merged in main's discovery-routing examples section.
- docs/realize-best-practices-gap.md: kept manage-campaigns mappings;
  merged in main's subgrouped Discovery table. Added reach-estimation +
  writes rows.
- tests/test-scenarios-read.md (rename conflict): took the rename split
  (test-scenarios → test-scenarios-read + -write per 0.3.0).

brand-check 0 FAIL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Claude Code auto-mode classifier flags every Realize write tool because the opaque account_id (e.g. pumikademoaccount) returned by search_accounts doesn't string-match the numeric ID (e.g. 1065940) the user typed — even though both are equivalent representations of the same account, and resolving via search_accounts is the plugin's contracted ID-routing pattern. Object-typed targeting params (country_targeting and siblings) compound the issue, rendering as [object Object] in the classifier's inspection view.

Add the 6 Realize write tools to .claude/settings.json's permissions.allow. The manage-campaigns skill's WRITE TARGET preview-then-confirm gate remains the structural safety — the classifier was redundant for the writes routed through that gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-brand-guardrails

# Conflicts:
#	CHANGELOG.md
#	agents/realize-analyst.md
#	docs/realize-best-practices-gap.md
Eval feedback uncovered two HG-5 (write-gate bypass) patterns:

- Q79: user pre-authorized 'no need to ask before each one' -> plugin
  applied 5 writes without per-write confirmation.
- Q95: user asked for '3 ad variations' -> plugin expanded scope to
  30 items across 10 campaigns without confirming the fan-out.

Changes:
- skills/manage-campaigns/SKILL.md: new 'Scope confirmation' section
  refusing skip-confirm framings + requiring scope confirmation for
  ambiguous-target requests. Added 2 gotchas reinforcing these rules.
- agents/realize-analyst.md: clarified that the per-write gate is
  non-bypassable even when the user pre-authorizes.
- tests/test-scenarios-write.md: added W8 (skip-confirm refusal) and
  W9 (ambiguous-target scope confirmation) anchored to eval Q79, Q95.

Refs: realize-plugin-fix-list.md FIX-S1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eval feedback found pervasive leakage of internal implementation details
into user-facing answers:
- Raw enums / payload syntax: EMPTY_DISPLAY, CVR_LEARNING_LIMITED,
  MAX_CONVERSIONS, conversion_rules: [], country_targeting: {...},
  INCLUDE [PHON], PENDING_APPROVAL (Q1, Q11, Q13, Q14, Q16, Q34, Q41,
  Q65, Q70, Q86 in the eval).
- Internal Taboola employee emails surfaced from change logs:
  gal.l@taboola.com (Q34), nandishwar.y@taboola.com (Q70),
  mai-ly.n@taboola.com (Q100).
- Vertica SQL queries shown to end users (Q35, Q83).
- MCP tool / skill name leakage: 'manage-campaigns skill',
  search_accounts, mcp__plugin_realize-ads-api__* (Q24, Q53).
- Lecturing-tone slips ('the wrong frame', 'silent failure mode',
  'silent diagnostic-quality killer' — Q41) and unexpanded RCA
  acronym + internal Signal 1/2 framework labels (Q41).

Changes:
- os/guardrails.md:
  - New 'Internal field names and enum values' subsection with
    payload-to-plain-English translation table.
  - New 'Internal tools, skills, and infrastructure' subsection
    banning MCP/skill names, other-MCP references, repo paths,
    data-warehouse references, and employee emails / names.
  - Expanded 'Banned tone patterns' with lecturing/wrong-frame,
    internal-process callouts, unexpanded acronyms, signal-framework
    labels, and 'what NOT to do'-led answers.
  - Self-check expanded with 4 new items covering all of the above.
- scripts/brand-check.sh:
  - FAIL: Vertica references; trc.* schema; first.last@taboola.com
    employee emails (support@ / billing@ aliases not flagged).
  - WARN: lecturing-tone phrasing; mandatory-pre-checks / silent-*;
    unexpanded RCA; Signal N framework; raw enum values in
    user-facing text.
  - All new patterns scoped to PUBLIC files (skip /os).

Refs: realize-plugin-fix-list.md FIX-T1, T2, T5, T6, B6, B9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nt-default)

Eval feedback found substance errors and mischaracterizations in the
plugin's knowledge layer:

- Q65: plugin recommended switching to Target CPA at $16 when actual
  campaign CPA was $26 — too harsh, scale would collapse. User flagged
  starting closer to actual then dropping gradually as best practice.
- Q13, Q16: plugin misunderstood predictive audience campaigns. Q16
  also confused Native (with STATIC_IMAGE/PERFORMANCE_VIDEO items)
  with Display (banner ads), claiming the campaign had "no Display
  creatives" when it was a Native campaign in the first place.
- Q41, Q13: plugin described account-default conversion goal as a
  'silent failure mode' / 'silent diagnostic-quality killer'. User
  flagged: account-default routing is a valid configuration when the
  include-in-total events are sensible — not a failure mode.
- Q41 broader: plugin surfaced internal framework labels ('mandatory
  pre-checks', 'RCA' unexpanded, 'Signal 1/2 chain', 'silent failure
  mode') in user-facing answers. Per B2 guardrails these belong in
  the model's internal scaffolding, not the user output.

Changes:
- skills/optimize-campaign/SKILL.md:
  - Added 'Internal vs user-facing terminology' callout at the top
    with a translation table. The framework labels (P1-P5, RCA,
    Signal 1-6, residual signal, restructure shock) stay for model
    reasoning; the callout instructs the model not to surface them
    to users.
  - Reworded P2 (campaign-level conversion goal check) to describe
    account-default routing as a valid configuration, not a 'silent
    failure mode'. Only flag if include-in-total events don't match
    user intent.
- knowledge/bidding.md:
  - New 'Target CPA calibration' subsection: set Target CPA at or
    above actual current CPA, then drop gradually in ~10-15% steps
    after 5-7 day stabilization. Explicit anti-pattern: don't start
    Target CPA at the user's aspirational goal when actual is far
    above.
- knowledge/targeting.md:
  - New 'Predictive audience campaigns — signal dependencies beyond
    the seed event' section: parallel campaigns on the same account
    feed the predictive model; pausing them starves it. Diagnostic
    table for stalled-predictive scenarios.
  - New 'Native vs Display creative type' section: campaigns are
    either Native or Display, locked at creation. Native with
    STATIC_IMAGE/PERFORMANCE_VIDEO items is NOT 'Display with no
    Display ads'. EMPTY_DISPLAY learning state on a Native-locked
    campaign is expected, not a problem. For Display reach, launch
    a separate Display campaign.

Refs: realize-plugin-fix-list.md FIX-B1, B2, B3, T4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emature commit)

Eval feedback found behavioral patterns the plugin needs upstream rules
for, before any skill is invoked:

- Q18, Q97: plugin over-engaged on questions that should have been
  one-sentence refusals. Q97 (creative copywriting OOS) produced 5
  angles of rewrites + critique. Q18 (pixel-firing OOS) ran a 5-minute
  deep-dive on 180 conversion rules.
- Q40, Q76, Q50: plugin attempted MCP calls in UI-only domains
  (EVEN traffic toggle, block-list edit, claimed no MCP campaign-
  creation tool exists), then surfaced 404s rather than refusing
  upfront with the UI redirect.
- Q62: plugin offered "platform-agnostic principles" for an Outbrain
  optimization request, when the right answer was to refuse the
  cross-platform ask entirely.
- Q69: plugin claimed item had no spend when it actually had spent —
  failed to validate the user's premise before reasoning from it.
- Q61, Q9, Q45: plugin used premature commitment language ("I'll
  create this campaign...") before the action had completed.
- Q41 (broader): plugin shipped a wall-of-text answer with sources
  footer enumerating MCP tool calls; multiple repeated config tables;
  no length discipline.

Changes:
- os/guardrails.md:
  - New 'Length target — default ≤ 250 words for routine answers'
    subsection with exemptions for write previews, multi-part
    diagnostics, structured tables; explicit 'stop and offer to
    continue' pattern if exceeding the budget legitimately.
  - New 'Refusals are short' subsection: one sentence + redirect;
    no enumeration of related capabilities, no internal-architecture
    walk-through, no hedging.
  - New 'Don't list sources / tool calls at the end of the answer'
    rule: scope footer (date/account/filters/attribution) is the
    only sourcing; never enumerate MCP tool names.
  - Self-check expanded with 3 new items.
- agents/realize-analyst.md:
  - New 'Upfront triage — capability check before any tool calls'
    section with classification table (UI-only / OOS outside Realize
    / cross-platform / malicious / in-scope-but-ambiguous-scope).
    Names Q18 + Q97 as the two over-engagement traps.
  - New 'Validate user claims before reasoning from them' section
    anchored to Q69.
  - New 'Don't pre-commit to a future action's success' section
    anchored to Q61; contrasts intent-announcing language (good)
    vs result-claiming language (premature).

Refs: realize-plugin-fix-list.md FIX-T3, B4, B5, B8, R1 (carried),
R2, R3, R5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eval feedback found three friction points and one alias-invention:

- Q30 user feedback: "Could also be helpful for the plugin to provide
  the user with a direct URL to go to in the UI." Today the plugin
  surfaces menu paths only ("Realize UI -> Campaigns -> overflow ->
  Delete"). A one-click deeplink reduces friction.
- Q73: plugin invented 'billing@taboola.com' alias when directing the
  user for a refund. That alias may not exist for all users. User
  feedback: "Better to say AM or support@taboola.com, better not to
  use other alias such as 'billing@taboola.com' or any other thing."
- Q65: plugin recommended "look at this again in a week to let delayed
  conversions catch up" — but the event in question was months in the
  past. The plugin reasoned from the user-stated date without checking
  against today's actual date.

Changes:
- os/guardrails.md:
  - New 'Support / contact aliases — only use approved destinations'
    subsection. Only the user's AM, support@taboola.com, and the
    in-platform UI are approved. Function-specific aliases
    (billing@, finance@, crt@, policy@) are explicitly banned.
  - New 'Date awareness — anchor recommendations to today's date'
    subsection. Computes days_elapsed = today - event_date and gates
    'wait and re-evaluate' language: > 14 days = data has settled,
    > 30 days = data is final.
- knowledge/realize-ui-deeplinks.md (new file):
  - Deeplink templates for Realize-UI-only actions (campaign edit,
    block list, Conversions, Pixel status, Audiences, Custom Rules,
    GenAI Ad Maker, Billing). Each with menu-path fallback.
  - Rules for when to surface a deeplink vs. fall back to menu-path
    only (route-protected pages, filter state, beta features).
- knowledge/manifest.json:
  - Registered the new realize-ui-deeplinks topic with tags so the
    agent picks it up when needed.
- skills/manage-campaigns/SKILL.md:
  - UI-fallback section preamble updated to instruct the skill to
    emit a deeplink alongside the menu path, citing
    realize-ui-deeplinks.md.

Refs: realize-plugin-fix-list.md FIX-B7, B10, R4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The realize-ui-deeplinks.md file shipped in B5 used a base URL of
ads.taboola.com with path-based account scoping
(/accounts/<account_id>/...). The real Realize UI lives at
ads.realizeperformance.com and uses query-string scoping
(?accountId=<numeric>). Example of a real Custom Rules URL:

  https://ads.realizeperformance.com/performance-rules
    ?locale=en
    &accountId=1009840
    &reportId=performance-rules
    ...

None of the deeplink templates I shipped would have resolved. Worse,
the whole 'click-once deeplink' scheme may not generalize across the
Realize UI — the path / query structure varies by page (performance-
rules vs other report views) and some flows likely don't have stable
deeplinks at all.

This commit:
- Deletes knowledge/realize-ui-deeplinks.md.
- Removes the manifest.json entry that registered the topic.
- Reverts the manage-campaigns/SKILL.md UI-fallback preamble to plain
  menu-path-only redirects, with an explicit instruction NOT to
  fabricate deeplinks.

If the team wants verified deeplinks later, capture the real URL for
each UI-only flow first (open the page in the Realize UI, copy the
URL, document the param schema), then re-introduce the file with
only verified entries.

Refs: realize-plugin-fix-list.md FIX-B10 (reverted; needs UI walk).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revert B5 deeplinks: URLs were fabricated, structure unverified
@chrishall-taboola

Copy link
Copy Markdown
Collaborator

mcp-write-surface.md:195-198 — nonexistent readback tools. Table lists get_all_campaigns, get_campaign_items, get_campaign_item — none exist in the MCP. Correct names: list_campaigns, list_items, get_item.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

mcp-write-surface.md:210 — invalid spending_limit_model="DAILY". Enum is NONE|MONTHLY|ENTIRE (API + MCP). For a daily budget use spending_limit_model="NONE" + daily_cap= + daily_ad_delivery_model="STRICT".

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

mcp-write-surface.md:214,240 — wrong conversion_rules shape. Sends [{"rule_id":""}]; API/MCP expect {"rules":[{"id":}]} (key id, nested under rules, integer not string).

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

mcp-write-surface.md:239 (block 232-243) — target_roas at create is impossible. Field name is wrong (API = roasGoal) and ROAS can't be set on create_campaign for any account; it's update-only, DCO-accounts-only, and MCP doesn't wire it at all. Remove the field/example. (Plugin fix now; separate upstream MCP issue if ROAS support is ever wanted.)

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

mcp-write-surface.md:252-254 — wrong publisher_bid_modifier shape. Sends [{"publisher_id":,"modifier_pct":+20}]; API/MCP expect {"values":[{"target":"<publisher_name>","cpc_modification":1.25}]} (publisher name not id; multiplier not percent).

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

CONTRIBUTING.md:37 — checklist - [ ] No write paths added to MCP tools contradicts the now-enabled writes. Reword to the real policy.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

Codex build (.codex-plugin/plugin.json) ships the full .mcp.json — including the 6 write tools — but none of the skills, so its claim that writes are "not available on Codex" is false and writes run without the manage-campaigns preview-confirm gate. Either port the write-gate into os/guardrails.md (or scope Codex to read-only) and document/test it, or drop the manifest until Codex is a real target.

Note the realize-mcp runs on different port for codex - so mcp install instructions should change if codex is supported. Also conflicts with name of repo.,

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

Fix contradictions between skill and knowledgebase:

  • skills/manage-campaigns/SKILL.md:156 — TARGET_CPA daily budget minimum contradicts the knowledge base. SKILL says TARGET_CPA = 5× CPA/day + 150× CPA/month;
    knowledge/bidding.md:18-19,123 and knowledge/budget.md say Target CPA = 10× CPA/day (same as Maximize Conversions). The 5×/150× figure is Enhanced CPC's minimum,
    mis-assigned to this row. Fix: set TARGET_CPA to 10× CPA/day ($50 min if CPA < $5) to match knowledge/bidding.md.
  • skills/manage-campaigns/SKILL.md:158 — SMART (Enhanced CPC) shown as having no minimum, contradicts the knowledge base. SKILL groups SMART, MAX_VALUE as "don't block on a
    hard minimum." But os/guardrails.md:126 maps SMART → Enhanced CPC, and knowledge/bidding.md:124 + knowledge/budget.md give Enhanced CPC a published 5× CPA/day + 150×
    CPA/month minimum. Fix: split SMART out and give it the 5× daily / 150× monthly minimum; leave MAX_VALUE as client-defined.
  • skills/manage-campaigns/SKILL.md:153 — cpc bid-lever gate excludes Enhanced CPC, contradicts the knowledge base. SKILL gates the cpc scalar bid to bid_strategy=FIXED
    only; knowledge/bidding.md:62-66 (and optimization-flow.md:224) put campaign-level CPC bids under both Enhanced CPC (SMART) and Fixed Bid (FIXED). Fix: allow cpc on SMART
    and FIXED.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

Committed permissions.allow pre-approves all 6 raw write tools → no permission prompt. The preview-then-confirm gate is otherwise just SKILL.md prose; this allowlist
deletes the one harness-level check, and ships it to every clone.

  • Fix: move to settings.local.json (per-user opt-in), or drop pre-approval and let the prompt fire. Echoes the write-posture point from earlier — this is its concrete
    config root.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

knowledge/manifest.json creative entry — phantom content ✓ confirmed

Manifest advertises tags + description for creative.md that don't exist in the file. Verified grep counts in creative.md: Wilson=0, validity tier=0, 3P=0, 1P=0,
create_display_item=0, ad_tag=0, asset_url=0. Description promises "Wilson score lower bound" + "1P-hosted + 3P JS-tag paths via MCP" — none present.

  • Fix: either trim the manifest to match creative.md, or add the missing Display-recipe + Wilson-ranking content.

See creative.md for the full Display recipe." No Display recipe in creative.md (no 3P/1P/ad_tag/asset_url). The real Display item shape lives in targeting.md:206-209.

  • Fix: repoint to the correct file, or add the recipe to creative.md

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

knowledge/campaign-structure.md:64 vs 66 — inconsistent bundling thresholds. CONFIRMED (low).

Both lines answer the same question — "when is budget too small to split platforms, so bundle instead?" — but with different, unreconciled units:

  • Line 64: bundle all platforms when "budget is so small that splitting would kill algo learning (under ~$5K total)."
  • Line 66: bundle when budget/campaign falls below learning threshold (< $1k/day for MAX_CONVERSIONS, < $500/day for FIXED).

The problem: one is a total figure, the other per-day + strategy-specific, with no relationship stated. They can disagree — e.g., a $3k/day campaign over a 1-day flight is
$3K total (< $5K → "bundle" by line 64) but $3k/day (> $1k/day → "split" by line 66). A reader can't tell which test governs.

  • Severity: low — guidance prose, not a hard gate; both point the same direction in the common case.
  • Fix: pick one basis (per-day is more actionable and matches the bid-strategy framing), or explicitly relate the $5K-total figure to the per-day thresholds (e.g., "≈ the
    per-day minimum × typical flight length").

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

skills/manage-campaigns/SKILL.md:52 says cpc_cap "valid on all strategies (… MAX_CONVERSIONS / TARGET_CPA / MAX_VALUE)." API + MCP both say MAX_CONVERSIONS only (CpcCapTransformer.java:39; registry.py:500). Sending cpc_cap on TARGET_CPA/MAX_VALUE → API 400. Note: knowledge/bidding.md is silent on this, so the SKILL is the lone offender. Fix: "valid only on MAX_CONVERSIONS.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

Campaign Groups are heavily mentioned in plugin - but does not yet have MCP support. Maybe remove it until we have MCP support?

MCP write-surface API fixes (skills/manage-campaigns/references/mcp-write-surface.md):
- Readback tool names: get_all_campaigns/get_campaign_items/get_campaign_item
  -> list_campaigns/list_items/get_item
- spending_limit_model="DAILY" (invalid enum) -> "NONE" + daily_cap +
  daily_ad_delivery_model="STRICT"
- conversion_rules shape: [{"rule_id":"..."}] -> {"rules":[{"id":<int>}]}
- target_roas at create removed; roasGoal documented as update-only,
  DCO-only, not MCP-wired
- publisher_bid_modifier: [{publisher_id,modifier_pct}] ->
  {"values":[{"target":<name>,"cpc_modification":<multiplier>}]}
- cpc_cap narrowed to MAX_CONVERSIONS only across scalar table, bid-lever
  matrix, refuse-and-reframe rules, failure-mode table

SKILL <-> knowledge consistency (skills/manage-campaigns/SKILL.md +
knowledge/bidding.md + knowledge/campaign-structure.md +
skills/optimize-campaign/references/optimization-flow.md):
- TARGET_CPA minimum 5x/150x -> 10x CPA/day (matches bidding.md)
- SMART (Enhanced CPC) split out with 5x/150x minimum
- cpc bid-lever gate: FIXED-only -> SMART + FIXED
- Canonical bid-lever matrix in bidding.md: dropped Target ROAS row,
  fixed cpc_cap row (MAX_CONVERSIONS only), Target CPA MAX_VALUE cell
  cleaned, editing-note guard added for downstream copies
- Bundling threshold: dropped "~$5K total"; kept per-day strategy rule
- optimization-flow.md target_roas reference updated

Codex + permissions safety:
- Ported write-tool gate from manage-campaigns SKILL to os/guardrails.md
  so it covers Codex (which doesn't load skills): 6 write tools listed,
  WRITE TARGET header mandatory, explicit Yes/No/Edit confirm flow,
  refuse confirmation-skip framings, scope-fan-out confirmation, UI
  fallback for non-MCP actions
- Removed write-tool allowlist from committed .claude/settings.json;
  added .claude/settings.local.json.example template for per-user opt-in;
  .gitignore'd settings.local.json
- INSTALL.md: optional opt-in section + experimental Codex install
  section with manifest-name + MCP URL/port caveats
- .codex-plugin/plugin.json description updated
- CONTRIBUTING.md PR checklist rewritten: stale "No write paths added"
  -> three new lines covering skill routing, write-gate inheritance,
  per-user opt-in pattern

Manifest + Campaign Groups + closed-gap restructure:
- knowledge/manifest.json: trimmed creative entry (removed phantom
  Wilson / validity-tier / 3P-JS-tag / 1P-hosted / create_display_item
  tags that don't exist in creative.md)
- knowledge/creative.md: added breadcrumb pointer to targeting.md for
  the Display item MCP payload shape
- knowledge/campaign-structure.md: added MCP-support-status callout at
  top of Campaign Groups section (assignment is UI-only today)
- docs/realize-best-practices-gap.md: closed-gap entries moved out of
  "Future ability" tables into "Closed gaps in this section:" subsections

Wire publisher block-list edits (responds to Chris's Jun 15 upstream
work "Add structured support: Block List via MCP"):
- agents/realize-analyst.md: removed "block-list edits" from UI-only
  triage; new explicit routing row; block-list note added under
  update_campaign Tool Reference entry
- skills/manage-campaigns/SKILL.md: new "Publisher block-list edits"
  recipe (search_publishers -> get_campaign -> merge -> top-N guard ->
  side-by-side preview -> confirm -> write -> verify)
- skills/manage-campaigns/references/mcp-write-surface.md: pre-existing
  "Block a publisher mid-flight" recipe shrunk to payload-shape sketch
  + cross-ref to SKILL.md (single source of truth, with dedup)
- docs/realize-best-practices-gap.md: update_blocklist + state-inspection
  rows moved to closed-gaps notes
- tests/test-scenarios-write.md: new W4b scenario covering the full
  flow + full-replace gotcha + cleanup
- CHANGELOG.md: Added entry under [Unreleased]

Guardrails v4 brand spec alignment:
- Added Backstage to Banned brand variations (defensive listing; content
  sweep across knowledge/skills/agents was clean)
- Removed Banned company-vs-platform framing section
- Removed Internal codename -> external name mapping section; runtime
  echo guard added to Internal field names section translating
  Backstage/blindspot/Syndicator ID/Auction report -> external terms
  if a user mentions them in their prompt
- Renamed Banned competitor terminology -> Banned industry terms
- scripts/brand-check.sh scanner labels renamed for consistency
- Self-check brand-name parenthetical expanded to mention Backstage

Verification: scripts/brand-check.sh passes (0 FAIL, 5 WARN — all are
pre-existing false-positives, none introduced here). Cross-file grep
sweeps confirm zero remaining instances of fabricated tool names, wrong
enum values, or stale section references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@amitl-levi

Copy link
Copy Markdown
Contributor Author

@chrishall-taboola — all 13 review items addressed in commit a0d6176. Walking through them in your order:

1. mcp-write-surface.md:195-198 — nonexistent readback tools

Renamed in the "Readback (after writes)" table: now list_campaigns, list_items, get_item. Cross-checked against the runtime MCP tool list.

2. mcp-write-surface.md:210 — invalid spending_limit_model="DAILY"

Both MaxConv and Target CPA payload examples now use spending_limit_model="NONE" + daily_cap + daily_ad_delivery_model="STRICT". Scalar-field table + the update_campaign intent table also updated to the NONE | MONTHLY | ENTIRE enum.

3. mcp-write-surface.md:214,240 — wrong conversion_rules shape

Both examples now send conversion_rules={"rules":[{"id":<int>}]}. Scalar-field row rewritten with explicit nesting / key / type notes.

4. mcp-write-surface.md:239target_roas at create is impossible

"Maximize Value with target ROAS" example removed; replaced with a plain Max Value example + a note: roasGoal is update-only, DCO accounts only, and not currently exposed by the MCP. Same note propagated to the scalar table (roasGoal row labeled NOT writable via MCP), knowledge/bidding.md, and optimize-campaign/references/optimization-flow.md. The canonical bid-lever matrix in bidding.md had its Target ROAS row dropped entirely.

5. mcp-write-surface.md:252-254 — wrong publisher_bid_modifier shape

Now {"values":[{"target":"<publisher_name>","cpc_modification":1.20}, ...]} with inline comments calling out "multiplier, not delta" and "publisher name, not ID". Scalar-field row also rewritten.

6. CONTRIBUTING.md:37 — "No write paths added" line

Replaced with three checklist items reflecting the real policy: any new write tool routes through manage-campaigns, the write-gate in os/guardrails.md covers every runtime, and pre-approval stays out of committed settings.json (per-user opt-in via the gitignored settings.local.json template).

7. Codex build — full .mcp.json without the gate; install instructions

Ported the write-gate into os/guardrails.md so Codex inherits it without needing skills. New Write tool gate section lists the 6 write tools, mandates the ▶ WRITE TARGET header, defines explicit Yes/No/Edit confirm, refuses confirmation-skip framings, requires scope-fan-out confirmation, and routes UI-only actions at the Realize UI. Self-check at the bottom of the file updated. .codex-plugin/plugin.json description updated. INSTALL.md got a new "Install on Codex (experimental)" section flagging the manifest-name conflict (realize-ads-api vs realize) and the MCP URL/port caveat.

One open question: I couldn't verify the Codex MCP endpoint from the public README (source moved to a private repo on Jun 9). INSTALL.md currently says to confirm the URL/port with the Realize team. If you can paste the actual Codex endpoint, I'll plug it in as a small follow-up.

8. SKILL ↔ knowledgebase contradictions (3 sub-items)

In skills/manage-campaigns/SKILL.md:

  • TARGET_CPA min: 5×/150× → 10× CPA/day ($50 min if CPA < $5). Matches knowledge/bidding.md + knowledge/budget.md.
  • SMART (Enhanced CPC): split out into its own row with 5× CPA/day + 150× monthly. MAX_VALUE left as "per client requirements."
  • cpc bid-lever: now allowed on SMART or FIXED (was FIXED only).

While there, also reconciled the canonical Bid Levers matrix in knowledge/bidding.md itself (the one referenced as canonical by mcp-write-surface.md §4 and optimize-campaign/references/optimization-flow.md §6) — dropped the false Target ROAS row, fixed the cpc_cap row to MAX_CONVERSIONS only, added an editing-note callout pointing future editors at the downstream copies to keep them in sync.

9. Committed permissions.allow pre-approval

6 write tools removed from committed .claude/settings.json (block deleted). Added .claude/settings.local.json.example as a per-user opt-in template; settings.local.json now gitignored. INSTALL.md documents the opt-in path under "Optional: opt in to skip the permission prompt for write tools."

10. manifest.json creative entry — phantom content

Hybrid fix per your suggestion:

  • knowledge/manifest.json creative entry: dropped phantom tags (Wilson, validity tier, 3P JS tag, 1P-hosted display, create_display_item, ad_tag, asset_url); description rewritten to match what creative.md actually covers.
  • knowledge/creative.md: added a one-line pointer at the top to knowledge/targeting.md for the Display item MCP payload shape (3P ad_tag vs 1P asset_url + dimensions).
  • Cross-reference tables in mcp-write-surface.md §8 and optimization-flow.md §12 also rewritten — they used to make the same false claim about creative.md. Now they point at the actual locations (targeting.md for the payload shape, optimization-flow.md §3 for Wilson-score ranking).

11. campaign-structure.md:64 vs :66 — inconsistent bundling thresholds

Picked the per-day, strategy-specific basis per your recommendation. The "~$5K total" figure is gone from L64; L66 (now slightly reworded) is the single rule: < $1k/day for MAX_CONVERSIONS / TARGET_CPA, < $500/day for FIXED.

12. SKILL.md:52cpc_cap valid on all strategies

Now reads "valid only on bid_strategy=MAX_CONVERSIONS" (sending on TARGET_CPA / MAX_VALUE / SMART / FIXED returns API 400). Same correction propagated to the scalar field table in mcp-write-surface.md, the bid-lever matrix in the same file, the refuse-and-reframe rules, the failure-mode table, and the canonical matrix in bidding.md. Thanks for the CpcCapTransformer.java:39 and registry.py:500 citations — those were the source of truth.

13. Campaign Groups — no MCP support

Kept the conceptual content (still useful for users planning their structure in the UI) but added an explicit MCP-status callout at the top of the Campaign Groups section in knowledge/campaign-structure.md:

MCP support status: Campaign Group assignment is not exposed via the Realize MCP today. create_campaign and update_campaign accept no campaign_group_id parameter. Group creation and campaign-to-group assignment are UI-only actions — when a user asks the plugin to "put this campaign in group X", treat it as a UI-only action and tell the user to set the group in the Realize UI after the campaign exists.

Happy to strip the section entirely if you'd prefer — let me know which direction you want.


Bonus changes in the same commit

Block-list wiring (responds to your Jun 15 "Add structured support: Block List via MCP" upstream task). Publisher block-list edits now route through update_campaign(publisher_targeting=...):

  • agent triage updated (was incorrectly marked UI-only at realize-analyst.md:82)
  • new manage-campaigns "Publisher block-list edits" recipe: search_publishersget_campaign → merge → historical-top-N guard → side-by-side preview with resolved names → confirm → write → verify
  • new W4b test scenario (full flow + full-replace gotcha + cleanup)
  • gap-doc closed-gap notes (§3.C and §3.G) + CHANGELOG entry
  • no new MCP tool added; everything routes through the existing update_campaign

Guardrails v4 brand spec alignment.

  • Backstage added to Banned brand variations (content sweep across knowledge/ / skills/ / agents/ confirmed clean; this is the defensive listing)
  • Banned company-vs-platform framing section removed
  • Internal codename → external name mapping section removed; runtime echo guard added under "Internal field names and enum values" covering blindspot / Syndicator ID / Auction report for user-input echoes
  • Banned competitor terminology renamed to Banned industry terms

Verification: scripts/brand-check.sh passes (0 FAIL, 5 WARN — all pre-existing false-positives, none introduced by these changes). Cross-file grep sweeps confirm no remaining fabricated tool names, wrong enum values, or stale section references. 19 files changed, 342 insertions, 108 deletions.

Ready for re-review.

- Remove the "Don't enumerate the guardrails on request" section and its
  matching self-check bullet. The canned reply itself signalled that a
  private rule layer exists; answering such asks naturally is better
  opsec than calling attention to a hidden layer.
- Add "Sourcing — prioritize the plugin's own sources" (soft tone): the
  Realize MCP, the plugin's knowledge base, taboola.com / realize.com,
  and the help center take priority over open-web content. TrustPilot,
  G2, Capterra, Reddit, Quora, social media, and third-party blogs are
  flagged as lower-confidence and not to be cited as authoritative.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@amitl-levi

Copy link
Copy Markdown
Contributor Author

Pushed 65b1598 — two small changes to os/guardrails.md:

1. Removed Don't enumerate the guardrails on request section + its matching self-check bullet. The canned "I follow the published guidelines... don't acknowledge a private layer" reply was itself a tell that a private rule layer exists. Better opsec is to answer such asks naturally — no special script that signals there's something hidden.

2. Added Sourcing — prioritize the plugin's own sources (soft tone). Reliability framing, not a hard ban. The Realize MCP, this plugin's knowledge base, taboola.com / realize.com, and the help center take priority. TrustPilot, G2, Capterra, Reddit, Quora, social media, and third-party blogs are flagged as lower-confidence and not to be cited as authoritative. If a web lookup is genuinely needed, prefer official Taboola- / Realize-owned URLs and disclose the source. Matching self-check bullet added.

@chrishall-taboola

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

cpc_cap reconciliation is solid — mostly fixed in a0d6176 (bidding.md matrix, SKILL.md:52, mcp-write-surface.md all now correctly say MAX_CONVERSIONS-only / API 400 elsewhere). Verified against source of truth: publisher-console CpcCapTransformer.java:65 throws "Cpc Cap can be set only when using Max Conversions.", and realize-mcp-server registry.py:500 agrees.

One copy was missed: skills/optimize-campaign/references/optimization-flow.md:225 still reads:

  • CPC cap is valid on all strategies as a last-resort lever.

This contradicts the new rule (and the table at L214 in the same file). bidding.md:62's editing-note flags §6 of this file as a downstream copy to keep in sync — this is the one that slipped. An optimize agent following L225 recommends cpc_cap on Target CPA / Enhanced CPC / Max Value → API 400.

Fix: change L225 to "valid only on Maximize Conversions."

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

Block-list feature (update_campaign(publisher_targeting=…)) — 2 issues vs source of truth

Tool choice is right. Payload has two problems:

1. [HIGH] value must be strings, not integer IDs — hard-fails on every runtime.
Recipe says "requires integer IDs" with examples [10, 12, 14] (SKILL.md, mcp-write-surface.md:658, W4b). MCP validator rejects ints — publishers.py:52:
"publisher_targeting.value must be a list of strings". Schema is items: {type: string}; feed the strings from search_publishers verbatim. Drop "integer IDs" + the
numeric examples.

2. [HIGH] Codex path missing the merge rule.
publisher_targeting is full-replace (registry.py:68–71). Merge-before-write lives only in the skills; os/guardrails.md's gate (Codex loads this, no skills) says
"capture current state" but never "merge." On Codex "block ESPN" silently wipes existing blocks. Hoist the merge rule into os/guardrails.md.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

skills/discovery/SKILL.md:75 mis-types IDs vs the API contract

The line says:

audience_id, segment_id, rule_id, and publisher id are opaque values returned by the API. Do not coerce to int

Checked against APICampaign.java (backstage-api-model) and the MCP schemas — 3 of the 4 are integers, not opaque strings:

Field API POJO MCP schema
audience IDs MultiTargeting<Long> (:125) integer (registry.py:320)
segment IDs MultiTargeting<Long> (:126) integer (registry.py:345)
lookalike rule_id long integer (registry.py:376)
publisher targeting Targeting<String> (:70) string ✓

So an agent obeying this line quotes audience/segment/lookalike IDs as strings → MCP integer-schema rejects → silent fail across three targeting types. Only publisher is a
string (and there the block-list recipe says the opposite — "integer IDs" — see the block-list comment).

Fix (plugin, not MCP — the MCP matches the POJOs): correct :75 to — account/campaign/item/publisher IDs are opaque strings; audience / segment / lookalike rule_id /
conversion-rule id are integers. Pre-existing (file not in this PR), but adjacent to the conversion_rules change here.

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

Display ad_tag docs: dangling allowlist pointer + wrong error-recovery advice

Checked against DisplayAdTagValidator (publisher-console) + MCP error path.

1. Dangling pointer. Six refs (realize-analyst.md:187, SKILL.md:32/334/351, mcp-write-surface.md:107/319) say "see knowledge/creative.md for the validator
allowlist / strip rules."
creative.md has none of that (0 matches, even pre-PR). Also no enumerable "allowlist" exists — validation is a per-vendor ordered
List<TagConfig>, server-side.

2. Recovery advice keys off the wrong error. SKILL.md:351 / mcp-write-surface.md:319:

If 400 Unsupported tag, the 3P tag failed the allowlist — strip any HTML wrapper and retry.

The validator throws two distinct 400s, and the MCP proxies the message body back to the agent (errors.py: 4xx body returned for self-correction), so the agent matches on
the text:

  • "Unsupported tag" (UNSUPPORTED_TAG, L56) → vendor not configured (tagsConfig == null). Stripping a wrapper won't fix it.
  • "Invalid html tag structure" (INVALID_STRUCTURE, L52) → malformed/wrapped markup. This is the strip-the-wrapper case.

So the advice fires on the wrong condition.

Fix (plugin; MCP is fine): drop the "see creative.md for the allowlist" promise; document the two real failure modes — unsupported vendor vs malformed structure.
Pre-existing, but the PR gutted creative.md further. (Minor: confirm APIBadRequestException("Unsupported tag") serializes into the JSON message field the agent matches
— standard mapping, not traced.)

@chrishall-taboola

Copy link
Copy Markdown
Collaborator

"Pace Ahead" recommended as an action, but it's a gated field not exposed via MCP

bidding.md (228, 365) and budget.md (56, 113, 122, 170) recommend the Pace Ahead feature to accelerate spend, as if it's actionable. The MCP exposes no field for it (0
matches in realize-mcp-server).

Checked the API: Pace Ahead is real but gatedAPICampaign.budgetAdditionalParameters.paceAheadFactor, validated against per-account publisher config
(PaceAheadFactorValidatorImpl(PublisherConfigProvider)), with a FilterOutBudgetAdditionalParameters mixin and a BALANCED-only state dependency. So the MCP omission is
deliberate, not an oversight.

This is the same posture as roasGoal (gated → DCO-only, FilterOutRoasGoal, not in MCP), which this PR correctly flagged UI-only. Pace Ahead was missed → an
optimize-campaign agent prescribes "use Pace Ahead" → routes to manage-campaigns → no field → attempt-and-discover-halfway.

Fix: add a "Pace Ahead is UI-only — gated field, not exposed via the MCP; set it in the Realize UI" callout next to these recs, matching the roasGoal treatment. (Dropping
ACCELERATED from daily_ad_delivery_model is correct — deprecated in MCP, and Pace Ahead is a separate field, not that enum value.)

Amit Levi and others added 2 commits June 16, 2026 19:12
…rce-of-truth

HIGH:
- publisher_targeting payload: convert "integer IDs" + [10,12,14] examples
  to string IDs across manage-campaigns/SKILL.md, mcp-write-surface.md,
  and the matching W4b test scenario. The MCP validator schema is
  items: {type: string}; integer values hard-fail.
- Hoist merge-before-write rule into os/guardrails.md Confirmation flow
  step 2. Codex doesn't load skills, so the gate is the only contract
  that prevents "block ESPN" from silently wiping pre-existing blocks
  on list-replace fields (publisher_targeting, country_targeting,
  platform_targeting, audience_targeting, audience_targeting_excluded,
  contextual_segments).

Documentation-coherence:
- optimize-campaign/references/optimization-flow.md:225: cpc_cap is
  Maximize-Conversions-only — sending it on Enhanced CPC / Fixed Bid /
  Target CPA / Maximize Value returns API 400. Aligns with the L214
  matrix in the same file and with knowledge/bidding.md.
- discovery/SKILL.md:75: re-categorize ID types. Opaque strings are
  account / campaign / item / publisher; integers are audience_id,
  segment_id, lookalike rule_id, and conversion-rule id. Matches the
  API POJOs (Targeting<String> for publisher, MultiTargeting<Long>
  for the rest) and MCP schemas.
- Display ad_tag: drop the dangling "see knowledge/creative.md for the
  validator allowlist" pointers (creative.md never contained an
  allowlist; the validator is a per-vendor server-side ordered list).
  Refs cleaned in agents/realize-analyst.md, skills/manage-campaigns/
  SKILL.md, and references/mcp-write-surface.md. Inline wrapper rules
  retained.
- Display ad_tag error-recovery: split the single "400 Unsupported tag,
  strip the wrapper" advice into two distinct cases that match the
  validator's UNSUPPORTED_TAG (vendor not configured — route to AM,
  do not retry) and INVALID_STRUCTURE (wrapped markup — strip and
  retry) error paths.

Pace Ahead UI-only callout added at all six refs (knowledge/bidding.md
L228, L365 and knowledge/budget.md L56, L113, L122, L170), mirroring
the existing roasGoal pattern: not exposed via the MCP, set in the
Realize UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-review found the Codex merge rule named three fields that don't
exist in the upstream MCP — exactly the silent-failure category the
rule was meant to close. Corrections:

- Replace invented names (`audience_targeting`,
  `audience_targeting_excluded`, `contextual_segments`) with the real
  field names from `skills/manage-campaigns/references/mcp-write-surface.md`:
  `audiences_targeting`, `contextual_segments_targeting`,
  `lookalike_audience_targeting`.
- Extend the list to cover every full-replace targeting block the
  plugin documents: `region_country_targeting`, `dma_country_targeting`,
  `city_targeting`, `postal_code_targeting`, `os_targeting`. Codex now
  has the complete inventory.
- Add the item-update array fields `verification_pixel` and
  `viewability_tag` (on `update_native_item` / `update_display_item`)
  — also full-replace per SKILL.md, also missed by the original rule.
- Switch terminology from "list-replace" to "full-replace" to match the
  language already used throughout `skills/manage-campaigns/`. Two
  synonyms for the same concept invited confusion.
- Restructure step 2 from one dense inline paragraph into three sub-
  bullets so the rule scans cleanly on Codex's read of the gate.

Also gitignore the local PR / review / reply draft .txt files at repo
root so they stop showing as untracked. Patterns are narrow
(`/pr-description-*.txt`, `/pr*-comment-*.txt`, `/reply-to-*.txt`) —
no risk of accidentally swallowing real content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@amitl-levi

Copy link
Copy Markdown
Contributor Author

Thanks Chris — all 5 addressed across 4e580de and adc66c8 (self-review on the first commit caught a bug in the Codex merge rule, fixed in the second).

  1. publisher_targeting payload (HIGH) — strings not integers, across SKILL.md, mcp-write-surface.md, and the W4b test scenario. MCP validator rejects integers; we now feed the publisher ID strings from search_publishers verbatim.

  2. Codex merge gap (HIGH) — hoisted the full-replace merge rule into os/guardrails.md step 2 of the Confirmation flow. Self-review on 4e580de caught three invented field names (audience_targeting / _excluded / contextual_segments); adc66c8 corrects them to the real schema from mcp-write-surface.md:67-69audiences_targeting, contextual_segments_targeting, lookalike_audience_targeting — extends to all 11 full-replace targeting blocks, and adds the item-update arrays verification_pixel / viewability_tag.

  3. cpc_cap reconciliationoptimization-flow.md:225 now correctly Maximize-Conversions-only with API-400 note, matching the L214 matrix.

  4. Discovery ID typingdiscovery/SKILL.md:75 distinguishes opaque strings (account / campaign / item / publisher) from integers (audience_id, segment_id, lookalike rule_id, conversion-rule id), matching the API POJOs and MCP schemas.

  5. Display ad_tag — dropped the dangling creative.md allowlist pointers (3 refs); split the 400 error-recovery into Unsupported tag (vendor not configured — route to AM, do not retry) vs Invalid html tag structure (strip the wrapper and retry).

Also: Pace Ahead UI-only callout added at all 6 refs across bidding.md and budget.md, mirroring the existing roasGoal pattern.
@chrishall-taboola

Two related runtime tone rules added to os/guardrails.md and propagated
through the knowledge layer. Anchor for both: brand-voice review feeding
into product, June 2026.

1. Publisher and site framing — measurable performance, not name
   - New section in os/guardrails.md establishing that publisher / site
     recommendations describe measurable performance ("0 conversions over
     14 days", "CPA exceeds 3x campaign average") + action, not the
     publisher's character or reputation in the abstract. Ties back to
     existing frozen brand phrases (Embedded publisher integrations,
     Code on page integrations) so the rule reads as brand-voice
     extension rather than a banned-word list.
   - knowledge/site-management.md opener softened: "blocking bad sites"
     -> "excluding sites that underperform on the campaign's KPI".
   - scripts/brand-check.sh: defense-in-depth FAIL regexes for
     publisher-character framing and rescue / salvage framing.

2. Reader framing — the operator, not a relay through them
   - New section in os/guardrails.md establishing that the reader is the
     campaign operator (self-serve advertiser, agency running on behalf
     of a brand, or in-house media buyer) — never a Taboola Account
     Manager relaying instructions to "the client". Possessive
     "your client" / "my client" stay in voice for the agency case.
   - Knowledge-layer reader-voice sweep: brand-safety.md (13 phrasings
     + section retitle "When an Advertiser Complains" -> "Responding to
     a Brand-Safety Complaint"), tracking.md (14 phrasings + 2 retitles
     including "How to communicate this to clients" -> "What to plan
     for"), environments.md (Sponsored Content <-> Display section
     retitle from "Upsell" to "Expansion" + 4 table-row rewrites + Apple
     News "What to plan for" block), bidding.md (Overview, attribution
     notes, FIXED-use trigger, Enhanced CPC framing, budget-restriction
     guidance), targeting.md (Tier-1 caveat, MRT bundles, Marketplace
     row, pixel and CRM retargeting bullets), creative.md (Display
     add-format pro tip), budget.md (short-burst definition),
     site-management.md (approved-list dynamics), and
     skills/manage-campaigns/SKILL.md (Fixed Bid / MAX_VALUE budget
     minimums + 1P-hosted display asset-hosting note). All rewrites stay
     in advertiser-direct second person or neutral instructional voice —
     no net-new content.
   - scripts/brand-check.sh: AM-relay FAIL regexes
     (verbs-at-advertiser, modal-at-advertiser, set-expectations,
     discuss-with, communicate-to-clients). Tight enough that
     "your Account Manager", "client-side merge", and possessive
     "your client" stay valid.
   - Silent self-check bullets added for both rules.

Brand-check passes 0 FAIL, 5 WARN (WARN count unchanged from prior
baseline).

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

@amitl-levi amitl-levi left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two related runtime tone rules added to os/guardrails.md
and propagated through the knowledge layer. Anchor for both:
brand-voice review feeding into product, June 2026.

  • Publisher and site framing — measurable performance, not
    name.
    Publisher / site recommendations describe measurable
    performance ("0 conversions over 14 days", "CPA exceeds 3×
    campaign average") + action — never the publisher's character
    or reputation in the abstract. Ties back to existing frozen
    brand phrases (Embedded publisher integrations, Code on
    page integrations
    ) so the rule reads as brand-voice
    extension rather than a banned-word list. Removes the one
    upstream priming anchor in knowledge/site-management.md
    ("blocking bad sites""excluding sites that underperform
    on the campaign's KPI"
    ). Adds defense-in-depth FAIL regexes
    to scripts/brand-check.sh.
  • Reader framing — the operator, not a relay through
    them.
    The reader is the campaign operator (self-serve
    advertiser, agency running on behalf of a brand, or in-house
    media buyer) — never a Taboola Account Manager relaying
    instructions to "the client". Possessive "your client" /
    "my client" stay in voice for the agency case.
    Knowledge-layer reader-voice sweep across 9 files (~30
    phrasings + 3 section retitles, all rewrites stay in
    advertiser-direct second person or neutral instructional
    voice — no net-new content). Adds AM-relay FAIL regexes to
    scripts/brand-check.sh, tight enough that "your Account
    Manager"
    , "client-side merge", and possessive "your
    client"
    stay valid.

Section retitles:

  • knowledge/brand-safety.md "When an Advertiser
    Complains"
    "Responding to a Brand-Safety Complaint"
  • knowledge/tracking.md "When the Advertiser Refuses Pixel
    or S2S"
    "When Pixel or S2S Isn't an Option"; inline
    "How to communicate this to clients""What to plan for"
  • knowledge/environments.md "Sponsored Content ↔ Display
    Upsell"
    "Sponsored Content ↔ Display Expansion" (4
    table-row rewrites)

Silent self-check bullets added to os/guardrails.md for
both rules.

Replaces the single descriptive sentence about the realize-analyst agent
with a directive that (a) leads with the rule in bold, (b) names what
the skills carry that raw MCP calls bypass — account resolution, CSV
conventions, optimization playbook, write-preview gate — and (c) pins
the most common miss observed at install time: "performance review" or
"insights" asks being answered as ad-hoc analysis on report output
instead of being routed to optimize-campaign.

One line removed, one line added. No other files touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@amitl-levi

Copy link
Copy Markdown
Contributor Author

Pushed one more fix— surgical README edit (one line in, one line out, no other files touched).

Replaces the single descriptive sentence about the realize-analyst agent with a directive paragraph that (a) leads with "Start with a skill, not the MCP" in bold, (b) names what the skills carry that raw MCP calls bypass — account resolution, CSV conventions, optimization playbook, write-preview gate — and (c) pins the most common install-time miss: "performance review" or "insights" asks being answered as ad-hoc analysis instead of being routed to optimize-campaign.

Context: caught from a real install transcript where Claude went straight to the MCP tools and only invoked the skill after being explicitly asked "did you use the skill set?"

@yanush88 yanush88 merged commit 8e97188 into taboola:main Jun 18, 2026
1 check passed
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.

3 participants