Skip to content

Research digest: the plan-as-data bet is uncontested in Ruby, and roast just vacated the lane #23

Open

Description

@minerva-sky

Survey of agentic's nearest neighbors: the Ruby agent gems (langchainrb, regent, sublayer), the Ruby LLM-layer gems (ruby_llm, raix), the closest conceptual competitor (Shopify's roast), the Rails one (active_agent), and the plan-and-execute frontier outside Ruby (LangGraph, OpenAI Agents SDK). Feature-level comparison against what this gem ships today, fenced by WORLD.md. Three findings, five integration candidates, four recorded non-candidates.

Finding 1: the plan-as-data bet is uncontested, and roast just vacated it

No Ruby framework ships LLM-generated inspectable plans, dependency-graph orchestration, durable journaling, or learning from execution history. langchainrb's Assistant is a conversational tool loop; regent is ReAct; sublayer is single-call generators plus triggers. The closest thing to competition was roast v0.x, whose killer feature was exactly ours: "every workflow execution automatically saved, resume from any step."

Then roast v1.0 (2026-02) rewrote workflows from YAML into a pure Ruby DSL and durable workflow replay did not survive — code search on the current repo returns zero hits for it (releases). Their plans are now code: a human can edit them, but no runtime or LLM can inspect, mutate, or replan one mid-flight. That is the precise gap WORLD.md's Direction ("the plan is the product") targets, and the best-funded neighbor just walked away from it. The lane is open.

Finding 2: the ecosystem's failure mode is provider-parity decay, and the small-core charter is the moat

Of the three Ruby agent frameworks, two are dormant (regent: no commits ~14 months; sublayer: same) and the third can't cut a release (langchainrb: active commits, but the last gem shipped 2025-05, with an open issue literally titled "Is this repo still maintained?"). The dominant open-issue theme across all three is provider-parity gaps: an 11-provider feature matrix is a treadmill that eats maintainers.

