monkeybot 2.1.1__py3-none-any.whl
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.
- monkeybot/__init__.py +3 -0
- monkeybot/cli/__init__.py +3 -0
- monkeybot/cli/__main__.py +8 -0
- monkeybot/cli/audio_io.py +8 -0
- monkeybot/cli/gateway_manager.py +17 -0
- monkeybot/cli/main.py +22 -0
- monkeybot/cli/push_to_talk.py +12 -0
- monkeybot/cli/realtime_client.py +13 -0
- monkeybot/core/__init__.py +19 -0
- monkeybot/core/attachments/__init__.py +22 -0
- monkeybot/core/attachments/catalog.py +62 -0
- monkeybot/core/attachments/config.py +52 -0
- monkeybot/core/attachments/freeze.py +158 -0
- monkeybot/core/attachments/resolve.py +70 -0
- monkeybot/core/attachments/store.py +180 -0
- monkeybot/core/attachments/text.py +72 -0
- monkeybot/core/attachments/tools.py +54 -0
- monkeybot/core/bootstrap.py +242 -0
- monkeybot/core/config/__init__.py +71 -0
- monkeybot/core/config/realtime_config.py +150 -0
- monkeybot/core/config/runtime_env.py +262 -0
- monkeybot/core/config/settings.py +341 -0
- monkeybot/core/config/validation.py +249 -0
- monkeybot/core/config/yaml_loader.py +45 -0
- monkeybot/core/context/__init__.py +781 -0
- monkeybot/core/context/campaign_context.py +8 -0
- monkeybot/core/context/common.py +14 -0
- monkeybot/core/context/curator.py +255 -0
- monkeybot/core/context/epoch.py +226 -0
- monkeybot/core/context/memory_prompt.py +222 -0
- monkeybot/core/context/tool_output_policy.py +270 -0
- monkeybot/core/context/tool_result_ingress.py +290 -0
- monkeybot/core/context/tool_shapers.py +361 -0
- monkeybot/core/hooks/__init__.py +261 -0
- monkeybot/core/llm/__init__.py +4 -0
- monkeybot/core/llm/provider.py +296 -0
- monkeybot/core/llm/realtime_provider.py +203 -0
- monkeybot/core/llm/usage.py +57 -0
- monkeybot/core/logging_utils.py +24 -0
- monkeybot/core/mcp/__init__.py +1 -0
- monkeybot/core/mcp/mcp_client.py +1215 -0
- monkeybot/core/mcp/ports_mcp.py +109 -0
- monkeybot/core/memory/__init__.py +24 -0
- monkeybot/core/memory/hook.py +413 -0
- monkeybot/core/memory/index_format.py +104 -0
- monkeybot/core/memory/integrity.py +180 -0
- monkeybot/core/memory/organizer.py +270 -0
- monkeybot/core/memory/storage_ops.py +139 -0
- monkeybot/core/memory/subsystem.py +91 -0
- monkeybot/core/messages/__init__.py +16 -0
- monkeybot/core/messages/convert_provider.py +41 -0
- monkeybot/core/messages/tool_integrity.py +262 -0
- monkeybot/core/messages/transform_context.py +84 -0
- monkeybot/core/path_safety.py +11 -0
- monkeybot/core/persistence/__init__.py +17 -0
- monkeybot/core/persistence/backends.py +236 -0
- monkeybot/core/persistence/db.py +28 -0
- monkeybot/core/persistence/durable_runs.py +286 -0
- monkeybot/core/persistence/firestore.py +658 -0
- monkeybot/core/persistence/firestore_scheduled_loops.py +336 -0
- monkeybot/core/persistence/history.py +156 -0
- monkeybot/core/persistence/postgres.py +895 -0
- monkeybot/core/persistence/runs.py +76 -0
- monkeybot/core/persistence/scheduled_loops.py +435 -0
- monkeybot/core/persistence/session_turn_locks.py +94 -0
- monkeybot/core/persistence/sqlite.py +218 -0
- monkeybot/core/persistence/sqlite_backend.py +74 -0
- monkeybot/core/persistence/thread_summary.py +61 -0
- monkeybot/core/persistence/transcript.py +194 -0
- monkeybot/core/persistence/usage.py +149 -0
- monkeybot/core/prompts/__init__.py +1 -0
- monkeybot/core/prompts/harness_prompt.py +197 -0
- monkeybot/core/prompts/prompt.py +215 -0
- monkeybot/core/runtime/__init__.py +1 -0
- monkeybot/core/runtime/context_budget.py +267 -0
- monkeybot/core/runtime/events.py +819 -0
- monkeybot/core/runtime/input_admission.py +154 -0
- monkeybot/core/runtime/loop.py +2374 -0
- monkeybot/core/runtime/provider_stream_mapper.py +159 -0
- monkeybot/core/runtime/realtime_loop.py +654 -0
- monkeybot/core/runtime/utterance_buffer.py +179 -0
- monkeybot/core/subagents/__init__.py +1 -0
- monkeybot/core/subagents/subagent_proto.py +331 -0
- monkeybot/core/subagents/subagent_worker.py +441 -0
- monkeybot/core/subagents/worker_pool.py +403 -0
- monkeybot/core/testing/__init__.py +1 -0
- monkeybot/core/testing/mocks_provider.py +86 -0
- monkeybot/core/testing/mocks_realtime_provider.py +137 -0
- monkeybot/core/tools/__init__.py +1 -0
- monkeybot/core/tools/core_tool_executor.py +1548 -0
- monkeybot/core/tools/inspector.py +226 -0
- monkeybot/core/tools/loop_inspector.py +45 -0
- monkeybot/core/tools/patch.py +480 -0
- monkeybot/core/tools/permission.py +284 -0
- monkeybot/core/tools/sandbox_executor.py +255 -0
- monkeybot/core/tools/spill_inventory.py +35 -0
- monkeybot/core/tools/terminal.py +381 -0
- monkeybot/core/tools/text_normalize.py +25 -0
- monkeybot/core/tools/types.py +33 -0
- monkeybot/core/tools/workspace_service.py +710 -0
- monkeybot/core/tools/workspace_tools.py +116 -0
- monkeybot/core/types/__init__.py +1 -0
- monkeybot/core/types/content_blocks.py +644 -0
- monkeybot/core/types/interfaces.py +156 -0
- monkeybot/core/types/types_tools.py +29 -0
- monkeybot/core/workspace/__init__.py +8 -0
- monkeybot/core/workspace/factory.py +45 -0
- monkeybot/core/workspace/gcs.py +130 -0
- monkeybot/core/workspace/local.py +162 -0
- monkeybot/core/workspace/protocol.py +45 -0
- monkeybot/core/workspace/s3.py +151 -0
- monkeybot/core/workspace_layout.py +27 -0
- monkeybot/gateway/__init__.py +1 -0
- monkeybot/gateway/bootstrap.py +18 -0
- monkeybot/gateway/main.py +47 -0
- monkeybot/gateway/realtime/__init__.py +31 -0
- monkeybot/gateway/realtime/app.py +321 -0
- monkeybot/gateway/realtime/deps.py +52 -0
- monkeybot/gateway/realtime/errors.py +81 -0
- monkeybot/gateway/realtime/guardrails.py +88 -0
- monkeybot/gateway/realtime/manager.py +77 -0
- monkeybot/gateway/realtime/metrics.py +144 -0
- monkeybot/gateway/realtime/routes.py +864 -0
- monkeybot/gateway/realtime/session.py +232 -0
- monkeybot/gateway/realtime/wire.py +412 -0
- monkeybot/gateway/realtime_main.py +49 -0
- monkeybot/gateway/sse/__init__.py +1 -0
- monkeybot/gateway/sse/app.py +733 -0
- monkeybot/gateway/sse/loop_port.py +31 -0
- monkeybot/gateway/sse/models.py +177 -0
- monkeybot/gateway/sse/reply_body.py +91 -0
- monkeybot/gateway/sse/routes.py +1101 -0
- monkeybot/gateway/sse/scheduler_routes.py +200 -0
- monkeybot/gateway/sse/scheduler_wiring.py +96 -0
- monkeybot/gateway/sse/session_bus.py +226 -0
- monkeybot/gateway/sse/sse.py +46 -0
- monkeybot/gateway/sse/workspace_layout.py +7 -0
- monkeybot/observability/__init__.py +220 -0
- monkeybot/observability/_state.py +10 -0
- monkeybot/observability/instrumentation.py +153 -0
- monkeybot/observability/propagation.py +65 -0
- monkeybot/observability/spans.py +455 -0
- monkeybot/providers/__init__.py +19 -0
- monkeybot/providers/_openai_compat.py +450 -0
- monkeybot/providers/_utils.py +473 -0
- monkeybot/providers/bedrock.py +145 -0
- monkeybot/providers/claude.py +125 -0
- monkeybot/providers/gemini.py +677 -0
- monkeybot/providers/gemini_live.py +398 -0
- monkeybot/providers/huggingface.py +129 -0
- monkeybot/providers/nvidia.py +104 -0
- monkeybot/providers/ollama.py +152 -0
- monkeybot/providers/openai.py +127 -0
- monkeybot/providers/pricing.py +60 -0
- monkeybot/providers/sampling.py +44 -0
- monkeybot/providers/vertex_claude.py +148 -0
- monkeybot/scaffold/__init__.py +33 -0
- monkeybot/scheduler/__init__.py +13 -0
- monkeybot/scheduler/__main__.py +4 -0
- monkeybot/scheduler/engine.py +333 -0
- monkeybot/scheduler/http_invoker.py +61 -0
- monkeybot/scheduler/interval.py +77 -0
- monkeybot/scheduler/tick_result.py +34 -0
- monkeybot/scheduler/worker.py +87 -0
- monkeybot/subagents/__init__.py +1 -0
- monkeybot/subagents/worker/__init__.py +1 -0
- monkeybot/subagents/worker/__main__.py +22 -0
- monkeybot/web_search/__init__.py +82 -0
- monkeybot/web_search/backends/__init__.py +5 -0
- monkeybot/web_search/backends/duckduckgo.py +32 -0
- monkeybot/web_search/backends/firecrawl.py +43 -0
- monkeybot/web_search/backends/tavily.py +45 -0
- monkeybot/web_search/protocol.py +25 -0
- monkeybot/web_search/tool.py +56 -0
- monkeybot-2.1.1.dist-info/METADATA +318 -0
- monkeybot-2.1.1.dist-info/RECORD +178 -0
- monkeybot-2.1.1.dist-info/WHEEL +4 -0
- monkeybot-2.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""Subprocess worker for the ``task`` tool: stdin :class:`SubagentEnvelope` JSON, stdout NDJSON events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import uuid
|
|
10
|
+
from collections.abc import AsyncIterator
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
|
|
16
|
+
from monkeybot.core.config.settings import (
|
|
17
|
+
auto_schema_enabled_from_config,
|
|
18
|
+
get_provider_config,
|
|
19
|
+
normalize_model_provider,
|
|
20
|
+
subagent_vertex_google_search_from_config,
|
|
21
|
+
)
|
|
22
|
+
from monkeybot.core.context import TurnContext, build_context
|
|
23
|
+
from monkeybot.core.llm.provider import (
|
|
24
|
+
Done,
|
|
25
|
+
Provider,
|
|
26
|
+
ProviderEvent,
|
|
27
|
+
TextDelta,
|
|
28
|
+
ToolCall,
|
|
29
|
+
UsageEvent,
|
|
30
|
+
)
|
|
31
|
+
from monkeybot.core.mcp.mcp_client import MCPClient
|
|
32
|
+
from monkeybot.core.memory.subsystem import MemorySubsystem
|
|
33
|
+
from monkeybot.core.persistence.backends import HistoryStore, create_storage_backend
|
|
34
|
+
from monkeybot.core.runtime.events import AgentEvent, Error, event_to_json
|
|
35
|
+
from monkeybot.core.runtime.loop import run as run_loop
|
|
36
|
+
from monkeybot.core.subagents.subagent_proto import (
|
|
37
|
+
SubagentEnvelope,
|
|
38
|
+
normalize_sqlite_db_url,
|
|
39
|
+
resolve_agent_project_root,
|
|
40
|
+
resolve_project_path,
|
|
41
|
+
resolve_subagent_agent_md_path,
|
|
42
|
+
)
|
|
43
|
+
from monkeybot.core.testing.mocks_provider import ScriptedFakeProvider
|
|
44
|
+
from monkeybot.core.tools.core_tool_executor import CoreToolExecutor
|
|
45
|
+
from monkeybot.core.tools.inspector import CommandTierInspector, RulesInspector, ToolInspector
|
|
46
|
+
from monkeybot.core.tools.permission import try_load_permission_inspector
|
|
47
|
+
from monkeybot.core.workspace import create_workspace_storage
|
|
48
|
+
from monkeybot.web_search import WebSearchTool
|
|
49
|
+
from monkeybot.web_search import build_backend as _build_web_search_backend
|
|
50
|
+
|
|
51
|
+
_BUILTIN_RUN_LOOP = run_loop
|
|
52
|
+
|
|
53
|
+
logger = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _subagent_memory_uri(envelope: SubagentEnvelope) -> str:
|
|
57
|
+
"""Envelope URI is authoritative; empty means no memory (ignore ambient env)."""
|
|
58
|
+
return envelope.memory_storage_uri.strip()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def init_observability() -> bool:
|
|
62
|
+
from monkeybot.observability import init_observability as _init
|
|
63
|
+
|
|
64
|
+
return _init()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def shutdown_observability() -> None:
|
|
68
|
+
from monkeybot.observability import shutdown_observability as _shutdown
|
|
69
|
+
|
|
70
|
+
_shutdown()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _attach_trace_from_envelope(envelope: SubagentEnvelope) -> object | None:
|
|
74
|
+
if not envelope.traceparent:
|
|
75
|
+
return None
|
|
76
|
+
try:
|
|
77
|
+
from opentelemetry import context as otel_context
|
|
78
|
+
|
|
79
|
+
from monkeybot.observability.propagation import extract_traceparent
|
|
80
|
+
except ImportError:
|
|
81
|
+
return None
|
|
82
|
+
ctx = extract_traceparent({"traceparent": envelope.traceparent})
|
|
83
|
+
if ctx is None:
|
|
84
|
+
return None
|
|
85
|
+
return otel_context.attach(ctx)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _detach_trace(token: object | None) -> None:
|
|
89
|
+
if token is None:
|
|
90
|
+
return
|
|
91
|
+
try:
|
|
92
|
+
from opentelemetry import context as otel_context
|
|
93
|
+
|
|
94
|
+
otel_context.detach(cast(Any, token))
|
|
95
|
+
except Exception:
|
|
96
|
+
logger.debug("trace detach failed", exc_info=True)
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _reset_trace_context_if_unattached(attach_token: object | None) -> object | None:
|
|
101
|
+
if attach_token is not None:
|
|
102
|
+
return None
|
|
103
|
+
try:
|
|
104
|
+
from opentelemetry import context as otel_context
|
|
105
|
+
|
|
106
|
+
return otel_context.attach(otel_context.Context())
|
|
107
|
+
except ImportError:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _clear_span_exporter_buffer() -> None:
|
|
112
|
+
try:
|
|
113
|
+
from opentelemetry import trace
|
|
114
|
+
|
|
115
|
+
provider = trace.get_tracer_provider()
|
|
116
|
+
multi = getattr(provider, "_active_span_processor", None)
|
|
117
|
+
processors = getattr(multi, "_span_processors", ()) if multi is not None else ()
|
|
118
|
+
for proc in processors:
|
|
119
|
+
exporter = getattr(proc, "span_exporter", None)
|
|
120
|
+
if exporter is not None and hasattr(exporter, "clear"):
|
|
121
|
+
exporter.clear()
|
|
122
|
+
except Exception:
|
|
123
|
+
logger.debug("span exporter buffer clear failed", exc_info=True)
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def _stream_run_loop_events(
|
|
128
|
+
body: str,
|
|
129
|
+
ctx: TurnContext,
|
|
130
|
+
*,
|
|
131
|
+
provider: Provider,
|
|
132
|
+
history: HistoryStore,
|
|
133
|
+
inspectors: list[ToolInspector],
|
|
134
|
+
tool_executor: CoreToolExecutor,
|
|
135
|
+
run_id: str,
|
|
136
|
+
max_turns: int,
|
|
137
|
+
vertex_google_search: bool = False,
|
|
138
|
+
) -> AsyncIterator[AgentEvent]:
|
|
139
|
+
if run_loop is _BUILTIN_RUN_LOOP:
|
|
140
|
+
async for evt in run_loop(
|
|
141
|
+
body,
|
|
142
|
+
ctx,
|
|
143
|
+
provider=provider,
|
|
144
|
+
history=history,
|
|
145
|
+
inspectors=inspectors,
|
|
146
|
+
tool_executor=tool_executor,
|
|
147
|
+
cancelled=None,
|
|
148
|
+
max_turns=max_turns,
|
|
149
|
+
vertex_google_search=vertex_google_search,
|
|
150
|
+
):
|
|
151
|
+
yield evt
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
from monkeybot.observability.spans import span_run
|
|
155
|
+
|
|
156
|
+
async with span_run(ctx, user_message=body):
|
|
157
|
+
async for evt in run_loop(
|
|
158
|
+
body,
|
|
159
|
+
ctx,
|
|
160
|
+
provider=provider,
|
|
161
|
+
history=history,
|
|
162
|
+
inspectors=inspectors,
|
|
163
|
+
tool_executor=tool_executor,
|
|
164
|
+
cancelled=None,
|
|
165
|
+
max_turns=max_turns,
|
|
166
|
+
vertex_google_search=vertex_google_search,
|
|
167
|
+
):
|
|
168
|
+
yield evt
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _resolve_provider() -> Provider:
|
|
172
|
+
mode = normalize_model_provider(os.environ.get("MODEL_PROVIDER", "google_vertexai"))
|
|
173
|
+
if mode != "fake":
|
|
174
|
+
return get_provider_config(provider=mode).provider
|
|
175
|
+
|
|
176
|
+
import json
|
|
177
|
+
|
|
178
|
+
raw = os.environ.get("MONKEYBOT_FAKE_PROVIDER_EVENTS", "")
|
|
179
|
+
if not raw:
|
|
180
|
+
return ScriptedFakeProvider(
|
|
181
|
+
[
|
|
182
|
+
TextDelta(
|
|
183
|
+
text="subagent fake provider: set MONKEYBOT_FAKE_PROVIDER_EVENTS for scripted tools."
|
|
184
|
+
),
|
|
185
|
+
UsageEvent(input_tokens=1, output_tokens=1, cached_tokens=0),
|
|
186
|
+
Done(),
|
|
187
|
+
]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
decoded = json.loads(raw)
|
|
191
|
+
turns: list[list[ProviderEvent]] = []
|
|
192
|
+
for turn in decoded:
|
|
193
|
+
events: list[ProviderEvent] = []
|
|
194
|
+
if not isinstance(turn, list):
|
|
195
|
+
continue
|
|
196
|
+
for item in turn:
|
|
197
|
+
if not isinstance(item, dict):
|
|
198
|
+
continue
|
|
199
|
+
k = item.get("kind")
|
|
200
|
+
if k == "text_delta":
|
|
201
|
+
events.append(TextDelta(text=str(item.get("text", ""))))
|
|
202
|
+
elif k == "usage":
|
|
203
|
+
events.append(
|
|
204
|
+
UsageEvent(
|
|
205
|
+
input_tokens=int(item.get("input_tokens", 0)),
|
|
206
|
+
output_tokens=int(item.get("output_tokens", 0)),
|
|
207
|
+
cached_tokens=int(item.get("cached_tokens", 0)),
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
elif k == "tool_call":
|
|
211
|
+
events.append(
|
|
212
|
+
ToolCall(
|
|
213
|
+
call_id=str(item["call_id"]),
|
|
214
|
+
name=str(item["name"]),
|
|
215
|
+
args=dict(item.get("args", {})),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
elif k == "done":
|
|
219
|
+
events.append(Done())
|
|
220
|
+
if events:
|
|
221
|
+
turns.append(events)
|
|
222
|
+
if not turns:
|
|
223
|
+
turns = [[TextDelta(text="hello"), Done()]]
|
|
224
|
+
flat: list[ProviderEvent] = [ev for turn in turns for ev in turn]
|
|
225
|
+
return ScriptedFakeProvider(flat)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
async def _async_main() -> None:
|
|
229
|
+
raw = sys.stdin.read()
|
|
230
|
+
if not raw.strip():
|
|
231
|
+
print(event_to_json(Error(request_id="", error="subagent_worker: empty stdin")), flush=True)
|
|
232
|
+
raise SystemExit(1)
|
|
233
|
+
|
|
234
|
+
envelope = SubagentEnvelope.from_json(raw)
|
|
235
|
+
attach_token: object | None = _attach_trace_from_envelope(envelope)
|
|
236
|
+
reset_token: object | None = None
|
|
237
|
+
|
|
238
|
+
agent_root = resolve_agent_project_root()
|
|
239
|
+
dotenv_path = agent_root / ".env"
|
|
240
|
+
if dotenv_path.is_file():
|
|
241
|
+
load_dotenv(dotenv_path)
|
|
242
|
+
|
|
243
|
+
ws = Path(os.environ["MONKEYBOT_SUBAGENT_WORKSPACE"]).resolve()
|
|
244
|
+
os.chdir(ws)
|
|
245
|
+
|
|
246
|
+
mem_uri = _subagent_memory_uri(envelope)
|
|
247
|
+
|
|
248
|
+
skills = Path(os.environ["MONKEYBOT_SUBAGENT_SKILLS_PATH"]).resolve()
|
|
249
|
+
|
|
250
|
+
if envelope.agent_md:
|
|
251
|
+
agent_md_path = Path(envelope.agent_md).expanduser().resolve()
|
|
252
|
+
if not agent_md_path.is_file():
|
|
253
|
+
agent_md_path = resolve_project_path(envelope.agent_md, agent_root)
|
|
254
|
+
else:
|
|
255
|
+
agent_md_path = resolve_subagent_agent_md_path(agent_root) or resolve_project_path(
|
|
256
|
+
"AGENT.md", agent_root
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
db_url = normalize_sqlite_db_url(
|
|
260
|
+
os.environ.get("DB_URL", "sqlite:///data/monkeybot.db"), agent_root
|
|
261
|
+
)
|
|
262
|
+
backend = create_storage_backend(db_url)
|
|
263
|
+
mcp: MCPClient | None = None
|
|
264
|
+
executor: CoreToolExecutor | None = None
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
await backend.open(run_schema=auto_schema_enabled_from_config())
|
|
268
|
+
|
|
269
|
+
mcp = MCPClient()
|
|
270
|
+
mcp_config = resolve_project_path(
|
|
271
|
+
os.environ.get("MCP_CONFIG", "monkeybot_config/mcp.json"), agent_root
|
|
272
|
+
)
|
|
273
|
+
strict = os.environ.get("MCP_STRICT_LOAD", "").strip().lower() in ("1", "true", "yes")
|
|
274
|
+
try:
|
|
275
|
+
await mcp.load_from_config(mcp_config, raise_on_error=strict)
|
|
276
|
+
except OSError as exc:
|
|
277
|
+
logger.info("MCP config skipped (%s): %s", mcp_config, exc)
|
|
278
|
+
|
|
279
|
+
inspectors: list[ToolInspector] = []
|
|
280
|
+
run_allow_cmds: list[str] | None = None
|
|
281
|
+
run_allow_paths: list[str] | None = None
|
|
282
|
+
tiers_path = resolve_project_path(
|
|
283
|
+
os.environ.get("COMMAND_ALLOWLIST_CONFIG", "monkeybot_config/command_allowlist.yaml"),
|
|
284
|
+
agent_root,
|
|
285
|
+
)
|
|
286
|
+
try:
|
|
287
|
+
tier_insp = CommandTierInspector(tiers_path)
|
|
288
|
+
inspectors.append(tier_insp)
|
|
289
|
+
run_allow_cmds = list(tier_insp.allowed_commands)
|
|
290
|
+
run_allow_paths = list(tier_insp.allowed_path_prefixes)
|
|
291
|
+
except FileNotFoundError:
|
|
292
|
+
logger.info("command tiers missing (%s); allowing all tool calls", tiers_path)
|
|
293
|
+
except Exception:
|
|
294
|
+
logger.exception("command tier load failed")
|
|
295
|
+
|
|
296
|
+
raw_rules = os.environ.get("MONKEYBOT_TOOL_DENIED_PATTERNS")
|
|
297
|
+
if raw_rules is None:
|
|
298
|
+
rules_patterns = ["rm -rf", "/etc/passwd", "DROP TABLE"]
|
|
299
|
+
else:
|
|
300
|
+
rules_patterns = [p.strip() for p in raw_rules.split(",") if p.strip()]
|
|
301
|
+
if rules_patterns:
|
|
302
|
+
inspectors.append(RulesInspector(rules_patterns))
|
|
303
|
+
|
|
304
|
+
perm_path = resolve_project_path(
|
|
305
|
+
os.environ.get("PERMISSION_CONFIG", "monkeybot_config/permissions.yaml"),
|
|
306
|
+
agent_root,
|
|
307
|
+
)
|
|
308
|
+
# Subagents have no interactive session to prompt: reuse the parent's
|
|
309
|
+
# ruleset, but resolve "ask" to deny instead of a confirm that can
|
|
310
|
+
# never be answered.
|
|
311
|
+
perm_insp = try_load_permission_inspector(perm_path, allow_ask=False)
|
|
312
|
+
if perm_insp is not None:
|
|
313
|
+
inspectors.append(perm_insp)
|
|
314
|
+
|
|
315
|
+
provider = _resolve_provider()
|
|
316
|
+
thread_id = f"subagent:{envelope.parent_run_id}:{uuid.uuid4().hex[:10]}"
|
|
317
|
+
request_id = f"sub-{uuid.uuid4().hex[:12]}"
|
|
318
|
+
|
|
319
|
+
cap_raw = os.environ.get("MODEL_CONTEXT_WINDOW", "200000").strip()
|
|
320
|
+
try:
|
|
321
|
+
context_window_tokens = max(1, int(cap_raw))
|
|
322
|
+
except ValueError:
|
|
323
|
+
context_window_tokens = 200_000
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
_ws_backend = _build_web_search_backend()
|
|
327
|
+
_ws_tool: WebSearchTool | None = (
|
|
328
|
+
WebSearchTool(_ws_backend) if _ws_backend is not None else None
|
|
329
|
+
)
|
|
330
|
+
except Exception:
|
|
331
|
+
logger.debug("web search backend init failed", exc_info=True)
|
|
332
|
+
_ws_tool = None
|
|
333
|
+
|
|
334
|
+
extra_tools = [_ws_tool] if _ws_tool is not None else []
|
|
335
|
+
|
|
336
|
+
memory: MemorySubsystem | None = None
|
|
337
|
+
if mem_uri:
|
|
338
|
+
storage = create_workspace_storage(mem_uri)
|
|
339
|
+
memory = MemorySubsystem(
|
|
340
|
+
storage=storage,
|
|
341
|
+
provider=provider,
|
|
342
|
+
model=envelope.model,
|
|
343
|
+
memory_uri=mem_uri,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
ctx = await build_context(
|
|
347
|
+
thread_id,
|
|
348
|
+
request_id,
|
|
349
|
+
agent_md_path=agent_md_path,
|
|
350
|
+
memory=memory,
|
|
351
|
+
skills_path=skills,
|
|
352
|
+
mcp_client=mcp,
|
|
353
|
+
parent_run_id=envelope.parent_run_id,
|
|
354
|
+
model=envelope.model,
|
|
355
|
+
include_task_tool=False,
|
|
356
|
+
workspace_root=ws,
|
|
357
|
+
context_window_tokens=context_window_tokens,
|
|
358
|
+
enable_context_curation=False,
|
|
359
|
+
extra_tools=extra_tools,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
executor = CoreToolExecutor(
|
|
363
|
+
workspace_root=ws,
|
|
364
|
+
memory=memory,
|
|
365
|
+
skills_path=skills,
|
|
366
|
+
mcp=mcp,
|
|
367
|
+
extra_tools=extra_tools,
|
|
368
|
+
run_command_allowed_commands=run_allow_cmds,
|
|
369
|
+
run_command_allowed_path_prefixes=run_allow_paths,
|
|
370
|
+
)
|
|
371
|
+
history = backend.history()
|
|
372
|
+
|
|
373
|
+
body = envelope.task.strip()
|
|
374
|
+
if envelope.context.strip():
|
|
375
|
+
body += "\n\n---\nContext from parent agent:\n" + envelope.context.strip()
|
|
376
|
+
|
|
377
|
+
max_turns_raw = os.environ.get("SUBAGENT_MAX_TURNS", "").strip()
|
|
378
|
+
if max_turns_raw:
|
|
379
|
+
max_turns = max(1, int(max_turns_raw))
|
|
380
|
+
else:
|
|
381
|
+
max_turns = max(1, int(os.environ.get("MAX_TURNS", "25")))
|
|
382
|
+
|
|
383
|
+
from monkeybot.observability.spans import span_subagent
|
|
384
|
+
|
|
385
|
+
reset_token = _reset_trace_context_if_unattached(attach_token)
|
|
386
|
+
_clear_span_exporter_buffer()
|
|
387
|
+
init_observability()
|
|
388
|
+
try:
|
|
389
|
+
# Subagents read memory (index, search_memory) via MemorySubsystem but do not
|
|
390
|
+
# register memory hooks — chat_log / raw / organizer writes stay on the parent
|
|
391
|
+
# gateway to avoid duplicate or conflicting durable memory updates.
|
|
392
|
+
async with span_subagent(
|
|
393
|
+
thread_id=thread_id,
|
|
394
|
+
request_id=request_id,
|
|
395
|
+
parent_run_id=envelope.parent_run_id,
|
|
396
|
+
subagent_type=envelope.subagent_type,
|
|
397
|
+
agent_md=envelope.agent_md,
|
|
398
|
+
):
|
|
399
|
+
async for evt in _stream_run_loop_events(
|
|
400
|
+
body,
|
|
401
|
+
ctx,
|
|
402
|
+
provider=provider,
|
|
403
|
+
history=history,
|
|
404
|
+
inspectors=inspectors,
|
|
405
|
+
tool_executor=executor,
|
|
406
|
+
run_id=request_id,
|
|
407
|
+
max_turns=max_turns,
|
|
408
|
+
vertex_google_search=subagent_vertex_google_search_from_config(),
|
|
409
|
+
):
|
|
410
|
+
print(event_to_json(evt), flush=True)
|
|
411
|
+
finally:
|
|
412
|
+
shutdown_observability()
|
|
413
|
+
finally:
|
|
414
|
+
_detach_trace(reset_token)
|
|
415
|
+
_detach_trace(attach_token)
|
|
416
|
+
if executor is not None:
|
|
417
|
+
await executor.aclose()
|
|
418
|
+
if mcp is not None:
|
|
419
|
+
for name in list(getattr(mcp, "_servers", {}).keys()):
|
|
420
|
+
await mcp.disconnect(name)
|
|
421
|
+
await backend.close()
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def main() -> None:
|
|
425
|
+
from monkeybot.core.logging_utils import normalize_log_level
|
|
426
|
+
|
|
427
|
+
logging.basicConfig(level=normalize_log_level(os.environ.get("LOG_LEVEL"), default="WARNING"))
|
|
428
|
+
try:
|
|
429
|
+
asyncio.run(_async_main())
|
|
430
|
+
except SystemExit:
|
|
431
|
+
raise
|
|
432
|
+
except KeyboardInterrupt:
|
|
433
|
+
raise
|
|
434
|
+
except Exception as exc:
|
|
435
|
+
logger.exception("subagent_worker fatal")
|
|
436
|
+
print(event_to_json(Error(request_id="", error=str(exc))), flush=True)
|
|
437
|
+
raise SystemExit(1) from exc
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
if __name__ == "__main__":
|
|
441
|
+
main()
|