The Art of Deduplication: How One Message Solved an Autonomous Agent's Double-Response Problem
In the complex world of autonomous LLM-driven agents, few problems are as insidious as the duplicate response. When an agent speaks twice about the same observation, it pollutes the context window, confuses the model, and erodes operator trust. Message [msg 4967] captures a pivotal moment in the debugging of precisely such a problem—a moment where the assistant, after an extensive investigation, crystallized its understanding of the root cause and began implementing a surgical fix.
The Context: An Autonomous Fleet Management Agent
The broader system under development was an LLM-powered agent responsible for managing a fleet of GPU proving instances on vast.ai. This agent ran on a 5-minute systemd timer, observed cluster state, made scaling decisions, and communicated with human operators through a persistent conversation log stored in SQLite. The architecture was ambitious: the agent had tools to launch and destroy instances, diagnose machine health, send alerts, and maintain a rolling memory of operator preferences.
But with complexity came fragility. The user had reported "double responses"—the agent appeared to respond twice to the same input, creating duplicate entries in the conversation history. This wasn't merely a cosmetic issue; duplicate responses meant the LLM's context window was filling with redundant information, crowding out genuinely useful observations and tool outputs. In the worst case, it could lead to context overflow, where the prompt exceeded the model's token limit and the agent stopped functioning entirely.
The Investigation: Tracing the Duplicate Trail
The assistant's investigation into the duplicate responses (spanning [msg 4961] through [msg 4964]) was thorough and methodical. It began by examining systemd logs for overlapping agent runs, checking whether the timer-based trigger and the path-unit trigger (which fired on state-change events) were colliding. The logs revealed that runs were occasionally overlapping—run #72 and #73 completed within seconds of each other at 20:34, before the file lock was fully deployed.
But the deeper issue was subtler. Even within a single run, the agent was producing multiple assistant messages. The pattern was visible in the conversation history: the agent would emit "Agent Reasoning" blocks (intermediate narrative text describing its thought process), then issue tool calls, then produce a final response. Both the intermediate reasoning blocks and the final response were being stored as assistant messages with the same run_id. When the LLM context was reconstructed for the next run, all of these messages were included—meaning the model saw both the intermediate speculation and the final answer, effectively processing the same input twice.
The assistant's investigation also uncovered that the file lock mechanism had a permission issue (the lock file was in a root-owned directory while the agent sometimes ran as a non-root user), and that stale lock files could persist after crashes. These were infrastructure problems, but the core logical problem was the duplicate assistant messages within a single run.
The Subject Message: A Decision Point
Message [msg 4967] is where the investigation transitions into implementation. The assistant states:
Need compute last assistant per run and skip earlier assistant tool_call messages. Also prompt rule. Let's edit.
This sentence is dense with meaning. It encapsulates two distinct fixes:
Fix 1: Compute last assistant per run. The assistant recognized that for each run_id, only the final assistant message should be included in the LLM context. All earlier messages from the same run—the intermediate reasoning blocks, the tool-calling narration, the speculative analysis—should be suppressed. This is a deduplication strategy at the context-assembly level: when building the prompt for the next agent cycle, the system should scan for assistant messages grouped by run_id and keep only the last one.
Fix 2: Add a prompt rule. Beyond filtering at the context level, the assistant also recognized the need to prevent the problem at the source. By adding a rule to the system prompt instructing the agent to avoid narrative text during tool-calling steps, the assistant aimed to reduce the production of duplicate messages in the first place. This is a classic prompt engineering intervention—shaping model behavior through explicit instruction rather than post-hoc filtering.
The phrase "Let's edit" signals that the assistant is about to implement these changes. It reads the file at the prompt rules section (around line 392) to understand the current state of the system prompt before making modifications.
The Knowledge Required
To understand this message, one needs several layers of context:
- The agent architecture: The agent runs as a Python script (
vast_agent.py) that calls a Go API backend (vast-manager). Conversation history is stored in SQLite and reconstructed into an LLM prompt on each cycle. - The run_id system: Each agent invocation gets a
run_id. Messages from the same invocation share this ID, allowing the system to group and filter them. - The context assembly pipeline: The function at line 1500 of
vast_agent.pybuilds the LLM messages array from the conversation history. This is where the deduplication logic would be inserted. - The prompt structure: The system prompt at lines 392-398 contains critical behavioral rules. The assistant needed to read this section to know where to add the new "no narrative during tool calls" rule.
- The duplicate problem: Prior messages established that the agent was producing both intermediate reasoning text and final responses within the same run, both being stored as assistant messages.
Assumptions and Potential Mistakes
The assistant made several assumptions in this message:
- That the last assistant message per run is the "correct" one. This assumes that intermediate messages are always inferior to the final response. In practice, this is reasonable—the final response incorporates the results of tool calls and represents the agent's concluded analysis. However, there's a risk: if a run produces only intermediate messages (e.g., it crashes before producing a final response), the filtering logic would need a fallback.
- That prompt rules are effective at changing model behavior. The assistant assumes that adding a rule like "Do not produce narrative text during tool-calling steps" will actually prevent the model from doing so. LLM behavior is stochastic, and prompt rules are guidelines, not guarantees. The post-hoc filtering (Fix 1) is the reliable mechanism; the prompt rule (Fix 2) is a best-effort optimization.
- That the file read at line 392 is the right place to add the rule. The assistant read the prompt rules section to understand the existing structure before editing. This is a sound approach, but it assumes that the prompt rules section is the appropriate location for the new instruction rather than, say, the system prompt preamble or a dedicated behavioral section.
The Output Knowledge Created
This message, though brief, creates significant output knowledge:
- A confirmed diagnosis: The duplicate response problem is caused by multiple assistant messages per run, not by parallel agent invocations (though that was a contributing factor).
- A two-pronged fix strategy: The problem requires both context-level filtering (deduplication at assembly time) and source-level prevention (prompt engineering).
- A specific implementation target: The assistant knows exactly where to edit—the context assembly function around line 1500 and the prompt rules section around line 392.
- A prioritization decision: The assistant chooses to "Implement in Python first" ([msg 4965]), meaning the deduplication logic will be implemented in the Python agent script before any Go backend changes. This is a pragmatic choice: the Python script is where the context is assembled, so that's where the fix belongs.
The Broader Significance
Message [msg 4967] illustrates a fundamental challenge in building reliable autonomous agents: the tension between expressiveness and determinism. The agent's "Agent Reasoning" blocks were designed to provide transparency—letting operators see what the model was thinking. But this very transparency created a feedback loop where the model's own reasoning was fed back into its context, causing confusion and context bloat.
The solution—filtering intermediate messages and prompting the model to be more concise—represents a mature understanding of LLM behavior. It acknowledges that models cannot reliably self-regulate their output structure, so the system must enforce structure at the infrastructure level. This is the same lesson that underlies techniques like chain-of-thought parsing, structured output formats, and tool-use frameworks: trust the model for content, but trust the system for structure.
The message also demonstrates the value of incremental, evidence-driven debugging. The assistant didn't guess at the cause of duplicate responses; it examined logs, parsed conversation histories, and traced the flow of messages through the system. Only when the pattern was empirically confirmed did it move to implementation. This discipline—investigate first, implement second—is the hallmark of reliable systems engineering, whether the system is a distributed database or an LLM-powered agent.
Conclusion
Message [msg 4967] is a small but pivotal moment in a much larger story of building a reliable autonomous agent. It captures the transition from investigation to implementation, from understanding the problem to designing the solution. The fix it initiates—deduplicating assistant messages and adding behavioral prompt rules—would go on to be deployed and verified, contributing to a more stable and trustworthy fleet management system. In the broader arc of the conversation, this message represents the kind of careful, principled engineering that separates hobby projects from production systems: the willingness to trace a bug to its root cause, design a targeted fix, and implement it with precision.