Event New¶
Author an event source in conversation — elicit what to watch, propose a grounded EventSourceDef draft, dry-run it with zero side effects, confirm, then activate it. The conversational front end for event_source_create; builds polling watchers (e.g. a stock-watcher) without hand-editing .md files.
Workflow name: event-new
Execution: main
Slash command: /wb-event-new
Override not allowed
Steps¶
| # | ID | Name | Type | Depends on |
|---|---|---|---|---|
| 1 | elicit |
Understand what to watch, what change matters, and the reaction | reasoning |
|
| 2 | propose |
Draft a grounded EventSourceDef proposal | reasoning |
elicit |
| 3 | dry_run |
Preview the proposed source with zero side effects | code |
propose |
| 4 | confirm |
Show the draft + preview; get explicit confirmation | reasoning |
dry_run |
| 5 | activate |
Write the validated source | code |
confirm |
| 6 | monitor |
Surface the first firings and how to pause | reasoning |
activate |
Step instructions¶
elicit¶
Reasoning step. Draw out three things in plain language: - watch — what state to watch and where (a URL + the field, e.g. "NVDA's price and CEO from this JSON quote endpoint"). - change — what change is worth a notification (e.g. "the CEO changes, or the price moves more than 5%"). - reaction — what should happen (a notification — confirm the user wants notify-only).
Advance with {"watch": "...", "change": "...", "reaction": "..."}.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result) — FastMCP silently drops unknown kwargs.
propose¶
Reasoning step. Turn the elicited intent into a structured draft — the exact
parameter set event_source_create takes. Translate "change" into a CEL condition
grounded to the extracted fields. Example draft for the stock-watcher:
{
"name": "nvda-watch",
"proposal": {
"name": "nvda-watch",
"source_type": "http_poll",
"url": "https://example.test/quote/NVDA.json",
"interval": "6h",
"extract_mode": "json_path",
"extract_path": "$",
"condition": "event.data.ceo != prev.data.ceo || abs(event.data.price - prev.data.price) / prev.data.price > 0.05",
"action": "notify",
"allowed_actions": ["notify"],
"autonomy": "notify_only"
}
}
Notes:
- name must be 1–64 chars, alphanumeric-start (it becomes the .md stem).
- Use extract_path: "$" (whole object) when the condition reads multiple fields
(event.data.ceo, event.data.price); use a scalar path ($.price) plus
event.data != prev.data when watching a single value.
- Keep action inside allowed_actions — the source is scoped to exactly the
actions you list.
- For "only if it's material" intents, add an optional semantic block to the
proposal, e.g. "semantic": {"question": "Is there material news that could move
NVDA?", "query": "NVDA news", "cooldown": "1h"}. It runs after the CEL condition;
omit it for pure threshold/equality watches.
Advance with {"name": "<name>", "proposal": { ...the full param set... }}.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result) — FastMCP silently drops unknown kwargs.
dry_run¶
Code step. Preview the proposed source with zero side effects — no file is written, nothing is published, no action runs:
`mcp__work-buddy__wb_run("event_source_dry_run", {"proposal": <propose.proposal>})`
The result reports current (the sampled value), changed, the would_emit
event, condition_passed, and would_fire. The first observation is a silent
baseline — would_fire is normally false on a brand-new source (it fires on the
next change), so read this step as "did the fetch + extraction + condition
compile and produce a sane value," not "will it fire right now." If ok is
false, surface the validation error and loop back to propose to fix the draft.
If the proposal has a semantic block, the dry-run reports it
(semantic_configured, semantic_pending) but does not evaluate it by default. To
actually test the gate (a real web search + local-model call), pass
{"proposal": <…>, "run_semantic": true} — only do so when the user wants to
confirm the materiality judgement before activating.
Advance with the dry-run result.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result) — FastMCP silently drops unknown kwargs.
confirm¶
Reasoning step. Show the user the draft in plain language plus the dry-run sample (the value it read, and the condition it will fire on). Ask for an explicit yes.
If the user wants changes, advance with {"confirmed": false} and loop back to
propose with their edits. Only advance with {"confirmed": true} on a clear yes.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result) — FastMCP silently drops unknown kwargs.
activate¶
Code step. Only run this on confirm.confirmed == true. Write the source by
calling event_source_create with the confirmed proposal:
`mcp__work-buddy__wb_run("event_source_create", <propose.proposal>)`
The op re-validates and writes <event_sources>/<name>.md. The poll tick re-loads
sources each run, so the watcher activates on the next poll — no restart. If the
result is {"success": false}, surface the errors and loop back to propose.
Advance with the create result.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result) — FastMCP silently drops unknown kwargs.
monitor¶
Reasoning step. Confirm the source is live, tell the user its poll interval and what it will notify on, and remind them they can pause it any time:
`mcp__work-buddy__wb_run("event_source_toggle", {"name": "<name>", "enabled": false})`
Surface the first firing when it lands (it arrives as a normal notification). Wrap up the workflow.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result) — FastMCP silently drops unknown kwargs.
Context¶
Author an event source in conversation. The loop elicits what to watch, proposes
a grounded draft, previews it with zero side effects, confirms, and only then
writes the source — mirroring /wb-dev-document's propose→confirm→apply gate.
Philosophy¶
An event source is a small, durable watcher: it polls some state, reacts only on
a meaningful change (a diff, not every fetch), evaluates a condition, and fires
a scoped action. Hand-authoring the .md is error-prone; this loop keeps the
draft grounded to what actually exists (registered source types and actions) and
shows a real preview before anything is written or fires.
Grounding (propose only what exists)¶
- source.type:
http_poll(GET a URL).fakeis test-only — don't propose it. - extract.mode:
json_path(e.g.$.quote.price),css, orhash(whole-payload). - action:
notifyonly. autonomy:notify_onlyonly. No action that changes state runs — a watcher notifies; the user acts. - condition: a CEL predicate over
event.data/prev.data(and thecurrentshorthand). The action fires only when it is true.abs()is available for threshold conditions. - semantic (optional Tier-3): an LLM gate layered on top of the CEL condition
—
{question, query, cooldown, debounce, min_confidence}. Add it only for "is this material?" judgements CEL can't express; it web-searches and asks a local model, runs only after CEL passes, and fails closed.
Never invent a source type, action, or autonomy the registry doesn't have. If the user wants something outside this set, say so plainly and stop — don't fabricate a source that will fail validation.