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.
Signal Interface
Section titled “Signal Interface”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.
The 5 Signal Types
Section titled “The 5 Signal Types”Message
Section titled “Message”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.
Heartbeat
Section titled “Heartbeat”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 remindersHooks are not user-configurable — they’re part of the agent’s internal lifecycle.
Webhook
Section titled “Webhook”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.
Channel Resolution
Section titled “Channel Resolution”How the agent decides where to send the response:
| Signal Type | Response Channel |
|---|---|
| Message | Same channel it came from |
| Heartbeat | channels.default |
| Cron | channels.default |
| Hook | Internal (no user-facing response) |
| Webhook | channels.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.
Event Queue
Section titled “Event Queue”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.
Adding Custom Signals
Section titled “Adding Custom Signals”Signals use the registry pattern — no switch statements. To add a new signal type:
- Implement the
Signalinterface - Register it in the signal registry
- 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}