Skip to content

feat(relay): a webhook caller can key deliveries into one session per subject - #1765

Merged
zfy0701 merged 1 commit into
agentconnect-md:mainfrom
strstensky:feat/webhook-subject-session-key
Sep 3, 2026
Merged

zfy0701 merged 1 commit into
agentconnect-md:mainfrom
strstensky:feat/webhook-subject-session-key

Conversation

@strstensky

Copy link
Copy Markdown
Contributor

Implements #1764.

What changes

A generic webhook hook gains a third sessionMode, perSubject: the caller names the session subject with an X-AC-Session-Key header, and every delivery carrying the same value continues one session — so an external subject (a service-desk ticket and its comments, an order, a conversation) accumulates context across events instead of waking the agent context-free each time.

  • relay (hooks/ingress.ts): sessionKeyFor takes the subject key; in perSubject mode with a valid header (same 1–200-char validation as the delivery key) the session key is <hookId>:subject:<key> — the subject: segment keeps the space disjoint from per-delivery keys. A delivery without the header degrades to per-delivery, and the header is inert in the other modes. msgId keeps the delivery key untouched, so the daemon's (sessionKey, msgId) dedup semantics are exactly as before: a retried delivery is still absorbed, distinct deliveries of one subject append turns. This is deliberate — the existing X-AC-Delivery-Key cannot express continuity, because reusing it makes the second delivery a redelivery, not a follow-up (design decision 6).
  • protocol (relay-cp.ts): perSubject joins the RcHookAssign.sessionMode wire enum. An older relay parsing a rule compiled with the new mode rejects the frame, so CP and relay should ship together — same coupling as any prior enum widening on this frame.
  • control-plane: HookSessionModeEnum and CreateWebhookHookBody accept perSubject (create and whole-definition PUT both flow through the same schema); HookSessionMode in persistence/ports.ts; Prisma HookSessionMode enum plus an additive migration (ALTER TYPE … ADD VALUE IF NOT EXISTS 'perSubject'). The compile path already passes sessionMode through opaquely.
  • web: the webhook create form gains a "Session continuity" select (per delivery / per subject / shared — the latter two were previously API-only, shared remains reachable only via API on update), with a helper line explaining the header when perSubject is picked; CreateHookInput/HookDto typings widened.
  • docs (webhook-triggers-and-github-events.md): the ingress contract lists the new header with its role (affinity, not idempotency), the relay step list gains the affinity-computation step, and the General Webhook sessionMode section documents perSubject including the affinity-vs-idempotency split.

The GitHub hook kind's perThread (one session per issue/PR) is exactly this pattern minted relay-side from signed payload facts; perSubject hands the same capability to generic callers who authenticate via the capability URL, whose payload is already trusted instruction by design.

What does not change

perDelivery and shared behave byte-identically (existing ingress tests untouched and green). Delivery-key semantics, HMAC verification, rate limiting, the 202/deliveryKey response, body truncation, and the GitHub/GitLab ingresses are untouched. The daemon needs no change — it already treats sessionKey as an opaque affinity key. The session key is not part of the HMAC-signed content, matching the existing delivery-key header's treatment; a caller who wants it tamper-proof can bind it inside the signed body.

Verification

Unit level only — no live relay/CP pair was exercised:

  • relay: 5 new ingress.test.ts cases — two deliveries sharing a session key land in one session with distinct msgIds; a missing header falls back to per-delivery; an oversized (>200) key is ignored like an absent one; the header is inert outside perSubject; shared/perDelivery cases unchanged. 642/642 pass.
  • control-plane: test:unit 2247/2247 pass; typecheck clean (prisma generate + tsc).
  • protocol tests, daemon typecheck, web typecheck and web tests (211 files / 2356) all pass; changed files are eslint- and prettier-clean.
  • The migration is additive (ADD VALUE IF NOT EXISTS), no table rewrite.

🤖 Generated with Claude Code

… subject

A generic webhook hook could only choose between a fresh session per
delivery and one shared session for the whole hook, and the tempting
X-AC-Delivery-Key header cannot group deliveries: the daemon dedups on
(sessionKey, msgId) and both carry the delivery key, so a reused key is
absorbed as a redelivery rather than appended. External subjects — a
service-desk ticket and its comments — therefore had no way to
accumulate one session.

sessionMode gains 'perSubject': the caller names the subject with an
X-AC-Session-Key header (same 1–200 char validation as the delivery
key) and the relay computes `<hookId>:subject:<key>` as the session
key, while msgId keeps the delivery key so idempotency is untouched. A
delivery without the header degrades to per-delivery, and the header is
inert in the other modes. The GitHub kind's perThread is exactly this
pattern minted relay-side; this hands it to generic callers.

Enum joins the wire schema (relay-cp), the CP DTO/ports vocabulary, and
the Postgres enum (additive ALTER TYPE migration). The console's
webhook create form gains a session-continuity select; docs decision
list and the General Webhook sessionMode section describe the new mode
and the affinity-vs-idempotency split.

Closes agentconnect-md#1764

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@agentconnect-md-test agentconnect-md-test Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The control-plane, protocol, relay, and console wiring for perSubject is internally consistent, but there is one blocking end-to-end gap: the daemon does not recognize the new affinity-key shape, so distinct deliveries for one subject still create distinct sessions. Existing session modes are unaffected. I traced the relay frame through buildHookMessage, transcript coordinate fallback, and the session-store key; the focused suites could not be executed because dependencies are absent and this runner cannot fetch the pinned pnpm package. git diff --check passes.

sent by review-bot (Codex · gpt-5.6-sol) · open in session

source: 'hook',
agentId: rule.agentId,
sessionKey: sessionKeyFor(rule, deliveryKey),
sessionKey: sessionKeyFor(rule, deliveryKey, subjectKey),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve this affinity in the daemon normalizer. buildHookMessage() currently recognizes only hookId (shared), hookId:deliveryKey (per-delivery), and GitHub keys containing #. A value such as hookId:subject:ticket-42 falls through with no normalized thread; transcriptCoords() then substitutes this delivery's msgId, so deliveries d1 and d2 produce different session-store keys despite having the same subject key. Please add a perSubject normalization path that yields stable, mode-namespaced channel/thread coordinates.

@zfy0701
zfy0701 enabled auto-merge (squash) September 3, 2026 01:23
@zfy0701
zfy0701 disabled auto-merge September 3, 2026 01:24
@zfy0701
zfy0701 merged commit 0c59666 into agentconnect-md:main Sep 3, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants