The problem this solves
Intent data is a stream of changes, and the wrong way to read a stream is to fetch the whole list every hour and diff it yourself. The cost is paid twice: credits for rows that did not move, and code that has to remember what it saw last time. A cursor fixes both. Each response carries a position, the next request sends it back, and only the rows whose intent on <topic> changed since that position return.
Your agent wants exactly that: a small hourly batch it can act on immediately - an account on list <name> whose research on <topic> just climbed - rather than a full refresh it has to reconcile. Between calls it should hold nothing but the cursor.
The rest is discipline that belongs in the loop from the first version: persist the cursor as soon as it arrives, back off on a rate-limit response, stop cleanly when the credit envelope is exhausted, and never lose a batch to a crash between the fetch and the handling.
How the mission runs
- Scope the signals call. The Signals Agent shows the request: the signals endpoint scoped to list <name> and to intent signals on <topic>, with the cursor from the previous call and a limit. The key is a Bearer token minted for this agent and scoped to signal and list reads. The first call omits the cursor and returns the current state plus the cursor to start from.
- Persist the cursor. The example stores the cursor in whatever your agent already has - a row in its database, a key-value entry, a file - and writes it the moment the response arrives. The loop reads it at the top of every hour, so a restart resumes from the last position rather than replaying a day of signals.
- Handle the batch. Each returned row carries the domain, the list row it belongs to, the topic, an intent strength, when the activity was first and last observed and what changed since the previous cursor. The example hands each row to a handler in order, and the handler is where your agent decides: open a task, ask the Personalization Agent for a first touch, or raise the account's priority.
- Back off and stop cleanly. The TypeScript wraps the call with a retry on the rate-limit response using the retry-after hint, logs and skips on a schema error, and stops the loop on the credit-envelope response so it resumes when the envelope resets. A network failure leaves the cursor untouched, so nothing is skipped.
- Run it every hour. The loop is scheduled with a plain interval or a cron entry, and an empty response is the normal result most hours. The example prints the count of changed rows, the new cursor and the credits metered per call to stderr, so an operator can watch it without reading the code.
The prompt
This is the exact objective the agent receives. Swap the obvious placeholders for your own domain, segment or channel and run it as-is from the console, Slack, or the API.
What comes back
A TypeScript file with the polling function, the cursor persistence, the handler hook and the retry and stop rules, plus a description of the response shape: rows with domain, list row id, topic, intent strength, first and last observed times and what changed, and the cursor to send next. Run hourly, your agent receives only the accounts on list <name> whose intent on <topic> moved since the last call, with nothing to diff and nothing to remember except the cursor.
Make it yours
- Subscribe to the signal-detected webhook instead of polling when your agent has a public endpoint; the payload has the same row shape and the cursor becomes unnecessary.
- Poll several kinds at once - intent on <topic>, hiring and funding - by widening the kinds filter, and let the handler branch on kind.
- Watch a handful of named domains rather than a list by pointing the call at the domains under watch, useful for a short strategic account set.
- Lower the interval to 15 minutes for a launch week and raise it back afterward; only changed rows return either way, so the cost tracks activity rather than frequency.
Frequently asked questions
What does a change mean for an intent row?
The intent strength on <topic> moved for that account since the cursor, activity was observed for the first time, or the account entered or left list <name>. The row says which, so the handler can treat a first observation differently from a climb.
Do I pay for empty polls?
An empty response is a light call. Credits meter on the signal rows returned, so a quiet hour costs close to nothing and a busy hour costs in proportion to what your agent receives.
What if my agent is down for a day?
It resumes from the stored cursor and receives every change since, in order, in one or a few pages. Nothing is lost while it was down, because the position lives in your store rather than in the loop's memory.
Why TypeScript?
Because the prompt asked for it. The same loop is a few lines in Python or Go; the endpoint, the cursor and the status codes are identical, and the CLI's signals command with a since filter is the no-code version for a quick check from the terminal.