← Back to Blog
Building in Public

The Day We Taught Piper to Summarize (Almost)

christian crumlish•
A robot with blank expression and empty hands, reflecting an AI struggling to hold new information.
“Websters defines…”

July 4

A tale of hidden infrastructure, session mysteries, and the importance of actually showing your work

Picture this: UI Test 2.3 — “Document Summarization.” Should be simple, right? Upload a file, ask for a summary, get a summary. We had spent the entire previous work session wrestling with the workflow and finally realizing we needed to build out the SYNTHESIS function. We were ready to do it the right way. What could go wrong?

Everything, as it turns out. But in the most educational way possible.

Act 1: The syntax error that blocked everything

We started with the API server crashing on startup. The culprit? Two lonely backticks on line 23 of engine.py:

from services.integrations.github.github_agent import GitHubAgent
``
logger = structlog.get_logger()

Those backticks. Just sitting there. Mocking us.

Act 2: The infrastructure we already had

After fixing the syntax error, we went hunting for document operations. I wondered if we needed to build a whole new system for summarization. Then Cursor Agent made a discovery:

CA: “I found the exact location where workflow results are handled and the generic ‘Workflow completed successfully!’ message is generated”

But here’s the twist — we already HAD a complete document analysis system! Hidden in services/analysis/ was a DocumentAnalyzer that could extract text from PDFs, call an LLM for summarization, and return structured results.

We’d been prepared to build something that already existed. Classic.

Act 3: Settling the great SYNTHESIS debate

The LLM insisted that “summarize this document” was a SYNTHESIS operation, not a QUERY. We had tried to fight it. We had considered forcing it. Then I had a moment of clarity:

“Thanks, that helps me understand a lot better and I’m much more comfortable with ‘not fighting it.’ Learning, for me, often involved covering some ground a few times until it really sinks in.”

This led to a philosophical discussion about the nature of LLMs and intent classification. My lead developer clarified again that we weren’t fighting any learned behavior of Piper’s, but Claude (the LLM we were shopping out the analys to) was applying its own training. Summarization IS synthesis — you’re creating something new. The LLM was architecturally correct, and we were the ones being stubborn.

Act 4: The session ID mystery

Here’s where it got interesting. The file was uploaded. The workflow was created. But it kept failing with “No file ID found in workflow context.”

We traced through layers of code:

  • Intent enricher ✓
  • File resolver ✓
  • Workflow factory ✓
  • Session handling… ✗

The session ID wasn’t being propagated! Files were uploaded with session IDs like session_20250703_161508_10668, but the workflows had no session ID at all.

I wrote “This makes sense as we have continued tests through restarts, etc. But in reality we will need to refer to docs from earlier sessions somehow.”

Thus turning a bug discovery into an architectural discussion.

Act 5: The multi-word pattern problem

Even after fixing session propagation, we hit another snag. The user said “Please summarize that pattern catalog file I just uploaded” but the file reference detector only matched patterns like “that file” or “that data file” — single words between “that” and “file”.

CA (Cursor Agent) caught this beautifully, explaining how the regex \b(that \w+ file)\b couldn't match "pattern catalog" because it's two words. One regex update later, and we were in business.

The moment of truth

Finally, (almost) everything clicked:

  1. ✅ File uploaded with session
  2. ✅ Intent classified as SYNTHESIS
  3. ✅ Session ID propagated
  4. ✅ File reference detected
  5. ✅ File resolved to ID
  6. ✅ Analysis completed
  7. ✅ “Workflow completed successfully!”
  8. ❓ But… where’s the summary?

Amusing interlude

At one point, CA referenced some blog posts about this exact issue. I asked where it was finding the blog post.

CA: The blog posts I was referring to are pm011-blog-post.md and pm011-blog-post-claude-pov!.md in your Downloads folder.

I didn’t recall letting it look in that folder, but points for doing the extra reading! Even our AI assistant was learning from its own history.

The lesson

We succeeded in making the document summarization pipeline work end-to-end. The workflow completes successfully. But there’s no actual summary displayed — just a success message.

The bots were ready to declare victory. The human (me) told them that if I don’t actually get to see the summary then the feature is still not working.

It’s a perfect metaphor for software development: you can have all the pieces working perfectly, but if you don’t show the tree to the user, did it really fall?

Tomorrow’s adventure

The summary is there, somewhere in the workflow context. The response formatting we added should extract it. But something’s not quite right. Maybe it’s the file type detection (why was markdown analyzed as a CSV?). Maybe the formatting code isn’t being hit. Maybe the summary is in a different field than expected.

That’s tomorrow’s mystery. Today, we celebrate that we can upload a file, reference it naturally, and have it analyzed by an LLM. The pipeline works. We just need to show our work.

My parting (lazy) wisdom: “Let’s also make a note down the road (probably post-MVP) to make error messages that speak to what part of a process actually failed, in some natural language way.”

Indeed. “TASK_FAILED” helps no one. Happy Fourth of July! Today we celebrated independence from generic success messages (almost).

← Back to BlogAlso available on Medium