# Regulation Monitor

> Use when the user asks to set up, run, or check a scheduled regulation, law, or regulatory-guidance monitor or tracker — triggers include "set up a regulation tracker for X", "monitor [regulation] for me", "run my regulation monitor", "what changed on [regulation] this week", or an unattended scheduler invoking a saved profile. On first invocation walk interactive setup (topics, jurisdictions, cadence, delivery), auto-discover the top authoritative sources per topic, and STOP for user confirmation before any monitoring. On subsequent runs visit only the locked source list plus user seeds, classify items, flag team-relevant ones via a WorkIQ-derived keyword match, and render a self-contained HTML dashboard with client-side sortable columns. Do NOT use for one-off legal research, reading a single document, computing compliance liability or filing position, or non-regulatory news monitoring.

- Skill: `kody-w/regulation-monitor` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add kody-w/regulation-monitor`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kody-w/regulation-monitor/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: kody-w (https://skillmd.com/u/kody-w)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kody-w/regulation-monitor

---


# Regulation Monitor

## Source discipline

This skill is deliberately **bounded and confirmed**. It does not run
open-ended web searches every run. Instead, at setup:

1. Run a small **discovery pass** to identify the top 5 authoritative
   sources per watch topic (regulators, official trackers, reputable
   trade press).
2. **Present the shortlist to the user and stop.** No monitoring runs
   until the user has confirmed the list.
3. The user can swap/remove any of the 5, lower the target, and supply
   their own **seed sources** on top.
4. The confirmed list is **locked into the profile config**.

Every subsequent run visits **only** those sources. A tightly-bounded
fallback web search (at most one query per topic, capped result count,
allowlist-filtered) is used only when a locked source is silent for a
topic in the window.

## Instructions

### Step 0 — First-run setup (only if no config exists)

If `config.json` for the requested profile does not exist, walk the user
through setup:

1. **Profile name** — kebab-case slug, e.g. `pillar-ii`.
2. **Watch topics** — 2 to 8 topics. For each: display name plus 3–8
   keywords the sweeps should look for.
3. **Jurisdictions** — countries, regions, states, sectors, or `global`.
4. **Cadence** — daily, weekly (default), biweekly, monthly.
5. **Window** — days to look back per run (default: matches cadence).
6. **Delivery target** — user's own email (default), a Teams chat, a Loop
   page, or "inline only".

### Step 1 — Auto-discover authoritative sources, then STOP for confirmation

**This is an interactive checkpoint. Run a small discovery pass to
identify candidate sources, then present them and stop. Do not start
the monitoring sweep (Step 5) until the user has explicitly confirmed
the source list.**

For each watch topic the user configured, propose up to **5 authoritative
sources** (default target 5; use fewer if the user asks or if the domain
has fewer canonical sources).

Preference order (pick the strongest 5 that exist for the topic):

1. The primary regulator / issuing body's official page for the topic.
   Examples: OECD's Pillar Two page, the European Commission's page for
   the AI Act, HHS OCR for HIPAA, EDPB for GDPR, ISSB for sustainability
   disclosure.
2. Government official journals and legislative trackers for the
   jurisdictions in scope. Examples: Federal Register, EUR-Lex, UK
   legislation.gov.uk, state legislature bill pages.
3. The relevant multilateral, standard-setting, or specialist body's
   page for the topic. Examples: OECD, UN, BIS, ISO, NIST (AI RMF,
   cyber), WHO/EMA (health), ILO (labor), FSB (financial stability).
4. A reputable public tracker or think tank whose focus matches the
   domain. Examples: Tax Foundation and MTC/NCSL for tax; IAPP and
   Future of Privacy Forum for privacy; Stanford HAI, Brookings AI, and
   the Ada Lovelace Institute for AI; ISSB and EFRAG for sustainability;
   SHRM and EPI for labor; KFF for healthcare policy.
5. Public alert pages from major professional-services or specialist
   firms that cover the domain (public URLs only, never subscriber
   content). Examples: KPMG / EY / PwC / Deloitte / BDO insight pages
   for tax and financial regulation; DLA Piper, Hogan Lovells, Wilson
   Sonsini, Cooley for tech / privacy / AI; Ropes & Gray for healthcare;
   Littler and Ogletree Deakins for labor.

Match the mix to the domain — do not force tax-style sources onto a
non-tax topic.

**Trackers and firm alerts are pointers, not authoritative sources.**
Sources in categories 4 and 5 are useful as indices to find primary
regulator/court/legislative material, but any item you record must cite
the underlying **official document or announcement** (regulator page,
official journal, court opinion) as its `source_url` — not the tracker
or firm alert that linked to it. If you only have the tracker link and
can't find the primary source, drop the item.

**How to find them:** for each topic, do a short bounded discovery
pass — one to three focused web searches against the reputable-domain
allowlist in `references/sources-and-taxonomy.md` — just enough to
identify canonical topic pages (not the regulator's home page). This
discovery pass is separate from the monitoring sweep and must be small.

**Present them and STOP.** Show the shortlist to the user and wait
before doing anything else:

> "Before I start monitoring, here are the 5 authoritative sources I'd
> watch for **Pillar II**:
>   1. OECD — Pillar Two: <url>
>   2. European Commission — Pillar Two implementation: <url>
>   3. HMRC — Multinational Top-up Tax: <url>
>   4. Tax Foundation — Global minimum tax tracker: <url>
>   5. KPMG — BEPS 2.0 tracker: <url>
>
> Want me to swap any out? And do you have any of your own sources
> (regulator pages, internal trackers, subscription-free trade alerts,
> etc.) you want me to add on top of these?"

**Wait for the user's reply.** The user may:

- Approve as-is → proceed.
- Ask to swap or remove one of the 5 → re-run discovery for that slot
  with the constraint they gave.
- Add their own seed URLs → append them to `seed_sources_by_topic`
  under the appropriate topic key. Seed sources are **not** counted
  against the "top 5" — a topic can end up with 5 auto-discovered plus
  N user seeds. A seed that spans multiple topics is added under each
  relevant topic key.
- Ask you to lower the target from 5 → honor it.

**Do not skip this confirmation, even on a re-setup.** If the user
later adds a topic, repeat this checkpoint for the new topic before
touching the sweep.

### Step 2 — Capture the WorkIQ org profile (setup)

Derive a light org profile from WorkIQ and show it for confirmation:

- `workiq_get_my_profile` → job title, department, office
- `workiq_get_my_manager` → manager and their department (context only)
- `workiq_get_relevant_people` (limit 10) → likely function-area
  collaborators

Propose a `function_area_keywords` list (5–15 words: department name and
variants, the user's job function, key collaborator team names, obvious
topic proxies). The user edits and confirms.

If WorkIQ is unavailable on the current platform, ask the user for
`function_area_keywords` manually. The rest of the skill works unchanged.

### Step 3 — Save the profile

Write `config.json`:

```json
{
  "profile_name": "pillar-ii",
  "watch_topics": [
    { "key": "pillar-two", "name": "OECD Pillar II / GloBE",
      "keywords": ["Pillar Two", "GloBE", "global minimum tax",
                   "IIR", "UTPR", "QDMTT", "DMTT", "top-up tax"] }
  ],
  "jurisdictions": ["global"],
  "sources_by_topic": {
    "pillar-two": [
      { "name": "OECD — Pillar Two",
        "url": "https://www.oecd.org/tax/beps/pillar-two-model-rules-in-a-nutshell.pdf",
        "kind": "regulator" }
    ]
  },
  "seed_sources_by_topic": {},
  "cadence": "weekly",
  "window_days": 7,
  "runtime_budget": {
    "max_items": 40,
    "max_fallback_searches": 2,
    "max_fetches_per_source": 2
  },
  "delivery": { "type": "email", "to": ["me@example.com"] },
  "workiq_context": {
    "captured_at": "2026-07-23T11:00:00Z",
    "department": "Global Tax Policy",
    "job_title": "Director, International Tax",
    "function_area_keywords": ["Pillar Two", "GloBE", "international tax",
                               "transfer pricing", "top-up tax"],
    "collaborator_teams": ["Transfer Pricing", "Tax Controversy"]
  },
  "last_run_at": null
}
```

### Step 4 — Load config and scope the sweep (every run)

- Read `config.json` for the profile.
- Resolve the window: from `last_run_at` (if set) to now, else the past
  `window_days`.
- Restate the scope back to the user in one line so they can interrupt
  if it looks wrong (interactive runs only).

### Step 5 — Sweep the locked source list (bounded)

Sweep proceeds in this order and stops when the budget is met:

1. **Every source in `sources_by_topic[topic]` and
   `seed_sources_by_topic[topic]` for each topic.** `web_fetch` each URL.
   Extract items dated within the window.
   - Cap `max_fetches_per_source` (default 2). If a source's index page
     links to individual items, follow at most that many per source.
2. **Bounded fallback search** only for topics where every locked source
   (auto-discovered + user seeds) returned zero items in the window. At
   most one `web_search` per topic, at most `max_fallback_searches`
   total across the run (default 2). Filter results by the reputable-
   domain allowlist. Discard non-matching results.
3. **Stop when `max_items` is reached** (default 40). Prefer regulator
   sources > tracker sources > firm alerts when trimming.

Runtime budget defaults, all configurable in `config.runtime_budget`:

- `max_items`: 40 — total items recorded per run.
- `max_fallback_searches`: 2 — `web_search` calls per run.
- `max_fetches_per_source`: 2 — items followed from one source's index.

Rules that always apply:

- Validate every date against the window; drop out-of-window items.
- Never bypass a paywall; skip subscriber-only content.
- Deduplicate items with the same title + jurisdiction, keeping the
  more authoritative source (regulator > tracker > firm alert).

### Step 6 — Classify each item

Record, for every item:

- **topic** — one of the profile's watch-topic keys.
- **jurisdiction** — from the profile's list, or `global` /
  `local: <name>`.
- **stage** — `proposed` / `in-consultation` / `passed` /
  `regulatory-guidance` / `in-force` / `litigation` / `withdrawn`.
- **date** — ISO date the source is dated or the action took place.
- **title** — the source's short title, verbatim.
- **summary** — 1–2 sentences in the skill's own words.
- **source_name** — publisher's short name.
- **source_url** — canonical public URL actually retrieved
  (http/https/mailto only; other schemes are dropped at render time).

Stage inference guidance is in `references/sources-and-taxonomy.md`.

### Step 7 — Flag team relevance (WorkIQ-derived)

For each item, set `relevant_to_your_team` to `true` if any
`workiq_context.function_area_keywords` phrase appears (case-insensitive)
in the item's title, summary, or matched topic keywords. Otherwise
`false`.

This is a soft highlight, not an impact rubric. The dashboard uses it
to sort and badge; the skill never says "this affects your business" —
that is a human judgment.

### Step 8 — Build the dashboard

1. Write items to `working/regulation-items.json` as a JSON object with the
   shape `{"items": [ ... ]}` (not a bare array). Each entry follows the
   Step 6 schema.
2. Run the bundled generator (single-line command):
   ```
   python scripts/build_dashboard.py --config config.json --items working/regulation-items.json --output output/regulation-dashboard.html
   ```
3. Verify the file was written before telling the user it is ready.

The dashboard is a single self-contained HTML file — KPI tiles (total
items, count per topic, team-relevant count), a table color-coded by
stage with **client-side sortable columns** (click any column header, or
focus and press Enter/Space, to sort), a team-relevant badge on flagged
rows, and every row linking to its primary source.

### Step 9 — Deliver and update last-run

- **Inline**: a short summary — window covered, item counts by topic,
  the top team-relevant items (title, jurisdiction, stage, date). If a
  topic produced no items, say **"No significant developments this
  period"** for that topic. Do not pad, do not speculate.
- **Scheduled runs**: send per the profile's delivery block. If `type`
  is `email`, send the HTML dashboard as an attachment to the addresses
  in `to`. The pre-authorized recipient is the user themselves. Any
  additional recipient requires explicit user confirmation on an
  interactive run and is never added on an unattended run.
- **Update the config** with `last_run_at = <now-ISO>`.

## Guardrails

- **Never fabricate** a bill number, date, quote, or enactment status.
  If a fact cannot be confirmed from a retrieved public source, mark it
  `[unverified]` in the summary. Report gaps honestly.
- **No speculation, rumors, or unofficial sources.** Do not include
  items sourced from anonymous leaks, social-media speculation,
  unattributed drafts, or "reportedly" / "expected to" claims without a
  named official source. If an item cannot be tied to a specific
  document or announcement from a source on the profile's locked list
  (or the reputable-domain allowlist for fallback search), drop it.
  Better silence than noise.
- **Report empty categories explicitly.** If a topic produced no items
  in the window, say **"No significant developments this period"** for
  that topic in both the inline summary and the dashboard. Do not pad
  with low-signal filler.
- **Public sources only.** Never bypass a paywall or reproduce
  paywalled or copyrighted text. Summarize in the skill's own words and
  link the source.
- **Locked source list.** The sweep only visits URLs in the profile's
  `sources_by_topic` and `seed_sources_by_topic`. That list is set at
  interactive setup with explicit user confirmation — the skill cannot
  start the monitoring sweep until the user has approved the sources.
  Fallback search is bounded by `runtime_budget.max_fallback_searches`
  and filtered by the reputable-domain allowlist. Do not add new
  sources on an unattended run — that requires interactive re-setup.
- **Monitoring, not advice.** The skill never states a filing position,
  a legal conclusion, or a business impact. `relevant_to_your_team` is
  a soft keyword-match highlight.
- **Confirm before external sends.** Emailing anyone other than the
  user requires explicit confirmation on an interactive run; unattended
  runs never add recipients.
- **Confidentiality and sensitivity.** If a retrieved source carries a
  confidentiality label or sensitivity marking (for example
  "Confidential", "Internal Only", "Restricted", or an enterprise
  information-protection label), or contains unreleased figures,
  customer or partner identifiers, or names that aren't public yet,
  drop it — this skill uses public sources only. Never add PII,
  customer identifiers, or non-public attribution to the dashboard
  beyond the function-area keywords the user confirmed at setup.
- **Cover exactly the requested topics** — no more, no fewer.
- **Do not reproduce third-party copyrighted text** verbatim; paraphrase
  in the skill's own words.
- **Verify delivery.** Confirm the dashboard file exists before
  reporting success. If the delivery block failed, report the failure.
- **Cite by exact source name** and validate every date against the
  requested window.

## References

- `references/sources-and-taxonomy.md` — reputable-domain allowlist,
  item classification taxonomy, stage-inference rules, and per-topic
  search-query templates.
- `scripts/build_dashboard.py` — self-contained dashboard generator
  (Python standard library only; embeds a small vanilla-JS sorter for
  the items table).

<!-- toaster:generated:begin -->

## Run this — do not improvise

This capability's deterministic implementation is a RAPP single-file agent, linked beside this file as `regulation_monitor_agent.py` and embedded as the fenced Python below (sha256 90fd34cb1ca1b2fe…; a byte-exact copy is also vaulted in the capsule comment at the end of this file). On a host with sandbox execution, run the linked file directly — if it is missing, write the fence contents verbatim to `regulation_monitor_agent.py` first:

```bash
python3 regulation_monitor_agent.py '{"key": "value"}'      # arguments as one JSON object
echo '{"key": "value"}' | python3 regulation_monitor_agent.py   # or on stdin
python3 regulation_monitor_agent.py --tool                      # emit the JSON tool contract
```

Treat stdout as a tool result. If it reports missing or unresolved inputs, stop and collect them. If it returns `steps`, execute those steps in order exactly as returned; if it returns `instructions`, follow them with the supplied inputs. Otherwise use the result verbatim. Do not invent behavior beyond that output. On a host without code execution, treat the Parameters schema and the code below as the exact specification and never paraphrase a step. Never edit inside the generated markers; a converter-equipped host can instead restore the original file checksum-verified with the installed `rapp-agent-converter/scripts/toast.py convert SKILL.md --to agent`.

````python  # rapp:deterministic
"""RegulationMonitor -- Use when the user asks to set up, run, or check a scheduled regulation, law, or regulatory-guidance monitor or tracker — triggers include "set up a regulation tracker for X", "monitor [regulation] for me", "run my regulation monitor", "what changed on [regulation] this week", or an unattended scheduler invoking a saved profile. On first invocation walk interactive setup (topics, jurisdictions, cadence, delivery), auto-discover the top authoritative sources per topic, and STOP for user confirmation before any monitoring. On subsequent runs visit only the locked source list plus user seeds, classify items, flag team-relevant ones via a WorkIQ-derived keyword match, and render a self-contained HTML dashboard with client-side sortable columns. Do NOT use for one-off legal research, reading a single document, computing compliance liability or filing position, or non-regulatory news monitoring.

Generated by the rapp skill from regulation-monitor. The RCI capsule at the bottom of this file carries the full original; `toast.py convert` restores it byte-exact."""

import json
import re
import sys

try:
    from agents.basic_agent import BasicAgent
except ImportError:  # running OUTSIDE a brainstem -- stay executable anyway.
    class BasicAgent:  # noqa: D101 - minimal stand-in, same contract
        def __init__(self, name=None, metadata=None):
            if name:
                self.name = name
            if metadata:
                self.metadata = metadata

        def perform(self, **kwargs):
            return "Not implemented."

        def system_context(self):
            return None

        def to_tool(self):
            return {"type": "function", "function": {
                "name": self.name,
                "description": self.metadata.get("description", ""),
                "parameters": self.metadata.get("parameters", {})}}

# The procedural layer, verbatim from the source capability.
INSTRUCTIONS = '# Regulation Monitor\n\n## Source discipline\n\nThis skill is deliberately **bounded and confirmed**. It does not run\nopen-ended web searches every run. Instead, at setup:\n\n1. Run a small **discovery pass** to identify the top 5 authoritative\n   sources per watch topic (regulators, official trackers, reputable\n   trade press).\n2. **Present the shortlist to the user and stop.** No monitoring runs\n   until the user has confirmed the list.\n3. The user can swap/remove any of the 5, lower the target, and supply\n   their own **seed sources** on top.\n4. The confirmed list is **locked into the profile config**.\n\nEvery subsequent run visits **only** those sources. A tightly-bounded\nfallback web search (at most one query per topic, capped result count,\nallowlist-filtered) is used only when a locked source is silent for a\ntopic in the window.\n\n## Instructions\n\n### Step 0 — First-run setup (only if no config exists)\n\nIf `config.json` for the requested profile does not exist, walk the user\nthrough setup:\n\n1. **Profile name** — kebab-case slug, e.g. `pillar-ii`.\n2. **Watch topics** — 2 to 8 topics. For each: display name plus 3–8\n   keywords the sweeps should look for.\n3. **Jurisdictions** — countries, regions, states, sectors, or `global`.\n4. **Cadence** — daily, weekly (default), biweekly, monthly.\n5. **Window** — days to look back per run (default: matches cadence).\n6. **Delivery target** — user's own email (default), a Teams chat, a Loop\n   page, or "inline only".\n\n### Step 1 — Auto-discover authoritative sources, then STOP for confirmation\n\n**This is an interactive checkpoint. Run a small discovery pass to\nidentify candidate sources, then present them and stop. Do not start\nthe monitoring sweep (Step 5) until the user has explicitly confirmed\nthe source list.**\n\nFor each watch topic the user configured, propose up to **5 authoritative\nsources** (default target 5; use fewer if the user asks or if the domain\nhas fewer canonical sources).\n\nPreference order (pick the strongest 5 that exist for the topic):\n\n1. The primary regulator / issuing body's official page for the topic.\n   Examples: OECD's Pillar Two page, the European Commission's page for\n   the AI Act, HHS OCR for HIPAA, EDPB for GDPR, ISSB for sustainability\n   disclosure.\n2. Government official journals and legislative trackers for the\n   jurisdictions in scope. Examples: Federal Register, EUR-Lex, UK\n   legislation.gov.uk, state legislature bill pages.\n3. The relevant multilateral, standard-setting, or specialist body's\n   page for the topic. Examples: OECD, UN, BIS, ISO, NIST (AI RMF,\n   cyber), WHO/EMA (health), ILO (labor), FSB (financial stability).\n4. A reputable public tracker or think tank whose focus matches the\n   domain. Examples: Tax Foundation and MTC/NCSL for tax; IAPP and\n   Future of Privacy Forum for privacy; Stanford HAI, Brookings AI, and\n   the Ada Lovelace Institute for AI; ISSB and EFRAG for sustainability;\n   SHRM and EPI for labor; KFF for healthcare policy.\n5. Public alert pages from major professional-services or specialist\n   firms that cover the domain (public URLs only, never subscriber\n   content). Examples: KPMG / EY / PwC / Deloitte / BDO insight pages\n   for tax and financial regulation; DLA Piper, Hogan Lovells, Wilson\n   Sonsini, Cooley for tech / privacy / AI; Ropes & Gray for healthcare;\n   Littler and Ogletree Deakins for labor.\n\nMatch the mix to the domain — do not force tax-style sources onto a\nnon-tax topic.\n\n**Trackers and firm alerts are pointers, not authoritative sources.**\nSources in categories 4 and 5 are useful as indices to find primary\nregulator/court/legislative material, but any item you record must cite\nthe underlying **official document or announcement** (regulator page,\nofficial journal, court opinion) as its `source_url` — not the tracker\nor firm alert that linked to it. If you only have the tracker link and\ncan't find the primary source, drop the item.\n\n**How to find them:** for each topic, do a short bounded discovery\npass — one to three focused web searches against the reputable-domain\nallowlist in `references/sources-and-taxonomy.md` — just enough to\nidentify canonical topic pages (not the regulator's home page). This\ndiscovery pass is separate from the monitoring sweep and must be small.\n\n**Present them and STOP.** Show the shortlist to the user and wait\nbefore doing anything else:\n\n> "Before I start monitoring, here are the 5 authoritative sources I'd\n> watch for **Pillar II**:\n>   1. OECD — Pillar Two: <url>\n>   2. European Commission — Pillar Two implementation: <url>\n>   3. HMRC — Multinational Top-up Tax: <url>\n>   4. Tax Foundation — Global minimum tax tracker: <url>\n>   5. KPMG — BEPS 2.0 tracker: <url>\n>\n> Want me to swap any out? And do you have any of your own sources\n> (regulator pages, internal trackers, subscription-free trade alerts,\n> etc.) you want me to add on top of these?"\n\n**Wait for the user's reply.** The user may:\n\n- Approve as-is → proceed.\n- Ask to swap or remove one of the 5 → re-run discovery for that slot\n  with the constraint they gave.\n- Add their own seed URLs → append them to `seed_sources_by_topic`\n  under the appropriate topic key. Seed sources are **not** counted\n  against the "top 5" — a topic can end up with 5 auto-discovered plus\n  N user seeds. A seed that spans multiple topics is added under each\n  relevant topic key.\n- Ask you to lower the target from 5 → honor it.\n\n**Do not skip this confirmation, even on a re-setup.** If the user\nlater adds a topic, repeat this checkpoint for the new topic before\ntouching the sweep.\n\n### Step 2 — Capture the WorkIQ org profile (setup)\n\nDerive a light org profile from WorkIQ and show it for confirmation:\n\n- `workiq_get_my_profile` → job title, department, office\n- `workiq_get_my_manager` → manager and their department (context only)\n- `workiq_get_relevant_people` (limit 10) → likely function-area\n  collaborators\n\nPropose a `function_area_keywords` list (5–15 words: department name and\nvariants, the user's job function, key collaborator team names, obvious\ntopic proxies). The user edits and confirms.\n\nIf WorkIQ is unavailable on the current platform, ask the user for\n`function_area_keywords` manually. The rest of the skill works unchanged.\n\n### Step 3 — Save the profile\n\nWrite `config.json`:\n\n```json\n{\n  "profile_name": "pillar-ii",\n  "watch_topics": [\n    { "key": "pillar-two", "name": "OECD Pillar II / GloBE",\n      "keywords": ["Pillar Two", "GloBE", "global minimum tax",\n                   "IIR", "UTPR", "QDMTT", "DMTT", "top-up tax"] }\n  ],\n  "jurisdictions": ["global"],\n  "sources_by_topic": {\n    "pillar-two": [\n      { "name": "OECD — Pillar Two",\n        "url": "https://www.oecd.org/tax/beps/pillar-two-model-rules-in-a-nutshell.pdf",\n        "kind": "regulator" }\n    ]\n  },\n  "seed_sources_by_topic": {},\n  "cadence": "weekly",\n  "window_days": 7,\n  "runtime_budget": {\n    "max_items": 40,\n    "max_fallback_searches": 2,\n    "max_fetches_per_source": 2\n  },\n  "delivery": { "type": "email", "to": ["me@example.com"] },\n  "workiq_context": {\n    "captured_at": "2026-07-23T11:00:00Z",\n    "department": "Global Tax Policy",\n    "job_title": "Director, International Tax",\n    "function_area_keywords": ["Pillar Two", "GloBE", "international tax",\n                               "transfer pricing", "top-up tax"],\n    "collaborator_teams": ["Transfer Pricing", "Tax Controversy"]\n  },\n  "last_run_at": null\n}\n```\n\n### Step 4 — Load config and scope the sweep (every run)\n\n- Read `config.json` for the profile.\n- Resolve the window: from `last_run_at` (if set) to now, else the past\n  `window_days`.\n- Restate the scope back to the user in one line so they can interrupt\n  if it looks wrong (interactive runs only).\n\n### Step 5 — Sweep the locked source list (bounded)\n\nSweep proceeds in this order and stops when the budget is met:\n\n1. **Every source in `sources_by_topic[topic]` and\n   `seed_sources_by_topic[topic]` for each topic.** `web_fetch` each URL.\n   Extract items dated within the window.\n   - Cap `max_fetches_per_source` (default 2). If a source's index page\n     links to individual items, follow at most that many per source.\n2. **Bounded fallback search** only for topics where every locked source\n   (auto-discovered + user seeds) returned zero items in the window. At\n   most one `web_search` per topic, at most `max_fallback_searches`\n   total across the run (default 2). Filter results by the reputable-\n   domain allowlist. Discard non-matching results.\n3. **Stop when `max_items` is reached** (default 40). Prefer regulator\n   sources > tracker sources > firm alerts when trimming.\n\nRuntime budget defaults, all configurable in `config.runtime_budget`:\n\n- `max_items`: 40 — total items recorded per run.\n- `max_fallback_searches`: 2 — `web_search` calls per run.\n- `max_fetches_per_source`: 2 — items followed from one source's index.\n\nRules that always apply:\n\n- Validate every date against the window; drop out-of-window items.\n- Never bypass a paywall; skip subscriber-only content.\n- Deduplicate items with the same title + jurisdiction, keeping the\n  more authoritative source (regulator > tracker > firm alert).\n\n### Step 6 — Classify each item\n\nRecord, for every item:\n\n- **topic** — one of the profile's watch-topic keys.\n- **jurisdiction** — from the profile's list, or `global` /\n  `local: <name>`.\n- **stage** — `proposed` / `in-consultation` / `passed` /\n  `regulatory-guidance` / `in-force` / `litigation` / `withdrawn`.\n- **date** — ISO date the source is dated or the action took place.\n- **title** — the source's short title, verbatim.\n- **summary** — 1–2 sentences in the skill's own words.\n- **source_name** — publisher's short name.\n- **source_url** — canonical public URL actually retrieved\n  (http/https/mailto only; other schemes are dropped at render time).\n\nStage inference guidance is in `references/sources-and-taxonomy.md`.\n\n### Step 7 — Flag team relevance (WorkIQ-derived)\n\nFor each item, set `relevant_to_your_team` to `true` if any\n`workiq_context.function_area_keywords` phrase appears (case-insensitive)\nin the item's title, summary, or matched topic keywords. Otherwise\n`false`.\n\nThis is a soft highlight, not an impact rubric. The dashboard uses it\nto sort and badge; the skill never says "this affects your business" —\nthat is a human judgment.\n\n### Step 8 — Build the dashboard\n\n1. Write items to `working/regulation-items.json` as a JSON object with the\n   shape `{"items": [ ... ]}` (not a bare array). Each entry follows the\n   Step 6 schema.\n2. Run the bundled generator (single-line command):\n   ```\n   python scripts/build_dashboard.py --config config.json --items working/regulation-items.json --output output/regulation-dashboard.html\n   ```\n3. Verify the file was written before telling the user it is ready.\n\nThe dashboard is a single self-contained HTML file — KPI tiles (total\nitems, count per topic, team-relevant count), a table color-coded by\nstage with **client-side sortable columns** (click any column header, or\nfocus and press Enter/Space, to sort), a team-relevant badge on flagged\nrows, and every row linking to its primary source.\n\n### Step 9 — Deliver and update last-run\n\n- **Inline**: a short summary — window covered, item counts by topic,\n  the top team-relevant items (title, jurisdiction, stage, date). If a\n  topic produced no items, say **"No significant developments this\n  period"** for that topic. Do not pad, do not speculate.\n- **Scheduled runs**: send per the profile's delivery block. If `type`\n  is `email`, send the HTML dashboard as an attachment to the addresses\n  in `to`. The pre-authorized recipient is the user themselves. Any\n  additional recipient requires explicit user confirmation on an\n  interactive run and is never added on an unattended run.\n- **Update the config** with `last_run_at = <now-ISO>`.\n\n## Guardrails\n\n- **Never fabricate** a bill number, date, quote, or enactment status.\n  If a fact cannot be confirmed from a retrieved public source, mark it\n  `[unverified]` in the summary. Report gaps honestly.\n- **No speculation, rumors, or unofficial sources.** Do not include\n  items sourced from anonymous leaks, social-media speculation,\n  unattributed drafts, or "reportedly" / "expected to" claims without a\n  named official source. If an item cannot be tied to a specific\n  document or announcement from a source on the profile's locked list\n  (or the reputable-domain allowlist for fallback search), drop it.\n  Better silence than noise.\n- **Report empty categories explicitly.** If a topic produced no items\n  in the window, say **"No significant developments this period"** for\n  that topic in both the inline summary and the dashboard. Do not pad\n  with low-signal filler.\n- **Public sources only.** Never bypass a paywall or reproduce\n  paywalled or copyrighted text. Summarize in the skill's own words and\n  link the source.\n- **Locked source list.** The sweep only visits URLs in the profile's\n  `sources_by_topic` and `seed_sources_by_topic`. That list is set at\n  interactive setup with explicit user confirmation — the skill cannot\n  start the monitoring sweep until the user has approved the sources.\n  Fallback search is bounded by `runtime_budget.max_fallback_searches`\n  and filtered by the reputable-domain allowlist. Do not add new\n  sources on an unattended run — that requires interactive re-setup.\n- **Monitoring, not advice.** The skill never states a filing position,\n  a legal conclusion, or a business impact. `relevant_to_your_team` is\n  a soft keyword-match highlight.\n- **Confirm before external sends.** Emailing anyone other than the\n  user requires explicit confirmation on an interactive run; unattended\n  runs never add recipients.\n- **Confidentiality and sensitivity.** If a retrieved source carries a\n  confidentiality label or sensitivity marking (for example\n  "Confidential", "Internal Only", "Restricted", or an enterprise\n  information-protection label), or contains unreleased figures,\n  customer or partner identifiers, or names that aren't public yet,\n  drop it — this skill uses public sources only. Never add PII,\n  customer identifiers, or non-public attribution to the dashboard\n  beyond the function-area keywords the user confirmed at setup.\n- **Cover exactly the requested topics** — no more, no fewer.\n- **Do not reproduce third-party copyrighted text** verbatim; paraphrase\n  in the skill's own words.\n- **Verify delivery.** Confirm the dashboard file exists before\n  reporting success. If the delivery block failed, report the failure.\n- **Cite by exact source name** and validate every date against the\n  requested window.\n\n## References\n\n- `references/sources-and-taxonomy.md` — reputable-domain allowlist,\n  item classification taxonomy, stage-inference rules, and per-topic\n  search-query templates.\n- `scripts/build_dashboard.py` — self-contained dashboard generator\n  (Python standard library only; embeds a small vanilla-JS sorter for\n  the items table).'

# Ordered commands lifted verbatim from the capability's own documentation.
STEPS = []


class RegulationMonitorAgent(BasicAgent):
    def __init__(self):
        self.name = 'RegulationMonitor'
        self.metadata = {
          "name": "RegulationMonitor",
          "description": "Use when the user asks to set up, run, or check a scheduled regulation, law, or regulatory-guidance monitor or tracker \u2014 triggers include \"set up a regulation tracker for X\", \"monitor [regulation] for me\", \"run my regulation monitor\", \"what changed on [regulation] this week\", or an unattended scheduler invoking a saved profile. On first invocation walk interactive setup (topics, jurisdictions, cadence, delivery), auto-discover the top authoritative sources per topic, and STOP for user confirmation before any monitoring. On subsequent runs visit only the locked source list plus user seeds, classify items, flag team-relevant ones via a WorkIQ-derived keyword match, and render a self-contained HTML dashboard with client-side sortable columns. Do NOT use for one-off legal research, reading a single document, computing compliance liability or filing position, or non-regulatory news monitoring.",
          "parameters": {
            "type": "object",
            "properties": {},
            "required": []
          }
        }
        super().__init__(name=self.name, metadata=self.metadata)

    def perform(self, **kwargs):  # toaster:generated-perform
        return json.dumps({"status": "ok", "instructions": INSTRUCTIONS,
                           "inputs": kwargs,
                           "note": "Prose-only capability: follow INSTRUCTIONS "
                                   "with the given inputs."}, indent=2)

if __name__ == "__main__":
    #     echo '{"arg": "value"}' | python3 regulation_monitor_agent.py
    #     python3 regulation_monitor_agent.py '{"arg": "value"}'
    #     python3 regulation_monitor_agent.py --tool          # emit the JSON tool contract
    _a = sys.argv[1:]
    if _a and _a[0] == "--tool":
        print(json.dumps(RegulationMonitorAgent().to_tool(), indent=2))
    else:
        _raw = _a[0] if _a else (sys.stdin.read().strip() or "{}")
        print(RegulationMonitorAgent().perform(**json.loads(_raw)))

# rci-capsule:v1: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

…(truncated)
