Skip to content

Signals

A Signal is an event that wakes the agent. Everything in Agent One starts with a signal flowing into the event queue. The agent never polls — it reacts.

Every signal implements a minimal Go interface:

type Signal interface {
ID() string
Listen(ctx context.Context, events chan<- Event) error
}

Signals push events into a shared queue. The agent loop dequeues and processes them one at a time.

A user sends text via a channel (CLI, WhatsApp, Telegram, ClickUp).

User sends "Track purchase: headphones $120" via WhatsApp
→ Message signal fires
→ Agent processes, calls purchase tool
→ Responds on WhatsApp (same channel it came from)

Message signals always respond on the originating channel. This is the most common signal type.

A periodic timer that fires at a fixed interval. Used for recurring checks that don’t need precise scheduling.

signals:
heartbeat:
enabled: true
interval: "30m"
prompt: "Check ClickUp for anything urgent and notify me if needed"

The agent evaluates the prompt, runs any necessary tool calls, and only notifies you if there’s something to report. “Nothing to report” responses are suppressed — no spam.

A scheduled alarm using standard cron syntax. For precise, calendar-based scheduling.

signals:
crons:
- schedule: "0 8 * * 1" # Monday 8am
prompt: "Weekly summary: open PRs, sprint tasks, purchase total"
- schedule: "0 17 * * 5" # Friday 5pm
prompt: "Weekly wrap-up: what did we accomplish?"

Crons are ideal for reports, summaries, and periodic workflows. They use the robfig/cron scheduler under the hood.

Internal lifecycle events. These fire on agent startup, shutdown, config changes, and other system events.

Agent starts
→ Hook signal fires (type: startup)
→ Agent loads memory, refreshes tokens, checks pending reminders

Hooks are not user-configurable — they’re part of the agent’s internal lifecycle.

External HTTP calls from third-party services. GitHub, Stripe, or any service that can send HTTP POST.

signals:
webhooks:
port: 9090
endpoints:
- path: "/github"
secret: "${GITHUB_WEBHOOK_SECRET}"
prompt: "Process this GitHub event and notify me if important"
- path: "/stripe"
secret: "${STRIPE_WEBHOOK_SECRET}"
prompt: "A Stripe payment event occurred. Summarize it."

The webhook server validates signatures, extracts the payload, and injects it as context for the prompt.

How the agent decides where to send the response:

Signal TypeResponse Channel
MessageSame channel it came from
Heartbeatchannels.default
Cronchannels.default
HookInternal (no user-facing response)
Webhookchannels.default

This means if you set channels.default: whatsapp, your cron reports and webhook alerts arrive on WhatsApp. Message replies always go back to whoever sent them.

Signals push events into a buffered queue. The agent loop dequeues events sequentially:

Signal A ──┐
Signal B ──┤──> Event Queue ──> Agent Loop (sequential processing)
Signal C ──┘

Events are processed one at a time to avoid race conditions on memory and tool state. The queue is buffered to handle bursts without blocking signal producers.

Signals use the registry pattern — no switch statements. To add a new signal type:

  1. Implement the Signal interface
  2. Register it in the signal registry
  3. The agent loop picks it up automatically
type MySignal struct { /* ... */ }
func (s *MySignal) ID() string { return "my-signal" }
func (s *MySignal) Listen(ctx context.Context, events chan<- Event) error {
// Push events when your trigger condition is met
}