Text Summarizer Agent

Overview

A text-summarization agent built on Google's Agent Development Kit, with an evaluation loop, an Obsidian second brain that answers "what did I learn on Tuesday?", clickable provenance on every answer, and optional read-only Gmail and web access.

What it does

Every turn is summarised into your vault as a linked note, and the note graph is browsable in Obsidian. Ask the same thing twice and the second time is answered from the vault with no model call at all — measured at 23.5 s for the first ask and 0.04 s for the repeat.

Every answer ends with a Sources block, and each cited note is a link that opens the actual file. The block is written in code, after the model answers: the model is asked only for the shape of a source line, and the vault name, the served model and the /vault/… href are all substituted afterwards. A note title that does not resolve to a real file is dropped rather than linked, because a link asserts the note exists.

Ask "What did you learn on Tuesday?" and it reads a day out of the vault instead of the conversation — so it covers notes written in sessions you never saw, and it costs no model call. That reader is also a command:

cd text_summarizer && uv run python -m text_summarizer.digest --date 2026-09-30

--json pipes cleanly into jq: every diagnostic goes to stderr, so stdout is nothing but the document.

Ask "me fala a temperatura para osasco amanhã" and it reports every daily metric for that place and day — humidity, rain chance, wind and gusts, UV, sunrise, evapotranspiration — from a keyless API. A name that is genuinely ambiguous is asked about rather than guessed: Springfield matches eight US cities, so the agent offers them instead of picking one, and the city, state and population it did resolve travel with the numbers so a wrong match is visible rather than confident.

Why it is built this way

The part most people find interesting is the evaluation loop: edit the agent's instructions in agent.py, run ADK evals, and watch response_match_score move. That is the actual point of the repo — everything else exists to make the loop measurable.

Two criteria, doing different jobs. tool_trajectory_avg_score is the hard gate: it is 1.0 or nothing, and it fails the moment the agent stops writing to the vault. response_match_score is ROUGE-1 word overlap — a way to catch wording regressions, not a measure of whether an answer got better. The auto-optimizer decides on the second one, which means a rewrite that deletes the persistence rules would score higher and get kept, so the loop refuses any rewrite that drops a rule before writing it.

End to end

Three recordings of the agent actually running, in the order the work happens. Same agent, same rules, same vault in all three — only the entry point differs. The screenshots are the dev UI at :8001 taken while a real turn was in flight; the panels are read back out of the live vault or transcribed from the code, never invented.

Main flow — text in, note out, repeat for free

6 scenes
Recording of the ADK dev UI running one summarization turn. The prompt sits
                    in the composer, then the event stream fills in with save_summary_to_second_brain
                    and log_conversation; the answer comes back as four bullets and a line naming
                    the note it saved. A second panel shows the model's own event with the
                    @@ADK_VAULT@@ and @@ADK_MODEL@@ sentinels next to the response the reader
                    actually gets, with the real vault and model substituted. The note that landed
                    on disk is shown with its frontmatter — date, fingerprint, generated_by_model,
                    generated_in_vault — and the same question asked again replays it in two
                    events with no model call.
What to watch: the cache key is hashed from the user's own message in code, which is why the last scene costs 0.04 s instead of half a minute — and why save_summary_to_second_brain never asks the model to repeat the text back. Open the GIF on its own.
  • 1 the prompt
  • 2 the turn runs
  • 3 the answer
  • 4 Sources written in code
  • 5 on disk, in the vault
  • 6 ask it again

Obsidian graph & look-up — retrieve before you answer

6 scenes
Recording and live-vault panels covering the second brain. The first panel
                    lists the real vault as it is on disk — dated notes under Second Brain, topic
                    stubs under Topics, the index and the chat log — with the rules for choosing
                    one vault. A second recording shows the agent calling search_text before it
                    answers. The notes it found are shown with their real titles, first bullets
                    and wiki links, followed by the citations in the real response, then what a
                    single save writes: one dated note, a stub per topic, one index line, one
                    chat-log entry. The last panel draws the graph those links make, with every
                    edge a real wikilink from a real note.
What to watch: retrieval happens before the summary, and the graph needs no maintenance — every save links the note to its topic stubs and the hub on its way past. The same files are what Obsidian opens; there is no plugin and no index to keep. Open the GIF on its own.
  • 1 one vault, real files
  • 2 rule 7: retrieve first
  • 3 the notes it found
  • 4 the citations
  • 5 one save, four writes
  • 6 the graph it grows

Gmail summarization — read-only inbox, same second brain

5 scenes
Recording and panels covering a Gmail turn. The first panel lists the three
                    GOOGLE_ variables and the read-only gmail.readonly scope, and the four tools
                    they add. A recording then shows the question being asked and the real
                    searches the agent builds: gmail_get_latest_messages, then gmail_search with
                    a subject: query and a from: query. The answer says plainly that no manager
                    mail was found rather than inventing any, and the last panel shows the turn
                    being saved through the same second-brain path as any other.
What to watch: nothing is special-cased. An inbox turn reuses the same summarizer, the same rules and the same vault; the only difference is which tools ran first. This recording's inbox had no manager mail, and the honest answer is the point — rule 11 forbids inventing email content. Open the GIF on its own.
  • 1 read-only, or nothing
  • 2 a question about the inbox
  • 3 the phrasing picks the query
  • 4 nothing found is reported
  • 5 saved like any other turn
Recorded, not staged: tools/capture_ui.py drives the real dev UI in Firefox and screenshots it mid-turn, and tools/build_gifs.py assembles the frames — reading the notes, topics and links it shows straight out of the live vault. Re-record with sh tools/check_stack.sh first; that script exists because a half-restarted stack produces a plausible recording of a broken run.