From a40ba6f251bac8d71b1592d412907bf20cf245ce Mon Sep 17 00:00:00 2001 From: Kishore Kumar Date: Sat, 11 Jul 2026 10:48:23 +0530 Subject: [PATCH] docs(connectors): explain verified GitHub App routing --- api-reference/error-codes.mdx | 19 +++++++- changelog.mdx | 23 +++++++++ cli/agentsfleet.mdx | 35 ++++++++++++++ fleets/authoring.mdx | 13 +++++ fleets/connectors.mdx | 90 +++++++++++++++++++++++++++++++++-- fleets/webhooks.mdx | 37 ++++++++++++-- 6 files changed, 207 insertions(+), 10 deletions(-) diff --git a/api-reference/error-codes.mdx b/api-reference/error-codes.mdx index c32ec0b..3c1d723 100644 --- a/api-reference/error-codes.mdx +++ b/api-reference/error-codes.mdx @@ -94,6 +94,8 @@ This page is generated from agentsfleetd's error registry (`make gen-error-codes | `UZ-WH-010` | 401 | Invalid webhook signature | Webhook signature verification failed. Confirm the signing secret stored for this provider (Slack/Clerk/other) matches the one configured upstream. | | `UZ-WH-011` | 401 | Stale webhook timestamp | Webhook request timestamp is outside the allowed 5-minute drift window. This may indicate a replay attack or clock skew. | | `UZ-WH-020` | 401 | Webhook credential not configured | No webhook credential is configured for this fleet's source. Run `agentsfleet secret add --data='{"webhook_secret":"..."}'` in the fleet's workspace, then resend. | +| `UZ-WH-021` | 404 | Connector installation is not mapped | Reconnect the provider App to the intended workspace before redelivering the event. | +| `UZ-WH-022` | 404 | No fleet subscription matched | Bind the repository and event to an active fleet with an approved integration grant. | | `UZ-WH-030` | 413 | Webhook payload too large | Webhook body exceeds the 1 MiB ingest limit. Reduce the payload size or filter at the source. | ## Slack @@ -143,6 +145,7 @@ This page is generated from agentsfleetd's error registry (`make gen-error-codes | `UZ-VAULT-001` | 400 | Secret data must be a non-empty JSON object | POST /secrets body must include a 'data' field that is a JSON object with at least one key. Bare strings, arrays, scalars, and `{}` are rejected. | | `UZ-VAULT-002` | 400 | Secret data too large | Stringified secret data exceeds 4KB. Compose the secret from fewer or shorter fields. | | `UZ-VAULT-003` | 404 | Secret not found | No secret matches this name in the workspace. List the workspace secrets to find a valid name, or create it first. | +| `UZ-VAULT-004` | 409 | Secret still referenced by model entries | One or more core.tenant_model_entries rows reference this secret_ref (M121). Remove those entries first, then delete the secret. The response detail names the exact count. | ## Model Providers @@ -150,15 +153,24 @@ This page is generated from agentsfleetd's error registry (`make gen-error-codes |---|---|---|---| | `UZ-PROVIDER-001` | 400 | secret_ref required when mode=self_managed | PUT body must include `secret_ref` naming a vault credential when `mode` is self_managed. | | `UZ-PROVIDER-002` | 400 | Secret row not found in vault | The named secret_ref has no vault row in the tenant's primary workspace. Run `agentsfleet secret add --data=@-` to create it. | -| `UZ-PROVIDER-003` | 400 | Secret JSON missing required field | Stored secret JSON must include `provider` and `model` (non-empty strings); `api_key` is required for a named provider but optional for an `openai-compatible` endpoint. Re-run `agentsfleet secret add` with the required fields. | +| `UZ-PROVIDER-003` | 400 | Secret JSON missing required field | Stored secret JSON must include `provider` (a non-empty string); `api_key` is required for a named provider but optional for an `openai-compatible` endpoint. `model` is optional — the model registry entry carries it, not the credential. Re-run `agentsfleet secret add` with the required fields. | | `UZ-PROVIDER-004` | 400 | Model not in library | The effective model is not present in core.model_library. Pick a model from the model-caps endpoint or request the library be extended. | | `UZ-PROVIDER-005` | 400 | Custom endpoint base_url invalid or unsafe | An openai-compatible credential needs a valid `base_url`: it must use https and must not target a loopback, private, link-local, or cloud-metadata host. A non-openai-compatible provider must not carry a `base_url`. | | `UZ-PROVIDER-006` | 404 | Library model not found | No core.model_library row matches this id. List the library to find a valid id, or add the model first. | | `UZ-PROVIDER-007` | 409 | Library model is the active platform default | This model is the active platform default. Point the default at another library model before deleting it. | | `UZ-PROVIDER-008` | 409 | Library model already exists | A library row for this provider and model already exists. Edit the existing row instead of adding a duplicate. | -| `UZ-PROVIDER-009` | 500 | Platform LLM key not configured | No active row in core.platform_llm_keys. An operator must set one via PUT /admin/platform-keys before tenants can switch to platform defaults. | +| `UZ-PROVIDER-009` | 500 | Platform LLM key not configured | No active row in core.platform_provider_defaults. An operator must set one via PUT /admin/platform-keys before tenants can switch to platform defaults. | | `UZ-PROVIDER-010` | 500 | Tenant has no primary workspace | The tenant row has no primary workspace — an onboarding invariant that should always hold. Contact support with the request id. | +## MODELS + +| Code | HTTP | Title | Common Causes | +|---|---|---|---| +| `UZ-MODELS-001` | 409 | Cannot delete the active model entry | This entry is the tenant's current active selection. Switch to a different entry first, then delete this one. | +| `UZ-MODELS-002` | 404 | Referenced secret not found | POST/PATCH secret_ref does not name a vault secret in the tenant's primary workspace. Store the secret first, or pick an existing one. | +| `UZ-MODELS-003` | 409 | Model entry already exists | An entry with this exact (model_id, secret_ref) pair already exists for this tenant. Edit the existing entry instead of adding a duplicate. | +| `UZ-MODELS-004` | 404 | Model entry not found | No core.tenant_model_entries row matches this id for the calling tenant. It may have already been deleted — refresh the list. | + ## Startup | Code | HTTP | Title | Common Causes | @@ -183,6 +195,7 @@ This page is generated from agentsfleetd's error registry (`make gen-error-codes | `UZ-RUN-012` | 402 | Lease renewal blocked: no credits | The tenant's balance can no longer cover continued execution; the lease may not be renewed and the run terminates gracefully. | | `UZ-RUN-013` | 400 | Renew body malformed | The renew request body could not be parsed; cumulative token counts default to zero and the slice meters its run-time fee only (never a negative charge). The lease is still renewed. | | `UZ-RUN-014` | 404 | Runner not found | No runner matches this runner_id. Verify the platform admin minted the runner before mutating it. | +| `UZ-RUN-015` | 402 | Lease renewal blocked: fleet budget exhausted | The fleet reached its own daily_dollars or monthly_dollars ceiling declared in TRIGGER.md; the lease may not be renewed and the run terminates. Distinct from UZ-RUN-012 (the tenant's credit pool), so an operator can tell the fleet author's own limit from a billing failure. | ## Execution @@ -200,6 +213,7 @@ This page is generated from agentsfleetd's error registry (`make gen-error-codes | `UZ-EXEC-012` | 500 | Runner fleet init failed | Runner fleet initialization failed. Check configuration. | | `UZ-EXEC-013` | 500 | Runner fleet run failed | Runner fleet execution failed. Check logs for details. | | `UZ-EXEC-014` | 400 | Runner invalid config | Runner configuration is invalid. Check config_json fields. | +| `UZ-EXEC-015` | 402 | Execution stopped: fleet budget exhausted | The control plane refused the lease renewal because the fleet reached its own daily_dollars or monthly_dollars ceiling (UZ-RUN-015). A fleet-scoped spend stop, kept distinct from a renewal-terminate (UZ-EXEC-008) so triage can tell the fleet author's own limit from a platform or billing stop. | ## Approvals @@ -263,3 +277,4 @@ This page is generated from agentsfleetd's error registry (`make gen-error-codes | `UZ-CONN-004` | 404 | Unknown connector provider | The `{provider}` segment does not match any provider in this deployment's connector registry. List the available providers from the dashboard connectors page (or the catalog endpoint once it ships). | | `UZ-CONN-006` | 502 | Connector OAuth exchange failed | The connector's OAuth code exchange or provider callback body was rejected. Start the connect again from the dashboard; if it repeats, verify the provider app credentials and redirect URL. | | `UZ-CONN-007` | 500 | Connector catalog lookup failed | The vault existence check for connector app/fleet keys failed (a Postgres error). Retry; if it persists, check DB connectivity and the vault schema state. | +| `UZ-CONN-008` | 403 | Connector installation ownership not verified | GitHub did not confirm that the authorizing user can access the submitted App installation, or that installation is already connected to another workspace. Start the connection again while signed in to the GitHub account that owns the installation. | diff --git a/changelog.mdx b/changelog.mdx index 2e82171..304af7b 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -22,6 +22,29 @@ export const STAGE_SELF_MANAGED_M66 = "$0.0001"; agentsfleet is in **stealth-mode testing** and pre-production. APIs and agent behavior may change between releases without long deprecation windows. Email [agentsfleet@agentmail.to](mailto:agentsfleet@agentmail.to) if you want a hand calibrating an agent or to join as a design partner. + + ## GitHub App events route to repository-bound fleets + + A connected GitHub App can now route Pull Request and failed workflow-run events to active fleets that explicitly subscribe to the repository and event and hold an approved GitHub grant. Connector inspection is also available from the `agentsfleet` command-line interface (CLI). + + ## What's new + + - **Repository-bound GitHub triggers** — add `repositories: [owner/repository]` beside `source: github` and `events` in `TRIGGER.md`; omission receives no managed App traffic. + - **Scoped fan-out** — one App delivery may wake several matching fleets in its connected workspace, while wrong-repository, wrong-event, inactive, and unapproved fleets receive nothing. + - **Verified installation ownership** — the GitHub callback exchanges a one-time user-authorization code, verifies access to the claimed installation, and refuses to move an installation already connected to another workspace. + - **Replay recovery** — replay protection uses the signed payload body per fleet, so changing the unsigned delivery header cannot bypass it and a retry fills a failed queue write without duplicating successful fleet events. + + ## API reference + + - `POST /v1/ingress/github` verifies the platform GitHub App signature, acknowledges signed `ping` checks, maps `installation.id` to a workspace, normalizes supported events, and returns `202` after routing matching fleets. + - `pull_request` events are accepted. `workflow_run` is accepted only for completed failed runs; successful runs are ignored. + + ## CLI + + - **Connector list** — `agentsfleet connector list [--workspace ] [--json]` reports `connected`, `not_connected`, `reconnect_required`, or `unconfigured` for every provider. + - **Connector status** — `agentsfleet connector status [--workspace ] [--json]` shows one provider's stable state and details without treating a valid disconnected state as an error. + + ## Fleet timeline recovers frames lost to a connection blip diff --git a/cli/agentsfleet.mdx b/cli/agentsfleet.mdx index 4afeecf..4616a9e 100644 --- a/cli/agentsfleet.mdx +++ b/cli/agentsfleet.mdx @@ -404,6 +404,41 @@ agentsfleet fleet-key delete --workspace 0198a7b0-3c2d-7f14-9a08-1b6e4d2f8c50 01 --- +## Connectors + +Connector commands are read-only. They show whether the platform has configured each provider and whether the active workspace has connected it. + +### `agentsfleet connector list` + +List GitHub, Slack, Zoho Desk, Jira, and Linear with a stable state and next action. + +```bash +agentsfleet connector list +agentsfleet connector list --workspace 0198a7b0-3c2d-7f14-9a08-1b6e4d2f8c50 +agentsfleet connector list --json +``` + +| State | Meaning | +|---|---| +| `connected` | The workspace connection is ready. | +| `not_connected` | The provider is available, but this workspace has not connected it. | +| `reconnect_required` | The saved provider authorization must be renewed. | +| `unconfigured` | The platform administrator has not configured the provider App keys. | + +### `agentsfleet connector status ` + +Show the stable state plus provider-specific details for one connector. + +```bash +agentsfleet connector status github +agentsfleet connector status slack --workspace 0198a7b0-3c2d-7f14-9a08-1b6e4d2f8c50 +agentsfleet connector status github --json +``` + +An unknown provider fails before an API request and points you to `agentsfleet connector list`. A server or network failure returns a non-zero exit code. A valid disconnected or unconfigured state is a successful inspection and returns zero. + +--- + ## Integration grants Integration grants are per-fleet permissions to reach a third-party service (Slack workspace, GitHub repo) that the fleet requested interactively. diff --git a/fleets/authoring.mdx b/fleets/authoring.mdx index e219571..d1a506a 100644 --- a/fleets/authoring.mdx +++ b/fleets/authoring.mdx @@ -134,6 +134,7 @@ Validation under this block is rigid: an unknown subkey is rejected with a typo | `triggers[].type` | yes | One of `webhook` \| `cron` \| `api`. See the table below. | | `triggers[].source` | per type | Required for `webhook`. Carries the upstream label (e.g. `github`, `slack`, `linear`) and selects the per-trigger URL `…/v1/webhooks/{fleet_id}/{source}` — see [Webhooks](/fleets/webhooks). | | `triggers[].events` | optional | Per-webhook event whitelist (1–16 entries, ≤64 chars each). Omit to accept every event the source delivers. | +| `triggers[].repositories` | optional | GitHub App repository subscription (1–64 exact `owner/repository` names, each ≤255 chars). Omission receives no managed App traffic; the manual per-fleet GitHub URL remains valid without it. | | `triggers[].signature.secret_ref` | optional | Vault key whose value verifies webhook signatures (first-class providers). | | `tools` | yes | Non-empty list of tool names from the [Tools catalogue](/fleets/tools). | | `credentials` | optional | Vault key names the fleet reads at event time. | @@ -151,6 +152,18 @@ Validation under this block is rigid: an unknown subkey is rejected with a typo A fleet can declare up to eight triggers and wake on any of them — e.g. one webhook from GitHub plus a cron sweep every 30 minutes. Any fleet can also be steered via `agentsfleet steer` regardless of declared triggers — steer always works. The `api` type is for fleets whose *primary* surface is steer (no inbound webhook). +For a fleet receiving events from the shared GitHub App, bind both the event and repository explicitly: + +```yaml +triggers: + - type: webhook + source: github + events: [pull_request] + repositories: [acme/payments] +``` + +The App installation belongs to the workspace. The `repositories` list belongs to this fleet. It narrows the repositories selected during App installation and prevents unrelated fleets in the same workspace from waking. + **Long-run continuations are not a trigger type.** When a run exhausts its context, the runtime re-enqueues the same event with `actor=continuation:` automatically. You don't declare this in `TRIGGER.md`; it happens regardless of which trigger type woke the original run. See [Context lifecycle](/concepts/context-lifecycle). ### Tools diff --git a/fleets/connectors.mdx b/fleets/connectors.mdx index f192a40..17cf44c 100644 --- a/fleets/connectors.mdx +++ b/fleets/connectors.mdx @@ -7,7 +7,7 @@ description: "How agentsfleet connects to GitHub, Slack, Zoho Desk, Jira, and Li A **connector** is how a workspace authorizes agentsfleet to act as a specific third-party account — your GitHub org, your Slack workspace, your Zoho Desk/Jira/Linear instance. The dashboard labels this surface **Integrations**; this guide uses "connector," the term the platform's own architecture docs use for the same thing. -Connectors are a different mechanism from [Secrets](/fleets/secrets). A secret is a static value you paste in yourself — a vendor API key referenced from `TRIGGER.md` as `${secrets..}`. A connector is a live OAuth relationship: you click **Connect**, your browser leaves for the provider, and you come back with nothing to type in — the provider-issued token is vaulted server-side. If a provider only needs a pasted API key (no browser round-trip), it's a secret, not a connector. +Connectors are a different mechanism from [Secrets](/fleets/secrets). A secret is a static value you paste in yourself — a vendor API key referenced from `TRIGGER.md` as `${secrets..}`. A connector is a live Open Authorization (OAuth) relationship: you click **Connect**, your browser leaves for the provider, and you come back with nothing to type in — the provider-issued token is vaulted server-side. If a provider only needs a pasted API key (no browser round-trip), it's a secret, not a connector. ## The connect round-trip @@ -33,11 +33,11 @@ Connecting a provider only proves the *workspace* is authorized — it does not ## The five providers -agentsfleet ships five connectors. Four are OAuth 2.0; GitHub is the exception — it's a GitHub App installation, not a code exchange, so its round-trip looks slightly different. +agentsfleet ships five connectors. Four use OAuth 2.0 to obtain their runtime credential. GitHub uses a short authorization-code exchange only to prove the returning user may access the claimed App installation; runtime access still uses short-lived installation tokens minted from the App private key. | Provider | Shape | Notes | |----------|-------|-------| -| **GitHub** | App install | No OAuth code exchange — you install the agentsfleet GitHub App on your account/org and the callback carries an `installation_id`. The broker mints short-lived installation tokens from the App's private key on demand. | +| **GitHub** | App install + ownership proof | You install the agentsfleet GitHub App on your account or organisation. The callback carries `installation_id`, one-time `code`, and signed `state`; agentsfleet verifies installation access before saving the route. The broker later mints short-lived installation tokens from the App private key on demand. | | **Slack** | OAuth 2.0 (no refresh) | Standard authorization-code exchange for a long-lived bot token — Slack doesn't issue a refresh token for this grant type, so there's nothing to refresh later. | | **Zoho Desk** | OAuth 2.0 + refresh | Zoho is multi-region: the authorize step always starts at Zoho's US accounts server, but the callback tells the platform which data center actually issued the code (US, EU, India, Australia, China, Japan, or Canada), and every later token refresh must hit that same regional accounts server. | | **Jira** | OAuth 2.0 + refresh | Atlassian's 3LO flow. The Jira **cloud id** for your site is *not* something you provide — it's resolved automatically from Atlassian's accessible-resources endpoint at callback time. | @@ -45,6 +45,90 @@ agentsfleet ships five connectors. Four are OAuth 2.0; GitHub is the exception For the three refresh-token providers (Zoho, Jira, Linear), agentsfleet automatically re-mints a fresh access token from the stored refresh token as needed — you never have to reconnect just because a token expired. A refresh failing because you revoked access on the provider's side is the one case that does ask you to reconnect. +## GitHub App setup and event routing + +The hosted deployment uses one GitHub App for every workspace. A platform administrator configures it once: + +1. Set the callback URL to `https://api.agentsfleet.net/v1/connectors/github/callback`. +2. Enable **Request user authorization during installation** so the callback receives a one-time authorization code. +3. Set the webhook URL to `https://api.agentsfleet.net/v1/ingress/github` and keep the webhook active. +4. Grant **Metadata: Read-only** and **Pull requests: Read & write**. Add **Contents** or other permissions only when a fleet needs them. +5. Subscribe to **Pull request** and **Workflow run** events. +6. Store the App identifier, public slug, private key, webhook secret, client identifier, and client secret through the platform-admin setup flow. These values are never entered in a customer workspace. + +The callback and webhook URLs have different jobs. The callback finishes a person's browser setup. The webhook URL receives signed machine events after setup. + +```mermaid +sequenceDiagram + participant PA as Platform administrator + participant GH as GitHub App + participant AF as agentsfleet + participant U as Workspace administrator + participant F as Fleet + + PA->>GH: Create App, set callback, webhook, permissions, events + PA->>AF: Store App identity, client credentials, and webhook secret + U->>AF: Sign up and create or select workspace W + U->>AF: Connect GitHub in workspace W + AF-->>U: Signed, single-use install URL + U->>GH: Install App on selected repositories + GH->>AF: Callback with installation_id, code, and state + AF->>GH: Exchange code and verify installation access + GH-->>AF: User may access installation + AF->>AF: Bind installation_id to workspace W if unowned or same-workspace + Note over AF: Another workspace owns it → 403, no changes + U->>AF: Install fleet and approve its GitHub grant + U->>F: Configure repository and event subscriptions + GH->>AF: Signed repository event + AF->>AF: Resolve installation → workspace → matching fleets + AF->>F: Queue one normalized event +``` + +### What a GitHub event is connected to + +GitHub sends an installation identifier, a repository name, an event name, and a diagnostic delivery identifier. It does not know your `workspace_id`, `fleet_id`, or signed-in user. + +agentsfleet derives those values in two steps: + +```mermaid +flowchart TD + E[GitHub delivery
installation + repository + event] --> V{Valid App signature?} + V -- no --> R[Reject] + V -- yes --> W[Installation maps to one workspace] + W --> S[Find active fleets in that workspace] + S --> M{Repository and event match?
GitHub grant approved?} + M -- no --> I[Ignore for that fleet] + M -- yes --> D[Hash signed body
claim digest slot for that fleet] + D --> Q[Queue one fleet event] +``` + +The App installation defines the largest set of repositories GitHub will expose. Each fleet narrows that set in `TRIGGER.md`: + +```yaml +triggers: + - type: webhook + source: github + events: [pull_request] + repositories: [acme/payments] +``` + +Two fleets may subscribe to the same repository and event. Each receives one event. A fleet with the wrong repository, the wrong event, no repository list, or no approved GitHub grant receives nothing. + +The browser-provided installation identifier is never trusted alone. Signed state proves which `agentsfleet` workspace initiated the connection, GitHub user authorization proves access to the installation, and the datastore refuses to move an existing installation to another workspace. For events, the signature-covered request body supplies the replay identity; changing the unsigned delivery header cannot create a second event. + + + The `github-pr-reviewer` repository walkthrough is not yet an end-to-end proof. Keep it in testing until a real repository-bound Pull Request reaches exactly one expected fleet, the fleet posts its review with a short-lived installation token, and replaying the same delivery creates no duplicate event or review. + + +### Inspect connector state from the command line + +```bash +agentsfleet connector list +agentsfleet connector status github +``` + +Use `--workspace ` to inspect a workspace other than your active one. Use `--json` for automation. The state is one of `connected`, `not_connected`, `reconnect_required`, or `unconfigured`; the last state means the platform administrator has not configured that provider's App keys. + ## Disconnecting Revoking access on the provider's side (uninstalling the GitHub App, removing the Slack app from your workspace, revoking the OAuth grant in Zoho/Jira/Linear) invalidates the vaulted credential. The next run that needs it surfaces a reconnect prompt in the dashboard rather than failing silently. diff --git a/fleets/webhooks.mdx b/fleets/webhooks.mdx index 17a6a95..470f3c3 100644 --- a/fleets/webhooks.mdx +++ b/fleets/webhooks.mdx @@ -3,16 +3,19 @@ title: "Webhooks" description: "How external systems deliver events to your fleet, and how the platform authenticates them." --- -Every webhook trigger declared in `TRIGGER.md` gets a unique, stable URL keyed by `source`: +Webhook triggers have two delivery paths: + +- **Managed App ingress** — GitHub App events arrive at `POST /v1/ingress/github`. The platform maps the App installation to a workspace, then selects fleets by repository, event, and approved grant. +- **Manual per-fleet webhook** — an operator registers a stable URL keyed by `fleet_id` and `source`: ``` https://api.agentsfleet.net/v1/webhooks/{fleet_id}/{source} ``` -External systems POST to this URL. The platform verifies the signature and delivers the event to the fleet. No tunneling, no ngrok, no custom servers on your side. `agentsfleet install --library` prints the full set of URLs (one per declared webhook trigger) at install time. +External systems POST to the manual URL. The platform verifies the signature and delivers the event to that fleet. No tunneling, no ngrok, no custom servers on your side. `agentsfleet install --library` prints the full set of manual URLs at install time. - **`agentsfleet install` prints the webhook URL(s) to register.** Add each printed URL to its upstream provider — on GitHub, **Settings → Webhooks** (or `gh api -X POST repos///hooks …`). The reference below covers the signature config the platform expects. + **`agentsfleet install` prints manual webhook URLs.** Register those URLs when you manage the upstream webhook yourself. A workspace connected to the shared GitHub App uses `/v1/ingress/github`; do not create a second repository webhook for the same event path. ## Declaring a webhook trigger @@ -37,11 +40,33 @@ x-agentsfleet: `source` is a label that appears in the activity stream (`github`, `slack`, `linear`, etc.) and selects the per-trigger inbound URL — `https://api.agentsfleet.net/v1/webhooks/{fleet_id}/{source}`. `events` is an optional whitelist of upstream event names (1–16 entries, ≤64 chars each); omit it to accept every event the source delivers. The `signature` block tells the platform how to verify the request. +For managed GitHub App traffic, add `repositories` with 1–64 exact `owner/repository` names. This list is required for App routing and fail-closed when omitted. It is optional for the manual per-fleet GitHub URL because that URL already identifies its target fleet. + +```yaml +triggers: + - type: webhook + source: github + events: [pull_request, workflow_run] + repositories: + - acme/payments +``` + +GitHub App ingress accepts Pull Request events and completed workflow runs whose conclusion is a failure. Successful workflow runs are ignored. + +```mermaid +flowchart LR + GH[GitHub App delivery] --> SIG[Verify App signature] + SIG --> WS[Installation → workspace] + WS --> SUB[Repository + event + approved grant] + SUB --> F1[Fleet A event stream] + SUB --> F2[Fleet B event stream] +``` + The singular `trigger:` shape is rejected at install with `UZ-AGT-008` (invalid fleet config); always declare the array form, even for a single webhook. ## Authentication -Every inbound webhook is authenticated before the fleet is woken. The platform supports **HMAC** signature verification — Hash-based Message Authentication Code, a standard scheme where the sender signs the payload with a shared secret and the receiver re-computes the signature to verify it. GitHub, Slack, Linear, Jira, Grafana, and most SaaS providers use HMAC. +Every inbound webhook is authenticated before the fleet is woken. The platform supports **Hash-based Message Authentication Code (HMAC)** signature verification, a standard scheme where the sender signs the payload with a shared secret and the receiver re-computes the signature to verify it. GitHub, Slack, Linear, Jira, Grafana, and most software-as-a-service providers use HMAC. ### Natively normalized providers @@ -108,6 +133,8 @@ The platform verifies the signature, enqueues the event, and returns `202 Accept Delivery is **at-least-once** — your provider may retry on transient failure, so the same event can arrive twice. Include a unique `event_id` in the body so the fleet can dedupe. +GitHub App deliveries derive replay identity from the signature-covered request body, independently for each matching fleet. The delivery header is diagnostic and cannot bypass replay protection. If one queue write fails, a GitHub retry fills only that missing fleet and does not duplicate fleets that already received the payload. + ## Responses | Status | Body | Meaning | @@ -122,7 +149,7 @@ Delivery is **at-least-once** — your provider may retry on transient failure, ## Approval gate webhooks -Some fleet actions — sending money, merging a PR, posting to a public channel — should not happen without a human in the loop. The **approval gate** lets a fleet pause mid-run, emit an approval request, and wait for a human to click approve or deny in the dashboard. +Some fleet actions — sending money, merging a Pull Request (PR), posting to a public channel — should not happen without a human in the loop. The **approval gate** lets a fleet pause mid-run, emit an approval request, and wait for a human to click approve or deny in the dashboard. For programmatic decisions (e.g. an internal tool calling back into agentsfleet), POST the decision to: