My demo program is a multi-agent application that accepts a topic BlogAgent, researches that topic (ResearcherAgent) and then drafts a blog post (AuthorAgent). The post is then reviewed by another agent (ReviewerAgent) who either accepts the draft or sends it back to the Author with requested changes.

I wanted to allow the user to refine the search after getting the draft. To do so, I needed short term memory. Here are the changes I made…
I implemented the search refinement flow by updating the research state and wiring it into the research step:
- In
ResearchState.cs, I added:CurrentSubTaskto represent the newest follow-up requestSearchRefinementsto keep the history of prior refinement requests
SearchRefinements is a list<string>. As the ResearchState is created the code checks to see if there are refinements. If so the new refinements are appended to the existing request
if (SearchRefinements.Count > 0)
{
string uniqueRefinements = string.Join(" | ", SearchRefinements
.Where(r => !string.IsNullOrWhiteSpace(r))
.Distinct(StringComparer.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(uniqueRefinements))
{
parts.Add($"Refinement history: {uniqueRefinements}");
}
}
BuildResearchQuery()to assemble a single, richer research prompt that includes:- the original task
- the active follow-up refinement
- previous refinement history
- prior research findings/context
StartFollowUp()to preserve the existing draft/research state while resetting the review cycle and recording the previous subtask as a refinement
public void StartFollowUp(string followUp)
{
ArgumentException.ThrowIfNullOrWhiteSpace(followUp);
string trimmed = followUp.Trim();
if (!string.IsNullOrWhiteSpace(CurrentSubTask) &&
!string.Equals(CurrentSubTask, trimmed, StringComparison.OrdinalIgnoreCase))
{
SearchRefinements.Add(CurrentSubTask.Trim());
}
SearchRefinements.Add(trimmed);
CurrentSubTask = trimmed;
ReviewNotes = "";
RevisionNumber = 0;
NextStep = "";
}
- In
ResearcherAgent.cs, I changed the research node to usestate.BuildResearchQuery()for each research pass, so the agent searches with both the original goal and the refinement history instead of starting from scratch.
public string BuildResearchQuery()
{
var parts = new List<string>();
if (!string.IsNullOrWhiteSpace(MainTask))
{
parts.Add($"Original task: {MainTask.Trim()}");
}
if (!string.IsNullOrWhiteSpace(CurrentSubTask))
{
parts.Add($"Follow-up refinement: {CurrentSubTask.Trim()}");
}
if (SearchRefinements.Count > 0)
{
string uniqueRefinements = string.Join(" | ", SearchRefinements
.Where(r => !string.IsNullOrWhiteSpace(r))
.Distinct(StringComparer.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(uniqueRefinements))
{
parts.Add($"Refinement history: {uniqueRefinements}");
}
}
if (ResearchFindings.Count > 0)
{
string context = string.Join("\n\n", ResearchFindings
.Where(f => !string.IsNullOrWhiteSpace(f))
.Select(f => f.Trim()));
if (!string.IsNullOrWhiteSpace(context))
{
parts.Add($"Prior research context:\n{context}");
}
}
return string.Join("\n\n", parts);
}
- In
ResearchStateTests.cs, I added coverage confirming that:- a follow-up preserves the original task
- the refinement is included in the generated search query
- earlier research context is retained
[Fact]
public void StartFollowUp_PreservesDraftAndResearchButResetsReviewCycle()
{
var state = new ResearchState
{
MainTask = "topic",
ResearchFindings = ["finding"],
Draft = "draft",
ReviewNotes = ResearchState.ApprovedMarker,
RevisionNumber = ResearchState.MaxRevisions,
NextStep = "END",
};
state.StartFollowUp("Add a caching section.");
Assert.Equal("Add a caching section.", state.CurrentSubTask);
Assert.Equal("draft", state.Draft);
Assert.Equal(["finding"], state.ResearchFindings);
Assert.Empty(state.ReviewNotes);
Assert.Equal(0, state.RevisionNumber);
Assert.Empty(state.NextStep);
}
[Fact]
public void BuildResearchQuery_IncludesOriginalTopicAndFollowUpRefinement()
{
var state = new ResearchState
{
MainTask = "How to build a blog app",
ResearchFindings = ["Concepts: context, prompts, and workflow.", "Drafting is done after research."],
CurrentSubTask = "Add a caching section."
};
string query = state.BuildResearchQuery();
Assert.Contains("How to build a blog app", query);
Assert.Contains("Add a caching section.", query);
Assert.Contains("Concepts: context, prompts, and workflow.", query);
Assert.Contains("Drafting is done after research.", query);
}
Why this enables refining a search
The crucial behavior is that a follow-up no longer discards the earlier work. The generated query now looks like:
- Original task
- Follow-up refinement
- Prior refinement history
- Earlier research findings
That means the next search is “narrowed and contextualized” rather than replaced, which is the actual refinement behavior you were asking for.
Note: The initial draft of this post was written by BlogWriter and then edited by Jesse Liberty
Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.
