Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[FEATURE] A public API for running a persisted chat's turns outside RubyLLM's loop

Open
#1,291 0 comments 1 reaction 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
ruby
Domain
api, backend

Research direction

Start by reading the public chat and provider APIs alongside the internal pieces named in the issue: RunClaims, ActiveRecord::ChatQueue#take, Skill::Loader#listing, and Tool.split_result. The request spans several persisted turn operations and asks which should become public, so first map their existing behavior and tests. Done means a supported, documented API lets an external agent loop drive turns without depending on :nodoc: internals.

Written by the indexing model from the issue text.

Description

enhancement
Scope check
  • This is core LLM communication (not application logic)
  • This benefits most users (not just my use case)
  • This can't be solved in application code with current RubyLLM
  • I read the Contributing Guide

I left the second box open on purpose: this is for people whose agent loop runs somewhere else. I think there will be more of them, but I would rather ask than claim it.

Due diligence
  • I searched existing issues
  • I checked the documentation
What problem does this solve?

I run persisted RubyLLM chats on the claude CLI, through the Claude Agent SDK, so they use a Claude subscription. The CLI owns the agent loop; RubyLLM keeps everything around it, and its Rails integration does almost all of the work: the records, approvals and their cards, the queue and steering, cancellation, the usage ledger, skills, code execution, MCP servers (OAuth included) and the chat's files. The chat's tools are served to the CLI as an MCP server and run in the app, each as the ToolCall it is recorded as.

What does not work through public API is driving a turn the way RubyLLM's own loop does. Today that takes these :nodoc: pieces:

What the turn needs What it uses today
Claim the chat, so running? sees the run and a second process steps aside writes claim_token and claimed_until the way RunClaims does
Take queued messages once each, steering or not ActiveRecord::ChatQueue#take
Record the turn's usage, with a status chat.ruby_llm_usages.create! with the ledger's columns
The system text RubyLLM would send: instructions, then the skill listing Skill::Loader#listing
Long tool results saved to the chat's files, attachments described Chat::FileReferences (spill, describe)
Tool progress while a tool runs outside the loop Support::ProgressReporter.listen
A tool's result split into text and attachments Tool.split_result
A provider that searches deferred tools itself overrides Provider#tool_search? (since #1283)

Each works, and I keep them behind one module with a test per piece. #1283 already broke one: Chat#tools now asks the provider about tool search, and my provider raised there, so every turn failed until I caught it.

Proposed solution

Mostly naming and documenting what exists. For example:

chat.claim_run { ... }                  # false when another process holds the chat
chat.take_queued(steering_only: false)  # the messages, once each
chat.record_usage(provider:, model:, tokens:, status:, message:)
chat.system_prompt                      # what the next request's system part would be
chat.with_files { |files| files.spill(tool, call, text) }

and a documented way for a provider to say "this provider's runtime searches tools itself", which #1283's tool_search? almost is.

Why this belongs in RubyLLM

These pieces are RubyLLM's own state: the claim columns, the queue, the ledger, the system text. Application code can only reach them by copying RubyLLM's internals, which is what I do now, and which breaks on internal changes like #1283.

If the whole is out of scope, the two that would help most on their own are the run claim and recording usage for work done elsewhere.

Dominant language
Ruby
Stars
4.5k
Forks
523
Avg merge
13h 49m
Merged PRs (30d)
221

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from crmne/ruby_llm

All issues in crmne/ruby_llm

Similar issues

More Ruby issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.