Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions loopx/capabilities/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .value_connectors.catalog_entry import VALUE_CONNECTORS_CATALOG_ENTRY
from .explore.catalog_entry import EXPLORE_CATALOG_ENTRY
from .auto_research.catalog_entry import AUTO_RESEARCH_CATALOG_ENTRY
from .deep_research.catalog_entry import DEEP_RESEARCH_CATALOG_ENTRY
from .public_safe_outbound.catalog_entry import PUBLIC_SAFE_OUTBOUND_CATALOG_ENTRY
from .registry import CapabilityRegistry

Expand All @@ -49,6 +50,7 @@
VALUE_CONNECTORS_CATALOG_ENTRY,
EXPLORE_CATALOG_ENTRY,
AUTO_RESEARCH_CATALOG_ENTRY,
DEEP_RESEARCH_CATALOG_ENTRY,
PUBLIC_SAFE_OUTBOUND_CATALOG_ENTRY,
)
# Preserve the original import surface while routing all reads through the registry.
Expand Down
39 changes: 39 additions & 0 deletions loopx/capabilities/deep_research/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Deep research (evidence-ledger research loop)

`/loopx-deepresearch` turns one user question into a bounded, auditable research
session: question, source, claim, and contradiction ledgers live in
`.loopx/deepresearch/research.json`, the packet (`loopx deepresearch status`)
owns what to research next and when to stop, and the final report keeps every
citation resolvable to a recorded source.

## Boundary with auto-research

The built-in `auto-research` capability and this capability both do "bounded
research" but own different truths and must not be merged casually:

| | auto-research | deep-research (this) |
| --- | --- | --- |
| Unit of work | a LoopX **goal** with role-scoped workers | one **session ledger** in a project |
| State authority | goal todos, hypotheses, rollout events | `.loopx/deepresearch/` ledger |
| Progression | worker contract + terminal decision/review | packet expeditions + stop conditions |
| Output | promoted/retired hypotheses in canonical evidence | citation-auditable markdown report |
| Use when | open exploration inside the LoopX control plane | a single user question needing an auditable, source-cited answer |

Choose one per question; they do not share state and neither can close the
other's work.

## Lifecycle

`start` opens a run; `close` is the explicit terminal transition; the next
`start` archives the closed run (state + report) under
`.loopx/deepresearch/archive/<closed-at>/` and begins fresh. `start --new-run`
auto-closes only a run whose stop conditions already fired; an active run
always requires an explicit `close` first. State files are only ever rotated by
these typed transitions — never edited by hand.

## Layout

- Domain state machine and report: `loopx/deepresearch.py`
- CLI: `loopx/cli_commands/deepresearch.py` (`loopx deepresearch …`)
- Host entry: the `/loopx-deepresearch` skill facade installed by
`loopx slash-commands --install`
5 changes: 5 additions & 0 deletions loopx/capabilities/deep_research/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

from .catalog_entry import DEEP_RESEARCH_CATALOG_ENTRY

__all__ = ["DEEP_RESEARCH_CATALOG_ENTRY"]
112 changes: 112 additions & 0 deletions loopx/capabilities/deep_research/catalog_entry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from __future__ import annotations

from typing import Any


