Skip to content

[FEATURE] Add Chat#step for single-iteration execution #690

@ramontayag

Description

@ramontayag

Problem

Chat#complete loops until the model stops calling tools. There's no way to run one iteration at a time — one provider call, execute any tool calls returned, then stop.

This matters for applications that want to run each LLM iteration as a separate background job. Without step, there's no clean way to do this:

  • complete holds the thread for the entire agentic loop
  • you can't set an iteration budget, and
  • you can't re-enqueue between iterations.

Proposed solution

Add Chat#step — identical to one iteration of complete, but returns the response instead of recursing:

response = chat.step   # one provider call + tool execution
response.tool_call?    # => true if the model wants to continue

complete would be refactored to delegate to step:

def complete(&)
  response = step(&)
  return response if response.is_a?(Tool::Halt)
  response.tool_call? ? complete(&) : response
end

No behaviour change to complete. Tool::Halt semantics preserved.

Why this belongs in the gem

The split between "one iteration" and "loop until done" is a property of the protocol, not application logic. Any application running agents in background jobs needs this. Putting it in user code means wrapping or monkey-patching Chat, which is worse.

Alternatives considered

  • Monkey-patching in an initializer — works but fragile across upgrades
  • Subclassing Chat — doesn't work cleanly with acts_as_chat
  • max_turns on complete — would require a new parameter and internal counter; step is simpler and more composable

P.S. I already have this in my fork to see if it addressed something I was working on

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions