@openlucaskaka/kagent 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +353 -0
  2. package/npm/bin/kagent-serve.js +6 -0
  3. package/npm/bin/kagent.js +6 -0
  4. package/npm/lib/App.js +524 -0
  5. package/npm/lib/app-state.js +224 -0
  6. package/npm/lib/approval-choice.js +25 -0
  7. package/npm/lib/commands.js +59 -0
  8. package/npm/lib/editor.js +188 -0
  9. package/npm/lib/ink-runner.js +41 -0
  10. package/npm/lib/kagent-home.js +39 -0
  11. package/npm/lib/launcher.js +221 -0
  12. package/npm/lib/protocol.js +33 -0
  13. package/npm/lib/provider-setup.js +139 -0
  14. package/npm/lib/python-runner.js +892 -0
  15. package/npm/lib/runtime-client.js +390 -0
  16. package/npm/lib/terminal-input.js +127 -0
  17. package/npm/lib/terminal-text.js +19 -0
  18. package/npm/lib/terminal-width.js +14 -0
  19. package/npm/lib/transcript.js +227 -0
  20. package/npm/lib/ui-components.js +247 -0
  21. package/npm/lib/update-manager.js +334 -0
  22. package/package.json +39 -0
  23. package/pyproject.toml +55 -0
  24. package/src/kagent/__init__.py +90 -0
  25. package/src/kagent/cli/__init__.py +5 -0
  26. package/src/kagent/cli/__main__.py +6 -0
  27. package/src/kagent/cli/commands.py +112 -0
  28. package/src/kagent/cli/conversation.py +127 -0
  29. package/src/kagent/cli/interactive.py +841 -0
  30. package/src/kagent/cli/main.py +685 -0
  31. package/src/kagent/cli/memory.py +460 -0
  32. package/src/kagent/cli/pending_approval.py +169 -0
  33. package/src/kagent/cli/provider.py +27 -0
  34. package/src/kagent/cli/session_commands.py +401 -0
  35. package/src/kagent/cli/stdio_runtime.py +784 -0
  36. package/src/kagent/cli/trace.py +67 -0
  37. package/src/kagent/cli/ui.py +931 -0
  38. package/src/kagent/core/__init__.py +0 -0
  39. package/src/kagent/core/agent.py +296 -0
  40. package/src/kagent/core/faults.py +11 -0
  41. package/src/kagent/core/invariants.py +47 -0
  42. package/src/kagent/core/normalization.py +81 -0
  43. package/src/kagent/core/planning.py +48 -0
  44. package/src/kagent/core/state.py +73 -0
  45. package/src/kagent/core/summary.py +64 -0
  46. package/src/kagent/core/tools.py +196 -0
  47. package/src/kagent/core/trace.py +57 -0
  48. package/src/kagent/eval/__init__.py +11 -0
  49. package/src/kagent/eval/cases.py +229 -0
  50. package/src/kagent/eval/evaluator.py +216 -0
  51. package/src/kagent/integrations/__init__.py +3 -0
  52. package/src/kagent/integrations/audit.py +95 -0
  53. package/src/kagent/integrations/backends.py +131 -0
  54. package/src/kagent/integrations/memory.py +301 -0
  55. package/src/kagent/ops/__init__.py +0 -0
  56. package/src/kagent/ops/batch.py +156 -0
  57. package/src/kagent/ops/doctor.py +255 -0
  58. package/src/kagent/ops/metrics.py +214 -0
  59. package/src/kagent/ops/release_evidence.py +877 -0
  60. package/src/kagent/ops/release_manifest.py +142 -0
  61. package/src/kagent/ops/trace_replay.py +285 -0
  62. package/src/kagent/providers/__init__.py +7 -0
  63. package/src/kagent/providers/embeddings.py +187 -0
  64. package/src/kagent/providers/llm.py +770 -0
  65. package/src/kagent/py.typed +1 -0
  66. package/src/kagent/runtime/__init__.py +28 -0
  67. package/src/kagent/runtime/action_graph.py +543 -0
  68. package/src/kagent/runtime/agent.py +2089 -0
  69. package/src/kagent/runtime/approval.py +64 -0
  70. package/src/kagent/runtime/cancellation.py +64 -0
  71. package/src/kagent/runtime/checkpoint_state.py +146 -0
  72. package/src/kagent/runtime/checkpoint_storage.py +270 -0
  73. package/src/kagent/runtime/context.py +65 -0
  74. package/src/kagent/runtime/file_transaction.py +195 -0
  75. package/src/kagent/runtime/hooks.py +74 -0
  76. package/src/kagent/runtime/metadata.py +116 -0
  77. package/src/kagent/runtime/patch_checkpoints.py +385 -0
  78. package/src/kagent/runtime/policy.py +50 -0
  79. package/src/kagent/runtime/presentation.py +205 -0
  80. package/src/kagent/runtime/redaction.py +130 -0
  81. package/src/kagent/runtime/sandbox.py +331 -0
  82. package/src/kagent/runtime/skills.py +66 -0
  83. package/src/kagent/runtime/steering.py +56 -0
  84. package/src/kagent/runtime/steps.py +255 -0
  85. package/src/kagent/runtime/task_state.py +40 -0
  86. package/src/kagent/runtime/tools.py +3532 -0
  87. package/src/kagent/runtime/types.py +240 -0
  88. package/src/kagent/runtime/workspace.py +597 -0
  89. package/src/kagent/service/__init__.py +26 -0
  90. package/src/kagent/service/__main__.py +6 -0
  91. package/src/kagent/service/active_runs.py +178 -0
  92. package/src/kagent/service/cli.py +737 -0
  93. package/src/kagent/service/contract.py +2571 -0
  94. package/src/kagent/service/errors.py +71 -0
  95. package/src/kagent/service/idempotency.py +584 -0
  96. package/src/kagent/service/router.py +884 -0
  97. package/src/kagent/service/run.py +150 -0
  98. package/src/kagent/service/runtime.py +1915 -0
  99. package/src/kagent/service/runtime_approval.py +20 -0
  100. package/src/kagent/service/runtime_cancel.py +192 -0
  101. package/src/kagent/service/runtime_lifecycle.py +229 -0
  102. package/src/kagent/service/runtime_metadata.py +21 -0
  103. package/src/kagent/service/runtime_policy.py +115 -0
  104. package/src/kagent/service/runtime_recovery.py +573 -0
  105. package/src/kagent/service/runtime_resume.py +382 -0
  106. package/src/kagent/service/runtime_resume_claim.py +116 -0
  107. package/src/kagent/service/runtime_run.py +350 -0
  108. package/src/kagent/service/runtime_status.py +2007 -0
  109. package/src/kagent/service/safety.py +139 -0
  110. package/src/kagent/service/server.py +114 -0
  111. package/src/kagent/service/status.py +233 -0
  112. package/src/kagent/service/trace_store.py +322 -0
  113. package/src/kagent/service/transport.py +24 -0
  114. package/src/kagent/utils/__init__.py +0 -0
  115. package/src/kagent/utils/config_validation.py +21 -0
  116. package/src/kagent/utils/json_output.py +23 -0
  117. package/src/kagent/utils/paths.py +473 -0
@@ -0,0 +1,196 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import Callable, Dict, List, Match, Optional, Pattern
6
+
7
+ ToolResult = Dict[str, str]
8
+ ToolHandler = Callable[[Match[str]], ToolResult]
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ToolSpec:
13
+ name: str
14
+ command: str
15
+ description: str
16
+ example: str
17
+ pattern: Pattern[str]
18
+ handler: ToolHandler
19
+
20
+
21
+ def execute_step(step: str) -> Optional[ToolResult]:
22
+ for tool in TOOLS:
23
+ match = tool.pattern.fullmatch(step)
24
+ if match:
25
+ return tool.handler(match)
26
+ return None
27
+
28
+
29
+ def registered_tool_names() -> List[str]:
30
+ return [tool.name for tool in TOOLS]
31
+
32
+
33
+ def registered_tool_metadata() -> List[Dict[str, str]]:
34
+ return [
35
+ {
36
+ "name": tool.name,
37
+ "command": tool.command,
38
+ "description": tool.description,
39
+ "example": tool.example,
40
+ }
41
+ for tool in TOOLS
42
+ ]
43
+
44
+
45
+ def matching_tool_name(step: str) -> Optional[str]:
46
+ for tool in TOOLS:
47
+ if tool.pattern.fullmatch(step):
48
+ return tool.name
49
+ return None
50
+
51
+
52
+ def expected_answer(step: Optional[str]) -> Optional[str]:
53
+ if step is None:
54
+ return None
55
+
56
+ execution = execute_step(step)
57
+ if execution is None:
58
+ return None
59
+ return execution["output"]
60
+
61
+
62
+ def _calculate(match: Match[str]) -> ToolResult:
63
+ left, right = match.groups()
64
+ return _numeric_result("calculate_sum", match, int(left) + int(right))
65
+
66
+
67
+ def _multiply(match: Match[str]) -> ToolResult:
68
+ left, right = match.groups()
69
+ return _numeric_result("multiply_numbers", match, int(left) * int(right))
70
+
71
+
72
+ def _subtract(match: Match[str]) -> ToolResult:
73
+ left, right = match.groups()
74
+ return _numeric_result("subtract_numbers", match, int(left) - int(right))
75
+
76
+
77
+ def _numeric_result(tool: str, match: Match[str], output: int) -> ToolResult:
78
+ return {
79
+ "tool": tool,
80
+ "input": match.group(0),
81
+ "output": str(output),
82
+ }
83
+
84
+
85
+ def _count_words(match: Match[str]) -> ToolResult:
86
+ text = match.group(1)
87
+ return _text_result("count_words", text, str(len([word for word in text.split() if word])))
88
+
89
+
90
+ def _lowercase_text(match: Match[str]) -> ToolResult:
91
+ text = match.group(1)
92
+ return _text_result("lowercase_text", text, text.lower())
93
+
94
+
95
+ def _uppercase_text(match: Match[str]) -> ToolResult:
96
+ text = match.group(1)
97
+ return _text_result("uppercase_text", text, text.upper())
98
+
99
+
100
+ def _reverse_text(match: Match[str]) -> ToolResult:
101
+ text = match.group(1)
102
+ return _text_result("reverse_text", text, text[::-1])
103
+
104
+
105
+ def _trim_text(match: Match[str]) -> ToolResult:
106
+ text = match.group(1)
107
+ return _text_result("trim_text", text, text.strip())
108
+
109
+
110
+ def _text_result(tool: str, text: str, output: str) -> ToolResult:
111
+ return {
112
+ "tool": tool,
113
+ "input": text,
114
+ "output": output,
115
+ }
116
+
117
+
118
+ def _text_tool(
119
+ name: str,
120
+ command: str,
121
+ description: str,
122
+ example: str,
123
+ handler: ToolHandler,
124
+ ) -> ToolSpec:
125
+ return ToolSpec(
126
+ name=name,
127
+ command=f"{command} text in 'text'",
128
+ description=description,
129
+ example=example,
130
+ pattern=re.compile(rf"{command}\s+text\s+in\s+['\"](.*)['\"]"),
131
+ handler=handler,
132
+ )
133
+
134
+
135
+ TOOLS = [
136
+ ToolSpec(
137
+ name="calculate_sum",
138
+ command="calculate N + M",
139
+ description="Add two integers.",
140
+ example="calculate 2 + 3",
141
+ pattern=re.compile(r"calculate\s+(-?\d+)\s*\+\s*(-?\d+)"),
142
+ handler=_calculate,
143
+ ),
144
+ ToolSpec(
145
+ name="count_words",
146
+ command="count words in 'text'",
147
+ description="Count whitespace-separated words in quoted text.",
148
+ example="count words in 'ship small reliable agents'",
149
+ pattern=re.compile(r"count\s+words\s+in\s+['\"](.*)['\"]"),
150
+ handler=_count_words,
151
+ ),
152
+ _text_tool(
153
+ "lowercase_text",
154
+ "lowercase",
155
+ "Convert quoted text to lowercase.",
156
+ "lowercase text in 'Agent Loop'",
157
+ _lowercase_text,
158
+ ),
159
+ ToolSpec(
160
+ name="multiply_numbers",
161
+ command="multiply N * M",
162
+ description="Multiply two integers.",
163
+ example="multiply 6 * 7",
164
+ pattern=re.compile(r"multiply\s+(-?\d+)\s*\*\s*(-?\d+)"),
165
+ handler=_multiply,
166
+ ),
167
+ _text_tool(
168
+ "reverse_text",
169
+ "reverse",
170
+ "Reverse quoted text while preserving character case.",
171
+ "reverse text in 'Agent Loop'",
172
+ _reverse_text,
173
+ ),
174
+ ToolSpec(
175
+ name="subtract_numbers",
176
+ command="subtract N - M",
177
+ description="Subtract the right integer from the left integer.",
178
+ example="subtract 10 - 4",
179
+ pattern=re.compile(r"subtract\s+(-?\d+)\s*-\s*(-?\d+)"),
180
+ handler=_subtract,
181
+ ),
182
+ _text_tool(
183
+ "trim_text",
184
+ "trim",
185
+ "Remove surrounding whitespace from quoted text.",
186
+ "trim text in ' agent loop '",
187
+ _trim_text,
188
+ ),
189
+ _text_tool(
190
+ "uppercase_text",
191
+ "uppercase",
192
+ "Convert quoted text to uppercase.",
193
+ "uppercase text in 'agent loop'",
194
+ _uppercase_text,
195
+ ),
196
+ ]
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from kagent.core.state import AgentState
4
+
5
+
6
+ def record_node_event(state: AgentState, node: str) -> None:
7
+ state["events"].append(
8
+ {
9
+ "node": node,
10
+ "status": state["status"].value,
11
+ "retry_count": state["retry_count"],
12
+ "step_retry_count": state.get("step_retry_count", 0),
13
+ }
14
+ )
15
+
16
+
17
+ def record_execution_attempt(
18
+ state: AgentState,
19
+ step: str,
20
+ tool: str,
21
+ output: str,
22
+ fault: str,
23
+ ) -> None:
24
+ state["execution_attempts"].append(
25
+ {
26
+ "step": step,
27
+ "tool": tool,
28
+ "output": output,
29
+ "fault": fault,
30
+ "retry": str(state["step_retry_count"]),
31
+ }
32
+ )
33
+
34
+
35
+ def copy_agent_state(state: AgentState) -> AgentState:
36
+ copied = dict(state)
37
+ copied["plan"] = list(state.get("plan", []))
38
+ copied["plan_validations"] = [
39
+ dict(item) for item in state.get("plan_validations", [])
40
+ ]
41
+ copied["step_results"] = list(state.get("step_results", []))
42
+ copied["tool_calls"] = [dict(item) for item in state.get("tool_calls", [])]
43
+ copied["execution_attempts"] = [
44
+ dict(item) for item in state.get("execution_attempts", [])
45
+ ]
46
+ copied["errors"] = list(state.get("errors", []))
47
+ copied["events"] = [dict(item) for item in state.get("events", [])]
48
+ copied["reflection_notes"] = list(state.get("reflection_notes", []))
49
+ copied["reflections"] = [dict(item) for item in state.get("reflections", [])]
50
+ copied["verification_results"] = [
51
+ dict(item) for item in state.get("verification_results", [])
52
+ ]
53
+ copied["fault_plan"] = {
54
+ key: list(value) for key, value in state.get("fault_plan", {}).items()
55
+ }
56
+ copied["fault_counters"] = dict(state.get("fault_counters", {}))
57
+ return copied
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = ["evaluate_agent", "registered_evaluation_cases"]
4
+
5
+
6
+ def __getattr__(name: str):
7
+ if name in __all__:
8
+ from kagent.eval import evaluator
9
+
10
+ return getattr(evaluator, name)
11
+ raise AttributeError(name)
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, Dict, List
5
+
6
+ CaseCheck = Callable[[Dict[str, Any]], bool]
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class EvaluationCase:
11
+ name: str
12
+ category: str
13
+ run: Callable[[], Dict[str, Any]]
14
+ check: CaseCheck
15
+
16
+
17
+ def build_evaluation_cases(AgentConfig, AgentStatus, run_agent) -> List[EvaluationCase]:
18
+ return [
19
+ EvaluationCase(
20
+ "multi_step_success",
21
+ "workflow",
22
+ lambda: run_agent(
23
+ "calculate 2 + 3 then count words in 'ship small reliable agents'",
24
+ config=AgentConfig(max_steps=4, max_retries=1),
25
+ ),
26
+ lambda result: (
27
+ result["status"] == AgentStatus.DONE
28
+ and result["step_results"] == ["5", "4"]
29
+ and result["retry_count"] == 0
30
+ and [item["passed"] for item in result["verification_results"]]
31
+ == ["true", "true"]
32
+ and [item["tool"] for item in result["plan_validations"]]
33
+ == ["calculate_sum", "count_words"]
34
+ ),
35
+ ),
36
+ EvaluationCase(
37
+ "self_correction_success",
38
+ "recovery",
39
+ lambda: run_agent(
40
+ "calculate 2 + 3",
41
+ config=AgentConfig(max_steps=3, max_retries=2),
42
+ fault_plan={"calculate 2 + 3": ["wrong-answer"]},
43
+ ),
44
+ lambda result: (
45
+ result["status"] == AgentStatus.DONE
46
+ and result["answer"] == "5"
47
+ and result["retry_count"] == 1
48
+ and result["reflections"][-1]["actual"] == "6"
49
+ and result["reflections"][-1]["expected"] == "5"
50
+ and [item["passed"] for item in result["verification_results"]]
51
+ == ["false", "true"]
52
+ ),
53
+ ),
54
+ EvaluationCase(
55
+ "retry_budget_failure",
56
+ "failure",
57
+ lambda: run_agent(
58
+ "calculate 2 + 3",
59
+ config=AgentConfig(max_steps=3, max_retries=1),
60
+ fault_plan={"calculate 2 + 3": ["wrong-answer", "wrong-answer"]},
61
+ ),
62
+ lambda result: (
63
+ result["status"] == AgentStatus.FAILED
64
+ and result["errors"][-1] == "retry budget exhausted"
65
+ ),
66
+ ),
67
+ EvaluationCase(
68
+ "uppercase_tool_success",
69
+ "tool",
70
+ lambda: run_agent(
71
+ "uppercase text in 'agent loop'",
72
+ config=AgentConfig(max_steps=3, max_retries=1),
73
+ ),
74
+ lambda result: (
75
+ result["status"] == AgentStatus.DONE
76
+ and result["answer"] == "AGENT LOOP"
77
+ and result["tool_calls"][-1]["tool"] == "uppercase_text"
78
+ ),
79
+ ),
80
+ EvaluationCase(
81
+ "unsupported_plan_failure",
82
+ "failure",
83
+ lambda: run_agent(
84
+ "calculate 1 + 1 then search the web",
85
+ config=AgentConfig(max_steps=4, max_retries=2),
86
+ ),
87
+ lambda result: (
88
+ result["status"] == AgentStatus.FAILED
89
+ and result["errors"] == ["unsupported planned step: search the web"]
90
+ and result["retry_count"] == 0
91
+ and [event["node"] for event in result["events"]] == ["planner"]
92
+ and result["plan_validations"][-1]["supported"] == "false"
93
+ ),
94
+ ),
95
+ EvaluationCase(
96
+ "empty_answer_recovery",
97
+ "recovery",
98
+ lambda: run_agent(
99
+ "uppercase text in 'agent loop'",
100
+ config=AgentConfig(max_steps=4, max_retries=2),
101
+ fault_plan={"uppercase text in 'agent loop'": ["empty-answer"]},
102
+ ),
103
+ lambda result: (
104
+ result["status"] == AgentStatus.DONE
105
+ and result["answer"] == "AGENT LOOP"
106
+ and result["reflections"][-1]["actual"] == ""
107
+ and result["reflections"][-1]["reason"] == "answer was empty"
108
+ and [item["passed"] for item in result["verification_results"]]
109
+ == ["false", "true"]
110
+ and [item["fault"] for item in result["execution_attempts"]]
111
+ == ["empty-answer", ""]
112
+ and result["execution_attempts"][-1]["tool"] == "uppercase_text"
113
+ ),
114
+ ),
115
+ EvaluationCase(
116
+ "per_step_retry_budget_success",
117
+ "recovery",
118
+ lambda: run_agent(
119
+ "calculate 2 + 3 then uppercase text in 'agent loop'",
120
+ config=AgentConfig(max_steps=4, max_retries=1),
121
+ fault_plan={
122
+ "calculate 2 + 3": ["wrong-answer"],
123
+ "uppercase text in 'agent loop'": ["empty-answer"],
124
+ },
125
+ ),
126
+ lambda result: (
127
+ result["status"] == AgentStatus.DONE
128
+ and result["retry_count"] == 2
129
+ and result["step_retry_count"] == 0
130
+ and result["step_results"] == ["5", "AGENT LOOP"]
131
+ and [item["retry"] for item in result["verification_results"]]
132
+ == ["0", "1", "0", "1"]
133
+ ),
134
+ ),
135
+ EvaluationCase(
136
+ "multiplication_tool_success",
137
+ "tool",
138
+ lambda: run_agent(
139
+ "multiply 6 * 7",
140
+ config=AgentConfig(max_steps=3, max_retries=1),
141
+ ),
142
+ lambda result: (
143
+ result["status"] == AgentStatus.DONE
144
+ and result["answer"] == "42"
145
+ and result["tool_calls"][-1]["tool"] == "multiply_numbers"
146
+ ),
147
+ ),
148
+ EvaluationCase(
149
+ "reverse_text_tool_success",
150
+ "tool",
151
+ lambda: run_agent(
152
+ "Reverse Text in 'Agent Loop'",
153
+ config=AgentConfig(max_steps=3, max_retries=1),
154
+ ),
155
+ lambda result: (
156
+ result["status"] == AgentStatus.DONE
157
+ and result["answer"] == "pooL tnegA"
158
+ and result["tool_calls"][-1]["tool"] == "reverse_text"
159
+ ),
160
+ ),
161
+ EvaluationCase(
162
+ "lowercase_text_tool_success",
163
+ "tool",
164
+ lambda: run_agent(
165
+ "Lowercase Text in 'Agent Loop'",
166
+ config=AgentConfig(max_steps=3, max_retries=1),
167
+ ),
168
+ lambda result: (
169
+ result["status"] == AgentStatus.DONE
170
+ and result["answer"] == "agent loop"
171
+ and result["tool_calls"][-1]["tool"] == "lowercase_text"
172
+ ),
173
+ ),
174
+ EvaluationCase(
175
+ "trim_text_tool_success",
176
+ "tool",
177
+ lambda: run_agent(
178
+ "Trim Text in ' Agent Loop '",
179
+ config=AgentConfig(max_steps=3, max_retries=1),
180
+ ),
181
+ lambda result: (
182
+ result["status"] == AgentStatus.DONE
183
+ and result["answer"] == "Agent Loop"
184
+ and result["tool_calls"][-1]["tool"] == "trim_text"
185
+ ),
186
+ ),
187
+ EvaluationCase(
188
+ "subtraction_tool_success",
189
+ "tool",
190
+ lambda: run_agent(
191
+ "subtract 10 - 4",
192
+ config=AgentConfig(max_steps=3, max_retries=1),
193
+ ),
194
+ lambda result: (
195
+ result["status"] == AgentStatus.DONE
196
+ and result["answer"] == "6"
197
+ and result["tool_calls"][-1]["tool"] == "subtract_numbers"
198
+ ),
199
+ ),
200
+ EvaluationCase(
201
+ "tool_error_recovery",
202
+ "recovery",
203
+ lambda: run_agent(
204
+ "reverse text in 'Agent Loop'",
205
+ config=AgentConfig(max_steps=4, max_retries=2),
206
+ fault_plan={"reverse text in 'Agent Loop'": ["tool-error"]},
207
+ ),
208
+ lambda result: (
209
+ result["status"] == AgentStatus.DONE
210
+ and result["answer"] == "pooL tnegA"
211
+ and result["reflections"][-1]["reason"] == "tool execution failed"
212
+ and [item["fault"] for item in result["execution_attempts"]]
213
+ == ["tool-error", ""]
214
+ ),
215
+ ),
216
+ EvaluationCase(
217
+ "empty_plan_failure",
218
+ "failure",
219
+ lambda: run_agent(
220
+ " ",
221
+ config=AgentConfig(max_steps=3, max_retries=1),
222
+ ),
223
+ lambda result: (
224
+ result["status"] == AgentStatus.FAILED
225
+ and result["errors"] == ["empty plan"]
226
+ and [event["node"] for event in result["events"]] == ["planner"]
227
+ ),
228
+ ),
229
+ ]