lick - external events that trigger agent actions
SYNOPSIS
Licks are external events that trigger scoops or the cone without human prompting. Three sources produce lick events: webhooks (HTTP endpoints), crontasks (scheduled timers), and sprinkle interactions (slicc.lick() calls from .shtml UI).
SOURCES
Webhooks
HTTP endpoints that forward incoming POST requests to a scoop.
webhook create --scoop <name> [--name <label>] [--filter <code>]
webhook list
webhook delete <id>
The --scoop flag is required. When a POST arrives, the payload is wrapped in a LickEvent and routed to the named scoop. The webhook URL is returned on creation.
Optional --filter takes a JS arrow function: (event) => false (drop), true (keep), or object (transform the payload). The event has: type, webhookId, webhookName, timestamp, headers, body.
Crontasks
Scheduled timers that fire lick events on a cron schedule.
crontask create --name <name> --cron "<expr>" [--scoop <name>] [--filter <code>]
crontask list
crontask delete <id>
Cron expression format: "min hour day month weekday". Special characters: * (any), - (range), , (list), / (step). The scheduler checks every 60 seconds.
Optional --filter takes a JS function: () => false (skip this tick), true (fire), or object (custom payload).
Sprinkle licks
Interactive .shtml panels and inline cards can send lick events via the bridge API:
slicc.lick({action: 'refresh', data: {key: 'value'}})
Buttons can also use data-action attributes for automatic lick dispatch:
<button data-action="approve">OK</button>
ROUTING
Lick events are routed as follows:
- Webhook/cron with --scoop: routed directly to the named scoop. Matching checks scoop name, folder, and folder with "-scoop" suffix.
- Webhook/cron without --scoop: routed to the cone.
- Sprinkle licks: always routed to the cone (targetScoop is undefined).
The lick arrives as a ChannelMessage with channel set to "webhook", "cron", or "sprinkle" and content formatted as:
[Webhook Event: <name>]
```json
{ ... payload ... }
```
BLOCKING VS FIRE-AND-FORGET
There are two interaction models for sprinkle licks:
- sprinkle chat (blocking): The
sprinkle chat '<html>'command renders inline HTML in the chat during a tool call. It blocks until the user clicks a button, then returns the action result as JSON to stdout. Use this for confirmations and choices that need a response. - Inline ```shtml cards (fire-and-forget): Agent shtml code blocks in chat messages are hydrated into sandboxed iframes after streaming. Button clicks send lick events to the cone as asynchronous messages — the agent does not block waiting for them.
Panel sprinkles (.shtml files opened via sprinkle open) also use fire-and-forget licks — clicks arrive as messages to the cone.
CONE DELEGATION RULE
The cone must NEVER handle lick events directly. When the cone receives a lick, it must always delegate to the appropriate scoop via feed_scoop. Example:
feed_scoop("giro-winners", "Lick event on YOUR sprinkle 'giro-winners':
Action: 'add-year'
Look up the next previous year and update the sprinkle.
Stay ready for more lick events.")
This applies to all lick types: webhooks, crontasks, and sprinkle interactions.
LICK EVENT STRUCTURE
{
type: 'webhook' | 'cron' | 'sprinkle',
webhookId?: string, // webhook only
webhookName?: string, // webhook only
cronId?: string, // cron only
cronName?: string, // cron only
sprinkleName?: string, // sprinkle only ('inline' for chat cards)
targetScoop?: string, // scoop name (undefined for sprinkle licks)
timestamp: string, // ISO 8601
headers?: Record<string, string>, // webhook only
body: unknown // the payload
}
RUNTIME COMPATIBILITY
Both webhook and crontask commands work in CLI and extension modes. In extension mode, crontask operations route through a proxy to the offscreen document where the LickManager runs. Webhook HTTP endpoints require the CLI server (webhooks are not available in pure extension mode).
EXAMPLES
# Create a webhook for GitHub PR events
webhook create --scoop pr-reviewer --name github \
--filter "(e) => e.body.action === 'opened'"
# Schedule a check every hour
crontask create --name hourly-check --scoop monitor --cron "0 * * * *"
# Workday mornings only
crontask create --name standup --scoop alerts --cron "0 9 * * 1-5"
# Sprinkle button that sends a lick
# (inside .shtml file)
<button onclick="slicc.lick({action: 'deploy', data: {env: 'prod'}})">Deploy</button>
# Blocking confirmation in chat
sprinkle chat '<button data-action="yes">Confirm</button>
<button data-action="no">Cancel</button>'
# Returns: {"action":"yes","data":null}
SEE ALSO
webhook(1), crontask(1), sprinkle(1), feed_scoop(1), scoop(1)