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:

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:

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)