[GH-ISSUE #7193] [Feature] Investigation-loop construct for agent-driven deep dives (loop engineering) #4299

Open
opened 2026-10-31 00:48:39 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @reynold-hu on GitHub (Oct 26, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7193

[Feature] Investigation-loop construct for agent-driven deep dives (loop engineering)

Problem

REA gives the driving agent one-shot tools. The decision "I am done" is made
by the host agent itself, and it usually stops at the first plausible answer.
Depth today depends on a human nagging the agent to dig deeper into a point —
push, push, push — and most of the time the dig still ends too shallow.

This is not a missing analysis tool. It is a missing loop construct. There is
no REA-owned object that records what an investigation set out to prove, what
it has actually established, and what is still unproven — so nothing in the
system can confront the agent with its own unfinished work before it declares
the job done.

Proposed scope (Phase 1): state plus enforcement, no driving

Phase 1 adds one pure domain module, src/domain/investigation/, with no tool
execution, no MCP wiring, no persistence, and no autonomous round-driving. It
defines the investigation object and enforces its rules; the driving agent
still calls tools and reports back. Autonomous round-driving is a later phase
built on top of this object, not smuggled into it.

The object:

  • Goal — the question the investigation must answer, as one statement.
  • Hypotheses — claims under investigation, each open, supported, or
    refuted.
  • Evidence ledger — entries with an id, a one-line summary, a source, and
    a kind (observation, derivation, inference). Full evidence lives
    behind the id, not in the ledger.
  • Gaps — unanswered questions, each blocking one or more hypotheses.
  • Depth budget — caller-set caps on rounds, hypotheses, and evidence,
    with absolute caps (1000 rounds, 1000 hypotheses, 10000 evidence entries).
  • Stopping criteria — goal_answered, abandoned, budget_exhausted.

The enforcement, stated plainly because it is the whole point:

  • A hypothesis can only be decided (supported or refuted) by citing evidence
    ids that already exist in the ledger. No evidence, no decision. An agent
    cannot hand-wave a hypothesis away.
  • A gap can only be resolved by citing ledger evidence.
  • requestStop with goal_answered throws while any hypothesis is undecided
    or any gap is open. The only way to stop with open items is abandoned,
    which leaves the open items visible in the record instead of silently
    dropping them.
  • confrontation returns exactly what is still unproven — ids and short
    statements, not full evidence bodies — so confronting the agent with its own
    unfinished work costs few tokens.

Operations: createInvestigation, addHypothesis, addEvidence, addGap,
decideHypothesis, resolveGap, recordRound, confrontation,
requestStop. Every function takes an investigation state and returns a new
validated state; state is data, not a live session.

Result model and Evidence mapping

The module deliberately does not call createAnalysisExecution and does not
construct Evidence records. Provider wiring — including which operation owns
this result — is a maintainer decision for the integration PR. For planning
purposes, the intended mapping is data-only: confidence derived, authority
analyst-inference, no execution environment. Nothing here executes a target,
so this feature is independent of the Unicorn emulation direction in #7142.

What Phase 1 deliberately does not do

  • No autonomous round-driving: the module does not execute tools, prompt the
    agent, or decide the next step by itself.
  • No MCP/application wiring and no persistence across sessions.
  • No hypothesis reversal: a decided hypothesis is terminal in Phase 1.
    Reversing a decision requires a new hypothesis that cites the new evidence;
    a supersede operation is a later phase.
  • No verdict model: this is not #4972's semantic-equivalence verdict. It is
    the loop that would make a deep investigation auditable, whatever the
    verdict mechanism turns out to be.

The token tension, designed in rather than patched later

Loops burn tokens. That is the same token-bloat concern behind #6426 and
#7109, and it is why budgets and stopping criteria are part of this design
from the start, not an afterthought:

  • Rounds are the loop's normal end condition: the round that exhausts
    max_rounds stops the investigation with budget_exhausted.
  • Hypothesis and evidence caps are guardrails that throw on overflow.
  • confrontation is token-cheap by construction: ids and short statements,
    never full evidence bodies. This follows the repo's token-first performance
    rule (smallest sufficient answer, necessary provenance, full Evidence
    accessible behind the id).

A later round-driving phase must report measured token usage per investigation
before it can claim the loop is economical, not just structural.

Gap verification (done before proposing)

  • Tree: no investigation-loop construct exists. src/domain/ has no
    investigation state machine; the word "investigation" in the tree is
    provider-identity constants and contract prose, and the "hypothesis"
    mentions are static/runtime correlation descriptions, not a loop object.
    The new module's identifiers (confrontation, requestStop,
    investigationStateSchema) occur nowhere else in src/.
  • Issues: search for "investigation loop" in morluto/rea returns 0 results.
  • Zero new dependencies: zod is already in the tree.

Validation (local, this proposal's own evidence)

  • Unit tests: 21/21 pass (src/domain/investigation/investigationLoop.test.ts).
  • Boundary tests: 6/6 pass (tests/boundary/investigation/), covering
    exact-cap acceptance, over-cap rejection, minimum-limit violations,
    absolute-cap violations, and stopping on the first round when
    max_rounds is 1.
  • Real-behavior script: 5/5 checks pass
    (scripts/verify-investigation-loop.mjs): a two-round investigation with
    2 hypotheses decided and 1 gap resolved; goal_answered blocked while
    items are open, allowed once clear.
  • Type check: scoped tsc --strict on the module is clean. Full-project
    tsc --noEmit could not complete on the contributor VM (killed, likely
    OOM); full check:pr must run before any PR leaves draft. This proposal
    also fixed one real type error found during that check: budgetSchema
    used .default({}), which zod v4 rejects at the type level because the
    output fields are required; it now uses .prefault({}) with identical
    runtime behavior, and all 27 tests still pass.

Open questions for the scope decision

  1. Placement: does this belong as a domain service, or as an auxiliary
    operation set alongside the existing session tooling?
  2. Operation naming: is confrontation the right name for the
    still-unproven report, or should it read as a status/check operation?
  3. Persistence: should a later phase persist investigations across sessions,
    or is the loop strictly per-session state?
  4. Round-driving: when the driving phase arrives, who owns the next-step
    decision — REA, or the host agent with REA surfacing the confrontation?
  5. Supersede: is hypothesis reversal a needed operation, or is
    new-hypothesis-cites-new-evidence sufficient?
  6. Confrontation detail: are ids plus short statements enough, or do some
    consumers need evidence summaries inline?

Scope decision requested

Per the contribution scope rule, this proposal asks for a maintainer scope
decision before any integration work: is a REA-owned investigation loop the
right shape for the "dig deeper" problem, and is the Phase 1 boundary above
(state plus enforcement, no driving, no wiring) the right first slice? The
Phase 1 implementation is ready locally with tests passing, and a draft PR
can follow once the direction is confirmed.

Originally created by @reynold-hu on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7193 # [Feature] Investigation-loop construct for agent-driven deep dives (loop engineering) ## Problem REA gives the driving agent one-shot tools. The decision "I am done" is made by the host agent itself, and it usually stops at the first plausible answer. Depth today depends on a human nagging the agent to dig deeper into a point — push, push, push — and most of the time the dig still ends too shallow. This is not a missing analysis tool. It is a missing loop construct. There is no REA-owned object that records what an investigation set out to prove, what it has actually established, and what is still unproven — so nothing in the system can confront the agent with its own unfinished work before it declares the job done. ## Proposed scope (Phase 1): state plus enforcement, no driving Phase 1 adds one pure domain module, `src/domain/investigation/`, with no tool execution, no MCP wiring, no persistence, and no autonomous round-driving. It defines the investigation object and enforces its rules; the driving agent still calls tools and reports back. Autonomous round-driving is a later phase built on top of this object, not smuggled into it. The object: - **Goal** — the question the investigation must answer, as one statement. - **Hypotheses** — claims under investigation, each `open`, `supported`, or `refuted`. - **Evidence ledger** — entries with an id, a one-line summary, a source, and a kind (`observation`, `derivation`, `inference`). Full evidence lives behind the id, not in the ledger. - **Gaps** — unanswered questions, each blocking one or more hypotheses. - **Depth budget** — caller-set caps on rounds, hypotheses, and evidence, with absolute caps (1000 rounds, 1000 hypotheses, 10000 evidence entries). - **Stopping criteria** — `goal_answered`, `abandoned`, `budget_exhausted`. The enforcement, stated plainly because it is the whole point: - A hypothesis can only be decided (supported or refuted) by citing evidence ids that already exist in the ledger. No evidence, no decision. An agent cannot hand-wave a hypothesis away. - A gap can only be resolved by citing ledger evidence. - `requestStop` with `goal_answered` throws while any hypothesis is undecided or any gap is open. The only way to stop with open items is `abandoned`, which leaves the open items visible in the record instead of silently dropping them. - `confrontation` returns exactly what is still unproven — ids and short statements, not full evidence bodies — so confronting the agent with its own unfinished work costs few tokens. Operations: `createInvestigation`, `addHypothesis`, `addEvidence`, `addGap`, `decideHypothesis`, `resolveGap`, `recordRound`, `confrontation`, `requestStop`. Every function takes an investigation state and returns a new validated state; state is data, not a live session. ## Result model and Evidence mapping The module deliberately does not call `createAnalysisExecution` and does not construct Evidence records. Provider wiring — including which operation owns this result — is a maintainer decision for the integration PR. For planning purposes, the intended mapping is data-only: confidence `derived`, authority `analyst-inference`, no execution environment. Nothing here executes a target, so this feature is independent of the Unicorn emulation direction in #7142. ## What Phase 1 deliberately does not do - No autonomous round-driving: the module does not execute tools, prompt the agent, or decide the next step by itself. - No MCP/application wiring and no persistence across sessions. - No hypothesis reversal: a decided hypothesis is terminal in Phase 1. Reversing a decision requires a new hypothesis that cites the new evidence; a supersede operation is a later phase. - No verdict model: this is not #4972's semantic-equivalence verdict. It is the loop that would make a deep investigation auditable, whatever the verdict mechanism turns out to be. ## The token tension, designed in rather than patched later Loops burn tokens. That is the same token-bloat concern behind #6426 and #7109, and it is why budgets and stopping criteria are part of this design from the start, not an afterthought: - Rounds are the loop's normal end condition: the round that exhausts `max_rounds` stops the investigation with `budget_exhausted`. - Hypothesis and evidence caps are guardrails that throw on overflow. - `confrontation` is token-cheap by construction: ids and short statements, never full evidence bodies. This follows the repo's token-first performance rule (smallest sufficient answer, necessary provenance, full Evidence accessible behind the id). A later round-driving phase must report measured token usage per investigation before it can claim the loop is economical, not just structural. ## Gap verification (done before proposing) - Tree: no investigation-loop construct exists. `src/domain/` has no investigation state machine; the word "investigation" in the tree is provider-identity constants and contract prose, and the "hypothesis" mentions are static/runtime correlation descriptions, not a loop object. The new module's identifiers (`confrontation`, `requestStop`, `investigationStateSchema`) occur nowhere else in `src/`. - Issues: search for "investigation loop" in morluto/rea returns 0 results. - Zero new dependencies: zod is already in the tree. ## Validation (local, this proposal's own evidence) - Unit tests: 21/21 pass (`src/domain/investigation/investigationLoop.test.ts`). - Boundary tests: 6/6 pass (`tests/boundary/investigation/`), covering exact-cap acceptance, over-cap rejection, minimum-limit violations, absolute-cap violations, and stopping on the first round when `max_rounds` is 1. - Real-behavior script: 5/5 checks pass (`scripts/verify-investigation-loop.mjs`): a two-round investigation with 2 hypotheses decided and 1 gap resolved; `goal_answered` blocked while items are open, allowed once clear. - Type check: scoped `tsc --strict` on the module is clean. Full-project `tsc --noEmit` could not complete on the contributor VM (killed, likely OOM); full `check:pr` must run before any PR leaves draft. This proposal also fixed one real type error found during that check: `budgetSchema` used `.default({})`, which zod v4 rejects at the type level because the output fields are required; it now uses `.prefault({})` with identical runtime behavior, and all 27 tests still pass. ## Open questions for the scope decision 1. Placement: does this belong as a domain service, or as an auxiliary operation set alongside the existing session tooling? 2. Operation naming: is `confrontation` the right name for the still-unproven report, or should it read as a status/check operation? 3. Persistence: should a later phase persist investigations across sessions, or is the loop strictly per-session state? 4. Round-driving: when the driving phase arrives, who owns the next-step decision — REA, or the host agent with REA surfacing the confrontation? 5. Supersede: is hypothesis reversal a needed operation, or is new-hypothesis-cites-new-evidence sufficient? 6. Confrontation detail: are ids plus short statements enough, or do some consumers need evidence summaries inline? ## Scope decision requested Per the contribution scope rule, this proposal asks for a maintainer scope decision before any integration work: is a REA-owned investigation loop the right shape for the "dig deeper" problem, and is the Phase 1 boundary above (state plus enforcement, no driving, no wiring) the right first slice? The Phase 1 implementation is ready locally with tests passing, and a draft PR can follow once the direction is confirmed.
Author
Owner

@XinwangTao commented on GitHub (Oct 26, 2026):

赞同这个方向:把"什么时候算 done"从 host agent 的主观判断变成 REA 自己可强制的状态对象,是治浅层挖掘的正确抽象——one-shot 工具 + 无状态决策是根因。看到 Phase 1 已经落成 domain module(commit 9630917),补充两个设计点供参考:1) 建议给 investigation 加显式的生命周期状态(open -> investigating -> concluded),stopping criteria 才能区分"正常收敛"与"被迫中断",避免 agent 在无 hypothesis 时无限空转;2) gaps 与 hypotheses 的 blocking 关系建议用显式引用而非文本描述,这样收敛判定可以机械化为"所有 open hypothesis 的 blocking gaps 均已关闭"。期待后续 phase 的 autonomous round-driving 能直接构建在这个对象上。

<!-- gh-comment-id:6411250867 --> @XinwangTao commented on GitHub (Oct 26, 2026): 赞同这个方向:把"什么时候算 done"从 host agent 的主观判断变成 REA 自己可强制的状态对象,是治浅层挖掘的正确抽象——one-shot 工具 + 无状态决策是根因。看到 Phase 1 已经落成 domain module(commit 9630917),补充两个设计点供参考:1) 建议给 investigation 加显式的生命周期状态(open -> investigating -> concluded),stopping criteria 才能区分"正常收敛"与"被迫中断",避免 agent 在无 hypothesis 时无限空转;2) gaps 与 hypotheses 的 blocking 关系建议用显式引用而非文本描述,这样收敛判定可以机械化为"所有 open hypothesis 的 blocking gaps 均已关闭"。期待后续 phase 的 autonomous round-driving 能直接构建在这个对象上。
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4299
No description provided.