Skip to content

Commit c5df91f

Browse files
supreme-gg-ggEItanyaclaude
authored
feat: human in the loop (confirmation) (kagent-dev#1398)
Supports confirmation flow for HITL (single and parallel tool use). Flow documented in `docs/architecture/human-in-the-loop.md` Todo: custom payload so the agent can invoke tools that require user input --------- Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io> Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io> Co-authored-by: Eitan Yarmush <eitan.yarmush@solo.io> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 05d0bde commit c5df91f

28 files changed

Lines changed: 2244 additions & 685 deletions

File tree

docs/architecture/human-in-the-loop.md

Lines changed: 487 additions & 0 deletions
Large diffs are not rendered by default.

go/api/v1alpha2/agent_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ func (s *Tool) ResolveHeaders(ctx context.Context, client client.Client, namespa
377377
return result, nil
378378
}
379379

380+
// +kubebuilder:validation:XValidation:message="each RequireApproval entry must also appear in ToolNames",rule="!has(self.requireApproval) || self.requireApproval.all(x, has(self.toolNames) && x in self.toolNames)"
380381
type McpServerTool struct {
381382
// The reference to the ToolServer that provides the tool.
382383
// +optional
@@ -385,8 +386,17 @@ type McpServerTool struct {
385386
// The names of the tools to be provided by the ToolServer
386387
// For a list of all the tools provided by the server,
387388
// the client can query the status of the ToolServer object after it has been created
389+
// +kubebuilder:validation:MaxItems=50
388390
ToolNames []string `json:"toolNames,omitempty"`
389391

392+
// RequireApproval lists tool names that require human approval before
393+
// execution. Each name must also appear in ToolNames. When a tool in
394+
// this list is invoked by the agent, execution pauses and the user is
395+
// prompted to approve or reject the call.
396+
// +optional
397+
// +kubebuilder:validation:MaxItems=50
398+
RequireApproval []string `json:"requireApproval,omitempty"`
399+
390400
// AllowedHeaders specifies which headers from the A2A request should be
391401
// propagated to MCP tool calls. Header names are case-insensitive.
392402
//

go/api/v1alpha2/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/config/crd/bases/kagent.dev_agents.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10091,17 +10091,33 @@ spec:
1009110091
type: string
1009210092
namespace:
1009310093
type: string
10094+
requireApproval:
10095+
description: |-
10096+
RequireApproval lists tool names that require human approval before
10097+
execution. Each name must also appear in ToolNames. When a tool in
10098+
this list is invoked by the agent, execution pauses and the user is
10099+
prompted to approve or reject the call.
10100+
items:
10101+
type: string
10102+
maxItems: 50
10103+
type: array
1009410104
toolNames:
1009510105
description: |-
1009610106
The names of the tools to be provided by the ToolServer
1009710107
For a list of all the tools provided by the server,
1009810108
the client can query the status of the ToolServer object after it has been created
1009910109
items:
1010010110
type: string
10111+
maxItems: 50
1010110112
type: array
1010210113
required:
1010310114
- name
1010410115
type: object
10116+
x-kubernetes-validations:
10117+
- message: each RequireApproval entry must also appear in
10118+
ToolNames
10119+
rule: '!has(self.requireApproval) || self.requireApproval.all(x,
10120+
has(self.toolNames) && x in self.toolNames)'
1010510121
type:
1010610122
allOf:
1010710123
- enum:

go/internal/controller/translator/agent/adk_api_translator.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,19 +1337,21 @@ func (a *adkApiTranslator) translateRemoteMCPServerTarget(ctx context.Context, a
13371337
return err
13381338
}
13391339
agent.SseTools = append(agent.SseTools, adk.SseMcpServerConfig{
1340-
Params: *tool,
1341-
Tools: mcpServerTool.ToolNames,
1342-
AllowedHeaders: mcpServerTool.AllowedHeaders,
1340+
Params: *tool,
1341+
Tools: mcpServerTool.ToolNames,
1342+
AllowedHeaders: mcpServerTool.AllowedHeaders,
1343+
RequireApproval: mcpServerTool.RequireApproval,
13431344
})
13441345
default:
13451346
tool, err := a.translateStreamableHttpTool(ctx, remoteMcpServer, agentHeaders, proxyURL)
13461347
if err != nil {
13471348
return err
13481349
}
13491350
agent.HttpTools = append(agent.HttpTools, adk.HttpMcpServerConfig{
1350-
Params: *tool,
1351-
Tools: mcpServerTool.ToolNames,
1352-
AllowedHeaders: mcpServerTool.AllowedHeaders,
1351+
Params: *tool,
1352+
Tools: mcpServerTool.ToolNames,
1353+
AllowedHeaders: mcpServerTool.AllowedHeaders,
1354+
RequireApproval: mcpServerTool.RequireApproval,
13531355
})
13541356
}
13551357
return nil
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
operation: translateAgent
2+
targetObject: agent
3+
namespace: test
4+
objects:
5+
- apiVersion: v1
6+
kind: Secret
7+
metadata:
8+
name: openai-secret
9+
namespace: test
10+
data:
11+
api-key: c2stdGVzdC1hcGkta2V5 # base64 encoded "sk-test-api-key"
12+
- apiVersion: kagent.dev/v1alpha2
13+
kind: ModelConfig
14+
metadata:
15+
name: test-model
16+
namespace: test
17+
spec:
18+
provider: OpenAI
19+
model: gpt-4o
20+
apiKeySecret: openai-secret
21+
apiKeySecretKey: api-key
22+
- apiVersion: kagent.dev/v1alpha2
23+
kind: Agent
24+
metadata:
25+
name: agent
26+
namespace: test
27+
spec:
28+
type: Declarative
29+
declarative:
30+
description: A file management agent
31+
systemMessage: You help users manage files.
32+
modelConfig: test-model
33+
tools:
34+
- type: MCPServer
35+
mcpServer:
36+
name: toolserver
37+
kind: Service
38+
toolNames:
39+
- read_file
40+
- write_file
41+
- delete_file
42+
requireApproval:
43+
- delete_file
44+
- write_file
45+
- apiVersion: v1
46+
kind: Service
47+
metadata:
48+
name: toolserver
49+
annotations:
50+
kagent.dev/mcp-service-path: "/mcp"
51+
namespace: test
52+
spec:
53+
ports:
54+
- name: mcp
55+
port: 8084
56+
targetPort: 8084
57+
protocol: TCP
58+
appProtocol: mcp

0 commit comments

Comments
 (0)