Skip to content

feat(events): optional outbound notification events on EventBridge - #626

Open
thenom wants to merge 2 commits into
aws-samples:mainfrom
thenom:feat/notification-events
Open

thenom wants to merge 2 commits into
aws-samples:mainfrom
thenom:feat/notification-events

Conversation

@thenom

@thenom thenom commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Issue #, if available: None

Description of changes:

Problem

Nothing outside the platform can react to what happens in it without polling the API. Common asks are a chat message when a gate is waiting for a human, a notice when a discussion is resolved, or an audit record when a space is created.

Solution

An opt-in outbound event feed on Amazon EventBridge. The platform publishes, and integrations subscribe with their own rules, in this deployment or another.

Terraform (root variables, all optional):

  • notification_events_enabled (default false) creates an aws_cloudwatch_event_bus in modules/api/lambda.
  • notification_event_bus_name (default empty, which means <project_name>-<environment>-events).
  • notification_event_source (default collaborative-ai-dlc). It's validated, and must not start with aws..
  • Only the three publishing Lambdas (projects, discussions, v2-orchestrator) get NOTIFICATION_EVENT_BUS, NOTIFICATION_EVENT_SOURCE, and a separate events:PutEvents policy on that bus alone.
  • New root outputs: notification_event_bus_name and notification_event_bus_arn.

Events:

Detail type When
space.created a space is created
gate.pending an intent waits for a human: an agent question or checkpoint, or an engine gate (validation, walking skeleton, batch, autonomy ladder, lane failure)
discussion.message a message is posted to an intent discussion
discussion.resolved an intent discussion is resolved

Every detail has schemaVersion (1) and an eventId for deduplication. gate.pending also carries need (answer for an agent question, approval for checkpoints and engine gates), kind, stage, intentTitle, startedBy and prompt (an agent question's first question), so a consumer can render a useful message without calling back into the API.

docs/using-the-platform/notification-events.md, added to the nav, documents every detail field, delivery semantics (normally once, at least once) and an example rule.

Publishing (lambda/shared/notification-events.js) is best-effort:

  • It never throws and gives up after two seconds. A missing bus, missing permission or EventBridge error only logs a warning, so the triggering action is never affected.
  • With no bus configured it does nothing and never loads the EventBridge client.
  • In the orchestrator, agent gates publish from a new durable step that only runs when a bus is configured. Engine gates publish from inside their existing gate-open step. Either way, without a bus the step sequence of existing executions is unchanged.

@aws-sdk/client-eventbridge is added to the root devDependencies at the version the other clients use (^3.1106.0). That's where lambda/shared resolves its clients. The projects and v2-orchestrator bundles include it, and discussions uses the runtime's SDK like its other clients.

Testing

  • lambda/shared/test/notification-events.test.js covers no bus (no client load), the entry shape and default source, a custom source, rejected entries, send failures, client load failures, the real client loaded lazily, and the gate.pending detail: need, the prompt from a questions array, clipping, and reading the gate and META only when not given them.
  • lambda/v2-orchestrator/test/engine-gate-notification.test.js covers engine gates: one publish per gate with the expected detail, nothing when the run was replaced, and an identical step sequence with and without a bus.
  • npm run lint, npm run format:check, and the discussions, projects, v2-orchestrator and shared vitest projects pass, including bundle-smoke.test.js. The projects and discussions bundles also import cleanly with a bus configured.
  • terraform fmt -check, terraform validate -no-tests and every terraform test suite from the Terraform workflow pass on Terraform 1.15.0, as does the SSO plan-privacy test. There's no new Terraform test: a plan of modules/api/lambda builds every Lambda package.
  • The same events run in our own deployment, feeding Microsoft Teams notifications.

Impact

Disabled (the default), nothing is created and no Lambda's configuration changes. Event details include user names, email addresses and message text; the docs page says so.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

There is no way for another system to react to platform activity, for
example a chat message when a gate needs a human, without polling the
API.

With notification_events_enabled (default false) the deployment creates
an EventBridge bus (notification_event_bus_name, default
<project>-<environment>-events). The projects, discussions and
v2-orchestrator Lambdas get NOTIFICATION_EVENT_BUS,
NOTIFICATION_EVENT_SOURCE (default collaborative-ai-dlc) and
events:PutEvents on that bus only. They publish:

- space.created when a space is created
- gate.pending when an intent waits at a gate, from a durable step so a
  replay does not publish twice
- discussion.message and discussion.resolved for intent discussions

Publishing (lambda/shared/notification-events.js) is best-effort: it
never throws, gives up after two seconds, and without a bus does nothing
and never loads the EventBridge client. The bus name and ARN are root
outputs, and docs/using-the-platform/notification-events.md documents
the event schema. Disabled, nothing is created and no Lambda changes.

Co-authored-by: ashley.fairclough <ashley.fairclough@cdl.co.uk>
Co-authored-by: Matt Houghton <matthew.houghton@cdl.co.uk>
gate.pending only fired for agent gates and its prompt was always null:
agent questions carry a questions JSON array, not a prompt.

- prompt falls back to the first entry of the gate's questions.
- gate.pending adds need (answer, or approval for checkpoints and engine
  gates), intentTitle and startedBy.
- Engine gates (validation, walking skeleton, batch, autonomy ladder,
  lane failure) publish gate.pending from inside their existing
  gate-open step, so the durable step sequence is unchanged. stage is
  the stage id where there is one, else a readable gate label.
- Every event's detail has schemaVersion (1) and an eventId for
  deduplication. The docs now say delivery is at least once.

All additive: no field is renamed or removed, and nothing changes
without a bus configured.

Co-authored-by: Adrian Robinson <adrian.robinson@cdl.co.uk>

This branch has not been deployed

No deployments
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.

1 participant