diff --git a/BlogWriter.Tests/ResearchStateTests.cs b/BlogWriter.Tests/ResearchStateTests.cs
index d090371..576c445 100644
--- a/BlogWriter.Tests/ResearchStateTests.cs
+++ b/BlogWriter.Tests/ResearchStateTests.cs
@@ -87,4 +87,22 @@ public void StartFollowUp_PreservesDraftAndResearchButResetsReviewCycle()
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);
+ }
}
diff --git a/Program.cs b/Program.cs
index eee65d8..99c79cd 100644
--- a/Program.cs
+++ b/Program.cs
@@ -194,7 +194,6 @@ void PrintResults(BlogSession session)
{
ResearchState result = session.State;
Console.WriteLine("\n\n========== RESULTS ==========");
- Console.WriteLine($"\nSession: {session.Id}");
Console.WriteLine($"\nTask: {result.MainTask}");
Console.WriteLine($"\n\n===Research Findings===\n ({result.ResearchFindings.Count}):");
foreach (string finding in result.ResearchFindings)
@@ -202,14 +201,17 @@ void PrintResults(BlogSession session)
Console.WriteLine($"- {finding}");
}
- Console.WriteLine($"\n\n===Draft:===\n{result.Draft}");
Console.WriteLine($"\n\n===Review Notes:===\n {result.ReviewNotes}");
- Console.WriteLine($"\n\nRevision Number: {result.RevisionNumber}");
+ Console.WriteLine($"\n\n===Draft:===\n{result.Draft}");
+
if (result.RevisionLimitReached)
{
Console.WriteLine("Note: Maximum revision limit reached; draft above printed as-is.");
}
Console.WriteLine("=============================");
+ Console.WriteLine($"\n\nRevision Number: {result.RevisionNumber}");
+ Console.WriteLine($"\nSession: {session.Id}\n");
+ Console.WriteLine("=============================");
}
diff --git a/ResearchState.cs b/ResearchState.cs
index 29514ec..e29a80a 100644
--- a/ResearchState.cs
+++ b/ResearchState.cs
@@ -23,17 +23,74 @@ public class ResearchState
/// Maximum target word count for the draft. Used by the author and reviewer stages.
public int MaxWords { get; set; } = DefaultMaxWords;
public List ResearchFindings { get; set; } = [];
+ public List SearchRefinements { get; set; } = [];
public string Draft { get; set; } = "";
public string ReviewNotes { get; set; } = "";
public int RevisionNumber { get; set; }
public string NextStep { get; set; } = "";
public string CurrentSubTask { get; set; } = "";
+ ///
+ /// Builds the full research query for a follow-up refinement. It keeps the
+ /// original task, includes the new refinement request, and folds in the
+ /// previously discovered research context so the next search narrows the
+ /// earlier work instead of replacing it.
+ ///
+ public string BuildResearchQuery()
+ {
+ var parts = new List();
+
+ 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);
+ }
+
/// Prepares an approved or revision-capped draft for a user-requested follow-up.
public void StartFollowUp(string followUp)
{
ArgumentException.ThrowIfNullOrWhiteSpace(followUp);
- CurrentSubTask = followUp.Trim();
+
+ 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 = "";
diff --git a/ResearcherAgent.cs b/ResearcherAgent.cs
index 05f41ff..e73dc6b 100644
--- a/ResearcherAgent.cs
+++ b/ResearcherAgent.cs
@@ -69,13 +69,17 @@ public async Task InvokeAsync(string query, CancellationToken cancellati
/// Research node that gathers information.
public async Task ResearchNodeAsync(ResearchState state, CancellationToken cancellationToken = default)
{
- string subTask = !string.IsNullOrEmpty(state.CurrentSubTask) ? state.CurrentSubTask : state.MainTask;
- _logger.LogInformation("Researching: {SubTask}", subTask);
+ string query = state.BuildResearchQuery();
+ string researchLabel = !string.IsNullOrWhiteSpace(state.CurrentSubTask)
+ ? state.CurrentSubTask
+ : state.MainTask;
+
+ _logger.LogInformation("Researching: {SubTask}", researchLabel);
string findings;
try
{
- findings = await InvokeAsync(subTask, cancellationToken);
+ findings = await InvokeAsync(query, cancellationToken);
string preview = findings.Length > 100 ? findings[..100] : findings;
_logger.LogInformation("Found: {Preview}...", preview);
}
@@ -86,8 +90,8 @@ public async Task ResearchNodeAsync(ResearchState state, Cancella
}
catch (Exception e)
{
- _logger.LogError(e, "Research failed for sub-task '{SubTask}'.", subTask);
- findings = $"Research on {subTask} - information gathered";
+ _logger.LogError(e, "Research failed for query '{Query}'.", query);
+ findings = $"Research on {researchLabel} - information gathered";
}
state.ResearchFindings.Add(findings);