Skip to content

Commit 8538648

Browse files
dcramercodex
andcommitted
ref(events): Trim event binding surface
Remove delivery targets and declarative filters from the first event prompt API. The current Slack event prompt path derives delivery from the Slack event envelope and only needs event id, scope, context blocks, and a prompt body. Refs #435 Co-Authored-By: GPT-5 Codex <codex@openai.com>
1 parent f7e92e3 commit 8538648

7 files changed

Lines changed: 19 additions & 100 deletions

File tree

packages/junior/src/chat/events/bindings.ts

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,12 @@ const eventBindingFrontmatterSchema = z
3030
})
3131
.optional(),
3232
scope: recordSchema.optional(),
33-
when: recordSchema.optional(),
3433
context: z
3534
.object({
3635
include: stringArraySchema.optional(),
3736
})
3837
.strict()
3938
.optional(),
40-
delivery: z
41-
.object({
42-
target: z.string().min(1),
43-
})
44-
.strict()
45-
.optional(),
4639
})
4740
.strict();
4841

@@ -54,13 +47,11 @@ export interface EventBindingFile {
5447
export interface ParsedEventBinding {
5548
body: string;
5649
contextInclude: string[];
57-
delivery?: Record<string, unknown> & { target: string };
5850
enabled: boolean;
5951
event: string;
6052
id: string;
6153
path: string;
6254
scope?: Record<string, unknown>;
63-
when?: Record<string, unknown>;
6455
}
6556

6657
type ParseResult =
@@ -132,8 +123,6 @@ export function parseEventBindingFile(file: EventBindingFile): ParseResult {
132123
body,
133124
contextInclude: result.data.context?.include ?? [],
134125
...(result.data.scope ? { scope: result.data.scope } : {}),
135-
...(result.data.when ? { when: result.data.when } : {}),
136-
...(result.data.delivery ? { delivery: result.data.delivery } : {}),
137126
},
138127
};
139128
}
@@ -154,15 +143,6 @@ function validateBindingAgainstDefinition(args: {
154143
}
155144
}
156145

157-
if (args.binding.delivery) {
158-
const deliveryTargets = new Set(
159-
args.definition.definition.deliveryTargets.map((target) => target.target),
160-
);
161-
if (!deliveryTargets.has(args.binding.delivery.target)) {
162-
return `${args.binding.path}: event binding "${args.binding.id}" references unsupported delivery target "${args.binding.delivery.target}" for event "${args.binding.event}"`;
163-
}
164-
}
165-
166146
if (args.binding.scope) {
167147
const allowedScopeKeys = args.definition.definition.scopeKeys ?? [];
168148
if (allowedScopeKeys.length === 0) {
@@ -177,20 +157,6 @@ function validateBindingAgainstDefinition(args: {
177157
}
178158
}
179159

180-
if (args.binding.when) {
181-
const allowedFilterKeys = args.definition.definition.filterKeys ?? [];
182-
if (allowedFilterKeys.length === 0) {
183-
return `${args.binding.path}: event binding "${args.binding.id}" uses when fields but event "${args.binding.event}" does not support filters`;
184-
}
185-
const allowed = new Set(allowedFilterKeys);
186-
const invalid = Object.keys(args.binding.when).find(
187-
(key) => !allowed.has(key),
188-
);
189-
if (invalid) {
190-
return `${args.binding.path}: event binding "${args.binding.id}" uses unsupported when field "${invalid}" for event "${args.binding.event}"`;
191-
}
192-
}
193-
194160
return undefined;
195161
}
196162

packages/junior/src/chat/events/dispatch.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ function findMatches(args: {
7171
(binding) => binding.enabled && binding.event === args.envelope.event,
7272
)
7373
.filter((binding) => recordMatches(binding.scope, args.envelope.scope))
74-
.filter((binding) => recordMatches(binding.when, args.envelope.payload))
7574
.map((binding) => ({ binding, definition }));
7675
}
7776

packages/junior/src/chat/events/slack.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ const slackChannelMessageCreatedDefinition: AgentEventDefinition = {
3939
},
4040
},
4141
},
42-
deliveryTargets: [{ target: "channel" }],
43-
filterKeys: ["actor", "text", "userId"],
4442
scopeKeys: ["channelId", "teamId"],
4543
};
4644

packages/junior/src/chat/plugins/agent-hooks.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ const AGENT_PLUGIN_EVENT_ID_RE = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9_]*)+$/;
6363
const AGENT_PLUGIN_EVENT_NAME_RE = /^[a-z][a-z0-9_]*$/;
6464
const AGENT_PLUGIN_EVENT_DEFINITION_KEYS = new Set([
6565
"contextBlocks",
66-
"deliveryTargets",
67-
"filterKeys",
6866
"scopeKeys",
6967
]);
7068

@@ -168,17 +166,6 @@ function validateEventDefinition(args: {
168166
`Trusted plugin event "${args.event}" from plugin "${args.plugin}" uses unsupported event definition field "${unsupportedKey}"`,
169167
);
170168
}
171-
if (
172-
!Array.isArray(args.definition.deliveryTargets) ||
173-
args.definition.deliveryTargets.length === 0
174-
) {
175-
throw new Error(
176-
`Trusted plugin event "${args.event}" from plugin "${args.plugin}" must declare at least one delivery target`,
177-
);
178-
}
179-
for (const target of args.definition.deliveryTargets) {
180-
validateEventName(target.target, "delivery target", args.plugin);
181-
}
182169
for (const contextName of Object.keys(args.definition.contextBlocks ?? {})) {
183170
validateEventName(contextName, "context block", args.plugin);
184171
}

packages/junior/tests/unit/events/event-bindings.test.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const definitions: RegisteredAgentEventDefinition[] = [
1616
source_comment: { description: "Triggering GitHub comment" },
1717
pull_request: { description: "GitHub pull request metadata" },
1818
},
19-
deliveryTargets: [{ target: "source_thread" }],
2019
},
2120
},
2221
];
@@ -34,14 +33,10 @@ describe("event binding files", () => {
3433
"event: github.pull_request.comment.created",
3534
"scope:",
3635
" repository: getsentry/junior",
37-
"when:",
38-
" actor: sentry-warden[bot]",
3936
"context:",
4037
" include:",
4138
" - source_comment",
4239
" - pull_request",
43-
"delivery:",
44-
" target: source_thread",
4540
]),
4641
});
4742

@@ -54,9 +49,7 @@ describe("event binding files", () => {
5449
path: "/repo/app/events/github/warden.md",
5550
body: "Review the event.",
5651
scope: { repository: "getsentry/junior" },
57-
when: { actor: "sentry-warden[bot]" },
5852
contextInclude: ["source_comment", "pull_request"],
59-
delivery: { target: "source_thread" },
6053
},
6154
});
6255
});
@@ -68,7 +61,7 @@ describe("event binding files", () => {
6861
"id: github-typo",
6962
"event: github.pull_request.comment.created",
7063
"delivrey:",
71-
" target: source_thread",
64+
" value: typo",
7265
]),
7366
});
7467

@@ -87,7 +80,6 @@ describe("event binding files", () => {
8780
path: "/repo/app/events/github/warden.md",
8881
body: "Review the event.",
8982
contextInclude: ["source_comment"],
90-
delivery: { target: "source_thread" },
9183
};
9284

9385
expect(validateEventBindings([binding], definitions)).toEqual({
@@ -155,8 +147,6 @@ describe("event binding files", () => {
155147
"context:",
156148
" include:",
157149
" - source_comment",
158-
"delivery:",
159-
" target: source_thread",
160150
]),
161151
},
162152
{

packages/junior/tests/unit/plugins/agent-hooks.test.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ describe("agent plugin hooks", () => {
350350
description: "Triggering GitHub comment",
351351
},
352352
},
353-
deliveryTargets: [{ target: "source_thread" }],
354353
},
355354
};
356355
},
@@ -368,7 +367,6 @@ describe("agent plugin hooks", () => {
368367
description: "Triggering GitHub comment",
369368
},
370369
},
371-
deliveryTargets: [{ target: "source_thread" }],
372370
},
373371
},
374372
]);
@@ -388,9 +386,7 @@ describe("agent plugin hooks", () => {
388386
hooks: {
389387
events() {
390388
return {
391-
"slack.channel.message.created": {
392-
deliveryTargets: [{ target: "source_thread" }],
393-
},
389+
"slack.channel.message.created": {},
394390
};
395391
},
396392
},
@@ -418,7 +414,6 @@ describe("agent plugin hooks", () => {
418414
return {
419415
"github.pull_request.comment.created": {
420416
defaultTools: { allow: ["github.comments.write"] },
421-
deliveryTargets: [{ target: "source_thread" }],
422417
} as any,
423418
};
424419
},

specs/event-prompts.md

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Define how Junior runs install-owned, version-controlled prompts in response to
1616
- Startup validation for event bindings.
1717
- Normalized inbound event envelopes, matching, idempotency, and dispatch.
1818
- Prompt compilation for event payloads and hydrated event context.
19-
- Runtime policy and delivery boundaries for event-triggered runs.
19+
- Runtime policy and Slack delivery boundaries for event-triggered runs.
2020

2121
## Non-Goals
2222

@@ -36,7 +36,7 @@ As an operator who customizes a Junior install, I want to add a Markdown file un
3636
Acceptance criteria:
3737

3838
1. The event binding lives in version control.
39-
2. Startup fails if the binding references an unknown event, unsupported context block, unsupported delivery target, unsupported selector, or empty prompt body.
39+
2. Startup fails if the binding references an unknown event, unsupported context block, unsupported selector, or empty prompt body.
4040
3. The plugin or platform integration supplies functionality and context; the install-owned Markdown body supplies the instruction.
4141
4. Unsupported frontmatter is rejected instead of ignored so typos do not create surprising behavior.
4242

@@ -78,7 +78,7 @@ As a trusted plugin author, I want to expose new event functionality without own
7878

7979
Acceptance criteria:
8080

81-
1. The plugin registers an event definition, supported selectors, context blocks, and delivery targets.
81+
1. The plugin registers an event definition, supported scope selectors, and context blocks.
8282
2. The plugin does not read `app/events/**/*.md` directly.
8383
3. The plugin does not choose which install bindings run.
8484
4. Raw provider payloads and credentials stay outside the model and binding frontmatter.
@@ -99,7 +99,7 @@ An **event run** is one core-created dispatched agent run for a matching `(bindi
9999

100100
### Plugin Event Definitions
101101

102-
Junior platform integrations may register built-in event definitions. Trusted plugins may register additional event definitions from app code. Declarative `plugin.yaml` manifests must not register event definitions because event definitions may own provider-specific normalization, filtering, hydration, and delivery behavior.
102+
Junior platform integrations may register built-in event definitions. Trusted plugins may register additional event definitions from app code. Declarative `plugin.yaml` manifests must not register event definitions because event definitions may own provider-specific normalization and hydration behavior.
103103

104104
The plugin-facing shape is:
105105

@@ -108,8 +108,6 @@ type EventDefinitions = Record<string, AgentEventDefinition>;
108108

109109
interface AgentEventDefinition {
110110
contextBlocks?: Record<string, AgentEventContextBlockDefinition>;
111-
deliveryTargets: AgentEventDeliveryTargetDefinition[];
112-
filterKeys?: string[];
113111
scopeKeys?: string[];
114112
}
115113
```
@@ -120,11 +118,9 @@ Event definitions own:
120118

121119
1. The normalized event payload shape.
122120
2. Supported binding `scope` keys.
123-
3. Supported binding `when` filter keys.
124-
4. Supported context block names and hydration/rendering functions.
125-
5. Supported delivery targets.
121+
3. Supported context block names and hydration/rendering functions.
126122

127-
The current implementation must reject unsupported event definition fields. Schema-backed value validation and event-level policy controls are future extensions that must wait for executor-level enforcement.
123+
Unsupported event definition fields must be rejected instead of treated as reserved behavior.
128124

129125
Plugins must not:
130126

@@ -155,9 +151,6 @@ scope:
155151
context:
156152
include:
157153
- source_message
158-
159-
delivery:
160-
target: channel
161154
---
162155

163156
A new root message was posted in the configured Slack channel.
@@ -176,9 +169,7 @@ Optional frontmatter fields:
176169

177170
- `enabled`: boolean, default `true`.
178171
- `scope`: event-definition-validated scope selector.
179-
- `when`: event-definition-validated declarative filters.
180172
- `context.include`: list of event-definition-supported context block names.
181-
- `delivery`: event-definition-supported delivery target.
182173

183174
The Markdown body must be non-empty after frontmatter removal. It is the event run instruction and must not contain secrets.
184175

@@ -194,7 +185,7 @@ Validation must reject:
194185
2. Duplicate binding ids across files.
195186
3. Binding ids that do not match the lowercase binding id format.
196187
4. Bindings that reference unknown events.
197-
5. `scope`, `when`, `context.include`, or `delivery` values unsupported by the referenced event definition.
188+
5. `scope` or `context.include` values unsupported by the referenced event definition.
198189
6. Markdown bodies that are empty after trimming.
199190
7. Frontmatter values that require code execution, environment expansion, or secret interpolation.
200191

@@ -259,9 +250,7 @@ The envelope scope is `{teamId, channelId}`. The payload includes `teamId`, `cha
259250

260251
### Matching And Idempotency
261252

262-
Core matches an event envelope against enabled bindings whose `event` matches the envelope event id. Matching is deterministic and uses only event-definition-owned `scope` and `when` filter logic.
263-
264-
V1 filters must stay declarative and bounded. Exact matches, set membership, and substring filters are acceptable. Arbitrary code, model calls, shell commands, and remote fetches are not filter mechanisms.
253+
Core matches an event envelope against enabled bindings whose `event` matches the envelope event id and whose `scope` selectors match the envelope scope. Matching is deterministic and must not use arbitrary code, model calls, shell commands, or remote fetches.
265254

266255
Multiple matching bindings are allowed. Each matching binding creates at most one event run for the source event. The idempotency key is:
267256

@@ -271,12 +260,10 @@ event:{binding_id}:{source_event_id}
271260

272261
Duplicate provider deliveries must return or recover the same event run for the same `(bindingId, sourceEventId)` pair.
273262

274-
Self-event suppression is required by default. Events authored by Junior's own bot, app, or delivery identity must not trigger event runs unless the event definition and binding explicitly allow that case.
263+
Self-event suppression is required. Events authored by Junior's own bot or app identity must not trigger event runs.
275264

276265
Directed user-message entry points preempt ambient event prompts. For example, a Slack root channel message that contains Junior's bot mention must route through the explicit mention path and must not also create a `slack.channel.message.created` event prompt run.
277266

278-
Event definitions may define per-event actor allowlists, actor denylists, and rate-limit controls. Core must enforce them before dispatch.
279-
280267
### Context Hydration
281268

282269
Bindings choose context blocks through `context.include`. Each included block must be supported by the event definition.
@@ -325,7 +312,7 @@ The compiled prompt must make these facts explicit:
325312

326313
### Dispatch And Delivery
327314

328-
Event runs use the core dispatch mechanism, but dispatch must be platform-neutral before event prompts can deliver outside Slack.
315+
Event runs use the core dispatch mechanism.
329316

330317
An event run dispatch record must store:
331318

@@ -339,12 +326,9 @@ An event run dispatch record must store:
339326
- safe correlation metadata
340327
- run mode `event_prompt`
341328

342-
Event run destinations are derived from the binding delivery target and event envelope. Delivery adapters own platform-specific final output behavior:
343-
344-
- Slack delivery posts to configured Slack conversations using Slack `mrkdwn`.
345-
- GitHub delivery posts to configured source issue, PR, or review-comment targets using GitHub-flavored Markdown.
329+
V1 event run destinations are derived from the normalized Slack event envelope. Slack delivery posts to the configured Slack conversation using Slack `mrkdwn`.
346330

347-
Delivery adapters must enforce final delivery idempotency using stable assistant message ids derived from the event run dispatch id.
331+
Delivery must enforce final Slack idempotency using stable assistant message ids derived from the event run dispatch id.
348332

349333
Event prompt runs may complete silently when the run succeeds with no assistant-visible text and no files. Silent success is an executor-level event-run behavior: Junior marks the dispatch completed and records the skipped delivery state without calling the platform delivery adapter. Ordinary user-message turns must continue to treat empty non-side-effect output as an execution failure.
350334

@@ -355,10 +339,10 @@ Event runs are autonomous system-actor runs.
355339
Core must enforce:
356340

357341
1. Disabled interactive auth continuation for system actors.
358-
2. No use of user OAuth tokens unless a future explicit credential-subject contract permits it for the event run.
342+
2. No use of user OAuth tokens during event runs.
359343
3. No schedule-management or event-binding-management tools during event runs.
360-
4. No Slack mutating tools during event runs until binding-level tool policy is implemented below the model; final event output goes through the delivery adapter.
361-
5. Rejection of unsupported binding and event-definition fields until new policy controls are implemented below the model.
344+
4. No Slack mutating tools during event runs; final event output goes through the delivery adapter.
345+
5. Rejection of unsupported binding and event-definition fields.
362346

363347
Prompt wording is not sufficient enforcement for tools, credentials, delivery, or repository mutation policy.
364348

@@ -460,8 +444,8 @@ Use integration tests for:
460444
- One event can intentionally match multiple bindings.
461445
- Self-authored provider events are suppressed by default.
462446
- Selected context blocks render as data and not as instruction text.
463-
- Event runs enforce tool availability and policy controls below the model.
464-
- Platform delivery posts to the configured target exactly once best effort.
447+
- Event runs enforce fixed tool availability below the model.
448+
- Slack delivery posts to the event envelope destination exactly once best effort.
465449
- Empty successful event prompt runs complete silently without platform delivery.
466450

467451
Use evals only when the behavior contract depends on model interpretation of the binding prompt or event context.

0 commit comments

Comments
 (0)