feat(delivery): onComplete=deliver fans notifications to webhook + Slack#258
Merged
Conversation
…slack
Adds a new lifecycle terminal action that fans the team's completion
signal out to one or more configured destinations. Per-target success
and failure are recorded on status.delivery[] independently — a Slack
outage does not roll the team back to Failed if the webhook succeeded.
API
- LifecycleSpec.onComplete: deliver
- LifecycleSpec.delivery[] of typed targets (webhook | slack | email |
google-drive) with per-type fields and a shared credentialsSecret /
artifactPath / message
- AgentTeamStatus.delivery[] records per-target outcome with timestamp
Senders
- internal/delivery: Dispatcher selects a Sender per target.Type
- WebhookSender: HTTP POST of {event, team, namespace, phase, message,
artifacts} as application/json; non-2xx → failure
- SlackSender: loads the incoming-webhook URL from credentialsSecret
(key: "slack-webhook-url") at dispatch time and posts a formatted
message; the operator never holds the URL at rest
- email and google-drive are declared in the API but their senders
return ErrNotImplemented — surfaces as a clean per-target failure
rather than a runtime panic, and unblocks API stability for future
backends
Scope note: the operator pod does not mount team output PVCs, so the
MVP delivers a metadata envelope (artifact list + identifiers), not
file payloads. Downstream systems are expected to fetch the actual
artifacts from persisted storage. A Job-based file-transfer pattern
is sketched in the design doc but deferred.
Tests
- delivery package: 89% coverage — dispatcher routing, all three
ErrNotImplemented branches, webhook payload + non-2xx, slack secret
loading + missing-key + non-2xx + message formatting
- controller: executeDelivery records per-target status (mixed
success/failure), no-ops on empty / nil lifecycle, executeOnComplete
routes the deliver case end-to-end
Sample
- config/samples/delivery-team.yaml — webhook + Slack targets with the
Secret setup command in the leading comment
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new lifecycle terminal action —
onComplete: deliver— that routesthe team's completion signal to one or more configured destinations. Two
production senders are wired in this PR (webhook + Slack); the API also
defines
emailandgoogle-driveso the surface stays stable for futurebackends, but their senders return a clean
ErrNotImplementedrecordedon
status.delivery[]rather than panicking.Per-target success and failure are independent: a Slack outage does not
roll the team back to
Failedif the webhook succeeded. This matchesthe design's best-effort posture for notifications — operators read
status.delivery[]to see exactly what landed.API
LifecycleSpec.onComplete: deliver(new enum value)LifecycleSpec.delivery[]— typed targets with discriminator + sharedcredentialsSecret/artifactPath/messagefieldsAgentTeamStatus.delivery[]— per-target outcome with timestampScope note
The operator pod does not mount team output PVCs, so the MVP delivers a
metadata envelope (artifact list + identifiers) rather than file
payloads. Downstream systems are expected to fetch the actual artifacts
from persisted storage. A Job-based file-transfer pattern is sketched
in the design doc but deferred — the discriminator + status shape is
already there to accommodate it without a future breaking change.
Sender details
Webhook — HTTPS POST of
{event, team, namespace, phase, message, artifacts}asapplication/json. Non-2xx is recorded as failure.Slack — the operator loads the incoming-webhook URL from the
configured
credentialsSecret(key:slack-webhook-url) at dispatchtime, posts a formatted message with the artifact list, never holds
the URL at rest.
Tests
internal/delivery— 89% coverage. Dispatcher routing, all threeErrNotImplementedbranches, webhook payload + non-2xx, Slack secretloading + missing-key + non-2xx + message formatting.
internal/controller—executeDeliveryrecords per-target status(mixed success/failure), no-ops on empty / nil lifecycle,
executeOnCompleteroutes thedelivercase end-to-end.Sample
config/samples/delivery-team.yaml— webhook + Slack targets, withthe Secret setup command in the leading comment.
Test plan
kubectl apply -f config/samples/delivery-team.yamlparses cleanlymake docs-apiclean (regenerated)🤖 Generated with Claude Code