Skip to content

Commit 0e5ad97

Browse files
AlexsJonesclaude
andcommitted
feat(gate): add response gate hooks with manual approval flow
Adds a "response gate" capability to PostRun lifecycle hooks: a hook with gate: true holds the agent's output until it approves, rejects, or rewrites it. The IPC bridge suppresses both the completion event and stream chunks for gated runs so nothing leaks to channels or the feed before approval. The controller resolves the gate verdict via an annotation on the AgentRun. Adds a manual approval flow on top of this primitive: instances can be flagged with requireApproval, which auto-installs a sleep-forever gate hook plus the RBAC needed to patch the verdict. Operators approve or reject via new buttons on the runs list, run detail page, and a dashboard alert bar, or via the new POST /api/v1/runs/:name/gate-verdict endpoint. A warning toast fires when a run enters PostRunning awaiting approval, and the feed pane shows an "Awaiting approval" placeholder so users see the held state instead of stale "Agent is working" text. Includes documentation in concepts/lifecycle-hooks.md and Cypress coverage for the automated approve/reject/rewrite flows, the manual approval UX, the dashboard widget, the toast notification, and the instance toggle. Also fixes a flaky run-notifications test that depended on same-millisecond watermark timing and a fast LLM transition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d95a1b5 commit 0e5ad97

30 files changed

+1724
-54
lines changed

