--- name: competitor-monitoring description: Keep watch on competitors (and, where sources allow, industry people) on a schedule and report only real changes. Use when someone wants a competitor monitored, asks what a watched company has been doing, wants to change or stop a watch — and always when a scheduled run tells you to. Triggers include "watch", "keep an eye on", "monitor competitor", "what is the competition doing", "what changed at X", "stop watching", and their German equivalents ("beobachte", "behalte im Auge", "was macht der Wettbewerb"). user-invocable: true metadata: hybridclaw: short_description: Scheduled competitor monitoring. category: business tags: - sales - competitors - monitoring - cron - memory related_skills: - search.news - search.web --- # Competitor Monitoring You watch competitors for a salesperson. They read the result in an app, not in the chat — the Sales Companion iOS app parses a fenced `watch` block from your daily memory note. Your job is not to write a nice report but to **detect reliably what changed since the last run**, and to leave out everything else. Keep the tone factual. No "exciting", no "interestingly". A salesperson reads this in twenty seconds in the morning. Write names, titles and details in the user's language. ## The four stores | What | Where | How | |---|---|---| | Schedule | one cron task per target | `cron` `add` / `update` / `remove` — chat only | | Watchlist | `watch/targets.json` in the workspace | `read` / `write` | | Comparison state | `watch/.json` in the workspace | `read` / `write` | | Result for the app | today's daily note | `memory` with `action: "append"`, `target: "daily"` | **`watch/targets.json` is the watchlist.** Scheduled runs cannot call `cron` at all — the tool is blocked there — so they can only know the full list from this file. Change it in the same step as the cron task, every time: ``` {"targets": [ {"id": "", "name": "", "kind": "company", "focus": "", "schedule": "", "url": "", "task_id": } ]} ``` This and every other `<…>` shape in this file is a **template**: fill in real values from the user's request, `watch/targets.json` or this run. Never write a template, or an example company name from this file, into any file. `task_id` is the number `cron add` reports ("Scheduled … task #42 …"); pass it as `taskId` to `update` and `remove`. Why these stores: the `memory` tool may only write today's daily note, and only memory files are synced to the platform, where the app reads them. A workspace file never reaches the app, so it holds your own state. ## A. Set up a watch This happens in a chat with the user, where `cron` is available. 1. Clarify **who** and **what to look for**. If the focus is missing, ask exactly once ("What should I watch at Allianz — prices, products, people?"). If the website is missing, look it up yourself; do not ask. 2. Derive a **stable id** from the name: lowercase ASCII, hyphens, no umlauts — `allianz-de`, `barmenia`, `jane-doe`. If `watch/targets.json` already has it, update that watch instead of adding a second one. 3. Create the cron task: ``` cron action=add cron=" * * *" tz="" channel="" prompt="Competitor monitoring: use the competitor-monitoring skill, read its SKILL.md and follow it. Target: (, ), kind=, focus: ." ``` - Pass `tz` with the user's IANA time zone so the run keeps its local time across daylight-saving changes. If you do not know it, ask; for a German-speaking user, `Europe/Berlin` is the usual answer. - `channel` is **required** when the request comes from a web chat session: the tool refuses to schedule there, because the output of a task created without a delivery channel would be discarded. Use the user's email address or another configured messaging target. If none is known, say so plainly instead of trying without one. 4. Add the target with its `task_id` to `watch/targets.json` (create the file if it is missing). 5. **Do not create `watch/.json`.** The first scheduled run records the baseline; a file created now would only be empty. 6. **Write the result block right away** (section C) with the updated list and `"findings": []`. Otherwise the new target only appears in the app after the first scheduled run. 7. Confirm in one sentence: "I'm watching Allianz from tomorrow morning, focus pet insurance." **Changing a watch** (time, focus): `cron action=update taskId=` from `watch/targets.json`, then update the entry there. Never add a second task for the same target. **Removing a watch:** `cron action=remove taskId=`, remove the entry from `watch/targets.json`, delete `watch/.json`, write the block again. ## B. A scheduled run You receive the task's prompt. `cron` is not available here; do not try it. Work strictly in this order: 1. **Read the watchlist and the previous state.** `read watch/targets.json` and `read watch/.json`. If the state file is missing or its `snapshots` are empty, this is a **baseline run**: fetch the sources, save them as the state (step 6) and report **nothing** as a finding — there is nothing to compare against yet. Still write the block with `"findings": []`. 2. **Fetch the current state.** Use several sources, not just one: - `web_search` with `freshness: "week"` on company name plus focus - `web_fetch` on the pages that belong to the focus (pricing, product, press pages) — they are listed in the state under `snapshots` - for `kind=person`, also search name plus role. Social activity is only partly visible with the available tools, so rely on interviews, talks, press releases and job changes. 3. **Compare.** For each source: did the substance change? Ignore counters, dates, cookie banners, ordering and rewording that does not change meaning. 4. **Judge.** A finding only counts if it falls into one of these categories. Use the category names as given; the app shows them as labels. | `category` | Example | |---|---| | `Preis` (price) | plan more expensive, new discount, tiers changed | | `Produkt` (product) | new product, benefit dropped, terms changed | | `Partnerschaft` (partnership) | cooperation, reseller, integration | | `Finanzierung` (funding) | funding round, acquisition, sale | | `Personal` (people) | change in management or sales leadership | | `Stellen` (hiring) | notable job ads that reveal a direction | Everything else — guide articles, blog posts, social chatter, redesigns, anniversaries — is `severity: "minor"` or does not belong in the block at all. `severity: "significant"` is only for what the salesperson **must know today because it changes a conversation**; only `significant` triggers a notification on their phone. When in doubt, `minor`: a phone that buzzes too often gets muted. 5. **Deduplicate.** Give every finding a **fingerprint**: category, source URL and the new value in a few words — `Preis|https://allianz.de/preise|Premium 27,90 €`. If the state's `reported` map already has that fingerprint, the change was reported before: drop it from this run. Otherwise assign a new id (see section C). 6. **Write the result block first** (section C). Only if the `memory` append succeeded, go on to step 7. If it fails — for example because today's note is full — stop and leave the state untouched, so the next run finds the same changes again instead of losing them. Reply with the failure line (step 8). 7. **Then update the state.** `write watch/.json`: ``` {"target": "", "snapshots": { "": {"fetched_at": "", "summary": "", "key_facts": {"": ""}}}, "reported": { "": {"id": "", "first_seen": ""}}} ``` Keep `summary` short — this file is your memory, not an archive. Drop `reported` entries older than 30 days; the app only sees about two weeks of notes anyway. 8. **Reply with a short digest.** Your final reply is delivered to the task's channel — usually the user's inbox — after **every** run, so it must be readable at a glance. Plain text in the user's language, no preamble, no JSON, no tables, no sign-off. Exactly one of these shapes: - significant findings, one line each, at most five: `: — ` - only minor findings: `: kleinere Änderungen, Details in der App.` - nothing new: `: nichts Neues.` - baseline run: `: Beobachtung läuft, Ausgangsstand gespeichert.` - the block could not be written: `: Lauf fehlgeschlagen — .` If you find nothing, write the block anyway, with `"findings": []`. The app tells "nothing happened" apart from "the run did not take place". ## C. The result block The app reads **only** this block. Append it with `memory` (`action: "append"`, `target: "daily"`) to the **end** of today's daily note. The shape — a template, not data. Every `<…>` is replaced with real values; the block you write must be valid JSON with no `<` or `>` left in it: ````markdown ```watch { "targets": [ {"id": "", "name": "", "kind": "", "focus": "", "schedule": "", "url": ""} ], "findings": [ {"id": "", "target": "", "date": "", "severity": "", "category": "", "title": "", "detail": "", "source": ""} ] } ``` ```` **Write only what is real.** `targets` comes from `watch/targets.json`; `findings` only from changes you saw in *this* run (or `[]`). A block that repeats this template's placeholders, or invents a company that is not on the watchlist, puts false alerts on the user's phone. Rules that are not negotiable: - **`targets` always holds the complete list** from `watch/targets.json` — not just this run's target — without the internal `task_id`. The app takes the list from the newest note; an incomplete list makes targets disappear. - **`kind`** is `company` or `person`. **`schedule`** is a human-readable label in the user's language and local time ("täglich 07:30"), not the cron expression. - **A finding's `id` is assigned once and never changes.** Scheme: `--`, recorded in the state's `reported` map under the finding's fingerprint. Never derive an id from today's date for a change that was already reported — the app remembers announced ids and would notify twice. - **Valid JSON**: double quotes, no comments, no trailing commas. The app discards a broken block — the whole run is then lost. - **`detail` stays within two sentences.** Daily notes are truncated at 20,000 characters when synced and the space is shared; long explanations belong in `watch/.json`, not here. - **`source` is the page where you saw it**, not the homepage. No verifiable source, no finding. ## What you do not do - Do not invent findings to make a run look successful. An empty run is a good result. - Do not estimate prices or figures. If it is not stated, it is not in there. - Do not report anything you only know from prior knowledge — only what you actually saw in this run. - Do not write anything beyond the digest in step 8. The report is the block; the digest only points at it. ## Prerequisites - **Cloud memory must be enabled** for the agent instance. It is off by default; without it the daily notes never leave the sandbox and the app shows nothing. If a user asks why the app stays empty, point them to the HybridClaw settings in the web workspace. - A delivery channel for scheduled tasks (see step A.3).