DEEP_RESEARCH_CATALOG_ENTRY: dict[str, Any] = {
"id": "deep-research",
"origin": "builtin",
"visibility": "public",
"provider_id": "loopx-core",
"documentation": {
"source_root": "loopx/capabilities/deep_research",
"site_root": "capabilities/deep-research",
"canonical": "README.md",
},
"title": "Evidence-ledger deep research loop",
"status": "experimental",
"real_world_anchor": (
"one user question driven by packet-cut expeditions over question, source, "
"claim, and contradiction ledgers, ending in a citation-auditable report"
),
"user_value": (
"Run a bounded, auditable research session per project: claims must come from "
"recorded sources, answers must cite recorded claims, and closeout is blocked "
"while any contradiction stays unresolved."
),
"entry_command": "loopx deepresearch start --question <text>",
"commands": [
{
"command": "loopx deepresearch start --question <text> [--max-sources N] [--new-run]",
"purpose": "Open a research run; archives the previous closed/stopped run instead of editing state files by hand.",
"write_boundary": ".loopx/deepresearch/ state and report files only",
},
{
"command": "loopx deepresearch status",
"purpose": "Emit the expedition packet: contract, ledgers, next expedition, stop conditions, evidence commands.",
"write_boundary": "read-only projection over the research ledger",
},
{
"command": "loopx deepresearch add-source --url-or-path <ref> --tool <tool> --claims-json <json>",
"purpose": "Record one consulted source and the claims extracted from it.",
"write_boundary": "one ledger transaction under the project research lock",

Check failure on line 42 in loopx/capabilities/deep_research/catalog_entry.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "one ledger transaction under the project research lock" 4 times.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaAqvgKGObA3UIzKZdlP&open=AaAqvgKGObA3UIzKZdlP&pullRequest=3453
},
{
"command": "loopx deepresearch add-subquestion --text <text> --from-claim <claim-id>",
"purpose": "Open a subquestion that derives from a recorded claim.",
"write_boundary": "one ledger transaction under the project research lock",
},
{
"command": "loopx deepresearch resolve-question --question-id <id> --answer <text> --evidence-claims <ids>",
"purpose": "Answer a recorded question citing recorded evidence claims; contradictions touching the evidence must be resolved in the same transition.",
"write_boundary": "one ledger transaction under the project research lock",
},
{
"command": "loopx deepresearch resolve-contradiction --contradiction-id <id> --sides-with <claim-id> --rationale <text>",
"purpose": "Close one open contradiction standalone with an explicit sides-with rationale.",
"write_boundary": "one ledger transaction under the project research lock",
},
{
"command": "loopx deepresearch close [--note <text>]",
"purpose": "Terminal transition marking this run closed and its ledger immutable; the next start archives it.",
"write_boundary": "marks the run closed; the next start performs the archive rotation",
},
{
"command": "loopx deepresearch report",
"purpose": "Render the citation-auditable markdown report from the ledgers.",
"write_boundary": "writes .loopx/deepresearch/report.md only",
},
],
"implemented_protocols": [
{
"schema_version": "loopx_deepresearch_state_v0",
"module": "loopx.deepresearch",
"doc": "loopx/capabilities/deep_research/README.md",

Check failure on line 74 in loopx/capabilities/deep_research/catalog_entry.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "loopx/capabilities/deep_research/README.md" 3 times.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaAqvgKGObA3UIzKZdlO&open=AaAqvgKGObA3UIzKZdlO&pullRequest=3453
},
{
"schema_version": "loopx_deepresearch_packet_v0",
"module": "loopx.deepresearch",
"doc": "loopx/capabilities/deep_research/README.md",
},
],
"smokes": [
"python3 -m pytest tests/test_deepresearch_command.py -q",
],
"docs": [
"loopx/capabilities/deep_research/README.md",
],
"boundaries": [
(
"Distinct from the auto-research capability: auto-research launches "
"role-scoped workers inside a LoopX goal for open exploration; "
"deep-research is a single-session, user-facing evidence ledger whose "
"truth is the .loopx/deepresearch state, not goal todos."
),
(
"The packet owns research progression and stop decisions; the model "
"executes bounded expeditions and records typed ledger transitions only."
),
(
"Claims exist only when a tool the agent actually ran produced them; "
"reports must keep every citation resolvable to a recorded source."
),
(
"One active run per project; finished or closed runs are archived under "
".loopx/deepresearch/archive/ by typed transitions, never by hand edits."
),
],
"next_real_step": (
"Keep the deep-research entrypoint as the single evidence-ledger authority and "
"reuse LoopX goal/todo/quota projections rather than adding a second scheduler."
),
}
11 changes: 11 additions & 0 deletions loopx/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
handle_project_command,
handle_project_lifecycle_command,
handle_pr_review_command,
handle_deepresearch_command,
handle_quota_command,
handle_ready_score_command,
handle_review_batch_command,
Expand Down Expand Up @@ -127,6 +128,7 @@
register_project_commands,
register_project_lifecycle_commands,
register_pr_review_command,
register_deepresearch_command,
register_quota_command,
register_ready_score_command,
register_review_batch_commands,
Expand Down Expand Up @@ -306,6 +308,7 @@ def build_parser() -> LoopXArgumentParser:
register_status_commands(sub, add_subcommand_format)
register_summary_all_command(sub, add_subcommand_format)
register_pr_review_command(sub, add_subcommand_format)
register_deepresearch_command(sub, add_subcommand_format)
register_slash_commands_command(sub, add_subcommand_format)
register_workflow_skills_command(sub, add_subcommand_format)
register_dreaming_commands(sub, add_subcommand_format)
Expand Down Expand Up @@ -810,6 +813,14 @@ def main(argv: list[str] | None = None) -> int:
if pr_review_result is not None:
return pr_review_result

deepresearch_result = handle_deepresearch_command(
args,
output_format=output_format,
print_payload=print_payload,
)
if deepresearch_result is not None:
return deepresearch_result

slash_commands_result = handle_slash_commands_command(
args,
output_format=output_format,
Expand Down
3 changes: 3 additions & 0 deletions loopx/cli_commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from .presentation import handle_presentation_command, register_presentation_commands
from .dash import handle_dash_command, register_dash_commands
from .pr_review import handle_pr_review_command, register_pr_review_command
from .deepresearch import handle_deepresearch_command, register_deepresearch_command
from .quota import handle_quota_command, register_quota_command
from .ready_score import handle_ready_score_command, register_ready_score_command
from .review_batch import handle_review_batch_command, register_review_batch_commands
Expand Down Expand Up @@ -177,6 +178,7 @@
"handle_project_lifecycle_command",
"handle_project_command",
"handle_pr_review_command",
"handle_deepresearch_command",
"handle_quota_command",
"handle_ready_score_command",
"handle_review_batch_command",
Expand Down Expand Up @@ -220,6 +222,7 @@
"register_project_lifecycle_commands",
"register_project_commands",
"register_pr_review_command",
"register_deepresearch_command",
"register_preset_commands",
"register_presentation_commands",
"register_dash_commands",
Expand Down
Loading
Loading