Commit graph

12 commits

Author SHA1 Message Date
Austin Schaefer
21030462b1 Prototype an agentic summarizer for max-turns recovery
Add summarize_partial_history: a one-shot writer-model pass that turns an
annotated transcript (tool calls with their args, so a fetch's URL or a
search's query stays attached to its result, plus results and interim
notes) into a proper footnote-style findings dump, instead of the flat
concatenation partial_findings_from_history produces on its own.

It's wired in as the MaxTurnsError recovery path in gather_findings, but
partial_findings_from_history stays as the fallback for an empty transcript
or if the summarizer call itself fails — the one guaranteed recovery path
shouldn't have a second turn-budget/model failure as a single point of
failure.

Observability: instrumented with the same #[tracing::instrument(fields(
gen_ai.agent.name = ...))] + spinner pattern as the researcher/reviewer/
writer phases, with info!/warn! events on success, empty-transcript
skip, and summarizer failure.

Also: cargo fmt across the crate (unrelated formatting drift had
accumulated), and adds unit tests for the new transcript_lines /
annotated_transcript_from_history helpers.
2026-08-18 11:54:23 +02:00
Austin Schaefer
e446046744 Add unit tests for the decomposed max-turns recovery helpers
Covers truncate, assistant_text, tool_result_text, and
partial_findings_from_history in isolation (fallback text, ordering,
truncation, and filtering out non-text content). gather_findings/
write_report/research still need a live ollama client and aren't covered
here.
2026-08-18 11:43:14 +02:00
Austin Schaefer
3ddc48fe18 Flatten partial_findings_from_history's nested for/match/if-let pyramid
Split into two filter_map-based helpers (assistant_text, tool_result_text)
and a shared truncate() so the extraction reads as a flat iterator chain
instead of four levels of nesting.
2026-08-18 11:30:45 +02:00
Austin Schaefer
187d327144 Write a partial report instead of erroring out when research hits max turns
When the researcher agent exhausts its turn budget mid-investigation, rig
now surfaces PromptError::MaxTurnsError with the chat history intact rather
than nothing at all. Catch it, reconstruct a findings dump from whatever
assistant text and tool results the run produced, and still write a report
from that — clearly flagged as incomplete — instead of propagating the
error and losing all the work.
2026-08-18 11:26:53 +02:00
Austin Schaefer
e1d6a20b3a Keep the writing-report spinner up until the stream's first chunk
Between sending the report prompt and the writer actually starting to
generate, the spinner was already dropped, so the terminal went blank
for however long that gap was. Thread the spinner into
write_text_stream instead and stop it right as the first chunk
arrives, covering the wait with the same "Writing report..." line
rather than clearing it early.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 13:21:23 +02:00
Austin Schaefer
5c50f75a8a Add emoji indicators for each research phase on the progress spinner
Tag each phase (research, web search, page fetch, review, rejection,
report writing) with a distinct emoji so the spinner line shows what's
happening at a glance. Search/fetch tool calls now update the active
spinner's message directly via a small shared handle, since they run
as plain tool functions without one threaded down to them otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:52:56 +02:00
Austin Schaefer
f53fcbd937 refactor: extract the streamed-text draining loop into its own module
write_text_stream() in the new stream.rs doesn't touch anything specific
to write_report (topic, findings, the agent) — it just drains a
MultiTurnStreamItem stream, writes each text chunk to a caller-provided
writer, and returns the accumulated string. Pulling it out lets it be
covered by unit tests against a mocked stream and an in-memory writer,
independent of a live model.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:37:11 +02:00
Austin Schaefer
b12f156a1b simplify: match the streamed Text item without destructuring its fields
No destructuring needed since only .text is used; matching the whole
Text struct and drops the now-unused rig::message::Text import.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:30:21 +02:00
Austin Schaefer
4f32dd83d6 perf: lock stdout once for the whole report stream, not per chunk
print!/println! each acquire stdout's lock internally; doing that per
streamed chunk in a tight loop adds needless contention. Lock once up
front and write!/writeln! through the held handle instead — which also
means the trailing newline must go through that same handle rather than
println!, since re-locking from the same thread would deadlock.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:20:35 +02:00
Austin Schaefer
469b7cf8c6 simplify: use print! + flush for streamed chunks, not an explicit stdout lock
print! already locks stdout per call, so the manual lock()/write!() was
extra ceremony over what the flush actually needed. Matches rig's own
cli_chatbot streaming example more closely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:19:08 +02:00
Austin Schaefer
55f4f7eeb3 feat: stream the report to the terminal as the writer generates it
Wires up starter.rs -> core.rs (CLI parsing and observability init moved
into core, main.rs left as a thin entry point) and switches the report
phase from Agent::prompt to rig's stream_prompt, printing each text
delta to stdout as it arrives instead of waiting for the full response.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 12:17:26 +02:00
Austin Schaefer
0bb5870f66 refactor: split into a Cargo workspace with per-project dependency sets
deep_research and swear_cleanup were sharing one Cargo.toml, so every
build compiled clap/indicatif/scraper/chrono (only needed by
deep_research) even when just building swear_cleanup for its own
course work, and vice versa. Moves each into its own workspace member
crate (deep_research/, swear_cleanup/) with an independent Cargo.toml
declaring only the deps it actually uses; common deps/versions are
pinned once via [workspace.dependencies] so the two don't drift.

Verified `cargo build -p swear_cleanup` alone no longer pulls in
clap/indicatif/scraper/chrono (schemars still compiles for it, but
that's a direct transitive dependency of rig itself, not something
this split can avoid). Also verified the relocated deep_research
binary still runs end-to-end against live Ollama with correct
footnote citations and sources.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 19:43:13 +02:00