Skip to content

Commit 5feff2f

Browse files
KavyaSree2610Kavya Sree Kaitepalli
andauthored
feat: add WritingBotRunner and base contracts for agent runners (#167)
* feat: add WritingBotRunner and base contracts for agent runners * fix: update MemoryTool reference and improve test context handling for WritingBotRunner --------- Co-authored-by: Kavya Sree Kaitepalli <kkaitepalli@microsoft.com>
1 parent 69e2f75 commit 5feff2f

4 files changed

Lines changed: 445 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Agent runner abstractions for the auto_memory package.
2+
3+
The only concrete runner that imports WritingBot lives in
4+
``writing_bot_runner`` so the core framework stays decoupled from the
5+
concrete bot implementation.
6+
7+
Exported names:
8+
9+
* ``IterationContext`` — immutable context passed to every :class:`AgentRunner`.
10+
* ``AgentResult`` — normalised result returned by every runner.
11+
* ``AgentRunner`` — structural protocol satisfied by any runner with a matching
12+
``run`` method.
13+
"""
14+
15+
from microbots.auto_memory.runners.base import AgentResult, AgentRunner, IterationContext
16+
17+
__all__ = ["IterationContext", "AgentResult", "AgentRunner"]
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Base contracts for agent runners: IterationContext, AgentResult, AgentRunner."""
2+
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass
6+
from typing import Protocol, runtime_checkable
7+
8+
from microbots.auto_memory.data_models import IterationStatus
9+
10+
11+
@dataclass(frozen=True)
12+
class IterationContext:
13+
"""Immutable context for one agent invocation.
14+
15+
Attributes
16+
----------
17+
task : str
18+
The task prompt to send to the agent.
19+
memory_dir : str
20+
Host-side directory that is both mounted into the agent container
21+
(``folder_to_mount``) and surfaced to the agent via :class:`~microbots.tools.tool_definitions.memory_tool.MemoryTool`.
22+
"""
23+
24+
task: str
25+
memory_dir: str
26+
27+
28+
@dataclass
29+
class AgentResult:
30+
"""Normalised result from a single agent run.
31+
32+
Attributes
33+
----------
34+
status : IterationStatus
35+
``PASSED`` on success, ``TIMEOUT`` when the agent ran out of time,
36+
``ERROR`` for all other failures.
37+
output : str | None
38+
The agent's final answer / thoughts when status is ``PASSED``.
39+
error : str | None
40+
Error description when status is not ``PASSED``.
41+
output_path : str | None
42+
Host-side path to the agent's output artefacts. Populated by the
43+
orchestrator after the agent run; runners leave this as ``None``.
44+
log_path : str | None
45+
Host-side path to the agent's execution log. Populated by the
46+
orchestrator after the agent run; runners leave this as ``None``.
47+
"""
48+
49+
status: IterationStatus
50+
output: str | None
51+
error: str | None
52+
output_path: str | None = None
53+
log_path: str | None = None
54+
55+
56+
@runtime_checkable
57+
class AgentRunner(Protocol):
58+
"""Structural protocol satisfied by any object with a matching ``run`` method."""
59+
60+
def run(self, ctx: IterationContext, timeout_s: int) -> AgentResult:
61+
"""Run the agent described by *ctx* and return a normalised result.
62+
63+
Parameters
64+
----------
65+
ctx : IterationContext
66+
Task description and memory directory for this invocation.
67+
timeout_s : int
68+
Maximum wall-clock time the agent may use, in seconds.
69+
70+
Returns
71+
-------
72+
AgentResult
73+
Normalised outcome of the agent run.
74+
"""
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""WritingBotRunner — the only module that imports microbots.bot.WritingBot.
2+
3+
This isolation keeps the core auto_memory framework decoupled from the
4+
concrete bot implementation.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from microbots.auto_memory.data_models import IterationStatus
10+
from microbots.auto_memory.runners.base import AgentResult, AgentRunner, IterationContext
11+
from microbots.bot.WritingBot import WritingBot
12+
from microbots.MicroBot import BotRunResult
13+
from microbots.tools.tool_definitions.memory_tool import MemoryTool
14+
15+
_TIMEOUT_ERROR_PREFIX = "Timeout of "
16+
17+
18+
class WritingBotRunner:
19+
"""Runs a :class:`~microbots.bot.WritingBot.WritingBot` for one iteration.
20+
21+
Satisfies the :class:`AgentRunner` protocol structurally.
22+
23+
Parameters
24+
----------
25+
model : str
26+
Model identifier forwarded to ``WritingBot`` (e.g.
27+
``"azure/gpt-4o"``).
28+
max_iterations : int, optional
29+
Maximum number of agent steps per run; forwarded to
30+
``WritingBot.run()``. Defaults to 20.
31+
"""
32+
33+
def __init__(self, model: str, max_iterations: int = 20) -> None:
34+
"""Store the model identifier and iteration cap used for every bot invocation.
35+
36+
Parameters
37+
----------
38+
model : str
39+
Model identifier forwarded to ``WritingBot`` (e.g. ``"azure/gpt-4o"``).
40+
max_iterations : int, optional
41+
Maximum number of agent steps per run. Defaults to 20.
42+
"""
43+
self._model = model
44+
self._max_iterations = max_iterations
45+
46+
# ------------------------------------------------------------------
47+
# AgentRunner implementation
48+
49+
def run(self, ctx: IterationContext, timeout_s: int) -> AgentResult:
50+
"""Create a fresh :class:`WritingBot`, run *ctx.task*, and return a
51+
normalised :class:`AgentResult`.
52+
53+
The bot is configured with:
54+
55+
* ``folder_to_mount=ctx.memory_dir`` so the container can read and
56+
write the iteration's memory directory.
57+
* ``MemoryTool(memory_dir=ctx.memory_dir)`` so the agent can call the
58+
``memory`` tool to persist structured notes.
59+
60+
Parameters
61+
----------
62+
ctx : IterationContext
63+
Iteration context carrying the task and memory directory.
64+
timeout_s : int
65+
Per-iteration timeout forwarded to ``WritingBot.run()``.
66+
67+
Returns
68+
-------
69+
AgentResult
70+
* ``PASSED`` when the bot completes the task successfully.
71+
* ``TIMEOUT`` when the bot's wall-clock limit is exceeded.
72+
* ``ERROR`` for all other failures (max-iterations reached, etc.).
73+
"""
74+
bot = WritingBot(
75+
model=self._model,
76+
folder_to_mount=ctx.memory_dir,
77+
additional_tools=[MemoryTool(memory_dir=ctx.memory_dir)],
78+
)
79+
80+
bot_result = bot.run(
81+
ctx.task,
82+
max_iterations=self._max_iterations,
83+
timeout_in_seconds=timeout_s,
84+
)
85+
86+
return self._map_result(bot_result)
87+
88+
# ------------------------------------------------------------------
89+
# Internal helpers
90+
91+
@staticmethod
92+
def _map_result(bot_result: BotRunResult) -> AgentResult:
93+
"""Convert a :class:`~microbots.MicroBot.BotRunResult` to an
94+
:class:`AgentResult`.
95+
96+
Parameters
97+
----------
98+
bot_result : BotRunResult
99+
Raw result returned by ``WritingBot.run()``.
100+
101+
Returns
102+
-------
103+
AgentResult
104+
Normalised result with ``PASSED``, ``TIMEOUT``, or ``ERROR`` status.
105+
106+
Notes
107+
-----
108+
Mapping rules:
109+
110+
* ``status=True`` → ``PASSED``; ``output`` carries the bot's final answer.
111+
* ``status=False``, error contains ``"Timeout"`` → ``TIMEOUT``.
112+
* ``status=False``, all other errors → ``ERROR``.
113+
"""
114+
if bot_result.status:
115+
return AgentResult(
116+
status=IterationStatus.PASSED,
117+
output=bot_result.result,
118+
error=None,
119+
)
120+
121+
error_msg = bot_result.error or "Unknown error"
122+
123+
if error_msg.startswith(_TIMEOUT_ERROR_PREFIX):
124+
return AgentResult(
125+
status=IterationStatus.TIMEOUT,
126+
output=None,
127+
error=error_msg,
128+
)
129+
130+
return AgentResult(
131+
status=IterationStatus.ERROR,
132+
output=None,
133+
error=error_msg,
134+
)

0 commit comments

Comments
 (0)