react-agent-harness 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {react_agent_harness-0.3.0/react_agent_harness.egg-info → react_agent_harness-0.3.2}/PKG-INFO +1 -1
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/README.md +17 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/agents/base.py +7 -5
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/runtime.py +3 -2
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/steering.py +29 -16
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/pyproject.toml +1 -1
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2/react_agent_harness.egg-info}/PKG-INFO +1 -1
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_steering.py +73 -22
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_streaming.py +64 -23
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/LICENSE +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/agents/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/annotation.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/checkpoint.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/cli.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/events.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/executor_bridge.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/hitl.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/llm/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/llm/_streaming.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/llm/auth.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/llm/claude_code.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/llm/openai.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/llm/openai_codex.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/otel.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/harness/utils.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/memory/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/memory/episodic_lance.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/memory/manager.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/memory/redis_store.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/memory/stores.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/memory/working.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/orchestrator/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/orchestrator/planner.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/SOURCES.txt +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/dependency_links.txt +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/entry_points.txt +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/requires.txt +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/top_level.txt +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/setup.cfg +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_agents_base.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_annotation.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_checkpoint_resume.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_claude_code_llm.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_cli.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_executor_bridge.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_http_fetch.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_llm_auth.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_mcp_adapter.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_memory.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_openai_codex_llm.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_openai_llm.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_orchestrator.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_otel.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_parse_action_json.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_redis_store.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_utils.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_vision.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tests/test_working_memory.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tools/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tools/builtin/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tools/builtin/fetch_image.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tools/builtin/http_fetch.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tools/mcp/__init__.py +0 -0
- {react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/tools/mcp/adapter.py +0 -0
|
@@ -909,3 +909,20 @@ key-bindings (like Enter-submits and Alt-Enter/Ctrl-J-newline) across both paths
|
|
|
909
909
|
|
|
910
910
|
See `examples/complex_sysaudit_demo.py` for stdin steering across three
|
|
911
911
|
agents alongside HITL on the shell tool.
|
|
912
|
+
|
|
913
|
+
## AgentConfig reference
|
|
914
|
+
|
|
915
|
+
| Field | Default | Description |
|
|
916
|
+
|---|---|---|
|
|
917
|
+
| `agent_id` | required | Unique identifier for the agent |
|
|
918
|
+
| `role` | required | Plain-English description used by the planner for agent selection |
|
|
919
|
+
| `system_prompt` | required | Base system prompt for the agent |
|
|
920
|
+
| `allowed_tools` | required | Tool names the agent may call |
|
|
921
|
+
| `max_steps` | `10` | Maximum ReAct iterations before the run is terminated |
|
|
922
|
+
| `max_wall_time_seconds` | (guardrail) | See `GuardrailConfig` |
|
|
923
|
+
| `memory_context_enabled` | `True` | Prepend relevant long-term memory to the system prompt |
|
|
924
|
+
| `confidence_from_llm` | `True` | Use the `confidence` field from the LLM response; set `False` to always return `1.0` |
|
|
925
|
+
| `working_memory_max_tokens` | `8000` | Token budget for in-context working memory before rolling summarisation kicks in |
|
|
926
|
+
| `hitl_tools` | `[]` | Tool names that require human approval before execution |
|
|
927
|
+
| `checkpoint_every` | `0` | Write a crash-resumable checkpoint every N steps; `0` disables periodic checkpoints |
|
|
928
|
+
| `stream_tokens` | `False` | Emit `TOKEN` events as the LLM streams. Disabled by default — enable if you want to render partial output in real time: `AgentConfig(..., stream_tokens=True)` |
|
|
@@ -61,6 +61,7 @@ class AgentConfig:
|
|
|
61
61
|
max_steps: int = 10
|
|
62
62
|
memory_context_enabled: bool = True
|
|
63
63
|
confidence_from_llm: bool = True # if False, confidence=1.0 on success
|
|
64
|
+
stream_tokens: bool = False # if True, TOKEN events are emitted as the LLM streams
|
|
64
65
|
working_memory_max_tokens: int = 8000 # WorkingMemory eviction threshold; tune per agent
|
|
65
66
|
hitl_tools: list[str] = None # tools requiring human approval; None = no HITL
|
|
66
67
|
checkpoint_every: int = 0 # write a resumable checkpoint every N steps; 0 = disabled
|
|
@@ -649,11 +650,12 @@ class BaseAgent:
|
|
|
649
650
|
messages=messages,
|
|
650
651
|
):
|
|
651
652
|
accumulated += token
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
653
|
+
if self.config.stream_tokens:
|
|
654
|
+
yield BusEvent(
|
|
655
|
+
type=EventType.TOKEN,
|
|
656
|
+
agent_id=self.config.agent_id,
|
|
657
|
+
token=token,
|
|
658
|
+
)
|
|
657
659
|
response = _parse_action_json(accumulated)
|
|
658
660
|
if response is None:
|
|
659
661
|
logger.warning(
|
|
@@ -781,8 +781,9 @@ class AgentRuntime:
|
|
|
781
781
|
run_id = str(uuid.uuid4())
|
|
782
782
|
tracer.start_run(run_id, task)
|
|
783
783
|
try:
|
|
784
|
-
async
|
|
785
|
-
|
|
784
|
+
async with self._steering_lifecycle():
|
|
785
|
+
async for event in self._run_agent_with_tracer(agent_id, task, tracer, run_id):
|
|
786
|
+
yield event
|
|
786
787
|
finally:
|
|
787
788
|
tracer.end_run()
|
|
788
789
|
|
|
@@ -150,7 +150,6 @@ class StdinRouter:
|
|
|
150
150
|
input_: Any | None = None,
|
|
151
151
|
output: Any | None = None,
|
|
152
152
|
history: Any | None = None,
|
|
153
|
-
patch_stdout_: bool = True,
|
|
154
153
|
) -> None:
|
|
155
154
|
self._task: asyncio.Task | None = None
|
|
156
155
|
self._stop = asyncio.Event()
|
|
@@ -159,8 +158,6 @@ class StdinRouter:
|
|
|
159
158
|
# subscription_id → (prefix, callback). prefix=None is catch-all.
|
|
160
159
|
self._subs: dict[int, tuple[str | None, Callable[[str], None]]] = {}
|
|
161
160
|
self._next_sub_id: int = 0
|
|
162
|
-
# Tests turn off patch_stdout to avoid interfering with pytest capture.
|
|
163
|
-
self._patch_stdout = patch_stdout_
|
|
164
161
|
self._session: PromptSession = PromptSession(
|
|
165
162
|
history=history or InMemoryHistory(),
|
|
166
163
|
input=input_,
|
|
@@ -282,18 +279,13 @@ class StdinRouter:
|
|
|
282
279
|
# ── Internals ─────────────────────────────────────────────────────────────
|
|
283
280
|
|
|
284
281
|
async def _run(self) -> None:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if claim is not None:
|
|
293
|
-
await self._serve_hitl(*claim)
|
|
294
|
-
self._hitl_claim = None
|
|
295
|
-
else:
|
|
296
|
-
await self._serve_steering()
|
|
282
|
+
while not self._stop.is_set():
|
|
283
|
+
claim = self._hitl_claim
|
|
284
|
+
if claim is not None:
|
|
285
|
+
await self._serve_hitl(*claim)
|
|
286
|
+
self._hitl_claim = None
|
|
287
|
+
else:
|
|
288
|
+
await self._serve_steering()
|
|
297
289
|
|
|
298
290
|
async def _serve_steering(self) -> None:
|
|
299
291
|
try:
|
|
@@ -575,10 +567,13 @@ class _StdinSteeringFactory:
|
|
|
575
567
|
self,
|
|
576
568
|
router: StdinRouter | None = None,
|
|
577
569
|
prefix_template: str = "{agent_id}",
|
|
570
|
+
patch_stdout_: bool = True,
|
|
578
571
|
) -> None:
|
|
579
572
|
self._router = router or StdinRouter()
|
|
580
573
|
self._owned = router is None
|
|
581
574
|
self._prefix_template = prefix_template
|
|
575
|
+
self._patch_stdout = patch_stdout_ and router is None # only patch when we own the router
|
|
576
|
+
self._patch_stdout_cm: Any | None = None
|
|
582
577
|
# Ref-counted lifecycle: nested AgentRuntime wraps (dispatch_stream
|
|
583
578
|
# → run_stream) re-enter the factory; only the outermost
|
|
584
579
|
# enter/exit actually starts/stops the router.
|
|
@@ -590,6 +585,9 @@ class _StdinSteeringFactory:
|
|
|
590
585
|
|
|
591
586
|
async def __aenter__(self) -> _StdinSteeringFactory:
|
|
592
587
|
if self._owned and self._enter_count == 0:
|
|
588
|
+
if self._patch_stdout:
|
|
589
|
+
self._patch_stdout_cm = patch_stdout(raw=True)
|
|
590
|
+
self._patch_stdout_cm.__enter__()
|
|
593
591
|
await self._router.__aenter__()
|
|
594
592
|
self._enter_count += 1
|
|
595
593
|
return self
|
|
@@ -598,11 +596,15 @@ class _StdinSteeringFactory:
|
|
|
598
596
|
self._enter_count = max(0, self._enter_count - 1)
|
|
599
597
|
if self._owned and self._enter_count == 0:
|
|
600
598
|
await self._router.__aexit__(exc_type, exc, tb)
|
|
599
|
+
if self._patch_stdout_cm is not None:
|
|
600
|
+
self._patch_stdout_cm.__exit__(exc_type, exc, tb)
|
|
601
|
+
self._patch_stdout_cm = None
|
|
601
602
|
|
|
602
603
|
|
|
603
604
|
def stdin_steering_factory(
|
|
604
605
|
router: StdinRouter | None = None,
|
|
605
606
|
prefix_template: str = "{agent_id}",
|
|
607
|
+
patch_stdout_: bool = True,
|
|
606
608
|
) -> _StdinSteeringFactory:
|
|
607
609
|
"""Return a steering factory that lifecycles its own StdinRouter.
|
|
608
610
|
|
|
@@ -616,7 +618,9 @@ def stdin_steering_factory(
|
|
|
616
618
|
`prefix_template` may reference `{agent_id}`; default subscribes
|
|
617
619
|
each agent to its own `agent_id`.
|
|
618
620
|
"""
|
|
619
|
-
return _StdinSteeringFactory(
|
|
621
|
+
return _StdinSteeringFactory(
|
|
622
|
+
router=router, prefix_template=prefix_template, patch_stdout_=patch_stdout_
|
|
623
|
+
)
|
|
620
624
|
|
|
621
625
|
|
|
622
626
|
# ── Direct-use shims (no AgentRuntime / no factory) ───────────────────────────
|
|
@@ -639,6 +643,7 @@ class StdinSteer:
|
|
|
639
643
|
agents: BaseAgent | list[BaseAgent],
|
|
640
644
|
*,
|
|
641
645
|
router: StdinRouter | None = None,
|
|
646
|
+
patch_stdout_: bool = True,
|
|
642
647
|
) -> None:
|
|
643
648
|
if not isinstance(agents, list):
|
|
644
649
|
agents = [agents]
|
|
@@ -647,9 +652,14 @@ class StdinSteer:
|
|
|
647
652
|
self._agents = agents
|
|
648
653
|
self._router = router or StdinRouter()
|
|
649
654
|
self._owned_router = router is None
|
|
655
|
+
self._patch_stdout = patch_stdout_
|
|
656
|
+
self._patch_stdout_cm: Any | None = None
|
|
650
657
|
self._sub_ids: list[int] = []
|
|
651
658
|
|
|
652
659
|
async def __aenter__(self) -> StdinSteer:
|
|
660
|
+
if self._patch_stdout:
|
|
661
|
+
self._patch_stdout_cm = patch_stdout(raw=True)
|
|
662
|
+
self._patch_stdout_cm.__enter__()
|
|
653
663
|
if self._owned_router:
|
|
654
664
|
await self._router.start()
|
|
655
665
|
# Always register one subscription per agent under its agent_id.
|
|
@@ -672,3 +682,6 @@ class StdinSteer:
|
|
|
672
682
|
self._sub_ids.clear()
|
|
673
683
|
if self._owned_router:
|
|
674
684
|
await self._router.stop()
|
|
685
|
+
if self._patch_stdout_cm is not None:
|
|
686
|
+
self._patch_stdout_cm.__exit__(exc_type, exc, tb)
|
|
687
|
+
self._patch_stdout_cm = None
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "react-agent-harness"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Multi-agent LLM orchestration: hybrid DAG planning, two-tier memory, streaming"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
@@ -18,7 +18,14 @@ from prompt_toolkit.output import DummyOutput
|
|
|
18
18
|
|
|
19
19
|
from agents.base import AgentConfig, BaseAgent
|
|
20
20
|
from harness.events import EventType
|
|
21
|
-
from harness.runtime import
|
|
21
|
+
from harness.runtime import (
|
|
22
|
+
AgentRegistry,
|
|
23
|
+
AgentRuntime,
|
|
24
|
+
BudgetGuard,
|
|
25
|
+
GuardrailConfig,
|
|
26
|
+
ToolRegistry,
|
|
27
|
+
Tracer,
|
|
28
|
+
)
|
|
22
29
|
from harness.steering import (
|
|
23
30
|
FileSteer,
|
|
24
31
|
StdinAgentSource,
|
|
@@ -57,7 +64,6 @@ def _piped_router():
|
|
|
57
64
|
router = StdinRouter(
|
|
58
65
|
input_=pipe_in,
|
|
59
66
|
output=DummyOutput(),
|
|
60
|
-
patch_stdout_=False,
|
|
61
67
|
)
|
|
62
68
|
yield router, pipe_in
|
|
63
69
|
|
|
@@ -322,20 +328,6 @@ async def test_router_routes_to_catchall_subscriber():
|
|
|
322
328
|
assert received == ["plain line"]
|
|
323
329
|
|
|
324
330
|
|
|
325
|
-
@pytest.mark.asyncio
|
|
326
|
-
async def test_router_default_patch_stdout_context_starts():
|
|
327
|
-
"""Default patch_stdout path uses a sync context manager but still runs in async loop."""
|
|
328
|
-
received: list[str] = []
|
|
329
|
-
with create_pipe_input() as pipe_in:
|
|
330
|
-
router = StdinRouter(input_=pipe_in, output=DummyOutput())
|
|
331
|
-
router.subscribe(None, received.append)
|
|
332
|
-
await router.start()
|
|
333
|
-
pipe_in.send_text("plain line\r")
|
|
334
|
-
await _drain()
|
|
335
|
-
await router.stop()
|
|
336
|
-
assert received == ["plain line"]
|
|
337
|
-
|
|
338
|
-
|
|
339
331
|
@pytest.mark.asyncio
|
|
340
332
|
async def test_router_routes_by_prefix():
|
|
341
333
|
a_received: list[str] = []
|
|
@@ -441,7 +433,7 @@ async def test_router_claim_next_line_resolves_with_typed_answer():
|
|
|
441
433
|
|
|
442
434
|
|
|
443
435
|
def test_router_rejects_star_as_subscription_prefix():
|
|
444
|
-
router = StdinRouter(
|
|
436
|
+
router = StdinRouter()
|
|
445
437
|
with pytest.raises(ValueError):
|
|
446
438
|
router.subscribe("*", lambda _t: None)
|
|
447
439
|
|
|
@@ -455,7 +447,7 @@ async def test_stdin_single_agent_no_prefix_needed():
|
|
|
455
447
|
with _piped_router() as (router, pipe_in):
|
|
456
448
|
await router.start()
|
|
457
449
|
try:
|
|
458
|
-
async with StdinSteer(a, router=router):
|
|
450
|
+
async with StdinSteer(a, router=router, patch_stdout_=False):
|
|
459
451
|
pipe_in.send_text("just do it\r")
|
|
460
452
|
await _drain()
|
|
461
453
|
finally:
|
|
@@ -469,7 +461,7 @@ async def test_stdin_single_agent_prefix_also_works():
|
|
|
469
461
|
with _piped_router() as (router, pipe_in):
|
|
470
462
|
await router.start()
|
|
471
463
|
try:
|
|
472
|
-
async with StdinSteer(a, router=router):
|
|
464
|
+
async with StdinSteer(a, router=router, patch_stdout_=False):
|
|
473
465
|
pipe_in.send_text("a: explicit\r")
|
|
474
466
|
await _drain()
|
|
475
467
|
finally:
|
|
@@ -484,7 +476,7 @@ async def test_stdin_multi_agent_prefix_routes():
|
|
|
484
476
|
with _piped_router() as (router, pipe_in):
|
|
485
477
|
await router.start()
|
|
486
478
|
try:
|
|
487
|
-
async with StdinSteer([a, b], router=router):
|
|
479
|
+
async with StdinSteer([a, b], router=router, patch_stdout_=False):
|
|
488
480
|
pipe_in.send_text("a: do A\r")
|
|
489
481
|
await _drain()
|
|
490
482
|
pipe_in.send_text("b: do B\r")
|
|
@@ -502,7 +494,7 @@ async def test_stdin_multi_agent_broadcast():
|
|
|
502
494
|
with _piped_router() as (router, pipe_in):
|
|
503
495
|
await router.start()
|
|
504
496
|
try:
|
|
505
|
-
async with StdinSteer([a, b], router=router):
|
|
497
|
+
async with StdinSteer([a, b], router=router, patch_stdout_=False):
|
|
506
498
|
pipe_in.send_text("*: stop now\r")
|
|
507
499
|
await _drain()
|
|
508
500
|
finally:
|
|
@@ -518,7 +510,7 @@ async def test_stdin_steer_registers_as_active_router():
|
|
|
518
510
|
await router.start()
|
|
519
511
|
try:
|
|
520
512
|
assert get_active_router() is None
|
|
521
|
-
async with StdinSteer(a, router=router):
|
|
513
|
+
async with StdinSteer(a, router=router, patch_stdout_=False):
|
|
522
514
|
assert get_active_router() is router
|
|
523
515
|
assert get_active_router() is None
|
|
524
516
|
finally:
|
|
@@ -661,3 +653,62 @@ async def test_stdin_steering_factory_subscribes_each_agent():
|
|
|
661
653
|
# Verify it actually subscribed.
|
|
662
654
|
assert "a" in router.active_prefixes()
|
|
663
655
|
assert "a" not in router.active_prefixes()
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
# ── AgentRuntime.run_agent_stream steering lifecycle ─────────────────────────
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
@pytest.mark.asyncio
|
|
662
|
+
async def test_run_agent_stream_starts_steering_lifecycle(llm, memory):
|
|
663
|
+
"""run_agent_stream must enter the steering lifecycle so the factory's
|
|
664
|
+
__aenter__/__aexit__ are called — regression test for the single-agent
|
|
665
|
+
steering bug where the lifecycle wrapper was missing."""
|
|
666
|
+
llm.routes = {
|
|
667
|
+
"react": lambda *_: {
|
|
668
|
+
"thought": "done",
|
|
669
|
+
"action": "finish",
|
|
670
|
+
"answer": "ok",
|
|
671
|
+
"confidence": 1.0,
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
entered = False
|
|
676
|
+
exited = False
|
|
677
|
+
|
|
678
|
+
class _LifecycleFactory:
|
|
679
|
+
"""Looks like a stdin_steering_factory — has both lifecycle and per-agent call."""
|
|
680
|
+
|
|
681
|
+
async def __aenter__(self):
|
|
682
|
+
nonlocal entered
|
|
683
|
+
entered = True
|
|
684
|
+
return self
|
|
685
|
+
|
|
686
|
+
async def __aexit__(self, *exc):
|
|
687
|
+
nonlocal exited
|
|
688
|
+
exited = True
|
|
689
|
+
|
|
690
|
+
def __call__(self, agent: BaseAgent):
|
|
691
|
+
import contextlib
|
|
692
|
+
|
|
693
|
+
@contextlib.asynccontextmanager
|
|
694
|
+
async def _noop():
|
|
695
|
+
yield
|
|
696
|
+
|
|
697
|
+
return _noop()
|
|
698
|
+
|
|
699
|
+
config = AgentConfig(agent_id="solo", role="r", system_prompt="react", allowed_tools=[])
|
|
700
|
+
agent_reg = AgentRegistry()
|
|
701
|
+
agent_reg.register(config)
|
|
702
|
+
|
|
703
|
+
runtime = AgentRuntime(
|
|
704
|
+
agent_registry=agent_reg,
|
|
705
|
+
tool_registry=ToolRegistry(),
|
|
706
|
+
memory=memory,
|
|
707
|
+
llm=llm,
|
|
708
|
+
steering_source_factory=_LifecycleFactory(),
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
events = [ev async for ev in runtime.run_agent_stream("solo", "test task")]
|
|
712
|
+
assert any(ev.type == EventType.TASK_DONE for ev in events)
|
|
713
|
+
assert entered, "steering lifecycle __aenter__ was never called"
|
|
714
|
+
assert exited, "steering lifecycle __aexit__ was never called"
|
|
@@ -5,6 +5,7 @@ Verifies that BaseAgent.run_stream() and Orchestrator.run_stream() yield the
|
|
|
5
5
|
expected BusEvent sequence, and that the blocking run() drains to the same
|
|
6
6
|
result the stream's DONE event carries.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
from agents.base import AgentConfig
|
|
@@ -20,8 +21,11 @@ from tests.conftest import EchoTool, ScriptedLLM
|
|
|
20
21
|
async def test_agent_run_stream_finish_yields_task_done(agent_factory):
|
|
21
22
|
"""Finish on first step → just one TASK_DONE event (no THOUGHT/ACTION pairs)."""
|
|
22
23
|
cfg = AgentConfig(
|
|
23
|
-
agent_id="a",
|
|
24
|
-
|
|
24
|
+
agent_id="a",
|
|
25
|
+
role="r",
|
|
26
|
+
system_prompt="finish.",
|
|
27
|
+
allowed_tools=[],
|
|
28
|
+
working_memory_max_tokens=2000,
|
|
25
29
|
)
|
|
26
30
|
agent = agent_factory(cfg)
|
|
27
31
|
events = [e async for e in agent.run_stream("hi")]
|
|
@@ -34,7 +38,8 @@ async def test_agent_run_stream_finish_yields_task_done(agent_factory):
|
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
async def test_agent_run_stream_tool_call_yields_action_and_observation(
|
|
37
|
-
agent_factory,
|
|
41
|
+
agent_factory,
|
|
42
|
+
llm: ScriptedLLM,
|
|
38
43
|
):
|
|
39
44
|
"""A tool-using step should yield THOUGHT → ACTION → OBSERVATION → ... → TASK_DONE."""
|
|
40
45
|
step = {"n": 0}
|
|
@@ -47,7 +52,9 @@ async def test_agent_run_stream_tool_call_yields_action_and_observation(
|
|
|
47
52
|
|
|
48
53
|
llm.routes = {"react": react}
|
|
49
54
|
cfg = AgentConfig(
|
|
50
|
-
agent_id="a",
|
|
55
|
+
agent_id="a",
|
|
56
|
+
role="r",
|
|
57
|
+
system_prompt="ReAct format.",
|
|
51
58
|
allowed_tools=["echo"],
|
|
52
59
|
)
|
|
53
60
|
agent = agent_factory(cfg, tools={"echo": EchoTool()})
|
|
@@ -68,7 +75,10 @@ async def test_agent_run_stream_tool_call_yields_action_and_observation(
|
|
|
68
75
|
async def test_agent_run_is_drain_of_run_stream(agent_factory):
|
|
69
76
|
"""run() and the TASK_DONE payload from run_stream() must agree."""
|
|
70
77
|
cfg = AgentConfig(
|
|
71
|
-
agent_id="a",
|
|
78
|
+
agent_id="a",
|
|
79
|
+
role="r",
|
|
80
|
+
system_prompt="finish.",
|
|
81
|
+
allowed_tools=[],
|
|
72
82
|
)
|
|
73
83
|
agent = agent_factory(cfg)
|
|
74
84
|
|
|
@@ -98,7 +108,11 @@ async def test_agent_forwards_token_events_when_llm_streams(agent_factory):
|
|
|
98
108
|
yield tok
|
|
99
109
|
|
|
100
110
|
cfg = AgentConfig(
|
|
101
|
-
agent_id="a",
|
|
111
|
+
agent_id="a",
|
|
112
|
+
role="r",
|
|
113
|
+
system_prompt="ReAct.",
|
|
114
|
+
allowed_tools=[],
|
|
115
|
+
stream_tokens=True,
|
|
102
116
|
)
|
|
103
117
|
agent = agent_factory(cfg)
|
|
104
118
|
agent._llm = StreamingLLM()
|
|
@@ -119,10 +133,20 @@ def _orchestrator_routes():
|
|
|
119
133
|
def planner(system, messages, kwargs):
|
|
120
134
|
return {
|
|
121
135
|
"tasks": [
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
{
|
|
137
|
+
"id": "t1",
|
|
138
|
+
"agent_id": "analyst",
|
|
139
|
+
"instruction": "do x",
|
|
140
|
+
"depends_on": [],
|
|
141
|
+
"on_failure": "skip",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "t2",
|
|
145
|
+
"agent_id": "reporter",
|
|
146
|
+
"instruction": "do y",
|
|
147
|
+
"depends_on": ["t1"],
|
|
148
|
+
"on_failure": "skip",
|
|
149
|
+
},
|
|
126
150
|
],
|
|
127
151
|
"rationale": "two tasks",
|
|
128
152
|
}
|
|
@@ -132,8 +156,10 @@ def _orchestrator_routes():
|
|
|
132
156
|
|
|
133
157
|
def extract(system, messages, kwargs):
|
|
134
158
|
return {
|
|
135
|
-
"semantic_facts": {},
|
|
136
|
-
"
|
|
159
|
+
"semantic_facts": {},
|
|
160
|
+
"episodic_summary": "ok",
|
|
161
|
+
"metadata": {},
|
|
162
|
+
"ttl_seconds": None,
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
return {
|
|
@@ -147,14 +173,24 @@ def _build_runtime(llm):
|
|
|
147
173
|
tools = ToolRegistry().register(EchoTool())
|
|
148
174
|
agents = (
|
|
149
175
|
AgentRegistry()
|
|
150
|
-
.register(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
.register(
|
|
177
|
+
AgentConfig(
|
|
178
|
+
agent_id="analyst",
|
|
179
|
+
role="r",
|
|
180
|
+
system_prompt="ReAct.",
|
|
181
|
+
allowed_tools=["echo"],
|
|
182
|
+
max_steps=2,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
.register(
|
|
186
|
+
AgentConfig(
|
|
187
|
+
agent_id="reporter",
|
|
188
|
+
role="r",
|
|
189
|
+
system_prompt="ReAct.",
|
|
190
|
+
allowed_tools=["echo"],
|
|
191
|
+
max_steps=2,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
158
194
|
)
|
|
159
195
|
memory = MemoryManager(
|
|
160
196
|
semantic_store=InMemorySemanticStore(),
|
|
@@ -162,10 +198,15 @@ def _build_runtime(llm):
|
|
|
162
198
|
llm=llm,
|
|
163
199
|
)
|
|
164
200
|
return AgentRuntime(
|
|
165
|
-
agent_registry=agents,
|
|
201
|
+
agent_registry=agents,
|
|
202
|
+
tool_registry=tools,
|
|
203
|
+
memory=memory,
|
|
204
|
+
llm=llm,
|
|
166
205
|
guardrail_config=GuardrailConfig(
|
|
167
|
-
max_total_cost_usd=5.0,
|
|
168
|
-
|
|
206
|
+
max_total_cost_usd=5.0,
|
|
207
|
+
max_wall_time_seconds=30,
|
|
208
|
+
max_replan_count=1,
|
|
209
|
+
confidence_threshold=0.5,
|
|
169
210
|
),
|
|
170
211
|
)
|
|
171
212
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/requires.txt
RENAMED
|
File without changes
|
{react_agent_harness-0.3.0 → react_agent_harness-0.3.2}/react_agent_harness.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|