_mureo-report-persist
Skill64 starsChanged 7 months agoApache-2.0View on GitHub
mureo: daily-check persistence steps — report summary, per-window rollups, day-grain history, display contract.
In this repository30 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- ad-fatigue-checkSkill
- audience-reviewSkill
- budget-pacingSkill
- budget-rebalanceSkill
- competitive-scanSkill
- creative-generateSkill
- creative-refreshSkill
- daily-checkSkill
- experimentSkill
- goal-reviewSkill
- incident-postmortemSkill
- lead-form-createSkill
- learnSkill
- monthly-reportSkill
- _mureo-amazon-adsSkill
- _mureo-google-adsSkill
- _mureo-learningSkill
- _mureo-meta-adsSkill
- _mureo-pro-diagnosisSkill
- _mureo-sharedSkill
- _mureo-state-schemaSkill
- _mureo-strategySkill
- onboardSkill
- rescueSkill
- search-term-cleanupSkill
- sync-stateSkill
- tracking-healthSkill
- weekly-reportSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 38
Reflect the FINAL state, and persist this LAST: Write this summary AFTER every STATE change and action_log entry you made this run (it is …
- Very specific: line 39
AFTER every STATE change and action_log entry you made this run (it is the last persistence step — keep it after any Operation Mode switch, …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
mureo Report Persistence
PREREQUISITE: Read
../_mureo-shared/SKILL.mdfor auth, global flags, and security rules, and../_mureo-state-schema/SKILL.mdfor the STATE.json schema.
Tool names below refer to the built-in platforms; a namespaced variant (see
_mureo-shared→ Namespaced built-in variants) is used with its prefix substituted.
This file holds the persistence steps of ../daily-check/SKILL.md — its steps 12–15, which write what the run already decided to STATE.json and the dashboard — kept here, so their numbers and every "step N" below refer to daily-check's steps and "this skill" means daily-check. Each is best-effort: a host without the tool it names skips that step, and the rest of the run still works.
Report summary
Persist the report summary (best-effort): Call
mureo_state_report_setwithreport="daily"and a concisesummaryobject so the read-only dashboard can render this report without re-running you. Persist in incremental mode too — step 10's diff-first report is computed against this stored summary, so skipping it blinds tomorrow's run. Follow this convention:generated_at: ISO 8601 timestamp of this run — useserver_nowperiod: the day reviewed, derived fromserver_now(e.g."2026-06-17")totals: the account's headline figures, using the canonical metric vocabulary —spend,conversions,cpa,ctr,clicks,impressions. This is the block the dashboard renders as figures. Raw numbers only:773957, not"¥773,957";0.0466, not"4.66%"— one of those keys carrying a string is refused, because it sits where the view reads a figure and renders as nothing. A key outside the vocabulary may ride along: it is stored, just not shown as a headline number. Omittotalsentirely if this run gathered no account-level figures.kpis: the per-platform split — the breakdown, not the headline row — measured only. Omit a platform that was not checked (auth_error/tools_absent) rather than persisting an estimate for it, and record the gap as a flag ({code:"custom", severity:"action", label:"<platform> not checked: <cause>"}) rather than as a sentence innarrative: tomorrow's incremental run diffs against this object, so a figure invented here becomes the baseline it trusts.flags: a list of structured flags — each a small object{code, severity, params}so the dashboard renders a coarse, localizable chip with the numbers on drill-down:code: a canonical vocabulary key — one ofgoals_met,cpa_over_target,cpa_under_target,cv_below_target,cv_above_target,spend_spike,cpa_spike,invalid_traffic_suspected,zero_cv_adspots,budget_overspend,budget_drift,tracking_suspect,zero_conversions,supply_tools_unconfigured,anomaly_baseline_insufficient,pending_observations,search_console_no_property,ga4_not_configured.severity: one ofaction/watch/info/positive(omit to take the code's default —info/positivekeep informational and good-news flags visually distinct from alarms).params: an object holding the DETAIL (adspot ids, yen, ctr, dates). Keep detail inparams, NOT baked into the code — write{"code":"invalid_traffic_suspected","params":{"adspot":"4311492","spend":115740,"cv":0,"ctr":0.0466}}, never a slug likeadspot_4311492_invalid_traffic_spike_115740yen.- For a finding outside the vocabulary use
{code:"custom", severity, label}wherelabelis a string or a{"ja":…,"en":…}map. Unknown non-customcodes are rejected. (A legacy bare-string flag still works but renders without the drill-down — prefer the object form.) - Example:
[{"code":"invalid_traffic_suspected","params":{"adspot":"4311492","spend":115740,"cv":0,"ctr":0.0466}}, {"code":"budget_drift","params":{"live":170000,"configured":150000,"unlogged":true}}, {"code":"goals_met"}] - Read the narrative back before you write this list. The narrative is written first and is where a reservation naturally lands; a flag is a chip with no room for one. So re-read what you just wrote, and for every reservation in it the matching flag must either carry the same reservation or not be raised at that severity. Put the reservation in
params— e.g.{"code":"zero_cv_adspots","severity":"watch","params":{"grain":"aggregate above the entity judged","undecidable":true}}— and drop the severity a step:actionis for a finding that is defensible without the caveat. A hedge innarrativebeside an assertion inflagspasses every validator and still tells the operator something you did not conclude.
narrative: the Healthy / Watch / Action-needed verdict and what you propose next, at most 400 characters — the tool refuses a longer one rather than truncating it (a sentence cut in half is worse than a long one). Do not restate the figures and do not list the findings here: numbers belong intotals, findings inflags— see../_mureo-strategy/SKILL.md→ Reports section for the before/after of the paragraph this replaces.
Reflect the FINAL state, and persist this LAST. Write this summary AFTER every STATE change and
action_logentry you made this run (it is the last persistence step — keep it after any Operation Mode switch, campaign pause, budget edit, etc.). If you changed something, thenarrative+flagsmust describe the POST-change state (e.g. "switched toEFFICIENCY_STABILIZE", not "recommend switching"; flag{code:"custom", severity:"info", label:"Switched to EFFICIENCY_STABILIZE"}, not a now-resolved recommendation). Stampgenerated_atat the moment you write this — so the dashboard's "Latest report" is never an older snapshot that contradicts the action log or STRATEGY.md. (If you only recommended a change and did NOT make it, the recommendation wording is correct — and there is then no matchingaction_logentry, so nothing contradicts it.)This is best-effort: if
mureo_state_report_setis unavailable (e.g. a pure file-mode host without the context MCP), skip it silently — the rest of this skill still works.
Per-window per-platform rollups
- Persist per-window per-platform rollups (best-effort): so the reporting dashboard's KPI tiles AND its Yesterday / Last 30 days period toggle have data, write each platform's totals to
platforms[<platform>].periods[<window>]using the canonical metric vocabulary (spend,impressions,clicks,conversions,cpa,ctr, the Metaresult_indicatorwhen present). Write every window whose numbers you already gathered this run — typicallyYESTERDAY(the prior-day totals) andLAST_30_DAYS(the trailing-30-day totals your step-4 health checks / step-8 goal progress already pulled). The period toggle only appears once two windows have data, so populatingLAST_30_DAYShere — from numbers you already hold — is what makes the toggle show even when a separatesync-staterun has not happened. On Code useWrite; on Desktop / Cowork callmureo_state_platform_metrics_set(passplatform,account_id, andperiods={"YESTERDAY": {…}, "LAST_30_DAYS": {…}}— include only the windows you actually have) — it merges per window key, so this never disturbs a window a priorsync-statewrote. Give every window you write aperiod_end— the last calendar date those figures cover,YYYY-MM-DD, in the ad account's own timezone (so aYESTERDAYwindow pulled for an account on JST names the JST yesterday, whatever the clock on this machine says). You are the only one who can: the server stampsfetched_atbecause it knows when the write happened, but it does not know the account's timezone and will not guess a date that could be off by one. Each window states its own — the windows end on different days and one date copied across them mislabels the rest. Without it the card is judged on when it was last written, which is how a run that wrote nothing leaves "Updated 14 hours ago" over figures from two days earlier. Honest scope / cost: only write a window whose numbers you actually pulled; do not fire an extra API call solely to populate a window, and write nothing at all for a platform recordedauth_errorortools_absent_for_<platform>this run — it has no numbers, and an empty tile is honest where an extrapolated one is not. In incremental mode you may only hold the window your analytics run covered — write just that one, and never mislabel a wider window asYESTERDAY. Best-effort: ifmureo_state_platform_metrics_setis unavailable, skip silently. But a window you did not write is never silent in the run output. This step is conditional by design, and a run that persisted the report (step 12) and the display (step 15) and said so reads as a complete run whether or not the cards moved. So name what you persisted and what you did not, per platform, the way step 3 names a platform it skipped and step 4 namesanalytics_not_available_for_<platform>: reportwindows_not_persisted_for_<platform>with the windows and the reason (no figures gathered for that window this run,analytics run covered a wider window,auth_error,tools_absent_for_<platform>,mureo_state_platform_metrics_setunavailable). A platform whose windows all landed says so in one line and needs nothing more. For a platform you could NOT collect, record why — callmureo_state_platform_not_collected_set(platform,account_id,reason: theauth_error/tools_absent_for_<platform>finding in one actionable sentence). If what failed was resolving the account itself — no accessible ad account, no customer id configured — passaccount_idas"": that is how mureo spells unknown, it is the one write tool that accepts it, and an unknown id never overwrites an id the entry already holds. Never invent a placeholder such as"unknown"or"none"— the same placeholder on two failed platforms reads as one ad account held under two keys, and the dashboard then withholds the client total rather than showing an inflated one. Writing no numbers is right, but silence alone leaves the card indistinguishable from an account that genuinely spent nothing, which is how a stale card goes unnoticed for days. Symmetrically, for every platform you DID collect, clear any note by calling the same tool withreasonomitted — nothing else retires it, and a note that outlives its failure is worse than none.
Day-grain history
- Persist the day-grain history (best-effort, zero extra platform API calls): step 4's delivery-collapse check already pulled a day-grain delivery report for the platforms it checked that way, and those rows are discarded the moment the check returns — so
platforms[<platform>].periodscan say what yesterday was and nothing can say whether it beat the day before. Fold the rows you already hold intoplatforms[<platform>].daily, a map keyed by calendar date (YYYY-MM-DD) whose values are totals-shaped rollups in the same canonical metric vocabulary (spend,impressions,clicks,conversions,cpa,ctr): sum that platform's per-campaign rows per date, one bucket per day. On Desktop / Cowork callmureo_state_platform_daily_set(passplatform,account_id, anddays={"2026-08-20": {…}, "2026-08-19": {…}}); on Code useWrite. It merges per date key, so a day already stored survives this write and re-writing a day just replaces that day. Never fire a platform call to fill this in — this step writes what step 4 fetched and nothing else; a platform whose collapse check ran server-side (mureo_analytics_run) returns findings, not rows, so you hold no daily rows for it and write none. Write only complete PAST days — today is still being spent into, and a part-spent day stored as a whole one is a false low that nothing ever corrects; the tool refuses today and any later date. Omit a day you have no figures for — never write zeros for it: a zero-filled day is indistinguishable from an account that stopped spending, and both the trend line and the collapse detector's baseline read it as one. Nothing needs backfilling: the history accumulates one run at a time, and mureo keeps the most recent 35 days in STATE.json and archives the days past that underhistory/daily/<YYYY-MM>.jsonrather than dropping them. Best-effort: ifmureo_state_platform_daily_setis unavailable, skip silently.
Display contract
Persist the display contract (best-effort): Call
mureo_state_display_setwith what the DASHBOARD shows. This is a different audience from the summary above — STATE.json is written for the next agent, and this small bounded section is the only thing the operator's screen reads. Write it in the SAME pass as the report, from the SAME figures, and reach no new verdict here: every value below is one you already decided, and this step only renders it.source: your own skill name — REQUIRED whenever you write any section, at most 24 characters. The contract is replaced wholesale by whoever writes it last, so this is what lets the card say whose answer it is showing.generated_atis stamped by the server — do not compute it.nav_message: the ONE line the operator should act on today — the single thing today needs, e.g."Brand Search stopped delivering 2 days ago — check the account in the UI". One line, not a summary of the report.highlights: at most 3 chips, and thetonemust match the verdict you already gave —goodfor a target met or a clear win,watchfor something to keep an eye on,badfor something deteriorating. Choose the three that matter; a fourth is refused and nothing is dropped for you. Map a finding's severity to a chip tone: action → bad / watch → watch / positive → good. info does NOT become a highlight — a neutral note would spend one of the 3 chips an action or a win needed, and it is still in the report for whoever wants it.breakdown.campaigns/.adgroups: one row per entity you judged, carrying figures you ALREADY hold —spend,mcpa(the measured CPA) andtarget_cpa— plusstatefrom the closed set (target_met/improving/watch/worsening/no_data) and anoteof at most 40 characters. Usecampaignsfor the campaigns you verdicted in step 10 andadgroupsfor any ad set / ad group you judged; thestateis the verdict you already reached, never a fresh one. Omit a figure you do not have:0states a perfect CPA rather than the absence of one, andno_datais the state for an entity with too little delivery to judge.proposals: the recommendations you did NOT carry out, one entry each —title(what to do) plusbody(why, in one line) plusdate=server_now's date asYYYY-MM-DD. Something you DID apply this run is the same entry withstatus: "done". Record each of these as a decision too —mureo_decision_recordwithstatus: "proposed", your reasoning inrationale, the figures you judged it on inmetricsand the effect you expect inexpected(the operator approves an effect, not only a call). Any change you DO make carriesreasonandexpectedon the mutating call. This section is the screen and is replaced whole by the next skill that writes it; the decision record is the one that survives, and it is what stops the next session re-proposing something the operator already turned down.stated_values: label + figure chips ONLY ({"label": "…", "value": …}) — the figures your verdicts rested on:{"label": "CVR", "value": 0.021},{"label": "goals met", "value": "3 of 7"}. No prose notes here. A sentence lands in a numeric column and the tool refuses it; anything that needs a sentence stays in the report'snarrativeabove, exactly where it goes today.
A verdict hedged in the report stays hedged here. This step renders the verdicts you already reached — including the reservations attached to them. If the
narrativesays you cannot tell,nav_messageandhighlightsmay not say you can: a finding must not read as certain merely because it moved to a shorter field. A chip holds 60 characters and the nav line 80, so there is no room for the reservation, and dropping it is exactly what turns "we cannot attribute this" into "this is today's top risk". The finding loses the slot, not the reservation — give the line to something you can state plainly, and leave the hedged finding in the report, where it is stated in full. Nothing is lost:narrativeandflagsare both on the drill-down.The bounds, verbatim from the tool: The dashboard reads THIS section and nothing else — keep your reasoning where it already goes. Every bound below refuses the write rather than truncating it, because a sentence cut in half is worse than a long one. nav_message: one line, at most 80 characters. highlights: at most 3 items of {tone, text}, tone one of good/watch/bad, text at most 60 characters. proposals: {title, body, status, date}, title at most 30 and body at most 80 characters, status one of proposed/done. breakdown.campaigns / breakdown.adgroups: rows of {name, spend, mcpa, target_cpa, state, note} — the three figures are raw numbers, state is one of target_met/improving/watch/worsening/no_data, note at most 40 characters. stated_values: {label, value}, label at most 24 characters and value a raw number or a string of at most 12 characters — a sentence there is refused, because it lands in a numeric column. Do NOT write the KPI funnel or the daily chart: mureo computes both from the stored totals.
Over a bound the write is REFUSED — shorten and rewrite, do not re-send the same sentence trimmed. Lead with the point and drop the connectives; a noun phrase is fine (
CPA 12% over target, notThe CPA is currently running about 12% above the target we agreed). Trimming one character at a time and calling again spends the run's context on a bound one rewrite would have met.One writer per run — and you may not be the first today. Within your own run the contract has ONE writer:
mureo_state_display_setREPLACES the whole section rather than merging into it, so compose every section you want on screen before calling, and never call twice. Across runs on the same day that same replacement is what the rule below is about — the morning's screen is what you are overwriting.You may not be the first writer today.
displayis REPLACED WHOLE and the last writer wins — there is no merge. Before you write it, read the current one (mureo_state_get). Of what another skill wrote TODAY, carry exactly one thing into your own write: itsproposalsthat are still live — not yet done, and not contradicted by what you just found. Everything else you write from your own run alone, because a screen assembled from two runs shows a moment that never happened. And carry over NOTHING ELSE: never copy another skill'snav_message,highlights,breakdownorstated_values, which would put its judgement under your name when you cannot vouch for it. Name yourself insourceso the screen says whose answer it is.This is best-effort: if
mureo_state_display_setis unavailable (e.g. a pure file-mode host without the context MCP), skip it silently — the rest of this skill still works.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

