Lightweight research harness (Rule 14): manages topic prompts, runs searches through the estate's own tools, keeps every repeatable step in code (search collection, JSON validation, retries, markdown emission) so the AI call is as small as necessary. Two modes by topics file: deep thinker research and recurring idea-scout over social/media sources.
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/research-sweep |
| Edition | 2021 |
| Targets | org-ask, tools-research-sweep, tools_research_sweep, cli_test, org_ask_cli_test |
| Public items | 37 across 0 modules |
| Tests | 30 |
What it is for
tools-research-sweep — lightweight research harness (Rule 14).
Code owns everything repeatable: topic/prompt assembly from TOML, search collection through the estate's own tools (tools-browser-test humanized_search), JSON-contract validation with bounded retries, markdown emission (overview + one doc per inverse-QA pair, the ingest shape tools-org-knowledge ingest-research expects), and the optional ingest hand-off. The AI call is a single small prompt per topic: preamble + topic + collected search results in, structured JSON out.
Capabilities
crate root
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub const OUTPUT_CONTRACT: & str |
fn write_files(out_dir : & Path, files : & (String, String)) -> Result <()> |
fn convert_bundle(raw : & str, keys : & String, source_line : & str,) -> Result <Vec <(String, String)>> |
pub const ASK_DISCIPLINE: & str |
fn ledger_line(date : & str, question : & str, answer : & str, routing : & serde_json::Value,) -> String |
Common
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct Common |
Config
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct Config |
Evidence
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
fn format_evidence(hits : & SearchHit) -> String |
pub struct Evidence |
fn parse_search_data(corpus : & str, raw : & str) -> Vec <Evidence> |
fn ask_prompt(question : & str, evidence : & Evidence, max_chars : usize,) ->(String, Vec <String>) |
fn entries_to_evidence(feed_name : & str, entries : & FeedEntry) -> Vec <Evidence> |
Exec
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub enum Exec |
Exec :: fn sys(timeout_secs : u64) -> Self |
Exec :: fn fixture(replies : Vec <Result <String, String>>) -> Self |
Exec :: async fn run(& self, program : & str, args : & String, stdin : Option <Vec <u8>>,) -> Result <String> |
Feed
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct Feed |
fn parse_feeds(text : & str) -> Result <FeedsFile> |
FeedEntry
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct FeedEntry |
fn extract_entries(xml : & str, max : usize) -> Vec <FeedEntry> |
FeedsFile
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct FeedsFile |
Finding
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct Finding |
Findings
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct Findings |
fn parse_findings(raw : & str) -> Result <Findings> |
fn emit_overview(f : & Findings, source_line : & str) -> String |
fn emit_files(key : & str, f : & Findings, source_line : & str) -> Vec <(String, String)> |
fn parse_bundle(raw : & str) -> Result <Vec <Findings>> |
InverseQa
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct InverseQa |
fn emit_qa(q : & InverseQa) -> String |
SearchHit
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct SearchHit |
fn parse_search_reply(raw : & str) -> Vec <SearchHit> |
SourceRef
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct SourceRef |
Topic
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct Topic |
fn parse_topics(text : & str) -> Result <TopicsFile> |
async fn run_topic(cfg : & Config, common : & Common, topic : & Topic, exec : & Exec,) -> Result <TopicResult> |
TopicResult
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct TopicResult |
TopicsFile
tools-research-sweep — lightweight research harness (Rule 14).
| Item |
|---|
pub struct TopicsFile |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
No public modules: the crate root is its whole surface.
Public surface
`crate root`
| Item | What it is |
|---|---|
pub struct TopicsFile | — |
pub struct Common | — |
pub struct Topic | — |
fn parse_topics(text : & str) -> Result <TopicsFile> | — |
pub struct Findings | — |
pub struct Finding | — |
pub struct SourceRef | — |
pub struct InverseQa | — |
pub const OUTPUT_CONTRACT: & str | The output contract appended to every prompt — the model returns ONLY this JSON object |
fn parse_findings(raw : & str) -> Result <Findings> | Strip an accidental markdown fence and parse the model reply. |
pub struct SearchHit | — |
fn parse_search_reply(raw : & str) -> Vec <SearchHit> | Extract hits from a tools-browser-test humanized_search JSON reply, wherever the results array sits in the envelope. |
fn format_evidence(hits : & SearchHit) -> String | — |
fn emit_overview(f : & Findings, source_line : & str) -> String | — |
fn emit_qa(q : & InverseQa) -> String | — |
fn emit_files(key : & str, f : & Findings, source_line : & str) -> Vec <(String, String)> | (relative file name, content) pairs for one topic's output. |
pub enum Exec | Subprocess seam: strict-argv via BoundedCommand, or a test fixture queue. |
Exec :: fn sys(timeout_secs : u64) -> Self | — |
Exec :: fn fixture(replies : Vec <Result <String, String>>) -> Self | — |
Exec :: async fn run(& self, program : & str, args : & String, stdin : Option <Vec <u8>>,) -> Result <String> | — |
pub struct Config | — |
pub struct TopicResult | — |
async fn run_topic(cfg : & Config, common : & Common, topic : & Topic, exec : & Exec,) -> Result <TopicResult> | — |
fn write_files(out_dir : & Path, files : & (String, String)) -> Result <()> | — |
fn parse_bundle(raw : & str) -> Result <Vec <Findings>> | Extract the research array from a workflow bundle ({"result":{"research": ...}}), a bare {"research":...}, or a bare array — the shapes the agentic pipeline produced before this tool existed. |
fn convert_bundle(raw : & str, keys : & String, source_line : & str,) -> Result <Vec <(String, String)>> | Convert a whole bundle to markdown files |
pub struct Evidence | One retrieved chunk of evidence for an ask() call. |
fn parse_search_data(corpus : & str, raw : & str) -> Vec <Evidence> | Parse tools-org-knowledge --json search output ({"data":{...}}) into evidence rows for one corpus. |
pub const ASK_DISCIPLINE: & str | The decision discipline the corpus itself prescribes, stated once in code so every answer is held to it. |
fn ask_prompt(question : & str, evidence : & Evidence, max_chars : usize,) ->(String, Vec <String>) | Assemble the single ask prompt from question + evidence |
fn ledger_line(date : & str, question : & str, answer : & str, routing : & serde_json::Value,) -> String | One ledger line per ask, so answers become scoreable claims (JSONL) |
pub struct FeedsFile | — |
pub struct Feed | — |
fn parse_feeds(text : & str) -> Result <FeedsFile> | — |
pub struct FeedEntry | — |
fn extract_entries(xml : & str, max : usize) -> Vec <FeedEntry> | Extract entries from an RSS (<item>) or Atom (<entry>) document |
fn entries_to_evidence(feed_name : & str, entries : & FeedEntry) -> Vec <Evidence> | Feed entries as ask-evidence: corpus news/<feed name>, capped summaries. |
No pub use re-exports: every item above is declared in this crate.
Boundary
Reaches into foundation.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-artifact-scaffold, tools-ask-ai-core, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/research-sweep |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_tools["tools"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-bounded-io` | foundation | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
anyhow | ^1 | — | no | always |
clap | ^4 | derive | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
tokio | ^1 | rt-multi-thread, macros | no | always |
toml | ^0.8 | — | no | always |
Development, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `tools-cli-conformance` | tools | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tempfile | ^3 | — | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["tools-research-sweep"] SELF -->|development| n_tools_cli_conformance["tools-cli-conformance"] SELF -->|runtime| n_foundation_bounded_io["foundation-bounded-io"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | org-ask | `src/bin/org-ask.rs` |
| bin | tools-research-sweep | `src/main.rs` |
| lib | tools_research_sweep | `src/lib.rs` |
| test | cli_test | `tests/cli_test.rs` |
| test | org_ask_cli_test | `tests/org_ask_cli_test.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 23 |
| Integration tests | 7 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 34 | 0 | 0 |
What the tests establish, by name:
convert_writes_real_files_from_a_bundle—tests/cli_test.rsrun_all_topics_succeed_writes_file_and_exits_zero—tests/cli_test.rsrun_counts_topic_failure_and_exits_nonzero—tests/cli_test.rsrun_with_missing_topics_file_fails_loud—tests/cli_test.rsdropped_count_is_evidence_minus_used—tests/org_ask_cli_test.rsledger_date_matches_real_utc_date_when_unspecified—tests/org_ask_cli_test.rsno_evidence_from_any_corpus_fails_loud—tests/org_ask_cli_test.rscivil_from_days_known_dates—src/bin/org-ask.rscli_conforms—src/bin/org-ask.rsask_evidence_parses_and_prompt_caps—src/lib.rsask_prompt_includes_evidence_exactly_at_the_budget_boundary—src/lib.rsbundle_converts_with_matching_keys—src/lib.rscap_body_trims_at_a_real_char_boundary_not_mid_char—src/lib.rscap_body_untouched_under_budget_and_boundary—src/lib.rsemit_qa_budget_is_additive_not_multiplicative—src/lib.rsemit_shapes_match_ingest_contract—src/lib.rsentries_to_evidence_leaves_summary_of_exactly_500_untouched—src/lib.rsentries_to_evidence_trims_at_a_real_char_boundary_not_mid_char—src/lib.rsentries_to_evidence_truncates_501_chars—src/lib.rsexec_sys_sets_timeout_and_4mib_output_cap—src/lib.rsfeeds_parse_rss_and_atom—src/lib.rsfindings_parse_tolerates_fences_and_chatter—src/lib.rsformat_evidence_empty_is_empty_nonempty_has_hits—src/lib.rspipeline_retries_bad_json_then_succeeds—src/lib.rsqa_body_caps_summary_never_resolution—src/lib.rssearch_reply_parses_nested_results—src/lib.rstopic_key_validation_rejects_each_condition_independently—src/lib.rstopics_parse_and_validate—src/lib.rswrite_files_writes_real_content_to_a_real_directory—src/lib.rscli_conforms—src/main.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 14 | 37 |
Public modules with a //! block | 0 | 0 |
pie showData
title Public items with rustdoc
"Documented" : 14
"No rustdoc detected" : 23
Metrics
| Metric | Value |
|---|---|
| Rust source files | 3 |
| Source lines | 1533 |
| Code lines | 1264 |
| Public API items | 37 |
| Public modules | 0 |
| Tests | 30 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 7 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 2
"enum" : 1
"function" : 17
"method" : 3
"struct" : 14
pie showData
title Rust source composition
"Code" : 1264
"Blank or comment" : 269
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.