Waking agents — nudges Module

Vaks PM · Integration guide · Agent orchestration · August 2026

What this guide covers. Vaks PM's AI agents work in pull mode: they fetch their own work. A nudge adds an event-driven path on top: the moment a task becomes relevant to an agent, Vaks PM calls a callback URL that its platform registered, to wake it. It is the piece that makes an agent responsive without polling in a loop. This page describes the mechanism end to end — events, subscription contract, headers, signature, capacity control. To wire it to an actual platform, see Autonomous agent · Copilot Studio.

Why a wake-up, when agents already pull

The agent model is deliberately pull-based: an agent asks for its next task, claims it atomically, works it, delivers. Nothing pushes it — that is what keeps the lifecycle server-governed and stops two agents from ever holding the same task. But pull alone leaves one question open: when does the agent ask again? Having it poll the API every few seconds is wasteful and adds latency; having it poll rarely means an agent that answers an hour after it is handed work.

The nudge solves exactly that, without changing the security model. It is a doorbell, not a key: Vaks PM simply signals “there may be work for you,” and the woken agent does what it would have done anyway — it pulls and claims with its own token. The nudge carries no authority and can unlock nothing the agent could not already do.

Provider-agnostic. Vaks PM only knows the callback URL. What receives it — a Power Automate flow behind a Copilot Studio agent, an n8n workflow, a home-grown runner — is irrelevant to it. The contract below is that of the Logic Apps “HTTP Webhook” trigger (so it is directly consumable by Copilot Studio / Power Automate), but nothing in it is Microsoft-specific.

The model at a glance

StepWhat happens
1. Subscribe (once)The agent's platform registers a callback URL with Vaks PM (POST /me/webhook-subscriptions), authenticated by the agent's token. Vaks PM returns an HMAC secret, shown only once.
2. EventA task becomes relevant to the agent — it is assigned to it, a review requests changes, a comment lands on a task it holds.
3. NudgeVaks PM POSTs a small signed JSON body to the callback URL. It carries just enough to identify the task and the agent — never sensitive content, never a token.
4. PullThe platform wakes the agent. It verifies the signature, then pulls and claims atomically with its own PAT (vaks_claim_task / POST /me/next-task/claim). From there it is the normal work loop.
A nudge is not an action command. It does not claim the task on the agent's behalf, grants it no rights and reserves nothing. A lost nudge has no consequence: the task stays in the queue and the agent will find it on its next pull — the wake-up is only a latency optimisation on top of a model that works without it.

When a nudge fires

Three events trigger a wake-up. They cover entering the loop and closing it after a review:

EventTriggerAgent woken
task.assignedA human assigns a task to the agent (or capacity frees up and an eligible task is offered to it — see capacity).The assigned agent.
task.changes_requestedA review sends a deliverable back with a change request.The agent holding the task (its claimant).
comment.createdA comment is added to a task an agent holds — typically a human answer to a question the agent asked.The task's assigned / claiming agents.

Firing is implicit: there is no organization-wide switch to flip. A nudge fires as soon as (1) the project is agent-enabled (agentsEnabled) and (2) an active subscription matches the agent concerned. An agent without a subscription simply receives nothing — and keeps working in pure pull mode.

Subscribe — the contract

Subscription is self-service and self-only: the subject of the subscription is always derived from the token bearer, never from an id passed in the body. An agent can only subscribe itself.

POST /api/v1/me/webhook-subscriptions
X-Api-Key: vaks_pat_<the agent's token>
Content-Type: application/json

{ "callbackUrl": "https://<your-platform>/callback", "events": ["task.assigned"] }

→ 201 Created
  Location: https://<your-domain>/api/v1/me/webhook-subscriptions/<id>
  { "id": "…", "callbackUrl": "…", "events": [...],
    "secret": "…",              ← shown ONCE ONLY (HMAC secret)
    "unsubscribeUrl": "https://…/me/webhook-subscriptions/<id>" }
Authenticate with X-Api-Key, not Authorization. Power Platform / Copilot Studio connectors reserve the Authorization header and strip it before the request leaves. A PAT placed in Authorization: Bearer would never arrive. Vaks PM therefore reads the token, as a fallback, from the non-reserved X-Api-Key header (value = the raw PAT). This is the same fallback used by a dedicated agent's MCP connector.

The operation is an upsert: one active subscription per subject. Calling POST again replaces the URL and regenerates the secret. GET /me/webhook-subscriptions returns the current subscription (without the secret), or null.

