test(e2e): convert chat-harness-subagent to llmKeywordRules (#4517)#4671
Conversation
…nsai#4517) Follow-up to tinyhumansai#4519 for the residual chat-harness-subagent spec (the orchestrator -> researcher fan-out). The delegated researcher runs its own agent-harness loop, so the mock's global forced-response FIFO can still be shifted by an off-turn call even after tinyhumansai#4519's ancillary no-tools gating — the scripted final canary then lands on the wrong turn or never renders. Switch this spec to `llmKeywordRules`: matches are case-insensitive substrings of the latest user/tool message per call and are never consumed, so any extra ancillary call falls through to the mock's dynamic default and leaves the scripted turns intact. Rules: - 'llama-research' -> orchestrator turn 1 -> research(prompt=...) - 'coded marker phrase' -> researcher turn -> RESEARCHER_REPLY text - 'researcher trace signal' -> orchestrator turn 2 -> final canary The sub-agent's user message is rendered as "Task:\n<arg>" by render_structured_handoff in archetype_delegation.rs, so DELEGATE_PROMPT surfaces verbatim to pickProbeText. The orchestrator's final turn sees RESEARCHER_REPLY as the role: tool message content via ToolResult::success(outcome.output) in dispatch.rs. The fire-and-forget title-gen call from threadSlice.ts shares the turn-1 probe but only consumes `content` (chat_with_system ignores tool_calls when tools were not requested) — benign for both callers. E2E-mock fixture only; no product/runtime change. Refs tinyhumansai#4517 (does not close — chat-harness-subagent-continue.spec.ts has a separate {{DYNAMIC_TASK_ID}} placeholder blocker tracked separately).
📝 WalkthroughWalkthroughThis PR updates an end-to-end test spec to mock the orchestrator/subagent chat flow using content-addressed ChangesKeyword Rule Mocking Migration
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/test/e2e/specs/chat-harness-subagent.spec.ts (1)
78-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRule order doesn't match the documented A→B→C turn sequence.
Header comment (lines 26-30) lists turns as A) orchestrator, B) researcher, C) orchestrator, but
KEYWORD_RULESis ordered B, C, A. Matching is substring-based and order-independent here since the keywords don't collide, so this is purely a readability nit — reordering to A, B, C would make the mapping easier to follow at a glance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/test/e2e/specs/chat-harness-subagent.spec.ts` around lines 78 - 98, The KEYWORD_RULES array is ordered B, C, A even though the surrounding comment documents the turn sequence as A, B, C, so reorder the entries in KEYWORD_RULES to match that documented flow. Keep the same rule contents in place, just move the orchestrator rule first, then the researcher rule, then the post-delegation rule so the mapping is easier to read alongside the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/test/e2e/specs/chat-harness-subagent.spec.ts`:
- Around line 78-98: The KEYWORD_RULES array is ordered B, C, A even though the
surrounding comment documents the turn sequence as A, B, C, so reorder the
entries in KEYWORD_RULES to match that documented flow. Keep the same rule
contents in place, just move the orchestrator rule first, then the researcher
rule, then the post-delegation rule so the mapping is easier to read alongside
the comment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0e351ec4-409f-4460-a068-a99224832cc8
📒 Files selected for processing (1)
app/test/e2e/specs/chat-harness-subagent.spec.ts
Summary
chat-harness-subagent.spec.tsfrom the sharedllmForcedResponsesFIFO tollmKeywordRules— content-addressed, never depleted.llama-research/coded marker phrase/researcher trace signal) so each of the 3 turns routes to exactly one rule viapickProbeText's substring match.Problem
Follow-up to #4519 for #4517. #4519 stopped the fire-and-forget thread-title-gen call (
chat_with_system,tools: None) from draining the FIFO queue, but the orchestrator→researcher fan-out spec was left as its own known follow-up (see #4519 body: "the genuinely multi-agent specchat-harness-subagent(orchestrator→researcher) additionally needs itsFORCED_RESPONSESto cover the sub-agent's own model calls (or convert tollmKeywordRules, which are content-addressed and never shifted)").The residual failure mechanism: the delegated researcher runs its own agent-harness loop, so the mock's global FIFO can still be shifted by an off-turn call. The scripted final canary then lands on the wrong turn (or never renders). Keyword rules are matched by a case-insensitive substring of the latest user/tool message per call and are never consumed, so any extra ancillary call that doesn't match a rule falls through to the mock's dynamic default and leaves the scripted turns intact.
Solution
app/test/e2e/specs/chat-harness-subagent.spec.ts:llmForcedResponses→llmKeywordRulesin thebefore/afterhooks.PROMPT= "Please delegate a llama-research task and return the marker."llama-research"Task:\n" + DELEGATE_PROMPT(rendered byrender_structured_handoffinsrc/openhuman/agent_orchestration/tools/archetype_delegation.rs)coded marker phraserole: toolmsg content =RESEARCHER_REPLY(viaToolResult::success(outcome.output)insrc/openhuman/agent_orchestration/tools/dispatch.rs)researcher trace signalThe title-gen call from
app/src/store/threadSlice.tsshares the turn-1 probe; it matches thellama-researchrule and gets bothcontentandtool_callsback.chat_with_system(no tools) consumescontent("Delegating to researcher.") as the title and ignores unexpectedtool_calls— benign for both callers.Validated against the mock in isolation (in-process, non-stream and stream): each of the three turn shapes routes to its intended rule and returns the expected content/tool_calls; the final canary lands as the turn-2 response.
Submission Checklist
itblocks (canary in DOM, IN_FLIGHT drains afterchat_done, persisted thread JSONL contains the canary); failure-path signal is preserved viaexpect(content).toContain(CANARY_FINAL)and the ≥2 mock-hit count check.app/test/e2e/specs/, no productsrclines under coverage.## Related— N/A: no feature IDs affected.Closes #NNNin the## Relatedsection — N/A: this PR only fixes one of the two specs Desktop chat tool-round E2E specs fail: TinyAgents harness model-call sequence desyncs the mock forced-response queue #4517 tracks; leaving the issue open for the siblingchat-harness-subagent-continue.spec.tsfollow-up (details below).Impact
chat-harness-subagent.spec.ts) that test(e2e): stop ancillary LLM calls draining the mock forced-response queue (#4517) #4519 flagged as still-failing after its own mock-side gating landed. No runtime, product, or platform behavior change.Related
chat-harness-subagent-continue.spec.tsshares the same FIFO pattern and has an unresolved{{DYNAMIC_TASK_ID}}literal placeholder in the scriptedcontinue_subagenttool-call args (spec line 86). Converting it to keyword rules alone won't make it pass —continue_subagentneeds a realtask_id: sub-<uuid>from the last[SUBAGENT_AWAITING_USER]envelope. Options for a follow-up: extend the mock to substitute template placeholders from a message-history parse, or rework the spec to drive the resume via a different mechanism.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/4517-harness-subagent-keyword-rules0308a6cd1Validation Run
pnpm --filter openhuman-app format:check— prettier ✓ (spec re-formatted; no other files touched)pnpm typecheck—tsc --noEmitonapp/tsconfig.json✓ 0 errorsValidation Blocked
command:pnpm debug e2e app/test/e2e/specs/chat-harness-subagent.spec.tserror:Full WDIO/CEF chain needs a built Tauri bundle + Appium XCUITest (macOS) ortauri-driver(Linux); not runnable in this session's environment.impact:Runtime side of the fix (real agent harness making the /chat/completions calls) will be exercised by Release CI on this PR; in-process mock probes above prove the routing side.Behavior Changes
Parity Contract
itassertions (canary in DOM,IN_FLIGHTdrains, thread JSONL persists canary) are unchanged; only the scripting mechanism swaps FIFO → content-addressed rules.defaultStreamScripthandles{content, toolCalls}identically whether it came from the FIFO or from a keyword rule (scripts/mock-api/routes/llm.mjs:249-253and:264-268); the non-stream path likewise (:622-639vs:642-668).Duplicate / Superseded PR Handling
Summary by CodeRabbit