api/v1alpha1/agentrun_types.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ type AgentRunStatus struct {
285285
// +optional
286286
PostRunJobName string `json:"postRunJobName,omitempty"`
287287

288+
// GateVerdict records the outcome of the response gate hook, if configured.
289+
// One of: approved, rejected, rewritten, timeout, error, allowed-by-default.
290+
// Empty when no gate hook is configured.
291+
// +optional
292+
GateVerdict string `json:"gateVerdict,omitempty"`
293+
288294
// Conditions represent the latest available observations.
289295
// +optional
290296
Conditions []metav1.Condition `json:"conditions,omitempty"`
@@ -332,6 +338,13 @@ type LifecycleHookContainer struct {
332338
// Defaults to 5 minutes.
333339
// +optional
334340
Timeout *metav1.Duration `json:"timeout,omitempty"`
341+
342+
// Gate makes this hook a response gate. When true, agent output is held
343+
// until this hook approves, rejects, or rewrites it by patching the
344+
// annotation sympozium.ai/gate-verdict on the AgentRun CR.
345+
// Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
346+
// +optional
347+
Gate bool `json:"gate,omitempty"`
335348
}
336349

337350
// LifecycleHooks defines pre and post run hooks for an agent.
@@ -356,6 +369,14 @@ type LifecycleHooks struct {
356369
// or delete ConfigMaps, read Secrets).
357370
// +optional
358371
RBAC []RBACRule `json:"rbac,omitempty"`
372+
373+
// GateDefault controls what happens when a gate hook fails or times out.
374+
// "allow" passes the original result through unchanged; "block" replaces
375+
// it with a policy error message. Defaults to "block".
376+
// +kubebuilder:validation:Enum=allow;block
377+
// +kubebuilder:default="block"
378+
// +optional
379+
GateDefault string `json:"gateDefault,omitempty"`
359380
}
360381

361382
// +kubebuilder:object:root=true

charts/sympozium/crds/sympozium.ai_agentruns.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ spec:
139139
PreRun hooks execute as init containers before the agent starts.
140140
PostRun hooks execute in a follow-up Job after the agent completes.
141141
properties:
142+
gateDefault:
143+
default: block
144+
description: |-
145+
GateDefault controls what happens when a gate hook fails or times out.
146+
"allow" passes the original result through unchanged; "block" replaces
147+
it with a policy error message. Defaults to "block".
148+
enum:
149+
- allow
150+
- block
151+
type: string
142152
postRun:
143153
description: |-
144154
PostRun containers execute in a follow-up Job after the agent completes.
@@ -176,6 +186,13 @@ spec:
176186
- value
177187
type: object
178188
type: array
189+
gate:
190+
description: |-
191+
Gate makes this hook a response gate. When true, agent output is held
192+
until this hook approves, rejects, or rewrites it by patching the
193+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
194+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
195+
type: boolean
179196
image:
180197
description: Image is the container image.
181198
type: string
@@ -226,6 +243,13 @@ spec:
226243
- value
227244
type: object
228245
type: array
246+
gate:
247+
description: |-
248+
Gate makes this hook a response gate. When true, agent output is held
249+
until this hook approves, rejects, or rewrites it by patching the
250+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
251+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
252+
type: boolean
229253
image:
230254
description: Image is the container image.
231255
type: string
@@ -530,6 +554,12 @@ spec:
530554
description: ExitCode of the agent container.
531555
format: int32
532556
type: integer
557+
gateVerdict:
558+
description: |-
559+
GateVerdict records the outcome of the response gate hook, if configured.
560+
One of: approved, rejected, rewritten, timeout, error, allowed-by-default.
561+
Empty when no gate hook is configured.
562+
type: string
533563
jobName:
534564
description: JobName is the name of the Job created for this run.
535565
type: string

charts/sympozium/crds/sympozium.ai_personapacks.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ spec:
216216
Lifecycle defines pre and post run hooks for this persona.
217217
Propagated to the generated SympoziumInstance's AgentConfig.
218218
properties:
219+
gateDefault:
220+
default: block
221+
description: |-
222+
GateDefault controls what happens when a gate hook fails or times out.
223+
"allow" passes the original result through unchanged; "block" replaces
224+
it with a policy error message. Defaults to "block".
225+
enum:
226+
- allow
227+
- block
228+
type: string
219229
postRun:
220230
description: |-
221231
PostRun containers execute in a follow-up Job after the agent completes.
@@ -253,6 +263,13 @@ spec:
253263
- value
254264
type: object
255265
type: array
266+
gate:
267+
description: |-
268+
Gate makes this hook a response gate. When true, agent output is held
269+
until this hook approves, rejects, or rewrites it by patching the
270+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
271+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
272+
type: boolean
256273
image:
257274
description: Image is the container image.
258275
type: string
@@ -303,6 +320,13 @@ spec:
303320
- value
304321
type: object
305322
type: array
323+
gate:
324+
description: |-
325+
Gate makes this hook a response gate. When true, agent output is held
326+
until this hook approves, rejects, or rewrites it by patching the
327+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
328+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
329+
type: boolean
306330
image:
307331
description: Image is the container image.
308332
type: string

charts/sympozium/crds/sympozium.ai_sympoziuminstances.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ spec:
114114
Lifecycle defines pre and post run hooks for agent runs.
115115
Propagated to AgentRunSpec.Lifecycle when runs are created.
116116
properties:
117+
gateDefault:
118+
default: block
119+
description: |-
120+
GateDefault controls what happens when a gate hook fails or times out.
121+
"allow" passes the original result through unchanged; "block" replaces
122+
it with a policy error message. Defaults to "block".
123+
enum:
124+
- allow
125+
- block
126+
type: string
117127
postRun:
118128
description: |-
119129
PostRun containers execute in a follow-up Job after the agent completes.
@@ -151,6 +161,13 @@ spec:
151161
- value
152162
type: object
153163
type: array
164+
gate:
165+
description: |-
166+
Gate makes this hook a response gate. When true, agent output is held
167+
until this hook approves, rejects, or rewrites it by patching the
168+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
169+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
170+
type: boolean
154171
image:
155172
description: Image is the container image.
156173
type: string
@@ -201,6 +218,13 @@ spec:
201218
- value
202219
type: object
203220
type: array
221+
gate:
222+
description: |-
223+
Gate makes this hook a response gate. When true, agent output is held
224+
until this hook approves, rejects, or rewrites it by patching the
225+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
226+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
227+
type: boolean
204228
image:
205229
description: Image is the container image.
206230
type: string

config/crd/bases/sympozium.ai_agentruns.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ spec:
139139
PreRun hooks execute as init containers before the agent starts.
140140
PostRun hooks execute in a follow-up Job after the agent completes.
141141
properties:
142+
gateDefault:
143+
default: block
144+
description: |-
145+
GateDefault controls what happens when a gate hook fails or times out.
146+
"allow" passes the original result through unchanged; "block" replaces
147+
it with a policy error message. Defaults to "block".
148+
enum:
149+
- allow
150+
- block
151+
type: string
142152
postRun:
143153
description: |-
144154
PostRun containers execute in a follow-up Job after the agent completes.
@@ -176,6 +186,13 @@ spec:
176186
- value
177187
type: object
178188
type: array
189+
gate:
190+
description: |-
191+
Gate makes this hook a response gate. When true, agent output is held
192+
until this hook approves, rejects, or rewrites it by patching the
193+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
194+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
195+
type: boolean
179196
image:
180197
description: Image is the container image.
181198
type: string
@@ -226,6 +243,13 @@ spec:
226243
- value
227244
type: object
228245
type: array
246+
gate:
247+
description: |-
248+
Gate makes this hook a response gate. When true, agent output is held
249+
until this hook approves, rejects, or rewrites it by patching the
250+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
251+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
252+
type: boolean
229253
image:
230254
description: Image is the container image.
231255
type: string
@@ -530,6 +554,12 @@ spec:
530554
description: ExitCode of the agent container.
531555
format: int32
532556
type: integer
557+
gateVerdict:
558+
description: |-
559+
GateVerdict records the outcome of the response gate hook, if configured.
560+
One of: approved, rejected, rewritten, timeout, error, allowed-by-default.
561+
Empty when no gate hook is configured.
562+
type: string
533563
jobName:
534564
description: JobName is the name of the Job created for this run.
535565
type: string

config/crd/bases/sympozium.ai_personapacks.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ spec:
216216
Lifecycle defines pre and post run hooks for this persona.
217217
Propagated to the generated SympoziumInstance's AgentConfig.
218218
properties:
219+
gateDefault:
220+
default: block
221+
description: |-
222+
GateDefault controls what happens when a gate hook fails or times out.
223+
"allow" passes the original result through unchanged; "block" replaces
224+
it with a policy error message. Defaults to "block".
225+
enum:
226+
- allow
227+
- block
228+
type: string
219229
postRun:
220230
description: |-
221231
PostRun containers execute in a follow-up Job after the agent completes.
@@ -253,6 +263,13 @@ spec:
253263
- value
254264
type: object
255265
type: array
266+
gate:
267+
description: |-
268+
Gate makes this hook a response gate. When true, agent output is held
269+
until this hook approves, rejects, or rewrites it by patching the
270+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
271+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
272+
type: boolean
256273
image:
257274
description: Image is the container image.
258275
type: string
@@ -303,6 +320,13 @@ spec:
303320
- value
304321
type: object
305322
type: array
323+
gate:
324+
description: |-
325+
Gate makes this hook a response gate. When true, agent output is held
326+
until this hook approves, rejects, or rewrites it by patching the
327+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
328+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
329+
type: boolean
306330
image:
307331
description: Image is the container image.
308332
type: string

config/crd/bases/sympozium.ai_sympoziuminstances.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ spec:
114114
Lifecycle defines pre and post run hooks for agent runs.
115115
Propagated to AgentRunSpec.Lifecycle when runs are created.
116116
properties:
117+
gateDefault:
118+
default: block
119+
description: |-
120+
GateDefault controls what happens when a gate hook fails or times out.
121+
"allow" passes the original result through unchanged; "block" replaces
122+
it with a policy error message. Defaults to "block".
123+
enum:
124+
- allow
125+
- block
126+
type: string
117127
postRun:
118128
description: |-
119129
PostRun containers execute in a follow-up Job after the agent completes.
@@ -151,6 +161,13 @@ spec:
151161
- value
152162
type: object
153163
type: array
164+
gate:
165+
description: |-
166+
Gate makes this hook a response gate. When true, agent output is held
167+
until this hook approves, rejects, or rewrites it by patching the
168+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
169+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
170+
type: boolean
154171
image:
155172
description: Image is the container image.
156173
type: string
@@ -201,6 +218,13 @@ spec:
201218
- value
202219
type: object
203220
type: array
221+
gate:
222+
description: |-
223+
Gate makes this hook a response gate. When true, agent output is held
224+
until this hook approves, rejects, or rewrites it by patching the
225+
annotation sympozium.ai/gate-verdict on the AgentRun CR.
226+
Only valid on PostRun hooks. At most one PostRun hook may set gate: true.
227+
type: boolean
204228
image:
205229
description: Image is the container image.
206230
type: string

0 commit comments

Comments
 (0)