New evidence for the already-open provider question (#22, not re-raised here): raix v2.0 deleted its own provider layer and now delegates entirely to ruby_llm (changelog). Its 2.0.2–2.0.6 patch run (dropped multimodal transcripts, unenforced tool budgets, lost schema richness) is a free checklist of what breaks at a translation boundary. The Ruby ecosystem is consolidating on ruby_llm as the provider abstraction. That strengthens #22's option 3 and argues even harder against a third homegrown multi-provider matrix.

Finding 3: the frontier invests in cheap recovery, not better initial plans

LangGraph's canonical plan-and-execute is an execute→replan loop; its checkpoints support rewind, human plan-editing, and forked branches (interrupts). OpenAI's Agents SDK makes paused runs a serializable RunState that resumes with full history (HITL docs). The durable-execution camp's critique of both (diagrid): checkpoints that die with the process aren't durability. Agentic's ExecutionJournal already survives the process; what's missing is everything downstream of that fact (#21). The bet is right. The payoff is unclaimed.

Integration candidates (patterns to borrow, not dependencies)

C1: Replan-on-failure (LangGraph pattern). On task failure, feed the plan plus journal-so-far back to the LLM and regenerate the remaining plan as a new plan version. Because agentic's plans are data, this is possible here and structurally impossible in roast — the highest-leverage differentiator found. Fence: squarely Direction ("better failure recovery"); depends on graph-capable plan serialization (#20). Medium cost: plan versioning plus one orchestrator hook.

C2: Per-task failure-tolerance flags (roast pattern). continue_on_failure, tolerated-error predicates, and a succeeded?(task) accessor downstream tasks can branch on. Roast proves the Ruby ergonomics (no_abort_on_failure!). Fence: small orchestrator/task-schema change, no new deps; pairs naturally with #10's stranded-dependents fix. Cheap.

C3: Serializable paused-run state (OpenAI RunState pattern). Make "paused awaiting human approval or plan edit" a first-class persistable orchestrator state, so edit-the-plan-then-resume becomes the workflow. Extends #21's resume from crash recovery to deliberate human-in-the-loop. Fence: builds on the journal and store we already have; the diagrid survive-process-death critique is the acceptance test. Medium cost.

C4: Span read-model over the journal (regent pattern). Group journal entries into typed spans (llm_call, tool_call, task) with per-span tokens and duration, rendered hierarchically. Regent's session tracing was its one standout feature. Fence: Direction ("better execution history"); read-model only, no journal format change required. Moderate cost.

C5: Task-execution callback (langchainrb pattern). One documented hook on the orchestrator's task boundary buys user-wired tracing, redaction, and test observation without any abstraction. raix's before_completion is the same idea at the LLM boundary. Near-free.

Non-candidates (recorded so future loops don't re-raise them)

  • Multi-provider feature matrix: the documented cause of death for half this survey; Provider strategy: the researched proposal WORLD.md asks for (measured coupling, three options, one lean) #22 owns the actual decision.
  • Trigger daemons / hosted gravity (sublayer): an anti-goal (not a hosted platform), and speculative until a concrete use exists.
  • Rails MVC agent layer (active_agent): different category; complementary (they do request/response, we do planning/execution), not something to absorb.
  • MCP client (raix): integration-store gravity; capabilities keep the core small. Revisit only if a real consumer asks.

Origin: loop:research session 2026-08-23. L0 analysis only; nothing here lands without its own proposal. Full vitals: ruby_llm 1.16.0 (~11.5M downloads), raix 2.0.6, langchainrb 0.19.5, regent 0.3.4, sublayer 0.2.9, roast-ai 1.2.0 (~300K downloads), active_agent 1.3.1.

Sources: Shopify/roast · crmne/ruby_llm · OlympiaAI/raix · patterns-ai-core/langchainrb · alchaplinsky/regent · sublayerapp/sublayer · activeagents/activeagent · LangGraph interrupts · OpenAI Agents SDK HITL · diagrid on durable execution

Activity

  1. minerva-sky commented on Sep 15, 2026

    @minerva-sky
    CollaboratorAuthor

    agentic#23 follow-up: C5 task-boundary callback + C2 failure-tolerance flags

    Date: 2026-09-14. Bean task-6936e519. Read against main at 847a565 (#34 merged) plus the #35/#37/#38 stack.

    C5: task-boundary callback — already shipped, undocumented

    PlanOrchestrator#initialize takes lifecycle_hooks: with seven callable no-op defaults, all at task boundaries: before_task_execution, task_slot_acquired, before_agent_build, after_agent_build, after_task_success, after_task_failure, plan_completed. Each receives keyword args (task_id, task, result/failure, duration). Task is also Observable and emits :status_change and :failure_occurred. That is strictly more than the candidate asked for (langchainrb has one callback; raix has before_completion).

    The gap is documentation: README references lifecycle_hooks exactly once, as an argument the learning system builds for you (line ~710). A user wiring tracing, redaction, or a test observer has to read the orchestrator source to learn the hook names and signatures.

    Verdict: no new code. One docs PR adding a "Lifecycle hooks" section to README (table of the seven hooks, their kwargs, the inline-on-fiber caveat already in the source comment, and a 10-line tracing example). Hooks are stable public API today, so document as such.

    C2: per-task failure tolerance — not present, cheap, one PR

    Today's failure path in handle_task_failure: retry if policy says so, request intervention for auth/permission/config errors, otherwise terminal. Since #34 a terminal failure moves every transitive dependent to :skipped with a TaskExecutionResult.skipped naming the failed dependency. There is no way to say "this task is allowed to fail; run its dependents anyway."

    Proposed shape, additive like #35:

    • Plan data: TaskDefinition gains on_failure: with values "skip_dependents" (default, today's behaviour) and "continue". Serialized in to_h only when non-default, parsed in from_hash, added to the planner's strict schema as an enum with one numbered prompt instruction ("set on_failure: continue only for tasks whose result is optional to the tasks after them"). Plan-as-data means the LLM can set it, a human can edit it, and a replan (C1) can flip it.
    • Orchestrator: add_task(task, deps, agent:, needs:, on_failure:) records the policy per task id. all_dependencies_met? treats a dependency as met when it is :completed OR (:failed AND its policy is continue). In handle_task_failure, the tail becomes: if terminal and policy is continue, call schedule_dependent_tasks instead of skip_dependents_of. Intervention-required failures never continue; a human is already in the loop.
    • Downstream branching: Task#succeeded?(task_or_id) and Task#failure_of(task_or_id), fed by a new record_dependency_failure next to record_dependency_output. output_of keeps returning nil for a failed dependency, so existing tasks that ignore the flag see no change. NamedOutputs gets the same pair for needs.
    • Status: overall_status stays :partial_failure when any task failed, tolerated or not. Hiding a tolerated failure from the plan result would be lying to the journal.
    • Not in v1: roast-style tolerated-error predicates (tolerate: ["TimeoutError"]). The retry policy already has a type list; add a per-task tolerated_errors only when a consumer asks. Also not in v1: a plan-level default. Per-task is enough and keeps the schema flat.

    Size: ~80 LOC across task_definition, task, plan_orchestrator, task_planner (schema + prompt line), plus ~12 specs (flag round-trips, dependent runs after tolerated failure, transitive case where a skipped-policy task sits between, intervention short-circuit, status stays partial_failure). One PR, one tick.

    Filed

  2. minerva-sky commented on Oct 15, 2026

    @minerva-sky
    CollaboratorAuthor

    Drain-loop status, 2026-10-14. Of the accepted lean (C5 + C2 this cycle): C5 turned out to already ship as lifecycle_hooks:, documented in #39; C2 is #40 (on_failure: continue), stacked on #38. Both await merge. The rest of the digest is sequenced on open PRs: C1 (replan-on-failure) waits on the #20 series (#35, #37, #38); C3 (serializable paused-run state) extends the resume path now in the #21 PR; C4 (span read-model) waits on #46's token threading in #52. Nothing further is drainable from this issue until those merge, so the loop is leaving it alone rather than stacking a fourth PR on unmerged bases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    loop:researchCompetitive / related-project research loopstatus:analyzedAnalyzed, awaiting decision

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions