agent_runtime 0.2.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/LICENSE.txt +21 -0
- data/README.md +234 -0
- data/lib/agent_runtime/agent.rb +150 -0
- data/lib/agent_runtime/agent_fsm.rb +446 -0
- data/lib/agent_runtime/audit_log.rb +62 -0
- data/lib/agent_runtime/decision.rb +35 -0
- data/lib/agent_runtime/errors.rb +28 -0
- data/lib/agent_runtime/executor.rb +85 -0
- data/lib/agent_runtime/fsm.rb +213 -0
- data/lib/agent_runtime/planner.rb +93 -0
- data/lib/agent_runtime/policy.rb +42 -0
- data/lib/agent_runtime/state.rb +81 -0
- data/lib/agent_runtime/tool_registry.rb +50 -0
- data/lib/agent_runtime/version.rb +8 -0
- data/lib/agent_runtime.rb +51 -0
- metadata +72 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d60d801b991450361a87cd0ae9ed9d254f58f836f3bfadeff35c52696f4ac38d
|
|
4
|
+
data.tar.gz: b8dbd1232137bd1b8746fb8772f27f2ab4664923dc3370e5be1c702875f0abf3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 87aeb7ede07631a7dc9bedf7db9549e7504a16dadc5d90818be64be67df1ddcd332fe2bf50889a7cfad5184da1f2a4a575d5f145f752b0a40eb770b7eb1ad4d8
|
|
7
|
+
data.tar.gz: b2b7fcf7d975a8faf5398fe17ba47d5682bf7fdec2a288497cf85497c9709f4eb2dbac9ac0d844777c897a032d175075c6741e57aaf2868aec7f2c554445db5c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - 2026-01-XX
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- FSM finalization now properly executes `handle_finalize` or `handle_halt` before returning
|
|
7
|
+
- Agent#run now always returns the last tool result when terminating
|
|
8
|
+
- Tool call argument parsing now handles JSON parse failures gracefully
|
|
9
|
+
- Audit logging now handles nil decisions without crashing
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Removed domain-specific code (DhanHQ helpers) from `lib/` directory
|
|
13
|
+
- Planning contract for FSM is now documented and consistent
|
|
14
|
+
- Tool calls are now enabled in FSM with basic tool definition conversion
|
|
15
|
+
- State#apply! now performs deep merge of nested hashes
|
|
16
|
+
- Removed hardcoded local paths from examples (now uses environment variables)
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
- Updated README to remove references to deleted console helpers
|
|
20
|
+
- Fixed documentation to match actual behavior
|
|
21
|
+
- Removed CONSOLE_TESTING.md (domain-specific content)
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - 2026-01-15
|
|
24
|
+
|
|
25
|
+
- Initial release
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shubham Taywade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# AgentRuntime
|
|
2
|
+
|
|
3
|
+
Deterministic, policy-driven runtime for tool-using LLM agents in Ruby.
|
|
4
|
+
|
|
5
|
+
AgentRuntime is a control plane. It coordinates planning, policy validation,
|
|
6
|
+
tool execution, and explicit state. It does not ship domain logic.
|
|
7
|
+
|
|
8
|
+
## What this gem is
|
|
9
|
+
- A small runtime to coordinate LLM decisions with Ruby tool execution.
|
|
10
|
+
- A formal FSM workflow (`AgentFSM`) with explicit states and history.
|
|
11
|
+
|
|
12
|
+
## What this gem is not
|
|
13
|
+
- Not a domain toolkit (no broker APIs, HTTP clients, or storage).
|
|
14
|
+
- Not a prompt library.
|
|
15
|
+
- Not a memory system.
|
|
16
|
+
|
|
17
|
+
## Strict usage rules (non-negotiable)
|
|
18
|
+
- Use `/generate` only for planning/decision outputs (`Planner#plan`).
|
|
19
|
+
- Use `/chat` only during execution/finalization (`Planner#chat_raw`, `Planner#chat`).
|
|
20
|
+
- The LLM never executes tools. Tools are Ruby callables and run in `Executor`.
|
|
21
|
+
- Tool results are injected as `role: "tool"` messages only after execution.
|
|
22
|
+
- Only `EXECUTE` loops. All other states are single-shot.
|
|
23
|
+
- Termination happens only on explicit signals:
|
|
24
|
+
`decision.action == "finish"`, `result[:done] == true`, or `MaxIterationsExceeded`.
|
|
25
|
+
- This gem does not add retries or streaming. Retry/streaming policy lives in
|
|
26
|
+
`ollama-client`.
|
|
27
|
+
|
|
28
|
+
If you violate any rule above, you are not using this gem correctly.
|
|
29
|
+
|
|
30
|
+
## Narrative overview (kept here, kept strict)
|
|
31
|
+
AgentRuntime is a domain-agnostic runtime that separates reasoning from
|
|
32
|
+
authority:
|
|
33
|
+
- LLM reasoning happens via `Planner` only.
|
|
34
|
+
- Ruby owns policy and execution.
|
|
35
|
+
- Tools are gated and executed outside the LLM.
|
|
36
|
+
- State is explicit and inspectable.
|
|
37
|
+
- Failures are visible via explicit errors and optional audit logs.
|
|
38
|
+
|
|
39
|
+
Architecture (conceptual):
|
|
40
|
+
Your application → AgentRuntime → `ollama-client` → Ollama server
|
|
41
|
+
|
|
42
|
+
This overview is informative only. The strict rules above are the contract.
|
|
43
|
+
|
|
44
|
+
## Core components (SRP map)
|
|
45
|
+
- `Planner`: LLM interface (`generate`, `chat`, `chat_raw`). No tools. No side effects.
|
|
46
|
+
- `Policy`: validates decisions before execution.
|
|
47
|
+
- `Executor`: executes tools via `ToolRegistry` only.
|
|
48
|
+
- `ToolRegistry`: maps tool names to Ruby callables.
|
|
49
|
+
- `State`: explicit, serializable state.
|
|
50
|
+
- `Agent`: simple decision loop using `Planner#plan` and tools.
|
|
51
|
+
- `AgentFSM`: formal FSM with explicit states and transition history.
|
|
52
|
+
- `AuditLog`: optional logging of decisions and results.
|
|
53
|
+
|
|
54
|
+
## API mapping
|
|
55
|
+
| Concern | Method | LLM endpoint | Where it belongs |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| Planning / decisions | `Planner#plan` | `/api/generate` | PLAN |
|
|
58
|
+
| Execution / tool calls | `Planner#chat_raw` | `/api/chat` | EXECUTE |
|
|
59
|
+
| Final response (optional) | `Planner#chat` | `/api/chat` | FINALIZE |
|
|
60
|
+
|
|
61
|
+
`Executor` never calls the LLM.
|
|
62
|
+
|
|
63
|
+
## Prerequisites
|
|
64
|
+
`agent_runtime` depends on `ollama-client`. See `PREREQUISITES.md`.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
Add this line to your application's Gemfile:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
gem "agent_runtime"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
And then execute:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bundle install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or install it yourself as:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
gem install agent_runtime
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### Single-step agent (`Agent#step`)
|
|
88
|
+
Use this for one-shot decisions or when you control the loop externally.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
require "agent_runtime"
|
|
92
|
+
require "ollama_client"
|
|
93
|
+
|
|
94
|
+
tools = AgentRuntime::ToolRegistry.new({
|
|
95
|
+
"fetch" => ->(**args) { { data: "fetched", args: args } },
|
|
96
|
+
"execute" => ->(**args) { { result: "executed", args: args } }
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
client = Ollama::Client.new
|
|
100
|
+
|
|
101
|
+
schema = {
|
|
102
|
+
"type" => "object",
|
|
103
|
+
"required" => ["action", "params", "confidence"],
|
|
104
|
+
"properties" => {
|
|
105
|
+
"action" => { "type" => "string", "enum" => ["fetch", "execute", "finish"] },
|
|
106
|
+
"params" => { "type" => "object", "additionalProperties" => true },
|
|
107
|
+
"confidence" => { "type" => "number", "minimum" => 0, "maximum" => 1 }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
planner = AgentRuntime::Planner.new(
|
|
112
|
+
client: client,
|
|
113
|
+
schema: schema,
|
|
114
|
+
prompt_builder: ->(input:, state:) {
|
|
115
|
+
"User request: #{input}\nContext: #{state.to_json}"
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
agent = AgentRuntime::Agent.new(
|
|
120
|
+
planner: planner,
|
|
121
|
+
policy: AgentRuntime::Policy.new,
|
|
122
|
+
executor: AgentRuntime::Executor.new(tool_registry: tools),
|
|
123
|
+
state: AgentRuntime::State.new,
|
|
124
|
+
audit_log: AgentRuntime::AuditLog.new
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
result = agent.step(input: "Fetch market data for AAPL")
|
|
128
|
+
puts result.inspect
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Multi-step loop (`Agent#run`)
|
|
132
|
+
Use this when the agent should iterate until it emits `finish` or a tool marks
|
|
133
|
+
`done: true`. This loop uses `/generate` only (no chat).
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
result = agent.run(initial_input: "Find best PDF library for Ruby")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Formal FSM workflow (`AgentFSM`)
|
|
140
|
+
`AgentFSM` is the explicit FSM driver. It uses `/generate` for PLAN and
|
|
141
|
+
`/chat` for EXECUTE. Tool execution happens only in OBSERVE.
|
|
142
|
+
|
|
143
|
+
Tool calling in EXECUTE requires Ollama tool definitions. This gem does not
|
|
144
|
+
auto-convert `ToolRegistry` entries to `Ollama::Tool` objects. If you need tool
|
|
145
|
+
calling, subclass `AgentFSM` and return tool definitions from
|
|
146
|
+
`build_tools_for_chat`.
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
class MyAgentFSM < AgentRuntime::AgentFSM
|
|
150
|
+
def build_tools_for_chat
|
|
151
|
+
# Return Ollama::Tool definitions here
|
|
152
|
+
[]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
agent_fsm = MyAgentFSM.new(
|
|
157
|
+
planner: planner,
|
|
158
|
+
policy: AgentRuntime::Policy.new,
|
|
159
|
+
executor: AgentRuntime::Executor.new(tool_registry: tools),
|
|
160
|
+
state: AgentRuntime::State.new,
|
|
161
|
+
tool_registry: tools,
|
|
162
|
+
audit_log: AgentRuntime::AuditLog.new
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
result = agent_fsm.run(initial_input: "Research Ruby memory management")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Tool safety model
|
|
169
|
+
- Tools are Ruby callables registered in `ToolRegistry`.
|
|
170
|
+
- LLM output never executes tools directly.
|
|
171
|
+
- Tool execution happens only in `Executor`.
|
|
172
|
+
- Tool results are recorded in state and (for FSM) injected as `role: "tool"`.
|
|
173
|
+
|
|
174
|
+
## Examples
|
|
175
|
+
|
|
176
|
+
### Quick Start
|
|
177
|
+
**Start here**: `examples/complete_working_example.rb` - A complete, runnable example demonstrating all features.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Make sure Ollama is running: ollama serve
|
|
181
|
+
ruby examples/complete_working_example.rb
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Available Examples
|
|
185
|
+
- `examples/complete_working_example.rb` ⭐ - **Complete working example** (recommended starting point)
|
|
186
|
+
- `examples/fixed_console_example.rb` - Minimal example for console use
|
|
187
|
+
- `examples/console_example.rb` - Basic console example
|
|
188
|
+
- `examples/rails_example/` - Rails integration example
|
|
189
|
+
- `examples/dhanhq_example.rb` - Domain-specific example (requires DhanHQ gem)
|
|
190
|
+
|
|
191
|
+
See `examples/README.md` for detailed documentation on all examples.
|
|
192
|
+
|
|
193
|
+
Examples are not part of the public API.
|
|
194
|
+
|
|
195
|
+
## Documentation
|
|
196
|
+
- `AGENTIC_WORKFLOWS.md`
|
|
197
|
+
- `FSM_WORKFLOWS.md`
|
|
198
|
+
- `SCHEMA_GUIDE.md`
|
|
199
|
+
- `PREREQUISITES.md`
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
After checking out the repo, run:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
bin/setup
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
To run tests:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
rake spec
|
|
212
|
+
# or
|
|
213
|
+
bundle exec rspec
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Test coverage reports are generated automatically. View the HTML report:
|
|
217
|
+
```bash
|
|
218
|
+
open coverage/index.html # macOS
|
|
219
|
+
xdg-open coverage/index.html # Linux
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
See `TESTING.md` for detailed testing and coverage information.
|
|
223
|
+
|
|
224
|
+
To run the console:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
bin/console
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Contributing
|
|
231
|
+
Bug reports and pull requests are welcome. Keep the API strict and small.
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
The gem is available as open source under the terms of the MIT License.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentRuntime
|
|
4
|
+
# Simple agent implementation with step-by-step execution and multi-step loops.
|
|
5
|
+
#
|
|
6
|
+
# This class provides a straightforward agent implementation that executes
|
|
7
|
+
# planning, validation, and execution steps in a loop until termination.
|
|
8
|
+
# Use this for simpler workflows where you don't need the full FSM structure.
|
|
9
|
+
#
|
|
10
|
+
# @example Single step execution
|
|
11
|
+
# agent = AgentRuntime::Agent.new(planner: planner, policy: policy, executor: executor, state: state)
|
|
12
|
+
# result = agent.step(input: "What is 2+2?")
|
|
13
|
+
#
|
|
14
|
+
# @example Multi-step agentic workflow
|
|
15
|
+
# agent = AgentRuntime::Agent.new(planner: planner, policy: policy, executor: executor, state: state)
|
|
16
|
+
# result = agent.run(initial_input: "Find the weather and send an email")
|
|
17
|
+
class Agent
|
|
18
|
+
# Initialize a new Agent instance.
|
|
19
|
+
#
|
|
20
|
+
# @param planner [Planner] The planner responsible for generating decisions
|
|
21
|
+
# @param policy [Policy] The policy validator for decisions
|
|
22
|
+
# @param executor [Executor] The executor for tool calls
|
|
23
|
+
# @param state [State] The state manager for agent state
|
|
24
|
+
# @param audit_log [AuditLog, nil] Optional audit logger for recording decisions
|
|
25
|
+
# @param max_iterations [Integer] Maximum number of iterations before raising an error (default: 50)
|
|
26
|
+
def initialize(planner:, policy:, executor:, state:, audit_log: nil, max_iterations: 50)
|
|
27
|
+
@planner = planner
|
|
28
|
+
@policy = policy
|
|
29
|
+
@executor = executor
|
|
30
|
+
@state = state
|
|
31
|
+
@audit_log = audit_log
|
|
32
|
+
@max_iterations = max_iterations
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Single step execution (non-agentic).
|
|
36
|
+
#
|
|
37
|
+
# Use this for one-shot decisions or when you control the loop externally.
|
|
38
|
+
# This method performs a single planning, validation, execution, and state update cycle.
|
|
39
|
+
#
|
|
40
|
+
# @param input [String] The input prompt for this step
|
|
41
|
+
# @return [Hash] The execution result hash
|
|
42
|
+
# @raise [PolicyViolation] If the decision violates policy constraints
|
|
43
|
+
# @raise [ExecutionError] If execution fails
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# result = agent.step(input: "Calculate 5 * 10")
|
|
47
|
+
# # => { result: 50 }
|
|
48
|
+
def step(input:)
|
|
49
|
+
decision = @planner.plan(
|
|
50
|
+
input: input,
|
|
51
|
+
state: @state.snapshot
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@policy.validate!(decision, state: @state)
|
|
55
|
+
|
|
56
|
+
result = @executor.execute(decision, state: @state)
|
|
57
|
+
|
|
58
|
+
@state.apply!(result)
|
|
59
|
+
|
|
60
|
+
@audit_log&.record(
|
|
61
|
+
input: input,
|
|
62
|
+
decision: decision,
|
|
63
|
+
result: result
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Agentic workflow loop (runs until termination).
|
|
70
|
+
#
|
|
71
|
+
# Use this for multi-step workflows where the agent decides when to stop.
|
|
72
|
+
# The loop continues until:
|
|
73
|
+
# - The decision action is "finish"
|
|
74
|
+
# - The result contains `done: true`
|
|
75
|
+
# - Maximum iterations are exceeded
|
|
76
|
+
#
|
|
77
|
+
# @param initial_input [String] The initial input to start the workflow
|
|
78
|
+
# @param input_builder [Proc, nil] Optional proc to build next input from result and iteration.
|
|
79
|
+
# Called as `input_builder.call(result, iteration)`. If nil, uses default builder.
|
|
80
|
+
# @return [Hash] Final result hash, always includes `done: true` and `iterations` count
|
|
81
|
+
# @raise [MaxIterationsExceeded] If maximum iterations are exceeded
|
|
82
|
+
# @raise [PolicyViolation] If any decision violates policy constraints
|
|
83
|
+
# @raise [ExecutionError] If execution fails
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# result = agent.run(initial_input: "Find weather and send email")
|
|
87
|
+
# # => { done: true, iterations: 3, ... }
|
|
88
|
+
#
|
|
89
|
+
# @example With custom input builder
|
|
90
|
+
# builder = ->(result, iteration) { "Iteration #{iteration}: #{result.inspect}" }
|
|
91
|
+
# result = agent.run(initial_input: "Start", input_builder: builder)
|
|
92
|
+
def run(initial_input:, input_builder: nil)
|
|
93
|
+
iteration = 0
|
|
94
|
+
current_input = initial_input
|
|
95
|
+
final_result = nil
|
|
96
|
+
|
|
97
|
+
loop do
|
|
98
|
+
iteration += 1
|
|
99
|
+
|
|
100
|
+
raise MaxIterationsExceeded, "Max iterations (#{@max_iterations}) exceeded" if iteration > @max_iterations
|
|
101
|
+
|
|
102
|
+
decision = @planner.plan(
|
|
103
|
+
input: current_input,
|
|
104
|
+
state: @state.snapshot
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@policy.validate!(decision, state: @state)
|
|
108
|
+
|
|
109
|
+
result = @executor.execute(decision, state: @state)
|
|
110
|
+
|
|
111
|
+
@state.apply!(result)
|
|
112
|
+
|
|
113
|
+
@audit_log&.record(
|
|
114
|
+
input: current_input,
|
|
115
|
+
decision: decision,
|
|
116
|
+
result: result
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Always set final_result before checking termination
|
|
120
|
+
final_result = result
|
|
121
|
+
|
|
122
|
+
break if terminated?(decision, result)
|
|
123
|
+
|
|
124
|
+
current_input = input_builder ? input_builder.call(result, iteration) : build_next_input(result, iteration)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
final_result || { done: true, iterations: iteration }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Check if the agent should terminate based on decision and result.
|
|
133
|
+
#
|
|
134
|
+
# @param decision [Decision] The current decision
|
|
135
|
+
# @param result [Hash] The execution result
|
|
136
|
+
# @return [Boolean] True if the agent should terminate
|
|
137
|
+
def terminated?(decision, result)
|
|
138
|
+
decision.action == "finish" || result[:done] == true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Build the next input for the loop iteration.
|
|
142
|
+
#
|
|
143
|
+
# @param result [Hash] The previous execution result
|
|
144
|
+
# @param _iteration [Integer] The current iteration number (unused)
|
|
145
|
+
# @return [String] The next input string
|
|
146
|
+
def build_next_input(result, _iteration)
|
|
147
|
+
"Continue based on: #{result.inspect}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|