@clawhub-sebclawops-b50fc4917d
Run approved long-running project work from file-backed state, continue through self-clearable tasks, pause cleanly at real gates, and recover across sessions.
--- name: project-loop description: Run approved long-running project work from file-backed state, continue through self-clearable tasks, pause cleanly at real gates, and recover across sessions. --- # Project Loop Use for approved multi-step work that must survive resets, compaction, interruption, approval gates, and stalled execution. Do not use for trivial one-turn work, open-ended autonomy, or projects without a clear owner. ## Core Rules - Project files, not chat, are the source of truth - Trust this order: `state.json` > `manifest.md` > `validation.md` > `handoff.md` > memory > chat - Only the `owner_agent` in `state.json` executes project tasks - Other agents may improve the skill, but do not run the loop - Do not wait for human confirmation between self-clearable tasks - Keep executing until an approval gate, blocker, or stop condition is reached - After completing a chunk and successfully updating state, immediately begin the next eligible self-clearable chunk in the same turn - Do not wait for a human response between self-clearable tasks after a successful state update - Stop only for a real blocker, approval gate, failed state update, or explicit stop condition - Before starting any task, check whether the output already exists - If the work already landed, mark it done and advance - On every resume, verify `state.json` against actual reality before executing - During active execution, send a status update at least every 5 minutes - A 5-minute update must include one of: artifact created, task completed, blocker, or exact next action in progress - Do not send empty “still working” updates ## Required Project Files Project path: ```text agents/<agent-id>/projects/<project-slug>/ ``` Required: ```text README.md manifest.md state.json validation.md ``` Optional: ```text handoff.md notes.md artifacts/ logs/ ``` ## `state.json` Minimum Fields - `project_id` - `owner_agent` - `status` - `phase` - `current_task_id` - `last_checkpoint` - `next_action` - `blocked_reason` - `awaiting_approval` - `approval_items` - `retry_count` - `max_retries` - `last_session_note` - `resume_instructions` - `interrupted` - `active_loop` Recommended additions: - `approved_objective` - `scope_guardrails` - `task_queue_snapshot` - `completed_tasks` - `blocked_tasks` - `deferred_tasks` - `artifacts` - `validation_status` - `last_error` - `last_error_at` - `budget_hints` - `resume_history` - `watchdog` ## Session Start and Resume Read in this order: 1. `state.json` 2. `manifest.md` 3. `validation.md` 4. `handoff.md` if present 5. only then memory or chat if still needed Never start by asking “what were we doing?” if project files exist. On resume: - read `status`, `phase`, `current_task_id`, `next_action`, `resume_instructions` - verify state against actual reality before doing anything - continue from recorded state, not guesswork ## State Machine States: - `Draft` - `Ready` - `Running` - `Validating` - `AwaitingApproval` - `Blocked` - `Paused` - `Done` - `Abandoned` Allowed transitions: ```text Draft -> Ready Ready -> Running Running -> Validating Validating -> Running Validating -> Done Running -> AwaitingApproval Validating -> AwaitingApproval Running -> Blocked Validating -> Blocked Running -> Paused AwaitingApproval -> Running Blocked -> Running Paused -> Running Any state -> Abandoned ``` Rules: - `Draft -> Ready` only when objective, scope, manifest, and validation exist - `Ready -> Running` only when the next chunk is sized and eligible - `Running -> Validating` only after outputs are recorded - `Validating -> Running` only after pass and a next task is eligible - `Validating -> Done` only when required tasks are complete and final checks pass - `Running/Validating -> AwaitingApproval` for true human-cleared boundaries - `Running/Validating -> Blocked` when a blocker cannot be self-cleared within retry rules - `Running -> Paused` only for interruption, manual pause, or no eligible task ## Execution Rules - One chunk = one objective + one validation target - If a task has more than one verb, split it - Split at boundary changes: local prep -> remote write, remote write -> validation, planning -> execution, authenticated action -> read-back - Never bundle multiple operations into one command if they can be independent - If any command or execution step is denied for obfuscation or size, split it into smaller independent steps and retry - This applies to file writes, API calls, shell commands, browser actions, and any other execution path A chunk is too large if it: - has more than one primary deliverable - crosses multiple boundaries - mixes planning, execution, and validation - touches too many pages, endpoints, or files at once - cannot be described with one clear done condition ## Progress Rules - Updating `state.json`, `manifest.md`, `validation.md`, or `handoff.md` alone does not count as execution progress unless the current task is explicitly documentation-only - Do not claim forward execution unless the external world changed or a required artifact for the current task was created - When marking a task complete, record concrete evidence in state and in your reply when relevant: artifact path, file changed, page changed, validation evidence, or blocker artifact - If you cannot point to the output, the task is not done - Do not spend more than 2 consecutive project turns on planning, loop setup, or state reshaping without either executing, validating, or escalating - If the user questions silence or progress, stop meta narration and report only: what changed, what artifact exists, what blocker exists, and what exact action is next - Do not report system status theatrically. Report concrete project facts only ## Validation Rules No task is complete until validation passes and the result is recorded. Validation may include: - artifact existence/content check - file diff or file review - browser verification of rendered state - API read-back - log/command review - human review for customer-facing release steps Validation records should capture: - task ID - method - pass/fail - timestamp - evidence location - next step If validation fails: - do not advance - record exact failure - retry only with a bounded corrective chunk or move to `Blocked` ## Approval and Pause Rules Self-clearable work may continue if it stays in scope. Human-cleared work includes: - credential handling - auth construction - authenticated remote writes - WordPress REST writes - installs or config changes - third-party sends - publish/live-release steps unless already pre-approved - actions requiring manual login or privileged access When a true approval gate is reached: - set `status` to `AwaitingApproval` - record the exact pending action in `approval_items` - record what prep is already done - continue only if another independent task is eligible - otherwise pause cleanly Do not pause at routine internal chunk boundaries. Pause only for: - real blocker - missing access or credentials - meaningful human decision - explicit stop/pause - genuine risk or scope boundary ## Interruption and Recovery If interrupted by unrelated work: - finish any atomic write already in progress if safe - update `state.json` immediately - set `interrupted=true` - set `active_loop=false` - set `status=Paused` unless already `AwaitingApproval` or `Blocked` When updating `state.json`, use a full-file write by default, not a partial edit. `state.json` changes frequently and exact-match edit operations are brittle on high-churn files. Use partial edit only if you have just read the current file and the change is truly small, stable, and low-risk. If `state.json` cannot be updated: - stop execution immediately - this is a hard stop, not a soft warning - do not continue the loop, do not start another chunk, and do not claim progress until state is repaired or successfully rewritten - report the exact failure and recovery step If a task may have partially executed: - verify actual reality first - do not blindly rerun - choose next action from observed state, not assumptions If state is missing or inconsistent: - reconstruct from manifest, validation, artifacts, and handoff - rewrite state before resuming ## Watchdog Rules Cron is a watchdog only, never the main workflow engine. - When creating a new project, automatically create a watchdog cron for that project - When a project moves to `Done` or `Abandoned`, remove the watchdog - The watchdog only reports when it takes an action or finds something worth surfacing - If there is nothing to resume, retry, or escalate, stay silent Watchdog may: - detect stale `Running` - detect resumable `Paused` - detect approval-cleared states ready to continue - trigger one bounded resume pass Watchdog must not: - invent new work - rewrite scope - cross approval gates without clearance - spam retries - chain arbitrary timer-based work Recommended cap: - one automatic resume attempt per cycle - no more than two automatic recovery attempts without new evidence ## Operating Sequence 1. Read `state.json` 2. Read `manifest.md` 3. Read `validation.md` 4. Read `handoff.md` if present 5. Verify current state against actual reality 6. Check whether the target output already exists 7. Pick one eligible chunk 8. Apply chunk sizing rules 9. Execute 10. Validate 11. Update `state.json` 12. Continue if self-clearable, otherwise pause or escalate cleanly FILE:README.md # Project Loop Lean orchestration skill for approved long-running work that must survive resets, interruption, approval gates, and stale sessions. ## Use It When - a project will outlast one session - chat history is not enough - work should continue through self-clearable tasks - approvals, validation, pause/resume, and recovery matter Do not use it for trivial one-turn tasks or open-ended autonomy. ## Core Model - project files are the source of truth - trust: `state.json` > `manifest.md` > `validation.md` > `handoff.md` > memory > chat - only the `owner_agent` in `state.json` runs project tasks - do not wait between self-clearable tasks - after completing a chunk and successfully updating state, immediately begin the next eligible self-clearable chunk in the same turn - stop only for a real blocker, approval gate, failed state update, or explicit stop condition - on resume, verify `state.json` against reality before executing - check whether output already exists before redoing work ## Required Files ```text agents/<agent-id>/projects/<project-slug>/ README.md manifest.md state.json validation.md ``` Optional: ```text handoff.md notes.md artifacts/ logs/ ``` ## State Machine States: - `Draft` - `Ready` - `Running` - `Validating` - `AwaitingApproval` - `Blocked` - `Paused` - `Done` - `Abandoned` Typical flow: ```text Draft -> Ready -> Running -> Validating -> Running -> Done ``` ## Key Rules - one chunk = one objective + one validation target - if a task has more than one verb, split it - split at boundary changes - if a command or step is denied for obfuscation or size, split and retry smaller - never bundle unrelated operations into one command - docs/state updates alone do not count as execution progress unless the task is documentation-only - do not claim completion without concrete evidence - when updating `state.json`, use a full-file write by default, not a partial edit. Partial edits are brittle on a high-churn state file - if `state.json` update fails, stop immediately. This is a hard stop. Do not continue, do not start another chunk, and do not claim progress until state is repaired - do not spend more than 2 consecutive turns in meta/planning without executing, validating, or escalating ## Progress Updates During active execution, send a status update at least every 5 minutes. A valid update must include one of: - artifact created - task completed - blocker found - exact next action in progress ## Approval and Pause Pause only for real gates: - missing access or credentials - human decision - real blocker - explicit stop/pause - meaningful risk boundary Do not pause at routine internal chunk boundaries. ## Validation No task is done until validation passes and evidence is recorded. Validation can include: - artifact checks - file review - browser verification - API read-back - rendered output checks - human review for customer-facing release steps ## Watchdog The watchdog is for project-loop recovery only. - auto-create when a new project is created - remove when project reaches `Done` or `Abandoned` - stay silent if there is nothing to resume, retry, or escalate - only report when it acts or finds something worth surfacing - never invent work or bypass approval gates ## Operating Flow 1. read `state.json` 2. read `manifest.md` 3. read `validation.md` 4. verify reality 5. check whether output already exists 6. pick one chunk 7. execute 8. validate 9. update `state.json` 10. continue or pause cleanly ## Note Project Loop is the orchestration layer. Project-specific truth should live in each project folder, not in chat.
Break a project, objective, or task list into safe, bounded one-time OpenClaw cron work blocks with dependencies, risk classification, and strong isolated ag...
--- name: project-workflow-scheduler description: Break a project, objective, or task list into safe, bounded one-time OpenClaw cron work blocks with dependencies, risk classification, and strong isolated agentTurn payloads. Use when an agent needs to stage multi-step work over time without autonomy theater, especially for overnight blocks, next-day follow-up, recap jobs, checkpoints, low-risk internal analysis, documentation cleanup, audits, asset prep, CRM cleanup, support follow-up, or phased project execution. --- # Project Workflow Scheduler Turn a multi-step project into a supervised sequence of one-time cron jobs. ## Use this pattern 1. Evaluate whether the work is cron-friendly. 2. Break it into bounded blocks. 3. Classify each block. 4. Schedule only the safe blocks. 5. Document what was scheduled and why. 6. Reassess after results before scheduling the next risky phase unless the user already approved the full sequence. Use the cron tool to propose or create one-time isolated `agentTurn` jobs. Bias toward fewer, better, well-scoped jobs. ## Decide if a block is cron-friendly Prefer blocks that are: - clearly scoped - auditable - low-risk or reversible - likely completable in one run - rich enough in context that a future isolated run can succeed without the parent session Good candidates: - project documentation cleanup - page or site audits - asset curation - follow-up checks - support-ticket follow-up prep - prep for the next project phase - safe internal analysis work - low-risk build prep tasks - recap, QA, and checkpoint passes Do not schedule: - live login flows - interactive browser approval flows - customer-facing sends without explicit approval - destructive actions - infra or config changes without explicit approval - purchases or commitments - vague "keep working until done" missions - work that depends on unstable browser state or fresh credential prompts - work that will likely require repeated subjective decisions from the user ## Classify every block Use exactly these buckets: - **Safe to schedule** - **Needs user approval first** - **Blocked by missing access** - **Should never be scheduled unattended** If a block is not clearly in the first bucket, do not schedule it. ## Break work into blocks Keep blocks small enough to finish in one run. Prefer fewer, better blocks over many tiny ones. For each block define: - **Block name** - **Objective** - **Inputs/context to include** - **Preconditions** - **Earliest safe run time** - **Expected output** - **Announce results or not** - **Whether a follow-up block should be scheduled later** Split large work by: - phase - dependency - risk boundary - handoff point - natural checkpoint Examples of clean splits: - audit -> recap -> implementation prep - cleanup -> QA -> follow-up - research -> asset prep -> doc update - migration audit -> staged change block -> verification block Avoid stuffing multiple fragile decisions into one run. ## Handle dependencies Mark each block as one of: - independent - depends on earlier block output - depends on user approval - depends on missing access If later work depends on likely outputs from earlier runs, write the later block so it explicitly references the expected artifacts or decisions to look for. Do not pre-schedule risky downstream blocks unless the user already approved that sequence. ## Write strong cron payloads Assume isolated `agentTurn` runs. Every scheduled block prompt should include: - the project name and current phase - the exact scope of this block - what already happened - what inputs/files/systems to inspect - what to produce by the end of the run - what not to do - how to report blockers - whether to recommend the next block instead of executing it Good payload traits: - bounded - concrete - context-rich - explicit about success criteria - explicit about forbidden actions Good prompt shape: ```text Project: <name> Block: <name> Objective: <one clear outcome> Context: - <important prior state> - <known files, folders, systems, links> - <assumptions that are safe> Do: - <step 1> - <step 2> - <step 3> Do not: - <unsafe or out-of-scope actions> - <customer-facing or destructive work> Expected output: - <deliverable> - <short recap> - <recommended next block if appropriate> If blocked: - report the blocker, what you tried, and the safest next step ``` ## Avoid autonomy theater Do not present scheduled work as autonomous ownership. The point is to create supervised, accountable work blocks that: - run later - stay bounded - leave an audit trail - make reassessment easy Prefer: - checkpoint jobs - recap jobs - verification jobs - prep jobs - follow-up jobs Not: - giant open-ended production runs - fuzzy missions with no stopping rule - chains that assume too much hidden context ## Scheduling guidance Prefer one-time jobs over recurring jobs for project orchestration. Common patterns: - same-night audit, prep, or cleanup block - next-morning recap block - next-day follow-up block after review - staged overnight sequence where each later block is safe only if earlier output is likely predictable Bias toward manual reassessment before scheduling the next risky phase. ## Documentation guidance When orchestration materially changes project state, recommend updating project docs so future sessions know: - what was scheduled - why it was scheduled - dependencies and assumptions - what results to look for next If useful, suggest a short handoff note or README update. ## Output format Produce: 1. **Short orchestration plan** 2. **Block-by-block schedule proposal** 3. **Recommended cron payload text for each scheduled block** 4. **Assumptions, dependencies, and risks** 5. **Doc update suggestions** if orchestration materially changes project state If the user has not yet approved scheduling, stop at the proposal. If the user already approved scheduling, create only the safe blocks and clearly note what still requires reassessment or approval. ## Example set Read `references/examples.md` when you need concrete patterns for: - website migration overnight blocks - CRM cleanup - support follow-up chain - audit + recap + next-step sequence - multi-phase projects where only early phases are safe to pre-schedule FILE:references/examples.md # Workflow Orchestrator Examples ## 1. Website migration project with overnight blocks ### Orchestration plan - Run a bounded pre-migration audit overnight. - Run a recap block in the morning. - Only schedule implementation prep after reviewing the audit. ### Blocks #### Block A: Pre-migration audit - Classification: Safe to schedule - Objective: Inventory files, dependencies, config touchpoints, rollback constraints, and migration risks. - Preconditions: Repo/filesystem access exists. - Earliest safe run time: Tonight - Expected output: Audit memo, risk list, proposed migration sequence - Announce: Yes - Follow-up: Morning recap block Payload skeleton: ```text Project: BOS website migration Block: Overnight pre-migration audit Objective: Produce a bounded migration-readiness audit. Context: - This is a staged migration project. - Focus only on discovery, inventory, risks, and rollout prep. - Assume the parent session may be gone by the time you run. Do: - inspect the project structure and deployment touchpoints - identify dependencies, env/config risks, and rollback concerns - produce a concise readiness memo with recommended next blocks Do not: - change production - deploy - delete anything - make infra or DNS changes Expected output: - migration-readiness summary - top risks - recommended next safe block ``` #### Block B: Morning recap - Classification: Safe to schedule - Objective: Compress the audit output into a decision-ready summary. - Preconditions: Audit output exists. - Earliest safe run time: Next morning - Expected output: Recap plus go/no-go recommendation for prep work - Announce: Yes - Follow-up: Manual review before risky phase #### Block C: Implementation prep - Classification: Needs user approval first - Objective: Prepare non-destructive migration artifacts. - Why not auto-schedule: Depends on audit findings and may surface meaningful tradeoffs. ## 2. CRM cleanup project ### Good sequence 1. Duplicate/rule audit 2. Cleanup proposal 3. Approved cleanup batch 4. QA pass #### Safe blocks - duplicate detection audit - field normalization proposal - post-cleanup QA report #### Needs approval - actual record merges - bulk status changes - customer-visible updates ## 3. Support-ticket follow-up chain ### Good sequence 1. Overnight ticket triage summary 2. Next-morning follow-up draft prep 3. Manual approval for customer-facing sends 4. Later status check block #### Example safe block - Review all open tickets in scope - group by urgency, owner, and stale age - draft recommended follow-up queue - do not send anything #### Example blocked/not safe unattended - sending replies to customers without approval - making refunds or commitments ## 4. Audit + recap + next-step sequence Use this when the task is ambiguous but safe to inspect. ### Sequence - Block A: audit current state - Block B: recap findings and propose next steps - Block C: prep artifacts for approved path This pattern is strong because it creates a natural checkpoint between discovery and action. ## 5. Multi-phase project where only the first few steps are safe ### Example A new internal dashboard project #### Safe to pre-schedule - requirements consolidation - asset and dependency inventory - README cleanup - QA checklist drafting #### Do not pre-schedule yet - production writes - live integration flips - customer-visible release steps ### Practical rule If later phases depend on subjective review, schedule only the first safe phase and add a recap block that explicitly recommends the next block instead of assuming it. ## Fast evaluation checklist Use this before scheduling: - Is the block clearly scoped? - Can it likely finish in one run? - Can it succeed without live approvals? - Is it reversible or low-risk? - Would the audit trail be clear afterward? - Does it avoid autonomy theater? - Should the next risky phase wait for review? If any answer is shaky, do not schedule it yet.
Shared Meta Ads skill for OpenClaw agents. Analyze Meta (Facebook and Instagram) ad accounts, campaigns, ad sets, ads, insights, and lead-form data with a pr...
---
name: openclaw-meta-ads
description: Shared Meta Ads skill for OpenClaw agents. Analyze Meta (Facebook and Instagram) ad accounts, campaigns, ad sets, ads, insights, and lead-form data with a production-safe read-first workflow. Use when an agent needs Meta Ads reporting, account audits, creative or audience diagnostics, lead-form review, or recommendations before live ad changes. Require explicit approval before any write action.
---
# OpenClaw Meta Ads
Use this skill for Meta Ads work across agents.
This is a shared skill, not a Poseidon-only skill.
Keep account-specific tokens, account IDs, and business rules outside the core skill.
## Use this skill for
- account and campaign performance reporting
- insights analysis
- campaign, ad set, and ad diagnostics
- lead-form and lead pipeline review
- creative and audience review
- structured recommendations before live changes
## Default stance
Start read-only.
Analyze first, recommend second, change last.
Do not make live Meta Ads changes without explicit approval.
## Access model
Meta account access is agent-specific.
The skill is shared, but each agent should use its own token, account ID, and permissions.
If credentials or permissions are missing, stop and fix access before pretending the skill can do real work.
## Setup and references
Read only what you need:
- `references/api-setup.md` for token, permission, and connection basics
- `references/account-structure.md` for campaign, ad set, ad, creative, insights, and leads structure
- `references/insights-queries.md` for common query patterns
- `references/audit-workflows.md` for practical review flows
- `references/optimization.md` for optimization heuristics and common mistakes
- `references/browser-fallback.md` only when API access is unavailable or UI confirmation is explicitly needed
## Safety rules
- never expose tokens or secrets in files or chat
- prefer read-only analysis before any operational change
- require explicit approval for pausing, enabling, editing, budget changes, or creative changes
- treat optimization guidance as heuristic, not universal truth
- scrub lead data for PII before broader reasoning when needed
## Output style
Lead with findings and recommended actions.
Keep reports practical:
- what is happening
- what looks wrong
- what to check next
- what to change, if approved
FILE:README.md
# openclaw-meta-ads
Shared Meta Ads skill for OpenClaw agents.
## Goal
Give agents a practical, production-safe way to analyze Meta Ads accounts, inspect campaign structure, review performance, audit lead flow, and recommend improvements without mixing in account-specific business rules.
## What this skill does well
- account and campaign reporting
- insights analysis
- audit workflows
- lead-form review
- creative and audience diagnostics
- optimization guidance grounded in practical heuristics
## What this skill is not
- a Poseidon-only skill
- a replacement for business-specific campaign strategy docs
- a license to make live account changes without approval
- a place to store tokens or account secrets
## Structure
```text
openclaw-meta-ads/
├── SKILL.md
├── README.md
├── references/
│ ├── api-setup.md
│ ├── account-structure.md
│ ├── insights-queries.md
│ ├── audit-workflows.md
│ ├── optimization.md
│ └── browser-fallback.md
└── scripts/
├── get_account_info.py
├── get_campaigns.py
├── get_insights.py
└── get_leads.py
```
## Practical use order
1. verify token, account ID, and permissions
2. run read-only account or insights queries
3. audit the area in question
4. recommend actions
5. only make live changes with explicit approval
## Notes
- Keep account-specific learnings in the relevant agent or project docs.
- Keep the shared skill general.
- This v1 is intentionally docs-first. Add scripts later only when the API calls are stable and worth standardizing.
- If API access is unavailable, use the browser fallback only when explicitly needed.
FILE:references/account-structure.md
# Account Structure
## Core hierarchy
Meta Ads usually works like this:
1. Ad Account
2. Campaign
3. Ad Set
4. Ad
5. Creative
## What each layer usually controls
### Ad Account
- overall billing and access context
- top-level reporting
- account-wide lists of campaigns, ad sets, ads, creatives, and forms
### Campaign
- objective
- top-level budget in some setups
- broad directional strategy
### Ad Set
- audience
- placements
- optimization goal
- schedule
- budget in many setups
### Ad
- final delivery object
- creative attachment
- copy and call to action
### Creative
- image, video, text, headline, destination, CTA
### Insights
- performance metrics at account, campaign, ad set, or ad level
- common fields: spend, impressions, clicks, ctr, cpc, cpm, reach, frequency, actions, cost_per_action_type
### Lead Forms and Leads
- instant forms can generate leads directly in Meta
- lead data is often sensitive and may also sync downstream into systems like HubSpot
## Common confusion points
- campaign objective does not tell the whole optimization story
- ad set choices often drive audience and delivery more than campaign labels do
- good CTR does not guarantee good lead quality
- lead count does not guarantee pipeline value
- attribution and reporting windows can distort apparent winners and losers
FILE:references/api-setup.md
# API Setup
## Access model
Meta Ads access is account-specific.
Each agent that uses this skill should have its own token, account ID, and permissions.
## Typical credentials
- `META_ADS_API_KEY` or equivalent bearer token
- account ID in `act_<id>` format when calling the Graph API
## Permissions
Common permissions include:
- `ads_read` for reporting and insights
- `ads_management` for live changes
- lead access permissions for form lead retrieval where applicable
## Credential handling
Do not store tokens in tracked files or workspace notes.
Use the approved secure credential workflow for the environment and expose tokens to the runtime through approved environment injection only.
## Connection basics
Before doing meaningful work, confirm:
- token is present
- token has the right scopes
- correct ad account is selected
- expected endpoints respond without permission errors
## Important notes
- Meta Graph API versions change over time. Keep examples current when updating the skill.
- Read access and write access are not the same thing.
- Some insights or lead endpoints may fail without the right combination of ad account, page, app, or business permissions.
FILE:references/audit-workflows.md
# Audit Workflows
## 1. Account health check
Start with:
- last 30 day account insights
- active campaign list
- major trend changes in spend, clicks, leads, and cost per result
Look for:
- spend rising faster than leads or qualified actions
- campaigns delivering but not producing useful downstream outcomes
- obvious account underdelivery
- active campaigns with stale creatives or weak CTR
## 2. Wasted spend review
Check:
- campaigns with meaningful spend and weak results
- ad sets spending without quality actions
- ads with weak CTR or poor downstream performance
- placements or audiences consuming spend inefficiently
## 3. Lead quality and lead flow review
Check:
- which forms are producing leads
- whether lead volume lines up with downstream CRM results
- whether one campaign is flooding low-quality leads
- whether lead routing and sync are still intact
## 4. Creative fatigue review
Check:
- repeated high spend on the same creative with declining CTR
- weak engagement compared to sister ads in the same ad set
- any quality or ranking indicators that suggest the ad is stale or mismatched
## 5. Before recommending changes
Separate findings into:
- clear problem
- likely cause
- recommended next step
- change that needs explicit approval
Keep reports practical and short.
FILE:references/browser-fallback.md
# Browser Fallback
Use this only when:
- API access is unavailable
- the user explicitly wants UI confirmation
- a specific setting or workflow is easier to verify in Meta Ads Manager
## Default stance
Prefer API-based reporting and repeatable analysis.
Use the browser for confirmation, screenshots, or UI-only checks.
## Basic workflow
1. Open Meta Ads Manager in the approved browser context.
2. Confirm the correct business and ad account are selected.
3. Navigate to the relevant area:
- campaigns
- ad sets
- ads
- reports
- instant forms or leads
4. Snapshot the visible data.
5. Report findings clearly.
## Cautions
- Wrong account or date range makes Meta UI analysis unreliable fast.
- Confirm attribution view and date range before drawing conclusions.
- Do not make live UI changes without explicit approval.
FILE:references/insights-queries.md
# Insights and Query Patterns
Use these as practical starting points.
Adapt fields and breakdowns to the actual endpoint and permissions available.
## Account-level reporting
Fetch:
- spend
- impressions
- reach
- clicks
- ctr
- cpc
- cpm
- actions
- cost_per_action_type
Use this for:
- quick performance summary
- last 7, 30, or 90 day review
- headline trend checks
## Campaign-level review
Fetch campaign identity plus insights for:
- objective
- status
- spend
- clicks
- ctr
- cpc
- leads or relevant actions
- cost per result
Use this for:
- top and bottom performer review
- wasted spend review
- scale or pause recommendations
## Ad set review
Fetch:
- audience or targeting summary
- optimization goal
- budget
- delivery status
- spend
- click and result metrics
Use this for:
- audience comparison
- placement or targeting issues
- weak delivery diagnostics
## Ad and creative review
Fetch:
- ad name and status
- creative identifiers
- spend
- impressions
- ctr
- cost per result
- quality or ranking metrics when available
Use this for:
- creative fatigue checks
- winner and loser identification
- ad-level troubleshooting
## Breakdown ideas
Useful breakdowns may include:
- age
- gender
- region or country
- publisher platform
- platform position
- device platform
- daily increments
## Lead retrieval
When lead forms are in play:
- identify the relevant form IDs
- retrieve leads carefully
- treat lead payloads as potentially sensitive
- prefer minimal necessary fields when analyzing quality or flow
FILE:references/optimization.md
# Optimization Heuristics
Use these as practical rules of thumb, not as universal truth.
## Structure
- Keep campaign goals clear. Mixed intent makes diagnosis harder.
- Ad sets usually control the real delivery logic, so weak performance often lives there.
- Separate testing from scaling when possible.
## Creative
- Good Meta performance often depends more on creative freshness than small targeting tweaks.
- High frequency with falling CTR is a fatigue warning.
- Strong click performance can still hide poor lead quality.
## Audience
- Broad audiences can work well, but only if creative and conversion signals are strong.
- Narrow audiences can protect relevance but choke delivery.
- Audience overlap can hide where spend is really being wasted.
## Budget and scaling
- Sudden budget jumps can destabilize delivery.
- Scale winners deliberately, not emotionally.
- A campaign can look efficient at low spend and fall apart when scaled.
## Measurement
- Cost per lead is not the same as cost per qualified lead.
- Attribution windows and view-through behavior can make performance look better or worse than business reality.
- Always compare platform results with downstream CRM outcomes when possible.
## Lead generation
- Cheap leads are not always good leads.
- Form friction that is too low can flood junk.
- Lead quality issues are often funnel issues, not just ad issues.
Structured retrospectives and execution-memory hygiene for OpenClaw agents. Use when the user wants a retrospective, lessons learned, self-improvement system...
---
name: retrospective-agent
description: Structured retrospectives and execution-memory hygiene for OpenClaw agents. Use when the user wants a retrospective, lessons learned, self-improvement system, correction logging, weekly review, or a clean way to capture reusable execution lessons without creating hidden memory or autonomous behavior.
---
# Retrospective Agent
Use this skill to capture execution lessons in a controlled, auditable way.
This skill exists to improve how the agent works over time.
It does not create a second factual memory system, rewrite identity, or invent autonomy.
## Core principles
- Keep factual continuity in existing memory files
- Keep execution lessons separate and scoped
- Prefer reports and recommendations over automatic changes
- Promote patterns only after repeated evidence
- Never infer preferences from silence
- Never rewrite persona, config, or outbound behavior on your own
## Memory split
### Use existing memory for
- facts
- events
- decisions
- dates
- people
- open tasks
Examples:
- `memory/YYYY-MM-DD.md`
- agent `MEMORY.md`
- project `README.md`
### Use retrospective-agent files for
- repeated corrections
- workflow improvements
- tool failure patterns
- success patterns worth repeating
- project or domain execution lessons
## Storage
Skill files live in:
- `workspace/skills/retrospective-agent/`
Operational data lives in:
- `workspace/ops/retrospective-agent/`
Expected first-pass files:
- `workspace/ops/retrospective-agent/corrections.md`
- `workspace/ops/retrospective-agent/weekly/`
- `workspace/ops/retrospective-agent/domains/`
- `workspace/ops/retrospective-agent/projects/`
- `workspace/ops/retrospective-agent/templates/`
If the ops folder or expected files do not exist, create only the minimum needed for the current task.
Do not create extra files "just in case".
## Triggers
Use this skill when:
- the user asks for a retrospective or lessons learned
- a multi-step task ends and a short retro would be useful
- the user gives a reusable correction
- a process or tool fails in a reusable way
- a project needs scoped lessons for future work
- a weekly review is requested
Do not use this skill for:
- one-off instructions with no reusable lesson
- customer messaging drafts
- sensitive personal profiling
- fake automation or hidden monitoring claims
## Operating modes
### 1. Post-task retrospective
Use after meaningful work.
Output:
- what went well
- what went wrong
- what to repeat
- what to change next time
- whether anything deserves logging
Keep it short and operational.
### 2. Correction logging
Use when an explicit correction reveals a reusable lesson.
Workflow:
1. capture the exact correction
2. classify it
3. choose scope: project, domain, or global execution lesson
4. append a concise entry if warranted
5. recommend promotion only after repeated evidence
### 3. Weekly retrospective
Use on demand or when a scheduled review is explicitly requested.
Output:
- recurring wins
- recurring misses
- repeated patterns
- candidate updates to memory, README files, or skills
## Scope hierarchy
Most specific wins:
1. project
2. domain
3. global execution lesson
If scope is unclear, prefer domain over global.
If still unclear, say so.
## Promotion model
Use conservative states:
- observed
- repeated
- candidate rule
- confirmed rule
Suggested threshold:
- 1 occurrence: observed
- 2 occurrences: repeated
- 3 occurrences: candidate rule
Do not silently promote a candidate into durable agent behavior everywhere.
Recommend the promotion and ask when confirmation matters.
## Guardrails
Never:
- rewrite `SOUL.md`
- rewrite `IDENTITY.md`
- rewrite `USER.md`
- patch config
- send messages
- install companion skills without approval
- infer preferences from silence
- store credentials, secrets, or sensitive personal data
- claim autonomous monitoring unless a real scheduler exists
## Workflow references
Read these only when needed:
- `references/workflow.md`
- `references/promotion-rules.md`
- `references/boundaries.md`
Use templates from:
- `assets/templates/post-task-retro.md`
- `assets/templates/weekly-retro.md`
- `assets/templates/lesson-entry.md`
## Style
Be honest, compact, and boring in a good way.
Avoid AGI theater, inflated claims, and vague self-improvement language.
Prefer operational wording like "lesson", "pattern", "correction", and "recommended update" over dramatic wording like "optimize myself" or "evolve".
## Output rule
Lead with the useful retrospective or lesson.
Do not narrate the framework unless the user asks.
FILE:README.md
# retrospective-agent
Structured retrospectives and execution-memory hygiene for OpenClaw agents.
## Goal
Capture reusable execution lessons without creating hidden memory, duplicate truth, or fake autonomy.
## What belongs here
- repeated corrections
- workflow improvements
- tool failure patterns
- success patterns worth repeating
- scoped project or domain lessons
## What does not belong here
- factual continuity already covered by `memory/YYYY-MM-DD.md` or `MEMORY.md`
- identity or values
- secrets or credentials
- silent behavior changes
## Data location
Operational notes for this skill live under:
- `workspace/ops/retrospective-agent/`
Suggested structure:
```text
workspace/ops/retrospective-agent/
├── corrections.md
├── weekly/
│ └── README.md
├── domains/
│ ├── communication.md
│ ├── ops.md
│ ├── research.md
│ └── writing.md
├── projects/
│ └── README.md
└── templates/
├── post-task-retro.md
├── weekly-retro.md
└── lesson-entry.md
```
## Modes
### Post-task retro
Short review after meaningful work.
### Correction logging
Capture explicit reusable corrections.
### Weekly retro
Summarize recurring wins, misses, and candidate improvements.
## Promotion model
- observed
- repeated
- candidate rule
- confirmed rule
Conservative by design. Recommend promotions, do not silently enforce them.
## Guardrails
- never rewrite persona files
- never patch config
- never send messages
- never infer preferences from silence
- never create hidden memory
## First-pass references
- `references/workflow.md`
- `references/promotion-rules.md`
- `references/boundaries.md`
## Templates
Skill templates:
- `assets/templates/post-task-retro.md`
- `assets/templates/weekly-retro.md`
- `assets/templates/lesson-entry.md`
Operational templates:
- `workspace/ops/retrospective-agent/templates/post-task-retro.md`
- `workspace/ops/retrospective-agent/templates/weekly-retro.md`
- `workspace/ops/retrospective-agent/templates/lesson-entry.md`
es/lesson-entry.md`
FILE:assets/templates/lesson-entry.md
# Lesson Entry
- Lesson:
Type:
Scope:
Source:
Count: 1
Status: observed
FILE:assets/templates/post-task-retro.md
# Post-Task Retrospective
## Task
-
## What went well
-
## What went wrong
-
## Repeat next time
-
## Change next time
-
## Logging recommendation
- none
FILE:assets/templates/weekly-retro.md
# Weekly Retrospective
## Wins
-
## Misses
-
## Repeated patterns
-
## Candidate updates
-
## Follow-up
- none
FILE:ops-sample/corrections.md
# Corrections
Use this file for concise, reusable execution corrections.
Do not store facts, secrets, or one-off instructions here.
## Entry format
- Lesson: <short reusable correction>
Type: <style | workflow | tool use | domain knowledge | project override>
Scope: <global | domain:<name> | project:<name>>
Source: <explicit correction | self-detected miss | retrospective>
Count: <number>
Status: <observed | repeated | candidate rule | confirmed rule>
First Seen: <YYYY-MM-DD>
Last Seen: <YYYY-MM-DD>
## Entries
- Lesson: Keep new skills report-first and non-autonomous by default
Type: workflow
Scope: global
Source: retrospective
Count: 1
Status: observed
First Seen: 2026-03-14
Last Seen: 2026-03-14
FILE:ops-sample/domains/communication.md
# Domain Lessons: communication
Use this file for reusable communication lessons that apply across channels or response styles.
## Active lessons
<!-- Add confirmed or high-signal domain lessons here -->
## Recent observations
<!-- Add tentative or repeated lessons here before promotion -->
FILE:ops-sample/domains/ops.md
# Domain Lessons: ops
Use this file for reusable operations and systems-work lessons.
## Active lessons
<!-- Add confirmed or high-signal domain lessons here -->
## Recent observations
<!-- Add tentative or repeated lessons here before promotion -->
FILE:ops-sample/domains/research.md
# Domain Lessons: research
Use this file for reusable research and analysis lessons.
## Active lessons
<!-- Add confirmed or high-signal domain lessons here -->
## Recent observations
<!-- Add tentative or repeated lessons here before promotion -->
FILE:ops-sample/domains/writing.md
# Domain Lessons: writing
Use this file for reusable drafting, editing, and structured writing lessons.
## Active lessons
<!-- Add confirmed or high-signal domain lessons here -->
## Recent observations
<!-- Add tentative or repeated lessons here before promotion -->
FILE:ops-sample/projects/README.md
# Project lesson files
Create one file per active project only when needed.
Naming:
- use the project folder or project identifier when obvious
- prefer stable names, for example `ops-mission-control.md`
Suggested sections:
- Active lessons
- Recent observations
- Reversals or superseded lessons
FILE:ops-sample/templates/lesson-entry.md
- Lesson:
Type:
Scope:
Source:
Count: 1
Status: observed
First Seen: YYYY-MM-DD
Last Seen: YYYY-MM-DD
FILE:ops-sample/templates/post-task-retro.md
# Post-Task Retrospective
Date: YYYY-MM-DD
Task: <clear task name>
Scope: <project | domain | mixed>
## Outcome
-
## What went well
-
## What went wrong
-
## Repeat next time
-
## Change next time
-
## Candidate lesson
- Lesson:
Type:
Scope:
Source: retrospective
Count: 1
Status: observed
First Seen: YYYY-MM-DD
Last Seen: YYYY-MM-DD
## Recommended write
- none
FILE:ops-sample/templates/weekly-retro.md
# Weekly Retrospective - YYYY-Www
## Wins
-
## Misses
-
## Repeated patterns
-
## Candidate promotions
-
## Candidate updates
- README:
- memory:
- skill:
- process:
## Follow-up
- none
FILE:ops-sample/weekly/2026-W11.md
# Weekly Retrospective - 2026-W11
## Wins
- Chose a clearer skill concept, `retrospective-agent`, instead of vague self-improvement branding.
- Built the skill in small controlled steps, which kept the structure honest and reviewable.
- Kept execution lessons separate from factual memory, which avoids duplicate truth.
- Added guardrails early, so the skill does not drift into persona rewrites or fake autonomy.
## Misses
- The first README scaffold drifted out of sync with the real ops structure and needed cleanup.
- Publish readiness was assumed a bit early before one real retrospective pass.
## Repeated patterns
- Report-first beats automation theater for this workspace.
- Workspace-native structure is better than importing a parallel memory system.
## Candidate promotions
- Keep new skills report-first and non-autonomous by default.
## Candidate updates
- README: keep ops structure examples synchronized with actual created files.
- memory: none yet.
- skill: consider adding one compact example retrospective in a reference file.
- process: do one honest real-use pass before calling a new skill publish-ready.
## Follow-up
- Use this skill on one more non-trivial task after publish and adjust only if something feels clumsy.
FILE:ops-sample/weekly/README.md
# Weekly retrospectives
Store one file per ISO week:
- `YYYY-Www.md`
Use weekly retros to summarize recurring wins, misses, and candidate improvements.
Keep them short, operational, and reviewable.
FILE:references/boundaries.md
# Boundaries
## Never store
- passwords
- API keys
- tokens
- SSH material
- financial account details
- medical information
- private third-party data
- sensitive personal profiling
- anything that would be creepy to surface later
## Never do
- rewrite `SOUL.md`
- rewrite `IDENTITY.md`
- rewrite `USER.md`
- patch config
- send messages
- claim autonomous monitoring that does not exist
- infer preferences from silence
- create hidden state outside the workspace
## Always prefer
- visible files
- concise entries
- source-aware lessons
- scoped rules over broad claims
- recommendations over automatic changes
## If unsure
Do less.
Leave a recommendation instead of a mutation.
FILE:references/promotion-rules.md
# Promotion Rules
Use conservative promotion logic.
## States
- observed
- repeated
- candidate rule
- confirmed rule
## Suggested thresholds
- 1 occurrence: observed
- 2 occurrences: repeated
- 3 occurrences: candidate rule
A candidate rule is not automatically a global rule.
Recommend promotion, then confirm when needed.
## Scope handling
Specific beats general:
1. project
2. domain
3. global
If a lesson only shows up inside one project, keep it there.
Do not promote project-local behavior into a global rule without strong evidence.
## Good candidates for promotion
- repeated workflow corrections
- repeated communication format preferences
- repeated tool-use lessons that reduce failures
- repeated process changes that improve outcomes
## Bad candidates for promotion
- one-off instructions
- temporary constraints
- mood-dependent preferences
- anything inferred from silence
- anything sensitive or invasive
## Reversal rule
If a confirmed rule is later contradicted:
1. do not delete history blindly
2. mark the previous rule as reversed or superseded
3. record the new rule with date and scope
4. prefer the most recent confirmed guidance at the same scope
FILE:references/workflow.md
# Workflow
## Post-task retrospective
Use after meaningful work.
1. Name the task clearly.
2. Summarize outcome in 2 to 5 bullets.
3. Extract reusable wins.
4. Extract reusable misses.
5. Decide scope:
- project
- domain
- global execution lesson
6. Recommend logging only if the lesson is reusable.
7. Keep the output short.
## Correction logging
1. Capture the exact correction.
2. Classify it:
- style
- workflow
- tool use
- domain knowledge
- project override
3. Check whether a similar lesson already exists.
4. If new, append a concise lesson entry.
5. If repeated, increment count and mark state.
6. If it reaches candidate-rule level, recommend promotion.
## Weekly retrospective
1. Review recent correction and lesson files.
2. Group similar patterns.
3. Identify top wins.
4. Identify top misses.
5. Suggest concrete updates:
- memory curation
- README updates
- skill updates
- process changes
6. Keep recommendations operational, not philosophical.
## Default writing rule
Prefer the smallest useful write.
Do not create extra files unless the current task needs them.
Shared Google Ads API skill for OpenClaw agents. Query account, campaign, ad group, keyword, search term, and performance data with local scripts and GAQL ex...
---
name: openclaw-google-ads
description: Shared Google Ads API skill for OpenClaw agents. Query account, campaign, ad group, keyword, search term, and performance data with local scripts and GAQL examples. Use when an agent needs Google Ads reporting, campaign audits, wasted spend checks, conversion-tracking review, or production-safe campaign analysis. Prefer read-first analysis and require explicit approval before live account changes.
---
# OpenClaw Google Ads
Use this skill for Google Ads API work across agents.
This is a shared skill, not a Sea Cool-only skill.
Keep account-specific practices in project docs or memory, not in the core skill.
## Use this skill for
- campaign and account performance reporting
- account health audits
- wasted spend review
- search term and keyword analysis
- conversion tracking review
- structured GAQL querying
- production-safe recommendations before live changes
## Default stance
Start read-only.
Analyze first, recommend second, change last.
Do not make live account changes without explicit approval.
## Access model
Google Ads API Basic Access is suitable for real production use.
If credentials are missing or invalid, stop and fix access before pretending the skill can do real work.
## Setup and references
Read only what you need:
- `references/api-setup.md` for credentials, auth flow, and connection testing
- `references/gaql-examples.md` for query patterns
- `references/audit-workflows.md` for practical account review flows
- `references/optimization.md` for optimization heuristics and common mistakes
- `references/browser-fallback.md` only when API access is unavailable or UI confirmation is explicitly needed
## Available scripts
- `scripts/authenticate.py`
- `scripts/gaql_query.py`
- `scripts/get_account_summary.py`
- `scripts/get_campaigns.py`
Use scripts for repeatable API work.
Use references for judgment.
## Safety rules
- never expose tokens or credentials in files or chat
- prefer read-only analysis before any operational change
- require explicit approval for pausing, enabling, editing, or budget changes
- treat optimization advice as heuristics, not universal truth
- when customer-identifying data appears in exported reports or account notes, apply PII protection before broader model use
## Output style
Lead with findings and recommended actions.
Keep reports practical:
- what is happening
- what looks wrong
- what to check next
- what to change, if approved
FILE:README.md
# openclaw-google-ads
Shared Google Ads API skill for OpenClaw agents.
## Goal
Give agents a practical, production-safe way to analyze Google Ads accounts, run structured queries, audit account health, and recommend improvements without mixing in account-specific business rules.
## What this skill does well
- account and campaign reporting
- GAQL-based querying
- audit workflows
- wasted spend analysis
- conversion tracking review
- optimization guidance grounded in practical heuristics
## What this skill is not
- a Sea Cool-only skill
- a replacement for business-specific campaign strategy docs
- a license to make live account changes without approval
- a place to store credentials
## Structure
```text
openclaw-google-ads/
├── SKILL.md
├── README.md
├── requirements.txt
├── references/
│ ├── api-setup.md
│ ├── gaql-examples.md
│ ├── audit-workflows.md
│ ├── optimization.md
│ └── browser-fallback.md
└── scripts/
├── authenticate.py
├── gaql_query.py
├── get_account_summary.py
└── get_campaigns.py
```
## Practical use order
1. verify credentials and connection
2. run read-only summary or GAQL queries
3. audit the account or campaign area in question
4. recommend actions
5. only make live changes with explicit approval
## Notes
- Keep account-specific learnings in the relevant agent or project docs.
- Keep the shared skill general.
- If API access is unavailable, use the browser fallback only when explicitly needed.
FILE:references/api-setup.md
# API Setup
## Access level
Google Ads API Basic Access is approved, so this skill can support real production reporting and operational workflows.
## Required credentials
- `GOOGLE_ADS_DEVELOPER_TOKEN`
- `GOOGLE_ADS_CLIENT_ID`
- `GOOGLE_ADS_CLIENT_SECRET`
- `GOOGLE_ADS_REFRESH_TOKEN`
- `GOOGLE_ADS_MANAGER_ACCOUNT_ID` optional
- `GOOGLE_ADS_CLIENT_ACCOUNT_ID` optional
## Credential handling
Do not tell users to store credentials in plaintext files.
For this OpenClaw environment, follow the current key protocol used by the workspace owner.
Keep credentials in secure storage and expose them to the runtime through approved environment injection.
## Dependencies
```bash
pip3 install -r requirements.txt
```
## Authentication flow
Run:
```bash
python3 scripts/authenticate.py
```
Use the result to obtain or refresh the Google Ads refresh token.
Do not paste credentials into tracked files.
## Test connection
```bash
python3 scripts/get_account_summary.py --account YOUR-ACCOUNT-ID
```
If this fails:
- verify all required environment variables exist
- verify the developer token is valid
- verify the refresh token is current
- verify the account ID is a 10-digit ID without dashes
## Operational note
Prefer manager-account-driven access when multiple accounts are involved.
Keep client account IDs explicit when running analysis to avoid checking the wrong account.
FILE:references/audit-workflows.md
# Audit Workflows
## 1. Account health check
Start with:
- account summary for the last 30 days
- campaign list with status and top-line metrics
- major trend changes in spend, clicks, conversions, and conversion value
Look for:
- spend up while conversions are flat or down
- conversions present but no conversion value where value should exist
- major campaigns paused unintentionally
- obvious account-level underdelivery or runaway spend
## 2. Wasted spend review
Check:
- zero-conversion campaigns with meaningful spend
- zero-conversion keywords with meaningful spend
- search terms with poor relevance
- campaigns with weak CTR and no downstream results
Questions to answer:
- what is spending without producing value?
- is the issue targeting, query quality, landing page quality, or tracking?
## 3. Conversion tracking review
Check:
- are the expected primary conversions active?
- are there recent conversions, or did tracking go dark?
- is the account mixing micro-conversions with primary conversions?
- is value tracking present and believable?
Watch for:
- inactive conversion actions
- call conversions with weak quality thresholds
- inflated reporting from counting the wrong actions
- sudden drops that smell like tracking breakage, not demand change
## 4. Campaign triage
For a weak campaign, review in order:
1. status and budget constraints
2. impressions and impression share if available
3. CTR
4. CPC and spend trend
5. conversions and CPA or ROAS
6. search term quality
7. landing page relevance and speed
## 5. Before recommending changes
Separate findings into:
- clear problem
- likely cause
- recommended next step
- change that needs explicit approval
Keep reports practical and short.
FILE:references/browser-fallback.md
# Browser Fallback
Use this only when:
- API access is unavailable
- the user explicitly wants UI confirmation
- a specific setting or workflow is easier to verify in the Google Ads interface
## Default stance
Prefer the API for reporting and repeatable analysis.
Use the browser for confirmation, screenshots, or UI-only checks.
## Basic workflow
1. Open ads.google.com in the approved browser context.
2. Confirm the correct account is selected.
3. Navigate to the relevant area:
- campaigns
- ad groups
- keywords
- search terms
- conversions
4. Snapshot the visible data.
5. Report findings clearly.
## Cautions
- Google Ads UI is heavy and easy to misread if the wrong account or date range is selected.
- Confirm date range before drawing conclusions.
- Do not make live UI changes without explicit approval.
FILE:references/gaql-examples.md
# GAQL Examples
## Notes
- Customer IDs are 10-digit numbers without dashes.
- Cost fields are returned in micros. Divide by 1,000,000 for currency units.
- Start with read-only reporting queries before considering any operational change.
## Campaign performance, last 30 days
```sql
SELECT
campaign.id,
campaign.name,
campaign.status,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value
FROM campaign
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
```
## Ad group performance
```sql
SELECT
ad_group.id,
ad_group.name,
ad_group.status,
campaign.name,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.ctr,
metrics.average_cpc,
metrics.conversions
FROM ad_group
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
```
## Keyword performance
```sql
SELECT
ad_group_criterion.criterion_id,
ad_group_criterion.keyword.text,
ad_group_criterion.keyword.match_type,
campaign.name,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.quality_score
FROM keyword_view
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
```
## Search terms report
```sql
SELECT
search_term_view.search_term,
campaign.name,
ad_group.name,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions
FROM search_term_view
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
```
## Zero-conversion keywords with meaningful spend
```sql
SELECT
ad_group_criterion.keyword.text,
ad_group_criterion.keyword.match_type,
campaign.name,
metrics.cost_micros,
metrics.clicks,
metrics.impressions,
metrics.conversions
FROM keyword_view
WHERE segments.date DURING LAST_30_DAYS
AND metrics.conversions = 0
AND metrics.cost_micros > 5000000
ORDER BY metrics.cost_micros DESC
```
## Search terms with spend and no conversions
```sql
SELECT
search_term_view.search_term,
campaign.name,
metrics.cost_micros,
metrics.clicks,
metrics.impressions,
metrics.conversions
FROM search_term_view
WHERE segments.date DURING LAST_30_DAYS
AND metrics.conversions = 0
AND metrics.cost_micros > 5000000
ORDER BY metrics.cost_micros DESC
```
## Conversion actions overview
```sql
SELECT
conversion_action.name,
conversion_action.category,
conversion_action.status,
conversion_action.primary_for_goal,
conversion_action.type
FROM conversion_action
ORDER BY conversion_action.name
```
## Device performance
```sql
SELECT
segments.device,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value
FROM campaign
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
```
## Geographic performance
```sql
SELECT
geographic_view.country_criterion_id,
geographic_view.location_name,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value
FROM geographic_view
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
```
## Daily trend
```sql
SELECT
segments.date,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value
FROM campaign
WHERE segments.date DURING LAST_30_DAYS
ORDER BY segments.date DESC
```
## Account overview
```sql
SELECT
customer.id,
customer.descriptive_name,
customer.status,
customer.currency_code,
customer.time_zone
FROM customer
```
FILE:references/optimization.md
# Optimization Heuristics
Use these as practical rules of thumb, not as absolute truth.
## Keyword strategy
- Review search terms regularly. Broad and phrase traffic drifts fast.
- Broad match usually needs strong bidding logic and good negatives.
- Long-tail keywords can improve efficiency, but too many low-volume fragments can stall spend.
- Theme-based ad groups are usually better than overly fragmented structures.
## Quality score and relevance
- Expected CTR, ad relevance, and landing page experience all matter.
- Keyword-to-ad-to-landing-page alignment usually beats clever copy.
- Landing page speed and clarity matter before more spend does.
## Bidding
- Aggressive target CPA can choke volume.
- Maximize Conversions without guardrails can waste budget.
- Manual CPC can be useful early, but often leaves money on the table once conversion volume is real.
- Bid changes should be evaluated against conversion lag, not same-day noise.
## Campaign structure
- Keep branded and non-branded search separate.
- Separate regions when budget control or reporting needs differ.
- Avoid mixing unlike intents in the same campaign when analysis gets muddy.
- Shared budgets can hide real demand and tradeoffs.
## Search term hygiene
- Negative keyword discipline compounds over time.
- Search terms often reveal landing page mismatch, not just keyword mismatch.
- If wasted spend clusters around a theme, fix the structure, not just the individual term.
## Conversion interpretation
- Verify which conversion actions actually matter.
- Micro-conversions can be useful, but they can also distort optimization if treated as primary value.
- Last-click views can hide assist behavior.
## Scaling
- Search volume has a ceiling.
- Higher spend often comes with worse efficiency.
- Scaling decisions should explicitly balance volume and efficiency, not pretend both always improve together.
FILE:requirements.txt
google-ads==24.1.0
google-auth-oauthlib>=0.5.0
google-auth>=2.0.0
FILE:scripts/authenticate.py
#!/usr/bin/env python3
"""
Google Ads API Authentication Script
Handles OAuth flow and stores refresh token for API access.
"""
import os
import sys
# OAuth scopes for Google Ads API
SCOPES = ['https://www.googleapis.com/auth/adwords']
def main():
# Import here so we can catch import errors
try:
from google_auth_oauthlib.flow import InstalledAppFlow
except ImportError:
print("Error: google-auth-oauthlib not installed")
print("Run: pip install google-auth-oauthlib")
sys.exit(1)
# Load client credentials from environment
client_id = os.environ.get('GOOGLE_ADS_CLIENT_ID')
client_secret = os.environ.get('GOOGLE_ADS_CLIENT_SECRET')
if not client_id or not client_secret:
print("Error: GOOGLE_ADS_CLIENT_ID and GOOGLE_ADS_CLIENT_SECRET must be set")
print("Make sure OAuth credentials are configured")
sys.exit(1)
# Create client config dict
client_config = {
"installed": {
"client_id": client_id,
"client_secret": client_secret,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"redirect_uris": ["http://localhost"]
}
}
# Run OAuth flow
flow = InstalledAppFlow.from_client_config(client_config, SCOPES)
# Use local server flow
credentials = flow.run_local_server(port=0)
# Store refresh token
refresh_token = credentials.refresh_token
print(f"\n{'='*60}")
print(f"REFRESH TOKEN: {refresh_token}")
print(f"{'='*60}")
print("\nDo not paste this into tracked files or plaintext workspace notes.")
print("Store it using the approved secure credential workflow for this OpenClaw environment.")
print("Expose it to the runtime through approved environment injection only.")
if __name__ == '__main__':
main()
FILE:scripts/gaql_query.py
#!/usr/bin/env python3
"""
Run custom GAQL queries against Google Ads API.
"""
import sys
import argparse
from google.ads.googleads.client import GoogleAdsClient
from google.protobuf.json_format import MessageToDict
def run_query(account_id, query):
"""Execute a GAQL query and print rows as dictionaries."""
client = GoogleAdsClient.load_from_env()
ga_service = client.get_service("GoogleAdsService")
search_request = client.get_type("SearchGoogleAdsRequest")
search_request.customer_id = account_id.replace('-', '')
search_request.query = query
print(f"Querying account: {account_id}")
print(f"Query: {query}")
print("-" * 80)
try:
results = ga_service.search(request=search_request)
count = 0
for row in results:
count += 1
print(f"\nRow {count}:")
row_dict = MessageToDict(row._pb, preserving_proto_field_name=True)
for key, value in row_dict.items():
print(f" {key}: {value}")
print(f"\nTotal rows: {count}")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description='Run GAQL query on Google Ads')
parser.add_argument('--account', required=True, help='Account ID (with dashes)')
parser.add_argument('--query', required=True, help='GAQL query string')
args = parser.parse_args()
run_query(args.account, args.query)
if __name__ == '__main__':
main()
FILE:scripts/get_account_summary.py
#!/usr/bin/env python3
"""
Get account performance summary from Google Ads API
"""
import os
import sys
import argparse
from google.ads.googleads.client import GoogleAdsClient
def get_account_summary(manager_account_id, days=30):
"""Get performance summary for the account."""
# Load config from env with proto_plus setting
config = {
"developer_token": os.environ.get("GOOGLE_ADS_DEVELOPER_TOKEN"),
"client_id": os.environ.get("GOOGLE_ADS_CLIENT_ID"),
"client_secret": os.environ.get("GOOGLE_ADS_CLIENT_SECRET"),
"refresh_token": os.environ.get("GOOGLE_ADS_REFRESH_TOKEN"),
"login_customer_id": os.environ.get("GOOGLE_ADS_LOGIN_CUSTOMER_ID", "").replace("-", ""),
"use_proto_plus": True
}
client = GoogleAdsClient.load_from_dict(config)
ga_service = client.get_service("GoogleAdsService")
query = f"""
SELECT
customer.id,
customer.descriptive_name,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions,
metrics.conversions_value
FROM customer
WHERE segments.date DURING LAST_{days}_DAYS
"""
search_request = client.get_type("SearchGoogleAdsRequest")
search_request.customer_id = manager_account_id.replace('-', '')
search_request.query = query
print(f"Account Summary (Last {days} Days)")
print("-" * 80)
try:
results = ga_service.search(request=search_request)
total_impressions = 0
total_clicks = 0
total_cost = 0
total_conversions = 0
for row in results:
customer = row.customer
metrics = row.metrics
cost = metrics.cost_micros / 1000000 if metrics.cost_micros else 0
print(f"Account: {customer.descriptive_name} ({customer.id})")
print(f" Impressions: {metrics.impressions:,}")
print(f" Clicks: {metrics.clicks:,}")
print(f" Cost: ,.2f")
print(f" Conversions: {metrics.conversions}")
print(f" Conv. Value: ,.2f")
print()
total_impressions += metrics.impressions
total_clicks += metrics.clicks
total_cost += cost
total_conversions += metrics.conversions
print("-" * 80)
print("TOTALS:")
print(f" Impressions: {total_impressions:,}")
print(f" Clicks: {total_clicks:,}")
print(f" Cost: ,.2f")
print(f" Conversions: {total_conversions}")
if total_clicks > 0:
cpc = total_cost / total_clicks
print(f" Avg CPC: .2f")
if total_impressions > 0:
ctr = (total_clicks / total_impressions) * 100
print(f" CTR: {ctr:.2f}%")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description='Get Google Ads account summary')
parser.add_argument('--account', required=True, help='Account ID (with dashes)')
parser.add_argument('--days', type=int, default=30, help='Number of days (default: 30)')
args = parser.parse_args()
get_account_summary(args.account, args.days)
if __name__ == '__main__':
main()
FILE:scripts/get_campaigns.py
#!/usr/bin/env python3
"""
Get campaigns from Google Ads API
"""
import os
import sys
import argparse
from google.ads.googleads.client import GoogleAdsClient
def get_campaigns(manager_account_id, client_account_id=None):
"""List all campaigns for the specified account."""
# Initialize client
client = GoogleAdsClient.load_from_env()
# Use client account if specified, otherwise use manager
customer_id = client_account_id or manager_account_id
ga_service = client.get_service("GoogleAdsService")
query = """
SELECT
campaign.id,
campaign.name,
campaign.status,
campaign.advertising_channel_type,
metrics.impressions,
metrics.clicks,
metrics.cost_micros,
metrics.conversions
FROM campaign
WHERE segments.date DURING LAST_30_DAYS
ORDER BY campaign.id
"""
search_request = client.get_type("SearchGoogleAdsRequest")
search_request.customer_id = customer_id.replace('-', '')
search_request.query = query
print(f"Campaigns for account: {customer_id}")
print("-" * 80)
try:
results = ga_service.search(request=search_request)
for row in results:
campaign = row.campaign
metrics = row.metrics
cost = metrics.cost_micros / 1000000 if metrics.cost_micros else 0
print(f"ID: {campaign.id}")
print(f"Name: {campaign.name}")
print(f"Status: {campaign.status.name}")
print(f"Channel: {campaign.advertising_channel_type.name}")
print(f"Impressions: {metrics.impressions}")
print(f"Clicks: {metrics.clicks}")
print(f"Cost: .2f")
print(f"Conversions: {metrics.conversions}")
print("-" * 80)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description='Get Google Ads campaigns')
parser.add_argument('--account', required=True, help='Account ID (with dashes)')
parser.add_argument('--client-account', help='Client account ID (for manager accounts)')
args = parser.parse_args()
get_campaigns(args.account, args.client_account)
if __name__ == '__main__':
main()
Local PII protection for OpenClaw agents. Scrubs customer data (names, phones, emails, addresses, credit cards, vessel names) before it reaches any AI model....
---
name: presidio-pii
version: 1.0.1
description: Local PII protection for OpenClaw agents. Scrubs customer data (names, phones, emails, addresses, credit cards, vessel names) before it reaches any AI model. Uses Microsoft Presidio running as local Docker containers. Supports reversible pseudonymization and fail-closed policy. Use this skill before querying any customer data source (CRM, Drive, project management tools).
homepage: https://github.com/sebclawops/presidio-pii
metadata:
clawdbot:
emoji: ""
requires:
bins:
- curl
- python3
- docker
files:
- "scripts/*"
- "configs/*"
---
# Presidio PII Protection
You have the Presidio PII skill. Customer data MUST be scrubbed before it reaches any AI model.
## When to Use
**ALWAYS** use this skill before processing data from:
- CRM systems (HubSpot, Salesforce, etc.)
- Cloud storage (Google Drive, Dropbox, etc.)
- Project management tools (TintWiz, Asana, etc.)
- Any source containing customer names, phones, emails, or addresses
**DO NOT** use for:
- Internal company data (product types, SOP terms, project statuses)
- General conversation with no customer data
- System administration tasks
## Fail-Closed Rule
**If Presidio is down, DO NOT query customer data sources.** Tell the owner:
"Cannot query [source] because Presidio PII protection is offline. Customer data will not be sent unprotected."
## How to Use
### Step 1: Check Health
```bash
bash SKILL_DIR/scripts/presidio-health.sh
```
If unhealthy, STOP. Do not proceed with the data query.
### Step 2: Scrub Data
After retrieving raw data from a source, pipe it through the scrubber:
```bash
echo "RAW DATA HERE" | python3 SKILL_DIR/scripts/presidio-scrub.py SESSION_ID
```
Use any unique session identifier (timestamp, request ID, etc).
The scrubber returns JSON:
```json
{
"text": "[PERSON_1] at [LOCATION_1], phone [PHONE_NUMBER_1]",
"pii_found": 3,
"entity_types": ["PERSON", "LOCATION", "PHONE_NUMBER"],
"mapping_file": "/path/to/mapping.json",
"session_id": "SESSION_ID"
}
```
Use the `text` field for all reasoning. The mapping file stays local.
### Step 3: Reason with Clean Data
Process the anonymized text normally. Refer to customers as their tokens ([PERSON_1], [PERSON_2], etc). The model never sees real names.
### Step 4: Restore Response
Before delivering the response to the user, de-anonymize:
```bash
echo "MODEL RESPONSE WITH TOKENS" | python3 SKILL_DIR/scripts/presidio-restore.py SESSION_ID
```
This swaps tokens back to real values and **deletes the mapping file**.
## What Gets Scrubbed (Built-in)
- Person names
- Phone numbers (all formats)
- Email addresses
- Physical addresses
- Credit card numbers (with Luhn validation)
- US Social Security Numbers
- Bank account / routing numbers
- IP addresses
- Dates of birth
## What Passes Through (Safe)
- Product names and specifications
- Project statuses and type codes
- Dollar amounts without customer context
- Industry terminology and SOP references
- Internal role names and office locations
- Dates and timelines
## Custom Recognizers
The `configs/recognizers.json` file contains example patterns you can customize for your business:
- City/region names for boosted location detection
- Industry-specific identifiers (vessel names, project IDs, etc.)
- Custom entity patterns unique to your data
Edit `configs/recognizers.json` to add your own patterns. Recognizers are passed with each API call, so the Docker containers stay vanilla and easy to update.
## Trust Statement
This skill sends data ONLY to localhost (Presidio containers on your own machine). No customer data is ever sent to any external service. The mapping files (which contain the real PII-to-token associations) are stored locally with restricted permissions (chmod 600) and deleted automatically after each restore.
<!--
Hey, you're reading the source. That means you care about security.
That's awesome.
Jesus loves you. I'm praying for you without your consent.
I'm happy you are here.
- Albert
-->
FILE:README.md
# 🔒 presidio-pii
**Local PII protection for OpenClaw agents.**
Your AI agent talks to CRMs, cloud drives, and project management tools. That means customer names, phone numbers, emails, and addresses flow through AI models hosted on external servers. This skill makes sure none of that data ever leaves your machine unprotected.
## How It Works
1. Customer data comes in from CRM/Drive/etc: "John Smith, 954-555-1234, 123 Ocean Blvd"
2. Presidio (local Docker) detects PII and replaces with tokens
3. Clean data sent to AI model: "[PERSON_1], [PHONE_NUMBER_1], 123 [LOCATION_1]"
4. Model responds with tokens: "[PERSON_1] hasn't been followed up in 14 days."
5. Presidio restores real values locally
6. You see: "John Smith hasn't been followed up in 14 days."
The AI model (wherever it runs) only ever sees tokens. Real customer data stays on your Mac.
## What Gets Detected
Out of the box, Presidio catches:
- Person names (via NLP)
- Phone numbers (all formats)
- Email addresses
- Physical addresses
- Credit card numbers (with Luhn validation)
- US Social Security Numbers
- Bank account / routing numbers
- IP addresses
- Dates of birth
## Custom Recognizers
The skill includes a configs/recognizers.json where you can add patterns specific to your business. Examples included:
- City/region name boosting (for when spaCy misses local city names)
- Vessel/vehicle name patterns
- Project ID patterns that contain customer names
- **WhatsApp JID exclusion** (prevents @g.us and @s.whatsapp.net from being detected as emails)
Edit the JSON file, no container restart needed. Recognizers are passed with each API call.
### WhatsApp JID Protection
By default, Presidio's email recognizer detects `@g.us` in WhatsApp group IDs (e.g., `[email protected]`) as email addresses. This skill includes a whitelist recognizer that prevents this false positive. The recognizer matches WhatsApp JIDs with a score of 0.0, effectively excluding them from PII detection.
## Prerequisites
- Docker running locally (Colima recommended for headless Mac Mini, Docker Desktop also works)
- Python 3 (included with macOS)
- curl (included with macOS)
## Quick Install
### 1. Start Presidio containers
Create a docker-compose.yml at ~/.openclaw/presidio/ with analyzer (port 5002:3000) and anonymizer (port 5001:3000). Then run: docker compose up -d
IMPORTANT: Presidio containers listen on port 3000 internally. Map 5002:3000 (analyzer) and 5001:3000 (anonymizer). If you map 5002:5002, you'll get empty replies.
### 2. Install the skill
clawhub install presidio-pii
Or manually copy the skill folder to ~/.openclaw/workspace/skills/
### 3. Test it
# Health check
bash ~/.openclaw/workspace/skills/presidio-pii/scripts/presidio-health.sh
# Scrub
echo 'John Smith at 123 Ocean Blvd, Miami' | python3 ~/.openclaw/workspace/skills/presidio-pii/scripts/presidio-scrub.py test1
# Restore
echo '[PERSON_1] at 123 [LOCATION_1], [LOCATION_2]' | python3 ~/.openclaw/workspace/skills/presidio-pii/scripts/presidio-restore.py test1
## Security
- 100% local. Presidio containers run on localhost. No data leaves your machine.
- Fail-closed. If Presidio is down, the skill blocks data queries rather than sending unprotected PII.
- Ephemeral mappings. Token-to-real-value mapping files are created per request, stored with chmod 600, and auto-deleted after restore.
- Vanilla containers. Custom recognizers are passed via API calls, not baked into containers. Pull updated images anytime without losing your config.
## Why Colima over Docker Desktop?
If you're running OpenClaw on a headless Mac Mini, Colima saves ~1-2GB RAM compared to Docker Desktop. It runs entirely from the terminal with no GUI overhead.
brew install docker docker-compose colima
brew services start colima
docker context use colima
## File Structure
presidio-pii/
SKILL.md -- Agent instructions (what to scrub, when, how)
skill.json -- Metadata and tags
README.md -- You're reading it
configs/
recognizers.json -- Custom PII patterns (edit for your business)
scripts/
presidio-health.sh -- Health check (are containers up?)
presidio-scrub.py -- Analyze + anonymize + save mapping
presidio-restore.py -- De-anonymize + delete mapping
## FAQ
Q: What if Presidio misses something?
A: No PII detection is 100%. Presidio + custom recognizers covers the vast majority of common patterns. You can always add more patterns to recognizers.json.
Q: Does this slow down responses?
A: Barely. The Presidio analysis (spaCy NER + regex) takes milliseconds. The Docker containers use about 500MB-1GB RAM combined and negligible CPU when idle.
Q: Can I use this with any AI model?
A: Yes. That's the point. Whether your model runs in the US, China, or anywhere else, it only sees tokens. The skill is model-agnostic.
Q: What about GDPR / CCPA compliance?
A: This skill helps you take "reasonable steps" to protect customer data. It's not a legal compliance solution on its own, but it significantly reduces your exposure. Consult a lawyer for formal compliance requirements.
## Why This Exists
I don't trust any AI provider with my customers' personal information. Not the American ones, not the Chinese ones, none of them. They're all logging whatever they want to train their models. If you're running a business and connecting your AI agent to customer data, you should assume that anything you send to an external model is being stored, analyzed, and used in ways you didn't agree to.
This skill keeps customer data on your machine where it belongs. The AI model gets tokens. Your customers get privacy. Everybody wins except the data harvesters.
Oh, and it's completely free and open source. Presidio is free (Microsoft open source). Colima is free. This skill is free. No subscriptions, no API keys, no usage fees. Just local software protecting your customers' data.
## License
MIT
## Author
Albert (@sebclawops - https://github.com/sebclawops)
FILE:configs/recognizers.json
{
"ad_hoc_recognizers": [
{
"name": "WhatsAppJID",
"supported_language": "en",
"patterns": [
{
"name": "whatsapp_group_id",
"regex": "\\d+@g\\.us",
"score": 0.0
},
{
"name": "whatsapp_user_id",
"regex": "\\d+@s\\.whatsapp\\.net",
"score": 0.0
}
],
"supported_entity": "WHATSAPP_JID"
},
{
"name": "SouthFloridaCities",
"supported_language": "en",
"patterns": [
{
"name": "sofla_cities",
"regex": "\\b(Jupiter|Juno Beach|Palm Beach Gardens|North Palm Beach|Palm Beach|West Palm Beach|Lake Worth|Lake Worth Beach|Lantana|Boynton Beach|Delray Beach|Highland Beach|Boca Raton|Boca Del Mar|Deerfield Beach|Lighthouse Point|Pompano Beach|Coral Springs|Coconut Creek|Margate|Tamarac|North Lauderdale|Lauderhill|Lauderdale Lakes|Plantation|Sunrise|Weston|Davie|Cooper City|Pembroke Pines|Miramar|Hollywood|Hallandale Beach|Hallandale|Aventura|Sunny Isles Beach|Sunny Isles|Golden Beach|Bal Harbour|Surfside|Miami Beach|North Miami Beach|North Miami|Miami Shores|Miami|Brickell|Wynwood|Edgewater|Midtown|Downtown Miami|Coral Gables|Coconut Grove|South Miami|Pinecrest|Palmetto Bay|Cutler Bay|Homestead|Florida City|Doral|Sweetwater|Hialeah|Hialeah Gardens|Miami Lakes|Miami Gardens|Opa-locka|Opa Locka|Fort Lauderdale|Ft Lauderdale|Ft\\. Lauderdale|Wilton Manors|Oakland Park|Lauderdale-by-the-Sea|Lauderdale by the Sea|Sea Ranch Lakes|Hillsboro Beach|Key Biscayne|Fisher Island|Star Island|Indian Creek|Bay Harbor Islands|Miami Springs|Virginia Gardens|Medley|Kendall|Tamiami|Westchester|Fontainebleau|Key Largo|Islamorada|Marathon|Key West)\\b",
"score": 0.85
},
{
"name": "greater_houston_cities",
"regex": "\\b(Katy|Houston|Sugar Land|Missouri City|Pearland|Cypress|Spring|The Woodlands|Humble|Pasadena|League City|Friendswood|Richmond|Rosenberg|Fulshear|Bellaire|West University Place|Memorial|River Oaks|Galleria|Energy Corridor|Clear Lake|Webster|Seabrook|Kemah|La Porte|Baytown|Conroe|Tomball|Magnolia)\\b",
"score": 0.85
},
{
"name": "florida_statewide",
"regex": "\\b(Tampa|St Petersburg|St\\. Petersburg|Clearwater|Orlando|Jacksonville|Tallahassee|Gainesville|Sarasota|Naples|Fort Myers|Ft Myers|Ft\\. Myers|Cape Coral|Bonita Springs|Marco Island|Ocala|Daytona Beach|Palm Coast|Port St Lucie|Port St\\. Lucie|Stuart|Vero Beach|Melbourne|Cocoa Beach|Titusville|Kissimmee|Lakeland|Winter Haven|Winter Park|Sanford|Altamonte Springs|Pensacola|Panama City|Destin|Fort Walton Beach)\\b",
"score": 0.4
}
],
"supported_entity": "LOCATION"
},
{
"name": "VesselName",
"supported_language": "en",
"patterns": [
{
"name": "vessel_my_prefix_quoted",
"regex": "MY\\s+\"[^\"]+\"",
"score": 0.9
},
{
"name": "vessel_my_prefix_unquoted",
"regex": "MY\\s+[A-Z][a-zA-Z\\s]+(?=\\s+\\d+)",
"score": 0.8
},
{
"name": "vessel_quoted_with_specs",
"regex": "\"[^\"]+\"\\s+\\d+[\\s']?\\s*\\w+",
"score": 0.85
}
],
"supported_entity": "VESSEL_NAME"
},
{
"name": "SeaCoolProjectID",
"supported_language": "en",
"patterns": [
{
"name": "project_id_with_name",
"regex": "[1-8]?\\d{4}\\s+(RES|COM|MY)\\s+[A-Z][a-zA-Z\\s'\\-\\.]+",
"score": 0.9
}
],
"supported_entity": "SEA_COOL_PROJECT"
}
]
}
FILE:scripts/presidio-health.sh
#!/bin/bash
ANALYZER_URL="-http://localhost:5002"
ANONYMIZER_URL="-http://localhost:5001"
analyzer_ok=false
anonymizer_ok=false
analyzer_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$ANALYZER_URL/analyze" \
-X POST -H "Content-Type: application/json" \
-d '{"text":"health check","language":"en"}' 2>/dev/null)
[ "$analyzer_status" = "200" ] && analyzer_ok=true
anonymizer_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$ANONYMIZER_URL/anonymize" \
-X POST -H "Content-Type: application/json" \
-d '{"text":"health check","anonymizers":{"DEFAULT":{"type":"replace","new_value":"[OK]"}},"analyzer_results":[]}' 2>/dev/null)
[ "$anonymizer_status" = "200" ] && anonymizer_ok=true
if $analyzer_ok && $anonymizer_ok; then
echo '{"status":"healthy","analyzer":"up","anonymizer":"up"}'
exit 0
elif $analyzer_ok; then
echo '{"status":"unhealthy","analyzer":"up","anonymizer":"down"}'
exit 1
elif $anonymizer_ok; then
echo '{"status":"unhealthy","analyzer":"down","anonymizer":"up"}'
exit 1
else
echo '{"status":"unhealthy","analyzer":"down","anonymizer":"down"}'
exit 1
fi
FILE:scripts/presidio-restore.py
#!/usr/bin/env python3
"""presidio-restore.py -- De-anonymize text by swapping tokens back to real values."""
import json, os, sys
MAPPING_DIR = os.environ.get("PRESIDIO_MAPPING_DIR", os.path.expanduser("~/.openclaw/presidio/mappings"))
def main():
keep = "--keep" in sys.argv
args = [a for a in sys.argv[1:] if a != "--keep"]
if not args:
print(json.dumps({"error": "Session ID required"})); sys.exit(1)
session_id = args[0]
if len(args) > 1:
text = " ".join(args[1:])
elif not sys.stdin.isatty():
text = sys.stdin.read().strip()
else:
print(json.dumps({"error": "No input text"})); sys.exit(1)
mf = os.path.join(MAPPING_DIR, f"{session_id}.json")
if not os.path.exists(mf):
print(json.dumps({"error": f"No mapping for session {session_id}", "text": text})); sys.exit(1)
with open(mf) as f:
mapping = json.load(f)
restored, count = text, 0
for token, original in mapping.get("reverse_map", {}).items():
if token in restored:
restored = restored.replace(token, original)
count += 1
if not keep:
try: os.remove(mf)
except OSError: pass
print(json.dumps({"text": restored, "restored": count, "session_id": session_id, "mapping_deleted": not keep}, indent=2))
if __name__ == "__main__":
main()
FILE:scripts/presidio-scrub.py
#!/usr/bin/env python3
"""presidio-scrub.py -- Analyze text for PII, anonymize with reversible tokens, store mapping."""
import json, os, sys, time, urllib.request
ANALYZER_URL = os.environ.get("PRESIDIO_ANALYZER_URL", "http://localhost:5002")
MAPPING_DIR = os.environ.get("PRESIDIO_MAPPING_DIR", os.path.expanduser("~/.openclaw/presidio/mappings"))
SKILL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RECOGNIZERS_FILE = os.path.join(SKILL_DIR, "configs", "recognizers.json")
def http_post(url, data):
payload = json.dumps(data).encode("utf-8")
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception:
return None
def check_health():
a = http_post(f"{ANALYZER_URL}/analyze", {"text": "health", "language": "en"}) is not None
b = http_post(os.environ.get("PRESIDIO_ANONYMIZER_URL", "http://localhost:5001") + "/anonymize",
{"text": "h", "anonymizers": {"DEFAULT": {"type": "replace", "new_value": "x"}}, "analyzer_results": []}) is not None
return a, b
def main():
session_id = sys.argv[1] if len(sys.argv) > 1 else str(int(time.time()))
if len(sys.argv) > 2:
text = " ".join(sys.argv[2:])
elif not sys.stdin.isatty():
text = sys.stdin.read().strip()
else:
print(json.dumps({"error": "No input text"})); sys.exit(1)
# Health check
a_ok, b_ok = check_health()
if not a_ok or not b_ok:
print(json.dumps({"error": "BLOCKED: Presidio is not healthy.", "analyzer": "up" if a_ok else "DOWN", "anonymizer": "up" if b_ok else "DOWN"}))
sys.exit(1)
# Load custom recognizers
recognizers = []
if os.path.exists(RECOGNIZERS_FILE):
with open(RECOGNIZERS_FILE) as f:
recognizers = json.load(f).get("ad_hoc_recognizers", [])
# Analyze
payload = {"text": text, "language": "en"}
if recognizers:
payload["ad_hoc_recognizers"] = recognizers
entities = http_post(f"{ANALYZER_URL}/analyze", payload)
if entities is None:
print(json.dumps({"error": "Analyzer returned no response"})); sys.exit(1)
if len(entities) == 0:
print(json.dumps({"text": text, "pii_found": 0, "mapping_file": None, "session_id": session_id}, indent=2)); return
# Remove overlapping entities (keep highest score, then longest match)
entities_by_score = sorted(entities, key=lambda x: (-x.get("score", 0), -(x["end"] - x["start"])))
filtered = []
for ent in entities_by_score:
overlap = False
for kept in filtered:
if ent["start"] < kept["end"] and ent["end"] > kept["start"]:
overlap = True
break
if not overlap:
filtered.append(ent)
entities = filtered
# Whitelist: exclude WhatsApp JIDs from PII detection
WHATSAPP_SUFFIXES = ("@g.us", "@s.whatsapp.net", "@lid", "@broadcast")
entities = [e for e in entities if not any(text[e["start"]:e["end"]].endswith(suffix) for suffix in WHATSAPP_SUFFIXES)]
if len(entities) == 0:
print(json.dumps({"text": text, "pii_found": 0, "mapping_file": None, "session_id": session_id}, indent=2)); return
# Build tokens
entities_fwd = sorted(entities, key=lambda x: x["start"])
type_counters, token_map, reverse_map = {}, {}, {}
for e in entities_fwd:
original = text[e["start"]:e["end"]]
if original in token_map:
continue
etype = e["entity_type"]
type_counters[etype] = type_counters.get(etype, 0) + 1
token = f"[{etype}_{type_counters[etype]}]"
token_map[original] = token
reverse_map[token] = original
# Replace (reverse order)
anonymized = text
for e in sorted(entities, key=lambda x: x["start"], reverse=True):
original = text[e["start"]:e["end"]]
if original in token_map:
anonymized = anonymized[:e["start"]] + token_map[original] + anonymized[e["end"]:]
# Save mapping
os.makedirs(MAPPING_DIR, exist_ok=True)
mf = os.path.join(MAPPING_DIR, f"{session_id}.json")
with open(mf, "w") as f:
json.dump({"session_id": session_id, "created": int(time.time()), "reverse_map": reverse_map, "entity_count": len(entities_fwd), "entity_types": list(type_counters.keys())}, f, indent=2)
os.chmod(mf, 0o600)
print(json.dumps({"text": anonymized, "pii_found": len(entities_fwd), "entity_types": list(type_counters.keys()), "mapping_file": mf, "session_id": session_id}, indent=2))
if __name__ == "__main__":
main()
FILE:skill.json
{
"name": "presidio-pii",
"version": "1.0.1",
"description": "Local PII protection for OpenClaw agents. Scrubs customer data (names, phones, emails, addresses, credit cards, SSNs) before it reaches any AI model. Uses Microsoft Presidio running as local Docker containers. Supports reversible pseudonymization and fail-closed policy.",
"author": "Albert (@sebclawops)",
"license": "MIT",
"homepage": "https://github.com/sebclawops/presidio-pii-skill",
"tags": ["security", "privacy", "pii", "presidio", "anonymization", "data-protection", "docker", "local", "gdpr", "ccpa"],
"architecture": {
"skill_md_tokens": "~900",
"design": "Three scripts: health check, scrub (anonymize), restore (de-anonymize). Mapping files stored locally and deleted after use.",
"scripts": 3,
"config_files": 1
},
"dependencies": {
"bins": ["curl", "python3", "docker"],
"containers": [
"mcr.microsoft.com/presidio-analyzer:latest",
"mcr.microsoft.com/presidio-anonymizer:latest"
]
},
"compatibility": {
"agents": ["openclaw"],
"minVersion": "2026.1.0"
}
}