Two subscription modes

Which token carries the subscription decides its scope:

Token usedScopeWhen to use it
Agent PAT (vaks_pat_…)Targeted at that agent. The callback receives only the nudges of the agent that bears the token.One flow (or runner) per agent. The simplest path when you have one or two agents.
Organization key (vaks_org_…) scoped nudge:manageOrg-level. One callback receives the nudges of every agent in the organization; each is identified in the nudge body and headers.“One flow for all agents”: a Copilot/Power Automate flow that switches on X-Vaks-Agent-Email and wakes the right agent.
The org nudge key reads nothing. The nudge:manage scope is deliberately minimal: it allows only managing the wake-up subscription — it grants no read of tasks, projects or anything else. Mint it in one click from the admin console (see below), without composing scopes by hand. A human session token is always refused (403): subscribing is reserved to machine identities.

Unsubscribe

Three forms, all idempotent (200 even if the subscription is already gone or inactive):

CallEffect
DELETE /me/webhook-subscriptions (no id)Deactivates the bearer's current subscription. Static URI — this is the form the “HTTP Webhook” trigger requires, which needs a fixed unsubscribe URL at design time, before it knows the id.
DELETE /me/webhook-subscriptions/<id>Deactivates by identifier — the one returned in the Location header at subscribe time.
POST /me/webhook-subscriptions/<id>/unsubscribePOST alias — some Logic Apps flows emit a POST rather than a DELETE on the unsubscribe URL. Same effect.

The outbound nudge — headers & body

Vaks PM POSTs a compact JSON body and a set of headers modelled on those of the generic webhooks:

POST <your callbackUrl>
Content-Type: application/json
User-Agent: Vaks-PM-Nudge/1.0
X-Vaks-Event: task.assigned                    ← event name
X-Vaks-Delivery: <delivery id>                  ← idempotency key for the receiver
X-Vaks-Signature: sha256=<hex>                  ← HMAC-SHA256(secret, raw body)
X-Vaks-Timestamp: 1723200000                   ← epoch seconds
X-Vaks-Agent-Id: <agent id>
X-Vaks-Agent-Email: agent-…@agents.invalid
X-Vaks-Agent-Name: Vaks%20Doc%20Agent          ← URL-encoded

{
  "event":      "task.assigned",
  "taskId":     "…",
  "projectId":  "…",
  "agentId":    "…",
  "agentEmail": "agent-…@agents.invalid",
  "agentName":  "Vaks Doc Agent"
}

The agent's identity appears both in the body and in the headers, on purpose: in org-level mode a flow can switch on the X-Vaks-Agent-Email header (a simple Switch) without parsing the body. The name is URL-encoded in the header (HTTP headers do not tolerate arbitrary characters); the body carries it plain.

What a nudge never contains. No task title, no description, no comment text, no token, no client data. Only opaque identifiers and enough to route. Everything else the agent fetches itself with its own token once woken — which guarantees it never sees, via the nudge, more than its rights allow it to read.

Verify the signature

Every nudge is signed with HMAC-SHA256 over the raw body, using the secret returned at subscribe time. The receiver recomputes and compares — that is what proves the nudge came from Vaks PM and was not tampered with.

// receiver-side pseudo-code
const expected = "sha256=" + hmacSha256(secret, rawBody).hex();
if (!timingSafeEqual(expected, header["X-Vaks-Signature"])) reject(401);

What the agent does next

Once woken, the agent returns to the normal loop — the nudge replaces none of its steps:

  1. It pulls and claims with its own PAT: vaks_claim_task for the exact task the nudge names, or vaks_claim_next_task to take the project's next eligible task.
  2. It reads the work context (vaks_get_work_context) — including, on a task.changes_requested, the most recent review verdict.
  3. It works, delivers, reports its cost, resubmits for review — exactly the work loop described elsewhere.
The nudge does not reserve the task. A task.assigned makes the task visible and wakes the agent, but does not lock it to it: the atomic claim is what assigns it. If another agent on the project pulls that task in the meantime, it gets it — consistent with the pull model. Assignment expresses intent, not a reservation.

Capacity & backpressure

Waking an agent that has no capacity to take the work achieves nothing — worse, it makes it pay for a conversation only to hit a refusal. A capacity control (adjustable, under Agent nudges) filters nudges at emission time against two independent thresholds:

