From 19c11ee3c0e9832f38f98077fde758017f785267 Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Wed, 17 Dec 2025 11:40:18 -0500 Subject: [PATCH 1/7] additional task tool --- dreadnode/agent/tools/tasking.py | 47 ++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index bc37f093..cafefbe7 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -1,7 +1,10 @@ +import typing as t + from loguru import logger +from pydantic import PrivateAttr from dreadnode.agent.reactions import Fail, Finish -from dreadnode.agent.tools.base import tool +from dreadnode.agent.tools.base import Toolset, tool, tool_method @tool @@ -9,14 +12,14 @@ async def finish_task(success: bool, summary: str) -> None: # noqa: ARG001, FBT """ Concludes the task by reporting a final status and a comprehensive summary. - This is the **final tool** to call when your planned sequence of actions is complete, \ - regardless of whether the outcome was successful. Use it when you have no more \ + This is the **final tool** to call when your planned sequence of actions is complete, + regardless of whether the outcome was successful. Use it when you have no more steps to take and are ready to present a final report. ## Best Practices - - Honest Status: The `success` flag must accurately reflect the final outcome. \ + - Honest Status: The `success` flag must accurately reflect the final outcome. If any part of the task failed or objectives were not met, it must be `False`. - - Comprehensive Summary: The `summary` is your final report. It must be a complete, \ + - Comprehensive Summary: The `summary` is your final report. It must be a complete, markdown-formatted document detailing all actions taken, tools used, and the results. Args: @@ -37,14 +40,14 @@ async def give_up_on_task(reason: str) -> None: """ Aborts the task when you are irrecoverably stuck and cannot make progress. - This tool is a last resort and should only be used when you have exhausted all \ - possible strategies and alternative approaches. It signals that you were unable \ + This tool is a last resort and should only be used when you have exhausted all + possible strategies and alternative approaches. It signals that you were unable to complete your assigned process. ## Best Practices - - Do Not Use for a Failed Outcome**: If the `finish_task` tool is available, use it to report failures. \ + - **Do Not Use for a Failed Outcome**: If the `finish_task` tool is available, use it to report failures. This tool is strictly for when you cannot *finish* your work. - - Provide a Clear Justification**: The `reason` must clearly explain why you are stuck. \ + - **Provide a Clear Justification**: The `reason` must clearly explain why you are stuck. Detail the final obstacle you could not overcome and the approaches you already tried. Args: @@ -56,3 +59,29 @@ async def give_up_on_task(reason: str) -> None: log_metric("task_give_up", 1) raise Fail("Agent gave up on the task.") + + +class TaskOutput(Toolset): + """ + Provides a stateful output buffer for accumulating task results. + + This toolset allows the agent to incrementally build up output across multiple + steps and tool calls, storing strings that can be retrieved later. + """ + + _outputs: list[str] = PrivateAttr(default_factory=list) + """Internal buffer storing accumulated output strings.""" + + @tool_method(catch=True, variants=["all"]) + async def output( + self, + content: t.Annotated[str, "The content to add to the output buffer."], + ) -> str: + """Adds content to the output buffer.""" + self._outputs.append(content) + return f"Output saved (total outputs: {len(self._outputs)})" + + @tool_method(catch=True, variants=["all"]) + async def get_output(self) -> list[str]: + """Lists all previously saved outputs in order.""" + return self._outputs \ No newline at end of file From 8b677a166c105bdb7152b80723d3170f1f9c4696 Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Wed, 17 Dec 2025 11:55:14 -0500 Subject: [PATCH 2/7] reverting some style edits --- dreadnode/agent/tools/tasking.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index cafefbe7..424f812b 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -12,14 +12,14 @@ async def finish_task(success: bool, summary: str) -> None: # noqa: ARG001, FBT """ Concludes the task by reporting a final status and a comprehensive summary. - This is the **final tool** to call when your planned sequence of actions is complete, - regardless of whether the outcome was successful. Use it when you have no more + This is the **final tool** to call when your planned sequence of actions is complete, \ + regardless of whether the outcome was successful. Use it when you have no more \ steps to take and are ready to present a final report. ## Best Practices - - Honest Status: The `success` flag must accurately reflect the final outcome. + - Honest Status: The `success` flag must accurately reflect the final outcome. \ If any part of the task failed or objectives were not met, it must be `False`. - - Comprehensive Summary: The `summary` is your final report. It must be a complete, + - Comprehensive Summary: The `summary` is your final report. It must be a complete, \ markdown-formatted document detailing all actions taken, tools used, and the results. Args: @@ -40,14 +40,14 @@ async def give_up_on_task(reason: str) -> None: """ Aborts the task when you are irrecoverably stuck and cannot make progress. - This tool is a last resort and should only be used when you have exhausted all - possible strategies and alternative approaches. It signals that you were unable + This tool is a last resort and should only be used when you have exhausted all \ + possible strategies and alternative approaches. It signals that you were unable \ to complete your assigned process. ## Best Practices - - **Do Not Use for a Failed Outcome**: If the `finish_task` tool is available, use it to report failures. + - **Do Not Use for a Failed Outcome**: If the `finish_task` tool is available, use it to report failures. \ This tool is strictly for when you cannot *finish* your work. - - **Provide a Clear Justification**: The `reason` must clearly explain why you are stuck. + - **Provide a Clear Justification**: The `reason` must clearly explain why you are stuck. \ Detail the final obstacle you could not overcome and the approaches you already tried. Args: @@ -84,4 +84,4 @@ async def output( @tool_method(catch=True, variants=["all"]) async def get_output(self) -> list[str]: """Lists all previously saved outputs in order.""" - return self._outputs \ No newline at end of file + return self._outputs From af58a524ab1c42ef0adb96e04d01b8dee0e84319 Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Wed, 17 Dec 2025 12:33:06 -0500 Subject: [PATCH 3/7] Update dreadnode/agent/tools/tasking.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dreadnode/agent/tools/tasking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index 424f812b..f4f04412 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -61,7 +61,7 @@ async def give_up_on_task(reason: str) -> None: raise Fail("Agent gave up on the task.") -class TaskOutput(Toolset): +class TaskOutputBuffer(Toolset): """ Provides a stateful output buffer for accumulating task results. From 766e6604dfbb6084de94e747d81ba31514ed2f9e Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Wed, 17 Dec 2025 12:33:24 -0500 Subject: [PATCH 4/7] Update dreadnode/agent/tools/tasking.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dreadnode/agent/tools/tasking.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index f4f04412..36a0d322 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -77,7 +77,18 @@ async def output( self, content: t.Annotated[str, "The content to add to the output buffer."], ) -> str: - """Adds content to the output buffer.""" + """ + Appends new content to the task's stateful output buffer. + + Use this method when you want to build up a longer or multi-part result + over several steps or tool calls, instead of returning everything at once. + Each call adds the provided `content` string to the existing buffer; + previously saved outputs are never overwritten or cleared by this method. + + The return value is a short confirmation message that includes the current + number of saved output entries, which can help you track how much content + has been accumulated so far. + """ self._outputs.append(content) return f"Output saved (total outputs: {len(self._outputs)})" From d0c118d72e9353e5f06d84fa86091005589a22d8 Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Wed, 17 Dec 2025 19:56:55 -0500 Subject: [PATCH 5/7] raja feedback --- dreadnode/agent/tools/tasking.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index 36a0d322..589c00ef 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -73,7 +73,7 @@ class TaskOutputBuffer(Toolset): """Internal buffer storing accumulated output strings.""" @tool_method(catch=True, variants=["all"]) - async def output( + async def add_output( self, content: t.Annotated[str, "The content to add to the output buffer."], ) -> str: @@ -96,3 +96,9 @@ async def output( async def get_output(self) -> list[str]: """Lists all previously saved outputs in order.""" return self._outputs + + @tool_method(catch=True, variants=["all"]) + async def clear_output(self) -> list[str]: + """Clears (deletes) all previously stored output. Warning, any cleared previously stored output is not recoverable once cleared.""" + self._outputs = [] + return "Output buffer cleared." From 8d6f0de9f120b978ac03ef03fd4fc9903bb15f87 Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Wed, 17 Dec 2025 19:59:11 -0500 Subject: [PATCH 6/7] raja feedback --- dreadnode/agent/tools/tasking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index 589c00ef..11ef3d9a 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -98,7 +98,7 @@ async def get_output(self) -> list[str]: return self._outputs @tool_method(catch=True, variants=["all"]) - async def clear_output(self) -> list[str]: - """Clears (deletes) all previously stored output. Warning, any cleared previously stored output is not recoverable once cleared.""" + async def clear_output(self) -> str: + """Clears (deletes) all previously stored output. Warning, any cleared stored output is not recoverable once cleared.""" self._outputs = [] return "Output buffer cleared." From db86333e80760cf87ad57ddd6d11863d99bbf8ce Mon Sep 17 00:00:00 2001 From: Michael Kouremetis Date: Thu, 18 Dec 2025 10:10:47 -0500 Subject: [PATCH 7/7] linting --- dreadnode/agent/tools/tasking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dreadnode/agent/tools/tasking.py b/dreadnode/agent/tools/tasking.py index 11ef3d9a..bb4373fc 100644 --- a/dreadnode/agent/tools/tasking.py +++ b/dreadnode/agent/tools/tasking.py @@ -96,7 +96,7 @@ async def add_output( async def get_output(self) -> list[str]: """Lists all previously saved outputs in order.""" return self._outputs - + @tool_method(catch=True, variants=["all"]) async def clear_output(self) -> str: """Clears (deletes) all previously stored output. Warning, any cleared stored output is not recoverable once cleared."""