Summary
Design and implement a plugin-defined reactive behavior system for the GitHub channel, enabling Junior to respond to GitHub events beyond explicit @junior mentions. Builds on #311 (the @mention entry point) by adding a behavior arbitration layer, new plugin hooks, and constrained execution modes.
The core concept: GitHub is a peer channel to Slack with its own system prompt, tools, and response surface. Plugins declare named behaviors and claim them in response to specific GitHub events. Core arbitrates claims and runs at most one agent per event.
Background
Issue #311 covers Phase 1: @junior mention in a comment → full agent turn → GitHub comment reply. This issue covers the architecture needed to support reactive event handling — where GitHub events from other bots or CI systems trigger predefined plugin behaviors.
Two distinct turn types both respond on GitHub:
|
Explicit mention |
Reactive event |
| Trigger |
@junior in comment |
Bot comment, CI result, etc. |
| Mode |
conversation |
reactive_behavior |
| Agency |
Full — user invoked |
Predefined behavior, constrained |
| Direct push |
Allowed if user asks |
Never |
| Create PR |
Yes |
Draft only, if configured |
| Opt-in |
No |
Yes — repo-scoped, default off |
Proposal
New plugin hooks
Add two hooks to AgentPluginHooks in @sentry/junior-plugin-api:
behaviors() — plugins declare named behavior definitions at startup. Auditable and validatable at startup time.
behaviors?(ctx: BehaviorRegistrationContext): Record<string, AgentBehaviorDefinition>;
webhookEvent() — invoked per GitHub webhook delivery. Returns a claim to invoke a named behavior, or ignored/rejected. No agent.dispatch in context — hooks propose, core disposes.
webhookEvent?(ctx: WebhookEventHookContext): Promise<WebhookEventResult | void>;
Return type is a claim referencing a declared behavior:
type WebhookEventResult =
| { kind: "ignored"; reason?: string }
| { kind: "rejected"; httpStatus: 400 | 401 | 403 | 422; reason?: string }
| { kind: "claim"; behaviorId: string; idempotencyKey: string; input: string; variables?: Record<string, unknown>; target?: GitHubResponseTarget };
Behavior definitions
Behavior definitions are declared at startup and include constrained execution policy:
interface AgentBehaviorDefinition {
channel: "github";
mode: "conversation" | "reactive_behavior";
prompt: { profile: string; systemPromptAddon?: string };
toolPolicy: ToolPolicy; // enforced by runner, not prompt guidance
limits?: { maxModelTurns?: number; maxToolCalls?: number };
constraints?: { allowDirectPush: false; requireDraftPullRequest?: boolean };
response: { destination: "source_thread" | "source_comment"; format?: "github_markdown" };
}
Extended tools() hook
Extend the existing tools() context with channel and run so plugins can conditionally register tools:
interface ToolRegistrationHookContext {
channel: AgentChannelContext; // { kind: "github", owner, repo, ... } or { kind: "slack", ... }
run: AgentRunContext; // { mode: "conversation" | "reactive_behavior", behaviorId?, toolPolicy? }
// ...existing Slack compat fields preserved
}
Multi-plugin safety: claim arbitration
Core collects all plugin claims before scheduling:
- 0 claims → ACK, no run
- 1 claim → validate + schedule exactly one run
- 2+ claims → ACK (prevent retry storm), log conflict, alert, no run
Storage-level uniqueness enforced on (source.platform, source.sourceEventId) for webhook-originated runs.
Delivery abstraction
Refactor dispatch runner to use a delivery adapter interface, replacing hardcoded Slack post calls:
SlackDeliveryAdapter — wraps existing Slack post logic
GitHubDeliveryAdapter — posts comments/PRs via GitHub App installation token
Reactive behavior opt-in
Default: disabled everywhere. Reactive behaviors require explicit repo-scoped enablement:
interface GitHubBehaviorEnablement {
enabled: boolean;
owner: string; repo: string;
plugin: string; behaviorId: string;
eventTypes: string[];
actorAllowlist?: string[]; // which bots/apps can trigger
bodyPatterns?: string[];
}
Implementation phases
Phase 1 (prerequisite — #311): GitHub adapter, @junior mention handling, GitHubDeliveryAdapter, GitHub system prompt.
Phase 2: behaviors() and webhookEvent() hooks, behavior arbiter, claim arbitration, storage uniqueness, behavior definitions validated at startup.
Phase 3 (gated on runner enforcement): Runner-level tool policy filtering + executor-level rejection, channel/run context in beforeToolExecute/sandboxPrepare, GitHub plugin's first reactive behavior (considerAndAddress for CI bot findings), opt-in config model.
Stop conditions for Phase 3
- Runner must filter tools by
toolPolicy before model exposure
- Executor must reject disallowed tool calls at runtime (not prompt-only)
- GitHub write credentials must not be injected in reactive mode
- Multiple claim arbitration must be enforced before shipping
- Reactive behaviors must be repo-scoped opt-in, default off
Related
Action taken on behalf of David Cramer.
Summary
Design and implement a plugin-defined reactive behavior system for the GitHub channel, enabling Junior to respond to GitHub events beyond explicit
@juniormentions. Builds on #311 (the @mention entry point) by adding a behavior arbitration layer, new plugin hooks, and constrained execution modes.The core concept: GitHub is a peer channel to Slack with its own system prompt, tools, and response surface. Plugins declare named behaviors and claim them in response to specific GitHub events. Core arbitrates claims and runs at most one agent per event.
Background
Issue #311 covers Phase 1:
@juniormention in a comment → full agent turn → GitHub comment reply. This issue covers the architecture needed to support reactive event handling — where GitHub events from other bots or CI systems trigger predefined plugin behaviors.Two distinct turn types both respond on GitHub:
@juniorin commentconversationreactive_behaviorProposal
New plugin hooks
Add two hooks to
AgentPluginHooksin@sentry/junior-plugin-api:behaviors()— plugins declare named behavior definitions at startup. Auditable and validatable at startup time.webhookEvent()— invoked per GitHub webhook delivery. Returns a claim to invoke a named behavior, or ignored/rejected. Noagent.dispatchin context — hooks propose, core disposes.Return type is a claim referencing a declared behavior:
Behavior definitions
Behavior definitions are declared at startup and include constrained execution policy:
Extended
tools()hookExtend the existing
tools()context withchannelandrunso plugins can conditionally register tools:Multi-plugin safety: claim arbitration
Core collects all plugin claims before scheduling:
Storage-level uniqueness enforced on
(source.platform, source.sourceEventId)for webhook-originated runs.Delivery abstraction
Refactor dispatch runner to use a delivery adapter interface, replacing hardcoded Slack post calls:
SlackDeliveryAdapter— wraps existing Slack post logicGitHubDeliveryAdapter— posts comments/PRs via GitHub App installation tokenReactive behavior opt-in
Default: disabled everywhere. Reactive behaviors require explicit repo-scoped enablement:
Implementation phases
Phase 1 (prerequisite — #311): GitHub adapter,
@juniormention handling,GitHubDeliveryAdapter, GitHub system prompt.Phase 2:
behaviors()andwebhookEvent()hooks, behavior arbiter, claim arbitration, storage uniqueness, behavior definitions validated at startup.Phase 3 (gated on runner enforcement): Runner-level tool policy filtering + executor-level rejection,
channel/runcontext inbeforeToolExecute/sandboxPrepare, GitHub plugin's first reactive behavior (considerAndAddressfor CI bot findings), opt-in config model.Stop conditions for Phase 3
toolPolicybefore model exposureRelated
Action taken on behalf of David Cramer.