Triggers
Triggers: what wakes an agent besides a message.
fulfilment = noclick.Agent.from_name(
"fulfilment", create_if_missing=True, instructions="You confirm new orders with the customer.",
triggers=[noclick.OnWebhook(name="orders", message="A new order arrived.")],
)An OnWebhook gives it a URL whose POST body is the turn, an OnEvent
subscribes it to a connection's app events, an OnPlatformEvent to
NoClick's own events (an operation failing, a connection breaking), and a
Cron wakes it on a schedule (an agent with one is an automation), and an
OnQueue makes each message on a queue a turn. Mail and calls wake it too, set
up where they live (Inbox(agent=), PhoneNumber.answer_with);
triggers() lists them as kinds email and call, and while its policy
may hold one of its tools for a person, as kind approval: an answered
approval wakes it in the held call's conversation.
A webhook, app event or queue trigger takes conversation=, a template over
what arrived ("issue-{issue.number}"): deliveries that render the same key
continue one conversation, and the turn's operation records the key as
input["conversation"].
Cron
noclick.Cron(expr, tz='UTC', message=None)A schedule: a five-field cron expression (minute, hour, day of month,
month, day of week: numbers, *, ranges, steps, lists and month or day
names) read in a timezone, so "0 9 * * 1-5" with
tz="America/New_York" means 9am New York time on weekdays, daylight saving
included. When both day fields are restricted a run must match both. An
agent's schedule wakes the agent with message as the turn (each run a
conversation of its own); a wake-up's every wakes one conversation at
each time, with its note (a routine: agent.wake); a channel's posts
message in the room; an app's
(invite-only beta) calls one of its paths. tz="user" reads it in a
user's agent's (or channel's) user's timezone, following them when they
move (UTC while they have none; an account agent refuses it, having no user);
a wake-up reads it in the timezone of the user whose conversation it wakes.
Every agent turn is told the date and time, so a schedule's message can say
"last month" or "this week" and mean it.
An IANA timezone, or "user": the agent's user's timezone, followed when they move.
'UTC')The turn's text when this schedule wakes an agent; apps ignore it.
None)Wake an agent every weekday morning:
digest = noclick.Agent.from_name(
"digest", create_if_missing=True, instructions="You summarize the overnight tickets.",
triggers=[noclick.Cron("0 8 * * 1-5", tz="Europe/London", message="Post the overnight summary")],
)Call an app's path every hour (invite-only beta):
app = noclick.Project.from_name("booking")
app.schedule(noclick.Cron("0 * * * *"), path="/jobs/reminders")Brief each user at 8am where they are:
with noclick.as_user("user_42"):
assistant = noclick.Agent.from_name("assistant")
assistant.add_trigger(noclick.Cron("0 8 * * *", tz="user", message="Brief me on today's calendar"))| expr | str | |
| tz | str | An IANA timezone, or |
| message | str | None | The turn's text when this schedule wakes an agent; apps ignore it. |
OnWebhook
noclick.OnWebhook(name, message=None, conversation=None)A URL that wakes the agent: each POST to it is a turn, its body the
message (a JSON body is passed as JSON). The URL is AgentTrigger.url;
anyone who has it can wake the agent, so keep it like a key. It stays the
same while a trigger of that name stays on the agent. message is
what the agent is told with every delivery, above the body.
Standing instructions sent with each delivery, above its body. A channel can subscribe to one too (channel.subscribe(noclick.OnWebhook(...))), its POSTs posted in the room.
None)The conversation a delivery continues: a template over its body, "order-{order.id}"; a NoClick-Conversation header wins.
None)Let your order system wake an agent:
fulfilment = noclick.Agent.from_name(
"fulfilment", create_if_missing=True, instructions="You confirm new orders with the customer.",
triggers=[noclick.OnWebhook(name="orders", message="A new order arrived. Confirm it with the customer.")],
)
[orders] = fulfilment.triggers()
print(orders.url) # POST your order JSON hereKeep each order in one conversation, keyed by a field of the body:
fulfilment = noclick.Agent.from_name("fulfilment")
fulfilment.add_trigger(noclick.OnWebhook(name="order-updates", conversation="order-{order.id}"))| name | str | |
| message | str | None | Standing instructions sent with each delivery, above its body. A channel can subscribe to one too ( |
| conversation | str | None | The conversation a delivery continues: a template over its body, |
OnEvent
noclick.OnEvent(connection, event, *, message=None, conversation=None, **filters)An app event from one of the agent's connections, such as a Slack
message or a new HubSpot contact. connection is a Connection or its
name, the agent's own user's; event names one of the integration's
app events (channel_message for Slack, contact_created for HubSpot; list them with
connection.events()) and each keyword argument narrows it the way the integration
takes it, like channel="C0123ABCD" (a Slack channel's id: slack.lookup("channel")
lists them). message is what the agent is told with every event, above it. A thread
(a Slack thread) stays one conversation, so the agent's replies land in it. conversation
keys conversations yourself, a template over the event as the app sent it:
"issue-{issue.number}" keeps everything about one issue in one. A channel subscribes
to the same events (channel.subscribe(noclick.OnEvent(...))), each posted in the room.
The connection needn't be connected yet: the trigger is error until it is.
NoneNoneAnswer every message in a Slack channel:
slack = noclick.Connection.from_name("slack", integration="slack", create_if_missing=True)
support = noclick.Agent.from_name(
"support", create_if_missing=True, instructions="You answer questions in #support.",
tools=[slack],
triggers=[noclick.OnEvent(slack, "channel_message", channel="C0123ABCD")],
)Or wake a sales agent for each new contact:
sales = noclick.Agent.from_name("sales")
sales.add_trigger(noclick.OnEvent("hubspot", "contact_created", message="Research this contact and draft a first email."))Pick up GitHub issues when they're labelled, one conversation per issue:
github = noclick.Connection.from_name("github", integration="github", create_if_missing=True)
triage = noclick.Agent.from_name("triage")
triage.add_trigger(noclick.OnEvent(github, "issue_labeled", repository="acme/api", conversation="issue-{issue.number}"))| connection | Any | |
| event | str | |
| filters | dict[str, Any] | |
| message | str | None | |
| conversation | str | None | |
| connection | Any | |
| event | str | |
| filters | ||
| message | str | None | |
| conversation | str | None |
OnPlatformEvent
noclick.OnPlatformEvent(type, filters=None, message=None, conversation=None)One of NoClick's own events (the types noclick.Event.types() lists, as your
webhooks receive them): an operation failing, a connection breaking, a channel
halting, a workflow's run failing, an agent built in the app asking for you, a
payment going through. type names one, and * matches the rest of a name
("operation.*"); filters are dotted paths into the event's data and the
values they must have. message is what the agent is told with every event,
above it; conversation keys conversations by a template over the event
("connection-{data.id}"), else each event is a conversation of its own.
The agent hears its user's events, and an agent of your account's hears every
event, your users' included. It never hears what its own turns caused (its
turn finishing), and a chain of agents waking each other this way stops five
turns deep. A channel subscribes to the same events
(channel.subscribe(noclick.OnPlatformEvent(...))).
NoneNoneNoneHave an agent look into every failed job of your coding agents:
triage = noclick.Agent.from_name(
"triage", create_if_missing=True, instructions="You find out why work failed and say what to do.",
triggers=[noclick.OnPlatformEvent("operation.failed", filters={"kind": "agent.task"},
message="A job failed. Find out why.")],
)One conversation per broken connection:
support = noclick.Agent.from_name("support")
support.add_trigger(noclick.OnPlatformEvent("connection.disconnected", conversation="connection-{data.id}",
message="Tell the user how to reconnect it."))Watch over the workflows built in the app, one conversation per workflow:
ops = noclick.Agent.from_name("ops")
ops.add_trigger(noclick.OnPlatformEvent("workflow.run.failed", conversation="workflow-{data.workflow_id}",
message="A workflow failed. Find out why; pause it if it's useless."))| type | str | |
| filters | dict[str, Any] | None | |
| message | str | None | |
| conversation | str | None |
OnQueue
noclick.OnQueue(queue, *, message=None, batch_size=1, concurrency=1, conversation=None)Each message on a queue becomes a turn: the agent reads message (its
standing instructions, if any) and then the message's JSON body. With
batch_size above 1 (up to 25), one turn takes up to that many waiting
messages at once, as a JSON list, for work that's cheaper in bulk.
queue is a Queue or its name, the agent's own user's.
Delivery follows the queue's rules: a turn that finishes acks its messages;
a turn that fails hands them back for another try (after 30 seconds per
attempt they've had), and a message that fails the queue's max_attempts
times becomes a dead letter, the turn's error its last_error. Each turn is
a conversation of its own, unless conversation keys it from the message
(one message a turn only; turns of one conversation take their turn one at a
time). concurrency (1 to 50) runs up to that many turns at once. While the
agent is paused, messages wait.
When the agent's budget, its user's cap or your account's funds run out, the
trigger pauses instead of failing turns: messages wait with their attempts
unchanged, the trigger's status is error saying why, and it carries on
by itself once there's money to spend again (it looks every half minute).
None11NoneLet an agent work through a queue your app fills:
invoices = noclick.Queue.from_name("invoices", create_if_missing=True)
support = noclick.Agent.from_name(
"support-bot", create_if_missing=True, instructions="You chase unpaid invoices by email.",
tools=["gmail.send_email", "stripe.list_invoices"],
triggers=[noclick.OnQueue(invoices, message="Chase this unpaid invoice.")],
)
invoices.put({"invoice": "INV-1042", "customer": "acme"})Ten messages a turn, by the queue's name:
digest = noclick.Agent.from_name("digest")
digest.add_trigger(noclick.OnQueue("webhook-buffer", batch_size=10, message="Summarize these events."))Five turns at once, each customer's messages in their own conversation:
enrich = noclick.Agent.from_name("enrich")
enrich.add_trigger(noclick.OnQueue("signups", concurrency=5, conversation="customer-{customer_id}"))| queue | Any | |
| message | str | None | |
| batch_size | int | |
| concurrency | int | |
| conversation | str | None | |
| queue | Any | |
| message | str | None | |
| batch_size | int | |
| concurrency | int | |
| conversation | str | None |
AgentTrigger
class AgentTriggerOne thing that wakes an agent, as agent.triggers() reads it.
kind is cron, webhook, event, platform, queue, email, call or
approval; spec is its definition (name
for a webhook; connection, event and filters for an app event; type and
filters for a NoClick event; message and
conversation where they were given; inbox_id and address for email;
phone_number_id and number for calls; cron, tz and message for a
schedule; queue, batch_size and concurrency for a queue; tools, the ones
policy may hold, for an approval). An approval
trigger is there while your policy may hold one of its tools for a person: an answered
approval wakes it in the held call's conversation. status is active, paused
(agent.pause()) or error, with error saying why it can't fire (a connection
that isn't connected, say, a queue's turns waiting for money, or its turns failing the
same way five times in a row, which stops it until the agent is resumed).
| id | str | |
| kind | str | |
| spec | dict[str, Any] | |
| status | str | |
| url | str | None | The URL a webhook trigger listens at (None for other kinds). |
| next_run | Any | When a schedule next wakes the agent (None for other kinds, and while paused or in error). |
| last_fired_at | Any | |
| error | str | None | |
| created_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
| defined | bool | Whether your code defines it ( |