mirror of
https://github.com/mikeyobrien/ralph-orchestrator.git
synced 2026-04-24 22:55:57 +03:00
[PR #197] fix(core): default_publishes of completion_promise must set completion_requested #190
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/ralph-orchestrator#190
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/mikeyobrien/ralph-orchestrator/pull/197
Author: @arjhun-personal
Created: 2/25/2026
Status: 🔄 Open
Base:
main← Head:fix/default-publishes-completion-requested📝 Commits (8)
159821dfeat(core): add hat scope enforcement, event chain validation, and human timeout routing63111d9docs: add upstream PR draft for hat scope enforcement479e6cbfeat(core): make hat enforcement opt-in via config flagsdd46589style: fix rustfmt formatting in hat enforcement code31c55efspec: add context window utilization tracking designadce9bcfix(core): record default_publishes topics in seen_topics for chain validation4da8468docs: add upstream PR draft for default_publishes seen_topics fixed40abefix(core): default_publishes of completion_promise must set completion_requested📊 Changes
14 files changed (+1361 additions, -4 deletions)
View changed files
📝
crates/ralph-bench/src/main.rs(+1 -0)📝
crates/ralph-cli/src/display.rs(+1 -0)📝
crates/ralph-cli/src/loop_runner.rs(+27 -0)📝
crates/ralph-core/src/config.rs(+21 -0)📝
crates/ralph-core/src/event_loop/loop_state.rs(+21 -0)📝
crates/ralph-core/src/event_loop/mod.rs(+144 -4)📝
crates/ralph-core/src/event_loop/tests.rs(+590 -0)📝
crates/ralph-core/src/hat_registry.rs(+79 -0)📝
crates/ralph-core/src/summary_writer.rs(+3 -0)➕
docs/specs/context-window-utilization.md(+151 -0)➕
upstream-PRs/default-publishes-seen-topics-body.md(+91 -0)➕
upstream-PRs/default-publishes-seen-topics.md(+47 -0)➕
upstream-PRs/hat-scope-enforcement-body.md(+58 -0)➕
upstream-PRs/hat-scope-enforcement.md(+127 -0)📄 Description
Relates to #187
Summary
Fixes an infinite loop caused by
check_default_publishesnot settingcompletion_requestedwhen the injected topic matches thecompletion_promise. The loop spins forever — the completion event exists on the bus butcheck_completion_event()never fires because the flag is only set byprocess_events_from_jsonl(), whichdefault_publishesbypasses entirely.Bug
There are two independent code paths that can emit the
completion_promiseevent:process_events_from_jsonl): Agent writesLOOP_COMPLETEto events JSONL → parsed →completion_requested = true→check_completion_event()fires → loop terminates.check_default_publishes): Agent writes no events → orchestrator injects default event → published to bus → butcompletion_requestedis never set →check_completion_event()returnsNone→ loop continues forever.The result: the final hat's
default_publishes: "LOOP_COMPLETE"fires aLOOP_COMPLETEevent on the bus (which wakes downstream/wildcard hats), but the loop never terminates. It cycles endlessly between hats that keep re-activating each other.Observed behavior
In a lexis-feature preset with 8 hats:
Steps to Reproduce
Configure a preset where the final hat has
default_publishesmatchingcompletion_promise:Run the loop. The final_committer hat activates when
all.builtarrives.The agent completes its work but does not explicitly write
LOOP_COMPLETEto JSONL (this is common — agents follow hat instructions imperfectly).check_default_publishesinjectsLOOP_COMPLETEon the bus.check_completion_event()checkscompletion_requested→false→ returnsNone.The
LOOP_COMPLETEevent on the bus wakes the wildcard subscriber (ralph), which re-dispatches to the next triggered hat, starting a new cycle.The loop never terminates.
Root Cause
check_default_publishes(event_loop/mod.rs:1403) callsself.bus.publish()but never setsself.state.completion_requested. This flag is only set inprocess_events_from_jsonl(line ~2157) when parsing events from the agent's JSONL output. Thedefault_publishespath bypasses JSONL entirely, so the flag is never set.Fix
Added a check: if the default topic matches
completion_promise, setcompletion_requested = truedirectly:Changes
crates/ralph-core/src/event_loop/mod.rscheck_default_publishes, doc comment updatecrates/ralph-core/src/event_loop/tests.rsTests
New regression test (
test_default_publishes_completion_promise_triggers_termination):completion_promise: "LOOP_COMPLETE"withrequired_events: ["all.built"]final_committerhat withdefault_publishes: "LOOP_COMPLETE"required_eventsby writingall.builtvia JSONLcheck_default_publishes(simulating agent writing no events)check_completion_event()returnsSome(TerminationReason::CompletionPromise)None, causing the infinite loopTest Plan
cargo test -p ralph-core test_default_publishes— 5 tests pass (including new regression test)cargo test -p ralph-core— 703 tests pass (no regressions)cargo test— full workspace passes🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.