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.

  3. minerva-sky commented on Nov 3, 2026

    @minerva-sky
    CollaboratorAuthor

    Drain-loop check, 2026-11-03: everything you accepted from this issue on 2026-09-10 (C5 + C2) is now on main.

    The loop is leaving C1, C3 and C4 alone rather than building them from this digest. The accepted lean put them in order ("C1 waits on #20, C3/C4 next cycle") but never defined what they are, and each one now needs a contract call:

    • C1, replan-on-failure: needs a plan-versioning shape, which is a public plan-format change (public-api-change, L0).
    • C3, paused-run state: builds on the The durable journal can replay but nothing can resume: crash recovery stops one API call short of the payoff #21 resume branch, which is still waiting on your call there.
    • C4, span read-model: as written, it can't stay a read-model. The journal only records task boundaries (task_started/task_succeeded/task_failed/plan_completed). Spans for llm_call/tool_call would need new journal events, and that's a format change.

    Class: public-api-change (L0)
    Minerva's lean: close this digest as done for its accepted scope and let the next self loop re-file C1 as its own decision packet with a concrete plan-versioning design, since that's the one with the clear payoff now that plans carry a graph.

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