Skip to content

logly/mureo

v0.10.31 Breaking

This release includes breaking changes for platform teams planning a safe upgrade.

✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

advertising agentic-ai ai-agents claude-code cli codex
+11 more
cursor facebook-ads gemini-cli google-ads marketing marketing-automation mcp meta-ads model-context-protocol python search-console

Summary

AI summary

Updates Key fixes, Highlights, and internal across a mixed release.

Full changelog

Highlights

  • Meta video creatives, end to end. meta_ads_creatives_create now
    builds a video creative as well as an image one: pass video_id plus a
    thumbnail and the tool emits object_story_spec.video_data instead of
    link_data. Two new read-only tools close the loop —
    meta_ads_videos_get returns the raw nested processing status so an
    agent can poll until an upload is ready, and meta_ads_videos_thumbnails
    lists the auto-generated thumbnails to pick one from. Meta tool count
    86 -> 88 (205 total).
  • No more stale "today". Nothing in the stack ever told the agent the
    current date, so it inferred one from STATE.json and /daily-check could
    short-circuit with "today's data is already fetched" against a date days
    in the past. mureo_state_get and mureo_strategy_get now both return
    server_now — the server's clock as ISO 8601 with a UTC offset — so
    a skill can establish the date from whichever file it reads first, and a
    Bash-less headless host never needs to shell out to date.
  • Date discipline rolled out to 15 skills. Every date-sensitive skill
    gains an explicit step-0 "establish today from server_now", adapted to
    what it computes: reporting windows, goal deadlines, experiment
    start/end and the no-peeking gate, and the observation_due dates that
    seven skills write forward from today. generated_at in each persisted
    report summary comes from the same value.
  • Allocation & learning-state diagnostic discipline. The pro-diagnosis
    catalogue gains three principles that hold identically on Meta and
    Google Ads — judge by marginal efficiency, not averages; respect
    learning states (learning-phase entities are Watch, not Action-needed);
    and don't hand-optimize inside an auto-allocated unit. Guard notes
    pointing back at the section were added across daily-check,
    budget-rebalance, audience-review, ad-fatigue-check and
    search-term-cleanup.
  • Video uploads no longer buffer the whole file in memory.
    upload_ad_video_file streams off disk, so a 1 GB upload no longer
    allocates 1 GB of RSS, and the shared retry loop rewinds every seekable
    multipart part before each attempt so a 429 provably re-sends an
    identical, non-empty body.

Behavior change worth knowing

No breaking changes in this release, but one server-side behavior change:
mureo_state_action_log_append now stamps the entry timestamp from
mureo's own clock.
A value supplied by the caller is still accepted by
the schema — the property could not be removed without breaking existing
callers under additionalProperties: false — but is now ignored, and
timestamp is no longer a required input. This closes the loop that let a
drifted model date be persisted and read back later as evidence of the
current date. The other action_log writers (native status toggles, plugin
mutations, creative-studio audits, rollback reversals) and both Creative
Studio manifest.json writers moved onto the same clock, so every entry
now carries a consistent offset-bearing local timestamp.

Key fixes

  • Meta video file upload rebuilt on the shared client machinery. It
    used to open its own httpx.AsyncClient and call a bare
    raise_for_status(), so Meta's error JSON (message / error_subcode /
    fbtrace_id) was discarded and the caller only saw httpx's generic
    400 Bad Request; retries, 429 handling and rate-limit monitoring did
    not apply either. It now routes through _post/_request, declares the
    real container MIME per extension, moves auth to the Bearer header, and
    raises the per-request timeout from 120s to 600s.
  • Meta video size cap raised from 100 MB to 1 GB, matching Graph's
    documented ceiling for the non-resumable upload, and both video upload
    tool descriptions now state the real format list and cap instead of the
    inaccurate "MP4, MOV. Recommended max 4 GB".
  • Meta Ads skill Tool Summary table completed. Five shipped tools were
    missing from the numbered table, so it listed 81 of 86 tools. It now
    covers all 88 and is renumbered sequentially. The ### videos section
    also documented a description parameter that neither tool accepts.

Added

  • Meta video creatives, end to end. meta_ads_creatives_create now builds
    a video creative as well as an image one: pass video_id plus exactly one of
    video_thumbnail_image_hash / video_thumbnail_image_url and the tool emits
    object_story_spec.video_data (thumbnail, title, message,
    link_description, and the destination link nested under
    call_to_action.value.link) instead of link_data. Video and image
    parameters are mutually exclusive. Two parameters that are optional for an
    image creative are enforced as hard requirements in video mode: a thumbnail
    (Meta requires one) and call_to_actionvideo_data has no link field,
    so the CTA is the only carrier of the destination and accepting a CTA-less
    video would silently discard the required link_url. link_url is injected
    into call_to_action.value.link automatically; callers pass only the button
    label. Two read-only tools complete the flow:
    meta_ads_videos_get returns the raw nested processing status
    (video_status / processing_phase) so an agent can poll until the upload is
    ready before creating a creative, and meta_ads_videos_thumbnails lists
    the auto-generated thumbnails (id, uri, is_preferred, height,
    width) to pick one from. Both are node-level Graph reads, not
    act_-scoped. Meta tool count 86 -> 88 (205 total).

  • Cross-platform allocation & learning-state diagnostic discipline. The
    pro-diagnosis framework catalogue (skills/_mureo-pro-diagnosis) gains an
    Allocation & Learning-State Discipline section covering three principles
    that hold identically on Meta and Google Ads: judge by marginal efficiency,
    not averages
    (a bad average is a candidate, not a verdict — saturated
    winners scale badly and suppressed losers look worse than they are);
    respect learning states (a Meta ad set in its learning phase or a Google
    Ads bid strategy in "Learning" is Watch-only, its cost per result is not
    steady-state, and edits that reset the clock need a reason worth the reset —
    prefer target steps of ~20% or less); and don't hand-optimize inside an
    auto-allocated unit
    (per-breakdown rows are outcomes of the platform's own
    allocation, not levers — Smart Bidding ignores manual bid adjustments except
    a −100% device opt-out, so cutting an expensive-looking row shrinks the
    optimizer's inventory rather than redirecting its budget). Each principle
    ships with per-platform manifestations and "what to say instead" phrasing for
    recommendations. Guard notes pointing back at the section were added across
    the operational skills: daily-check (learning-state entities are Watch, not
    Action-needed), budget-rebalance (every reallocation reason must be
    marginal, not an average ranking), audience-review (no breakdown-average
    exclusions; validate via /experiment and act at the platform's optimization
    unit), ad-fatigue-check (learning-phase instability is not creative
    fatigue), and search-term-cleanup (systematic waste vs low-volume terms with
    a bad average). /experiment now advertises breakdown-exclusion and
    reallocation hypotheses as things it validates.

Changed

  • Meta video upload no longer buffers the whole file in memory.
    upload_ad_video_file hands httpx the open file handle instead of reading
    the file into a bytes object first, so a 1 GB upload streams off disk
    rather than allocating 1 GB of RSS. The handle is kept open for the whole
    request, and the shared _request retry loop now rewinds every seekable
    part of a files mapping to byte 0 before each attempt — so a 429 or
    transport retry provably re-sends an identical, non-empty body. Behaviour on
    the wire is unchanged (source part, real filename, real container MIME,
    600s timeout), and the image path (base64 bytes form body) is untouched.
    Resumable/chunked upload for files above the 1 GB Graph ceiling remains
    unimplemented; oversized files still fail validation locally.

  • Internal: Meta AdVideo operations split into their own mixin. The video
    upload/read machinery (upload_ad_video, upload_ad_video_file,
    get_ad_video, list_ad_video_thumbnails and their constants/MIME map)
    moves from mureo/meta_ads/_creatives.py to a new sibling
    mureo/meta_ads/_videos.py (VideosMixin, registered on
    MetaAdsApiClient), bringing _creatives.py from 939 lines back under the
    800-line budget. The split follows the Graph node each module talks to:
    /advideos + /{video-id} here, /adcreatives there — so the
    video-creative build helpers stay with create_ad_creative and neither
    module imports the other. Pure move, no behaviour change; every public
    client method keeps its name and signature.

  • Configure-UI asset split: auth_wizards_meta.js (internal). The Meta
    halves of the auth wizards — the hosted-connector setup card, the
    connection-method chooser and the system-user token card — moved out of
    auth_wizards.js (1248 lines, over the 800-line house budget) into a new
    mureo/_data/web/auth_wizards_meta.js, loaded immediately before it and
    reached through window.MUREO_AUTH_META, the same plain-global sharing
    pattern the other configure-UI assets already use. A pure move: no renamed
    functions, i18n keys, DOM classes or logic, and no behavior change for
    operators.

  • action_log timestamps are stamped server-side (#460).
    mureo_state_action_log_append now writes the entry timestamp from
    mureo's own clock (ISO 8601 with UTC offset); a value supplied by the caller
    is still accepted by the schema — the property could not be removed without
    breaking existing callers under additionalProperties: false — but is
    ignored, and timestamp is no longer a required input. This closes the
    loop that let a drifted model date be persisted and read back later as
    evidence of the current date. The other action_log writers (native status
    toggles, plugin mutations, creative-studio audits, rollback reversals) moved
    onto the same clock, so every entry now carries a consistent offset-bearing
    local timestamp instead of a UTC one.

  • Creative Studio generation manifests use the shared clock too (#460).
    The created_at field of both manifest.json writers
    (creative_studio_generate_visual and creative_studio_compose) was the
    last datetime.now(timezone.utc) left in the tree — a second clock in the
    same module whose audit entries already used mureo.core.clock, and one
    that rolled the day at the wrong moment for an operator on a positive UTC
    offset (an evening run filed under tomorrow). Both now stamp
    server_now_iso(), so the manifest and its audit entry agree and the value
    is reachable by the same test seam. The only consumer, the read-only
    creative gallery, passes created_at through as a display string and orders
    runs by run id, so the format change is presentational.

Fixed

  • /daily-check no longer runs on a stale notion of "today" (#460).
    Nothing in the stack ever told the agent the current date, so it inferred
    one from STATE.json — reports.daily.period, last_synced_at, action_log
    timestamps — and sometimes short-circuited with "today's data is already
    fetched, re-displaying" against a date days in the past. The two context
    read tools now return server_now, the server's clock as ISO 8601 with
    a UTC offset (2026-07-28T10:12:33+09:00, host-local because ad operations
    reason in local days): mureo_state_get and mureo_strategy_get both carry
    it, so a skill can establish the date from whichever file it reads first —
    and a Bash-less headless host never needs to shell out to date.
    server_now is a response-envelope field only: the STATE.json parser
    ignores unknown top-level keys, so a copy echoed back into the file is
    dropped by the next mureo write instead of fossilising a "today".
    daily-check, sync-state and budget-pacing gain an explicit first step
    that makes server_now the only source of the current date, forbids the
    already-fetched short-circuit (a daily check always fetches fresh platform
    data for the period ending on server_now's date), and — for
    budget-pacing — derives the pacing month and the elapsed / remaining day
    counts from it.

  • The same date discipline now covers every date-sensitive skill (#460).
    The first three skills were the ones the bug was observed in; twelve more
    derive a date from "today" and were equally exposed. Each gains the same
    step-0 "establish today from server_now", adapted to what it actually
    computes: weekly-report ends its 7-day window on server_now's date
    instead of "7 days from today"; monthly-report names the previous full
    calendar month, the MTD cut-off and the explicit YYYY-MM-DD..YYYY-MM-DD
    month-before-last range from the clock; goal-review measures days-remaining
    against each Goal's Deadline from it (a drifted date silently flips
    on-track / at-risk / off-track); experiment derives start_time,
    end_time and the "has the window closed?" gate from it, so the no-peeking
    rule cannot be defeated by an early evaluation; and incident-postmortem
    only scores an action whose observation_due has genuinely passed. The seven
    skills that write an observation_due counted forward from today —
    budget-rebalance, creative-refresh, search-term-cleanup,
    audience-review, ad-fatigue-check, rescue, tracking-health — now
    count it from server_now's date, so an outcome review is never scheduled
    for the wrong day. generated_at in each persisted report summary comes from
    the same value. ad-fatigue-check additionally records that its
    week-over-week windows are platform-side period tokens (last_7d /
    LAST_7_DAYS), resolved by the platform's own clock, and must not be
    hand-built as date ranges.

  • Meta video file upload rebuilt on the shared client machinery.
    meta_ads_videos_upload_file used to open its own httpx.AsyncClient and
    call a bare raise_for_status(), so Meta's error JSON (message /
    error_subcode / fbtrace_id) was discarded and the caller only saw httpx's
    generic 400 Bad Request; retries, 429 handling and rate-limit monitoring
    did not apply either. The upload now routes through _post/_request (which
    regained optional files= multipart support), surfacing Meta's diagnostics
    for free. The multipart part keeps its documented source field name and the
    real filename but now declares the real container MIME per extension
    (video/mp4, video/quicktime, video/x-msvideo, video/x-ms-wmv,
    video/x-matroska) instead of application/octet-stream; the file bytes are
    materialized so a retry re-sends the full body; auth moved from an
    access_token form field to the Bearer header; and the per-request timeout
    went from 120s to 600s.

  • Meta video size cap raised from 100 MB to 1 GB, matching Graph's
    documented ceiling for the non-resumable upload. Both video upload tool
    descriptions now state the truth — supported formats MP4 / MOV / AVI / WMV /
    MKV, up to 1 GB via these tools, larger files needing resumable upload (not
    yet supported) — replacing the inaccurate "MP4, MOV. Recommended max 4 GB".

  • .flv no longer accepted by the video upload handler. The MCP handler's
    extension tuple allowed .flv while the client's allow-list did not, so an
    .flv file passed the handler gate only to be rejected one layer deeper with
    a different message. The client set is now the single source of truth.

  • Meta Ads skill docs corrected for the video tools: the ### videos
    section documented a description parameter that neither tool accepts, and
    stated neither the real format list nor the size cap. The creatives.create
    entry also understated its required parameters (page_id / link_url).

  • Meta Ads skill Tool Summary table completed. Five shipped tools were
    missing from the numbered table — meta_ads_pages_list,
    meta_ads_pages_upload_photo, meta_ads_pixels_create,
    meta_ads_targeting_search and meta_ads_targeting_categories — so it
    listed 81 of 86 tools. The table now covers all 88 and is renumbered
    sequentially.

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track logly/mureo

Get notified when new releases ship.

Sign up free

About logly/mureo

Framework for AI agents (Claude Code, Cursor, Codex, Gemini) to operate Google Ads, Meta Ads, and Search Console. Grounded in a local STRATEGY.md — not metric-chasing. Defense-in-depth security, local-first. Apache 2.0.

All releases →

Related context

Beta — feedback welcome: [email protected]