ThresholdScopeWhat it limits
ConcurrencyOne agent, across all projects.How many tasks a single agent can run in parallel. At the cap, it is not woken for one more.
Review backpressureOne project.How many agent deliverables sit awaiting human review in the project. At the cap, agents stop being woken so they do not race ahead of reviewers. Counts only agent tasks — a human review queue never blocks agents.

Reserving a slot is atomic (to prevent three approvals in quick succession from waking nine agents for three slots) and is only consumed at claim time. When a project's review queue is saturated, a review_queue_full alert fires once for the project — not one per blocked agent. When a slot frees up (a deliverable is approved), the agent is woken and drains one task; if the queue goes back over the cap, it re-saturates immediately, as designed.

Parallelism is a choice, not a defect to fix. Several concurrent agent runs is the point — assigning three tasks at once wakes three simultaneous runs. The parallelism setting is a cost knob: lowering it serialises the work (fewer agent conversations in flight at a time), raising it parallelises more. Set it to your budget.

Retries & auto-disable. A failed delivery is retried (exponential backoff), with a 10 s timeout per attempt. A subscription that accumulates 5 terminal failures is automatically disabled — a dead callback does not retry forever. It reappears as “auto-disabled” in the admin console, where it can be re-enabled after the cause is fixed.

Callbacks to a private IP (on-premise)

By default, Vaks PM applies strict anti-SSRF protection to every outbound URL: private, loopback and metadata addresses are refused. That is the right default in shared SaaS. But an on-premise deployment often has its orchestrator (n8n, a runner) on the same private network — the callback then legitimately targets a LAN IP.

This case is allowed without lifting the general protection, via a double lock:

Always blocked, no matter what. Loopback (127.0.0.0/8, ::1) and cloud metadata addresses (169.254.0.0/16) stay refused even if declared in a CIDR — these are the classic SSRF targets, and no legitimate case needs to reach them. Also, the allowed ports stay 80, 443, 8080 and 8443: a LAN callback must listen on one of them. The generic webhook path itself stays strict, with no exception.

Admin console & delivery log

Everything is driven under Admin → AI Agent Management → Agent nudges (agent:manage right — administrators, or holders of the AI administrator grant):

Endpoint summary

EndpointWho calls itRole
POST /me/webhook-subscriptionsAgent (PAT) or org key nudge:manageSubscribe (upsert). Returns the secret + Location.
GET /me/webhook-subscriptionsSameCurrent subscription (no secret).
DELETE /me/webhook-subscriptionsSameUnsubscribe the current subscription (static URI).
DELETE /me/webhook-subscriptions/:idSameUnsubscribe by id.
POST /me/webhook-subscriptions/:id/unsubscribeSamePOST unsubscribe alias.
GET /admin/agent-webhook-subscriptionsAdmin (agent:manage)List subscriptions.
POST /admin/agent-webhook-subscriptions/connector-keyAdminGenerate the org connector key.
GET /admin/agent-webhook-subscriptions/deliveriesAdminDelivery log.
GET · PUT /admin/agent-webhook-subscriptions/internal-cidrsAdminOn-premise mode & internal CIDRs.

Troubleshooting

SymptomCause & fix
Subscribe returns 403 “tokens only…”You are calling with a human session token. Use an agent PAT, or an org key scoped nudge:manage.
The org key returns 403 “must be scoped nudge:manage”The key has the wrong scope. Regenerate it via Generate the connector key, which sets the scope automatically.
The token “does not arrive” (-32001 / 401)You put it in Authorization: Bearer from a Power Platform connector, which strips it. Use the X-Api-Key header = raw PAT.
No nudge arrives even though a task is assignedCheck that the project is agent-enabled, that the agent is a member of it, and that the subscription is active. When capacity is saturated, the nudge is intentionally withheld — check the concurrency / backpressure thresholds.
The callback is called but rejected (4xx in the log)The delivery log shows the code and a snippet of the response. Often a mis-recomputed signature (body re-serialization) or a callback domain not allowed by webhook governance.
LAN callback refused (400)On-premise mode is closed, or the range is not declared, or the port is not in 80/443/8080/8443. See LAN callbacks.
The subscription went “auto-disabled”5 terminal failures in a row. Fix the callback then re-enable it in the console.

See also: Autonomous agent · Copilot Studio — wire up a dedicated agent (MCP tool + wake-up trigger) · AI agents — the work loop, governance, budgets · Agent authentication — how an agent gets its token · Webhooks — the generic HTTP callbacks · all integrations.