mureo AGENTS.md

A repository guide for mureo, a local-first assistant for managing and auditing online advertising accounts. It works with official platform interfaces and keeps credentials on the user's machine.

In plain words
What is it for?
Use it when working on advertising-platform integrations, strategy-driven ad workflows, audit trails, browser assets, or the project's Python and JavaScript tests.
Why use it?
It clarifies the project's lightweight architecture and testing commands, so changes do not assume a database, web framework, or language-model software library.

Instructions file for CodexOpenCode

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add instructions/logly/mureo/agents-md
Clone the repo
git clone --depth 1 https://github.com/logly/mureo

Made for: Codex, OpenCode.

Per session 10,087 This file is loaded in full into every session.
When invoked 10,087 The same file — it is already loaded in full.
Security scan A 0 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.10087 $0.10087
Opus 5 $0.05044 $0.05044
Sonnet 5 $0.02017 $0.02017
Haiku 4.5 $0.01009 $0.01009

Measured 2d ago against content hash 567c3f27f738, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

mureo AGENTS.md scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

AGENTS.md · 505 lines

How it starts

The opening of the file, as written. The whole thing — 505 lines — stays where its author put it; the contents beside it link to each section on GitHub.

AGENTS.md

Guidelines for AI agents contributing to the mureo codebase.

Project Overview

mureo — your local-first AI ad ops crew. Find waste, audit changes, run ad accounts safely.

Works with Claude Code, Cursor, Codex & Gemini. mureo sits on top of the official ad-platform MCPs and gives your AI a strategy to follow, an outcome to be measured against, and an audit trail you can show to anyone — credentials never leave your machine.

mureo combines strategy context, workflow commands, and domain knowledge to help AI agents achieve marketing goals across platforms. Provides MCP tools for direct platform operations and workflow commands for strategy-driven ad operations via Claude Code slash commands. Designed for AI agents — no database, no LLM SDK, no web framework.

Build & Test

pip install -e ".[dev]"
pytest tests/ -v
pytest tests/ --cov=mureo --cov-report=term-missing

# Browser assets (only when mureo/_data/web/ changes). Node's built-in
# runner — no package.json, no dependencies, no build step.
node --test tests/js/*.test.js

Architecture

mureo/
├── google_ads/          # Google Ads API client (Mixin composition)
│   ├── client.py        # GoogleAdsApiClient (main entry)
│   ├── mappers.py       # Response mapping to structured dicts
│   ├── _enum_names.py   # map_enum_name + SDK-derived int->name maps (raw protobuf has no .name, #588)
│   ├── _placement_mappers.py # Negative-placement + group_placement_view row mappers (#544/#547)
│   ├── _ads.py          # AdsMixin (RSA create/update/status/list)
│   ├── _ads_display.py  # DisplayAdsMixin (RDA create + RDAUploadError)
│   ├── _asset_groups.py # AssetGroupsMixin (Performance Max asset groups: one read for
│   │                    #   text + images, and the text swap via one atomic
│   │                    #   GoogleAdsService.mutate, #590/#626). Owns both field-type
│   │                    #   tables — text width limits and image dimension rules
│   ├── _asset_groups_images.py # AssetGroupImagesMixin (the P-MAX image swap, #626:
│   │                    #   existing asset id or local upload, one atomic mutate)
│   ├── _keywords.py     # KeywordsMixin (add/remove/suggest/diagnose)
│   ├── _placements.py   # PlacementsMixin (negative placements: sites/apps/app categories, #544)
│   ├── _analysis.py     # AnalysisMixin aggregator, composing the split modules below
│   ├── _analysis_auction.py / _analysis_btob.py / _analysis_budget.py    # + _analysis_constants.py
│   ├── _analysis_keywords.py / _analysis_performance.py / _analysis_rsa.py / _analysis_search_terms.py
│   ├── _diagnostics.py  # DiagnosticsMixin (campaign diagnosis)
│   ├── _extensions.py   # ExtensionsMixin aggregator, composing the split modules below
│   ├── _extensions_callouts.py / _extensions_conversions.py / _extensions_sitelinks.py / _extensions_targeting.py
│   ├── _monitoring.py   # MonitoringMixin (anomaly detection/reporting)
│   ├── _creative.py     # CreativeMixin (LP analysis/message match)
│   ├── _media.py        # MediaMixin (image asset upload)
│   ├── _rsa_validator.py     # RSA ad text validator
│   ├── _rda_validator.py     # RDA input validator (display ads)
│   ├── _rsa_insights.py      # RSA asset performance insights
│   ├── _intent_classifier.py # Search term intent classification
│   ├── _message_match.py     # Message match evaluator
│   ├── _gaql_validator.py    # GAQL input validators (ASCII-only ID/date whitelists — see below)
│   └── accounts.py           # Accessible-customer / account listing
├── meta_ads/            # Meta Ads API client (Mixin composition)
│   ├── client.py        # MetaAdsApiClient (main entry)
│   ├── mappers.py       # Response mapping
│   ├── _campaigns.py    # CampaignsMixin
│   ├── _ad_sets.py      # AdSetsMixin
│   ├── _ads.py          # AdsMixin
│   ├── _creatives.py    # CreativesMixin (image/carousel/collection/dynamic)
│   ├── _videos.py       # VideosMixin (AdVideo upload/status/thumbnails)
│   ├── _audiences.py    # AudiencesMixin
│   ├── _pixels.py       # PixelsMixin
│   ├── _placement_exclusions.py  # PlacementExclusionsMixin (ad-set publisher exclusions, #544)
│   ├── _insights.py     # InsightsMixin
│   ├── _analysis.py     # AnalysisMixin
│   ├── _catalog.py      # CatalogMixin (product catalogs/feeds)
│   ├── _conversions.py  # ConversionsMixin (Conversions API / CAPI)
│   ├── _hash_utils.py   # SHA-256 PII hashing for CAPI
│   ├── _leads.py        # LeadsMixin (lead forms/leads)
│   ├── _page_posts.py   # PagePostsMixin (page posts/boost, page photos for an Instant Form cover)
│   ├── _instagram.py    # InstagramMixin (accounts/media/boost)
│   ├── _split_test.py   # SplitTestMixin (A/B split tests)
│   ├── _ad_rules.py     # AdRulesMixin (automated rules)
│   ├── _conversion_count.py  # Conversion-count parsing helper
│   ├── _period.py       # Date-period resolution helper
│   └── accounts.py      # Ad-account listing
├── search_console/      # Google Search Console API client (reuses Google OAuth2 credentials)
│   └── client.py        # SearchConsoleApiClient
├── mcp/                 # MCP server (Google/Meta/Search Console + Rollback/Analysis + Analytics registry, Creative Studio, Learning, mureo Context)
│   ├── server.py                          # MCP Server entry point (stdio-based)
│   ├── _helpers.py                        # Shared handler utilities
│   ├── tools_google_ads.py                # Google Ads tool definitions (aggregator)
│   ├── _tools_google_ads_*.py             # Tool definition sub-modules
│   ├── _handlers_google_ads.py            # Google Ads base handlers
│   ├── _handlers_google_ads_extensions.py # Extensions handlers
│   ├── _handlers_google_ads_analysis.py   # Analysis handlers
│   ├── tools_meta_ads.py                  # Meta Ads tool definitions (aggregator)
│   ├── _tools_meta_ads_*.py               # Tool definition sub-modules
│   ├── _handlers_meta_ads.py              # Meta Ads base handlers
│   ├── _handlers_meta_ads_extended.py     # Extended handlers
│   ├── _handlers_meta_ads_other.py        # Other handlers
│   ├── tools_search_console.py            # Search Console tool definitions
│   ├── _handlers_search_console.py        # Search Console handlers
│   ├── tools_rollback.py                  # rollback_plan_get / rollback_apply
│   ├── _handlers_rollback.py              # Rollback handlers (lazy-resolve dispatcher)
│   ├── tools_batch.py                     # mureo_batch_begin / _end / _status (#549)
│   ├── _handlers_batch.py                 # Batch lifecycle handlers
│   ├── tools_change_import.py             # mureo_external_changes_import (#545)
│   ├── _handlers_change_import.py         # Change-import handler
│   ├── tools_analysis.py                  # analysis_anomalies_check / analysis_delivery_collapse_* / analysis_exclusion_impact_preview
│   ├── _handlers_analysis.py              # Anomaly detector composition handler
│   ├── _handlers_delivery_collapse.py     # Delivery-collapse check / diagnose handlers (#546)
│   ├── _handlers_exclusion_impact.py      # analysis_exclusion_impact_preview handler (#547)
│   ├── exclusion_preflight.py             # Pre-dispatch exclusion sizing + refusal + notice (#547)
│   ├── exclusion_sources.py               # Built-in per-platform delivery sources for the above (#547)
│   ├── tools_analytics_registry.py        # mureo_analytics_modules_list / mureo_analytics_run (#440)
│   ├── tools_creative_studio.py           # creative_studio_* (visual generation + compose)
│   ├── tools_learning.py                  # mureo_learning_insights_get / mureo_consult_advisor
│   ├── tools_learning_preflight.py        # mureo_learning_reset_preflight (#548)
│   ├── tools_mureo_context.py             # STRATEGY.md / STATE.json + mureo_outcome_evaluate tools
│   ├── _handlers_mureo_context.py         # Context (STRATEGY/STATE) handlers
│   ├── _client_factory.py                 # Per-platform BYOD-vs-live client factory
│   └── tool_provider.py                   # Third-party plugin → MCP tool exposure layer (#89)
├── cli/                 # Typer CLI (setup + auth + configure + BYOD + providers + rollback + repair; ad ops are via MCP)
│   ├── main.py          # CLI entry point (`mureo` command)
│   ├── setup_cmd.py     # `mureo setup claude-code` / `cursor` / `codex` / `gemini`
│   ├── setup_codex.py   # Codex install-kit: MCP, credential guard, operational + foundation skills
│   ├── setup_gemini.py  # Gemini extension manifest at ~/.gemini/extensions/mureo/
│   ├── native_skills.py # Deploy/remove plugin native slash skills via `mureo.native_skills` group (#439)
│   ├── configure_cmd.py # `mureo configure` — open the local web configuration UI
│   ├── byod_cmd.py      # `mureo byod import` / `status` / `remove` / `clear`
│   ├── providers_cmd.py # `mureo providers list` / `install` / `uninstall` (official MCP catalog)
│   ├── service_cmd.py   # `mureo service` — install/restart the auto-start configure daemon (#241)
│   ├── upgrade_cmd.py   # `mureo upgrade` — pipx venv-aware bulk upgrade of mureo + plugins
│   ├── auth_cmd.py      # `mureo auth setup` / `status` / `check-*` / `upgrade-google`
│   ├── rollback_cmd.py  # `mureo rollback list` / `show` (inspection only; apply routes through MCP)
│   ├── repair_cmd.py    # `mureo repair platform-key` — drop a platforms entry the DOCUMENT
│   │                    #   shows to be wrong (duplicate of a resolvable key, or empty stub);
│   │                    #   dry run by default, backs up first (#610/#616).
│   │                    #   `--all` sweeps every client, summary first, one prompt (#614).
│   │                    #   `--key <k> --drop-duplicate` records the operator's OWN decision
│   │                    #   about a duplicate whose two keys both resolve (#636); not with --all
│   ├── _repair_preview.py # What that command prints — the half that has to be TRUE: why an
│   │                    #   entry can go, what is NOT changed (scoped to the plan), every
│   │                    #   same-account sibling, and conversion_action_types (#616/#617/#618).
│   │                    #   Also the not_collected note an entry carries (#643) and both
│   │                    #   entries of a duplicate mureo hands back to decide (#645)
│   ├── _repair_clients.py # Which STATE.json files `--all` sweeps — reuses the Reports tab's
│   │                    #   optional list_clients / state_store_for_client seam, never a second one
│   ├── _state_file.py   # The shared `--state-file` option + workspace default (rollback + repair)
│   ├── _tty.py          # TTY-safe helpers for non-interactive setup + terminal_safe() scrubbing
│   └── web_auth.py      # Browser-based OAuth wizard spawned by `mureo configure` (per-platform creds)
├── context/             # File-based strategy context (no DB)
│   ├── strategy.py      # STRATEGY.md parser/writer
│   ├── state.py         # STATE.json parser/writer
│   ├── models.py        # StrategyEntry, StateDocument, CampaignSnapshot, ActionLogEntry (rollback_of, batch_id), BatchRecord
│   ├── batch.py         # Batch id minting + the action_log stamping rule (#549)
│   ├── daily.py         # The day-grain history write MINUS the file (#690/#710):
│   │                    #   `with_platform_daily(doc, ...)` (guard + per-date merge +
│   │                    #   trim) and `capped_platform_daily(daily)` (the 35-day
│   │                    #   retention rule on its own), for a writer that lands
│   │                    #   `daily` inside its OWN atomic document write.
│   │                    #   `set_platform_daily` is the path-based wrapper, so both
│   │                    #   routes keep one set of semantics. Optional `as_of_date`
│   │                    #   = today in the ACCOUNT's timezone: an account closes its
│   │                    #   day locally, so the host clock refused a complete
│   │                    #   yesterday-JST on a UTC host every night
│   ├── observations.py  # What closes an action_log observation, and which are
│   │                    #   past due (#651). ONE rule for `mureo_state_get(
│   │                    #   action_log="pending")` and the Reports triage count;
│   │                    #   two entry shapes (rendered dict / ActionLogEntry)
│   ├── monthly_budget.py # The `## Custom: Monthly Budget` reader (#652) — the operator's
│   │                     #   INTENDED monthly spend, twin of `guardrails_from_strategy_text`.
│   │                     #   Separate type from `Guardrails` on purpose: a target is not a
│   │                     #   ceiling. "Not set" is a first-class answer (never 0), a total
│   │                     #   derived from `max_total_daily_budget` says so, and the figure
│   │                     #   stays in STRATEGY.md — never copied into STATE.json
│   ├── platform_monthly_budget.py # Rung 2 of that precedence (#656): the sum of the
│   │                     #   per-campaign monthly budgets, for platforms that DECLARED they
│   │                     #   have the concept (`register_monthly_budget_support`; core
│   │                     #   declares none). A third `source`, so a configured sum is never
│   │                     #   shown as an agreed target. No total is stored — it is computed
│   │                     #   on read — and an incomplete or uncollected campaign set is
│   │                     #   withheld rather than summed short, reported as
│   │                     #   `IncompletePlatform(platform, reason)` so an operator is told
│   │                     #   WHICH gap it is (a wrong declaration, a sync behind, a failed
│   │                     #   collection) instead of reading a debug log. The two splits are
│   │                     #   separate fields — `per_platform` is only ever what the operator
│   │                     #   wrote — and the type refuses a split that does not match `source`
│   ├── platform_guards.py # Write-time platform-key guards (#534/#609) — the one "is this key
│   │                      #   real?" answer every other surface asks, including the read-side
│   │                      #   label path (`installed_platform_names`, shared since #631)
│   ├── platform_repair.py # The repair half (#610): plan + drop an entry under an unresolvable
│   │                      #   key. Unresolvable is the FILTER, not the criterion — the document
│   │                      #   must show the entry wrong (#616), and conversion_action_types is
│   │                      #   never dropped (#617). `drop_duplicates` is the one widening: a key
│   │                      #   the OPERATOR named, dropped only against a same-account sibling
│   │                      #   (#636). Drops, never merges; backs up; no action_log
│   └── errors.py        # Context-specific errors
├── analysis/            # Analysis utilities
│   ├── lp_analyzer.py   # Landing page analyzer
│   ├── anomaly_detector.py  # Zero-spend / CPA-spike / CTR-drop detection (pure, sample-size-gated)
│   ├── delivery_collapse.py # Delivery-collapse detection (#546) — weekday-aware baseline taken
│   │                        #   from the platform's own daily delivery, never from action_log
│   ├── delivery_collapse_config.py  # ## Guardrails -> CollapseThresholds (the only I/O half)
│   ├── collapse_diagnosis.py        # Change x metric timeline + elimination ladder; reports the
│   │                                #   open questions, never a cause it did not evidence
│   ├── exclusion_impact/    # Delivery-impact preview for bulk exclusions (#547; pure)
│   │   ├── models.py        # DeliveryRecord / ExclusionTarget / ExclusionImpact + coverage verdicts
│   │   ├── matching.py      # Per-entity-kind match rules (host, app id, negative keyword match type)
│   │   ├── estimator.py     # The share itself — incremental and cumulative
│   │   ├── rules.py         # ## Guardrails exclusion keys, the one refusal decision, inert-rule reporting
│   │   └── surfaces.py      # Which tools are exclusion surfaces (mureo's + plugin-registered)
│   └── tracking/        # Tracking-parameter consistency: platform-neutral detector + per-platform URL accessors (#550)
├── rollback/            # Rollback feature (allow-list gated, append-only audit trail)
│   ├── models.py        # RollbackStatus / RollbackPlan + batch verdicts (BatchCoverage, BatchRollbackPlan)
│   ├── planner.py       # plan_rollback(ActionLogEntry) -> RollbackPlan | None
│   ├── batch.py         # plan_batch_rollback(doc, batch_id) -> every member, gaps included (#549)
│   └── executor.py      # execute_rollback(...) -> appends ActionLogEntry(rollback_of=index)
├── adapters/            # Provider adapters wrapping each ad-platform client as a registry Protocol
├── analytics/           # Analytics-module registry for external MCP / plugin platforms (#120)
├── change_import/       # Import changes made OUTSIDE mureo into action_log (#545)
│   ├── models.py        # ExternalChange / ChangeFeedResult / ChangeImportOutcome
│   ├── protocol.py      # ChangeFeedProvider Protocol (`mureo.change_feeds` entry-point group)
│   ├── registry.py      # Discovery + per-plugin fault isolation + lookup
│   ├── dedupe.py        # Already-imported vs mureo's own change (why one is approximate)
│   ├── importer.py      # Watermark, action_log write, per-platform outcome
│   └── builtin/         # Native feeds — google_ads only today; see docs/change-import.md
├── core/                # Extension Protocols + file-backed impls + RuntimeContext; provider & skill discovery
├── providers/           # Official MCP provider catalog + one-command install helpers (#86)
├── policy/              # Built-in policy gates (strategy_gate) — ship with OSS, run by default
│   ├── learning_rules.py     # Per-platform learning-period facts + their first-party sources (#548)
│   ├── platform_model.py     # Per-platform delivery-model prose + first-party sources (#648) —
│   │                         #   the ONLY always-on plugin channel: rendered into the MCP
│   │                         #   server's `instructions`, scoped to the platforms served
│   └── learning_reset.py     # Reset-class + learning-state pre-flight the gate refuses on
├── learning/            # Read-side /learn companion: insight federation across configured sources
├── creative_studio/     # Creator-grade ad-creative (image) generation via pluggable providers
├── byod/                # Bring Your Own Data — CSV-backed offline analysis (see BYOD Mode below)
├── web/                 # Local `mureo configure` UI — stdlib http.server on 127.0.0.1 (no web framework)
├── demo/                # `mureo demo init` synthetic-bundle bootstrap (round-trips through BYOD)
├── auth.py              # Credentials management (~/.mureo/credentials.json + env vars + Meta token auto-refresh)
├── auth_setup.py        # Interactive setup wizard (browser OAuth flow)
├── credential_guard.py  # Blocks AI agents from reading ~/.mureo/credentials.json
├── logging_setup.py     # Configure-server logging (#581): rotating ~/.mureo/logs/configure.log,
│                        #   MUREO_LOG_LEVEL, and the HTTP access-log query scrubber. Installed by
│                        #   the configure entry point only — never at import time, never on root.
└── throttle.py          # Rate limiting (token bucket + rolling hourly cap)

skills/                       # Native slash skills — one `<name>/SKILL.md` per skill (migrated from
│                             #   .claude/commands in #439; invocable as `/<name>`). Plugins contribute
│                             #   more via the `mureo.native_skills` entry-point group, deployed/removed
│                             #   by `mureo/cli/native_skills.py`.
├── onboard/, daily-check/, rescue/, budget-rebalance/, …   # ~20 operational (workflow) skills
│   └── SKILL.md
└── _mureo-*/                 # 7 foundation skills: _mureo-shared / _mureo-strategy / _mureo-google-ads /
    └── SKILL.md              #   _mureo-meta-ads / _mureo-amazon-ads / _mureo-learning / _mureo-pro-diagnosis
docs/integrations.md          # Platform discovery + external MCP integration guide

Read the full file on GitHub · 505 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 2d ago First seen · 505 lines · 10,087 tokens per session scan A 567c3f27f738

Subscribe to this mod's changes

mureo AGENTS.md is an instructions file published in the GitHub repository logly/mureo (42 stars, last pushed 2d ago), licensed Apache-2.0. It adds 10,087 tokens to every session, about $0.0504 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.