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.
Files changed (178) hide show
  1. monkeybot/__init__.py +3 -0
  2. monkeybot/cli/__init__.py +3 -0
  3. monkeybot/cli/__main__.py +8 -0
  4. monkeybot/cli/audio_io.py +8 -0
  5. monkeybot/cli/gateway_manager.py +17 -0
  6. monkeybot/cli/main.py +22 -0
  7. monkeybot/cli/push_to_talk.py +12 -0
  8. monkeybot/cli/realtime_client.py +13 -0
  9. monkeybot/core/__init__.py +19 -0
  10. monkeybot/core/attachments/__init__.py +22 -0
  11. monkeybot/core/attachments/catalog.py +62 -0
  12. monkeybot/core/attachments/config.py +52 -0
  13. monkeybot/core/attachments/freeze.py +158 -0
  14. monkeybot/core/attachments/resolve.py +70 -0
  15. monkeybot/core/attachments/store.py +180 -0
  16. monkeybot/core/attachments/text.py +72 -0
  17. monkeybot/core/attachments/tools.py +54 -0
  18. monkeybot/core/bootstrap.py +242 -0
  19. monkeybot/core/config/__init__.py +71 -0
  20. monkeybot/core/config/realtime_config.py +150 -0
  21. monkeybot/core/config/runtime_env.py +262 -0
  22. monkeybot/core/config/settings.py +341 -0
  23. monkeybot/core/config/validation.py +249 -0
  24. monkeybot/core/config/yaml_loader.py +45 -0
  25. monkeybot/core/context/__init__.py +781 -0
  26. monkeybot/core/context/campaign_context.py +8 -0
  27. monkeybot/core/context/common.py +14 -0
  28. monkeybot/core/context/curator.py +255 -0
  29. monkeybot/core/context/epoch.py +226 -0
  30. monkeybot/core/context/memory_prompt.py +222 -0
  31. monkeybot/core/context/tool_output_policy.py +270 -0
  32. monkeybot/core/context/tool_result_ingress.py +290 -0
  33. monkeybot/core/context/tool_shapers.py +361 -0
  34. monkeybot/core/hooks/__init__.py +261 -0
  35. monkeybot/core/llm/__init__.py +4 -0
  36. monkeybot/core/llm/provider.py +296 -0
  37. monkeybot/core/llm/realtime_provider.py +203 -0
  38. monkeybot/core/llm/usage.py +57 -0
  39. monkeybot/core/logging_utils.py +24 -0
  40. monkeybot/core/mcp/__init__.py +1 -0
  41. monkeybot/core/mcp/mcp_client.py +1215 -0
  42. monkeybot/core/mcp/ports_mcp.py +109 -0
  43. monkeybot/core/memory/__init__.py +24 -0
  44. monkeybot/core/memory/hook.py +413 -0
  45. monkeybot/core/memory/index_format.py +104 -0
  46. monkeybot/core/memory/integrity.py +180 -0
  47. monkeybot/core/memory/organizer.py +270 -0
  48. monkeybot/core/memory/storage_ops.py +139 -0
  49. monkeybot/core/memory/subsystem.py +91 -0
  50. monkeybot/core/messages/__init__.py +16 -0
  51. monkeybot/core/messages/convert_provider.py +41 -0
  52. monkeybot/core/messages/tool_integrity.py +262 -0
  53. monkeybot/core/messages/transform_context.py +84 -0
  54. monkeybot/core/path_safety.py +11 -0
  55. monkeybot/core/persistence/__init__.py +17 -0
  56. monkeybot/core/persistence/backends.py +236 -0
  57. monkeybot/core/persistence/db.py +28 -0
  58. monkeybot/core/persistence/durable_runs.py +286 -0
  59. monkeybot/core/persistence/firestore.py +658 -0
  60. monkeybot/core/persistence/firestore_scheduled_loops.py +336 -0
  61. monkeybot/core/persistence/history.py +156 -0
  62. monkeybot/core/persistence/postgres.py +895 -0
  63. monkeybot/core/persistence/runs.py +76 -0
  64. monkeybot/core/persistence/scheduled_loops.py +435 -0
  65. monkeybot/core/persistence/session_turn_locks.py +94 -0
  66. monkeybot/core/persistence/sqlite.py +218 -0
  67. monkeybot/core/persistence/sqlite_backend.py +74 -0
  68. monkeybot/core/persistence/thread_summary.py +61 -0
  69. monkeybot/core/persistence/transcript.py +194 -0
  70. monkeybot/core/persistence/usage.py +149 -0
  71. monkeybot/core/prompts/__init__.py +1 -0
  72. monkeybot/core/prompts/harness_prompt.py +197 -0
  73. monkeybot/core/prompts/prompt.py +215 -0
  74. monkeybot/core/runtime/__init__.py +1 -0
  75. monkeybot/core/runtime/context_budget.py +267 -0
  76. monkeybot/core/runtime/events.py +819 -0
  77. monkeybot/core/runtime/input_admission.py +154 -0
  78. monkeybot/core/runtime/loop.py +2374 -0
  79. monkeybot/core/runtime/provider_stream_mapper.py +159 -0
  80. monkeybot/core/runtime/realtime_loop.py +654 -0
  81. monkeybot/core/runtime/utterance_buffer.py +179 -0
  82. monkeybot/core/subagents/__init__.py +1 -0
  83. monkeybot/core/subagents/subagent_proto.py +331 -0
  84. monkeybot/core/subagents/subagent_worker.py +441 -0
  85. monkeybot/core/subagents/worker_pool.py +403 -0
  86. monkeybot/core/testing/__init__.py +1 -0
  87. monkeybot/core/testing/mocks_provider.py +86 -0
  88. monkeybot/core/testing/mocks_realtime_provider.py +137 -0
  89. monkeybot/core/tools/__init__.py +1 -0
  90. monkeybot/core/tools/core_tool_executor.py +1548 -0
  91. monkeybot/core/tools/inspector.py +226 -0
  92. monkeybot/core/tools/loop_inspector.py +45 -0
  93. monkeybot/core/tools/patch.py +480 -0
  94. monkeybot/core/tools/permission.py +284 -0
  95. monkeybot/core/tools/sandbox_executor.py +255 -0
  96. monkeybot/core/tools/spill_inventory.py +35 -0
  97. monkeybot/core/tools/terminal.py +381 -0
  98. monkeybot/core/tools/text_normalize.py +25 -0
  99. monkeybot/core/tools/types.py +33 -0
  100. monkeybot/core/tools/workspace_service.py +710 -0
  101. monkeybot/core/tools/workspace_tools.py +116 -0
  102. monkeybot/core/types/__init__.py +1 -0
  103. monkeybot/core/types/content_blocks.py +644 -0
  104. monkeybot/core/types/interfaces.py +156 -0
  105. monkeybot/core/types/types_tools.py +29 -0
  106. monkeybot/core/workspace/__init__.py +8 -0
  107. monkeybot/core/workspace/factory.py +45 -0
  108. monkeybot/core/workspace/gcs.py +130 -0
  109. monkeybot/core/workspace/local.py +162 -0
  110. monkeybot/core/workspace/protocol.py +45 -0
  111. monkeybot/core/workspace/s3.py +151 -0
  112. monkeybot/core/workspace_layout.py +27 -0
  113. monkeybot/gateway/__init__.py +1 -0
  114. monkeybot/gateway/bootstrap.py +18 -0
  115. monkeybot/gateway/main.py +47 -0
  116. monkeybot/gateway/realtime/__init__.py +31 -0
  117. monkeybot/gateway/realtime/app.py +321 -0
  118. monkeybot/gateway/realtime/deps.py +52 -0
  119. monkeybot/gateway/realtime/errors.py +81 -0
  120. monkeybot/gateway/realtime/guardrails.py +88 -0
  121. monkeybot/gateway/realtime/manager.py +77 -0
  122. monkeybot/gateway/realtime/metrics.py +144 -0
  123. monkeybot/gateway/realtime/routes.py +864 -0
  124. monkeybot/gateway/realtime/session.py +232 -0
  125. monkeybot/gateway/realtime/wire.py +412 -0
  126. monkeybot/gateway/realtime_main.py +49 -0
  127. monkeybot/gateway/sse/__init__.py +1 -0
  128. monkeybot/gateway/sse/app.py +733 -0
  129. monkeybot/gateway/sse/loop_port.py +31 -0
  130. monkeybot/gateway/sse/models.py +177 -0
  131. monkeybot/gateway/sse/reply_body.py +91 -0
  132. monkeybot/gateway/sse/routes.py +1101 -0
  133. monkeybot/gateway/sse/scheduler_routes.py +200 -0
  134. monkeybot/gateway/sse/scheduler_wiring.py +96 -0
  135. monkeybot/gateway/sse/session_bus.py +226 -0
  136. monkeybot/gateway/sse/sse.py +46 -0
  137. monkeybot/gateway/sse/workspace_layout.py +7 -0
  138. monkeybot/observability/__init__.py +220 -0
  139. monkeybot/observability/_state.py +10 -0
  140. monkeybot/observability/instrumentation.py +153 -0
  141. monkeybot/observability/propagation.py +65 -0
  142. monkeybot/observability/spans.py +455 -0
  143. monkeybot/providers/__init__.py +19 -0
  144. monkeybot/providers/_openai_compat.py +450 -0
  145. monkeybot/providers/_utils.py +473 -0
  146. monkeybot/providers/bedrock.py +145 -0
  147. monkeybot/providers/claude.py +125 -0
  148. monkeybot/providers/gemini.py +677 -0
  149. monkeybot/providers/gemini_live.py +398 -0
  150. monkeybot/providers/huggingface.py +129 -0
  151. monkeybot/providers/nvidia.py +104 -0
  152. monkeybot/providers/ollama.py +152 -0
  153. monkeybot/providers/openai.py +127 -0
  154. monkeybot/providers/pricing.py +60 -0
  155. monkeybot/providers/sampling.py +44 -0
  156. monkeybot/providers/vertex_claude.py +148 -0
  157. monkeybot/scaffold/__init__.py +33 -0
  158. monkeybot/scheduler/__init__.py +13 -0
  159. monkeybot/scheduler/__main__.py +4 -0
  160. monkeybot/scheduler/engine.py +333 -0
  161. monkeybot/scheduler/http_invoker.py +61 -0
  162. monkeybot/scheduler/interval.py +77 -0
  163. monkeybot/scheduler/tick_result.py +34 -0
  164. monkeybot/scheduler/worker.py +87 -0
  165. monkeybot/subagents/__init__.py +1 -0
  166. monkeybot/subagents/worker/__init__.py +1 -0
  167. monkeybot/subagents/worker/__main__.py +22 -0
  168. monkeybot/web_search/__init__.py +82 -0
  169. monkeybot/web_search/backends/__init__.py +5 -0
  170. monkeybot/web_search/backends/duckduckgo.py +32 -0
  171. monkeybot/web_search/backends/firecrawl.py +43 -0
  172. monkeybot/web_search/backends/tavily.py +45 -0
  173. monkeybot/web_search/protocol.py +25 -0
  174. monkeybot/web_search/tool.py +56 -0
  175. monkeybot-2.1.1.dist-info/METADATA +318 -0
  176. monkeybot-2.1.1.dist-info/RECORD +178 -0
  177. monkeybot-2.1.1.dist-info/WHEEL +4 -0
  178. monkeybot-2.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,82 @@
1
+ """Pluggable web search for monkeybot.
2
+
3
+ Usage
4
+ -----
5
+ Call :func:`build_backend` once at startup (reads env vars). Pass the
6
+ returned backend to :class:`~monkeybot.core.tools.core_tool_executor.CoreToolExecutor`
7
+ via ``extra_tools``. ``None`` means web search is disabled.
8
+
9
+ Bring-your-own backend
10
+ -----------------------
11
+ Implement :class:`~monkeybot.web_search.protocol.WebSearchBackend` and wrap it
12
+ in a :class:`~monkeybot.core.context.CustomTool`::
13
+
14
+ class MyBackend:
15
+ name = "my_search"
16
+ async def search(self, query, *, max_results=5): ...
17
+
18
+ tool = web_search_custom_tool(MyBackend())
19
+ executor = CoreToolExecutor(..., extra_tools=[tool])
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import os
25
+
26
+ from monkeybot.web_search.backends.duckduckgo import DuckDuckGoBackend
27
+ from monkeybot.web_search.backends.firecrawl import FirecrawlBackend
28
+ from monkeybot.web_search.backends.tavily import TavilyBackend
29
+ from monkeybot.web_search.protocol import SearchResult, WebSearchBackend
30
+ from monkeybot.web_search.tool import WebSearchTool
31
+
32
+ __all__ = [
33
+ "SearchResult",
34
+ "WebSearchBackend",
35
+ "DuckDuckGoBackend",
36
+ "TavilyBackend",
37
+ "FirecrawlBackend",
38
+ "WebSearchTool",
39
+ "build_backend",
40
+ ]
41
+
42
+
43
+ def build_backend() -> WebSearchBackend | None:
44
+ """Construct the configured backend from environment variables.
45
+
46
+ ``WEB_SEARCH_BACKEND`` selects the implementation:
47
+
48
+ * ``duckduckgo`` (default) — no API key required; uses the ``ddgs`` package.
49
+ * ``tavily`` — requires ``TAVILY_API_KEY``.
50
+ * ``firecrawl`` — requires ``FIRECRAWL_API_KEY``.
51
+ * ``none`` — disables web search entirely.
52
+
53
+ Returns ``None`` when ``WEB_SEARCH_BACKEND=none``.
54
+ """
55
+ backend_name = os.environ.get("WEB_SEARCH_BACKEND", "duckduckgo").lower().strip()
56
+
57
+ if backend_name == "none":
58
+ return None
59
+
60
+ if backend_name == "tavily":
61
+ api_key = os.environ.get("TAVILY_API_KEY", "").strip()
62
+ if not api_key:
63
+ raise ValueError(
64
+ "WEB_SEARCH_BACKEND=tavily requires TAVILY_API_KEY to be set."
65
+ )
66
+ return TavilyBackend(api_key=api_key)
67
+
68
+ if backend_name == "firecrawl":
69
+ api_key = os.environ.get("FIRECRAWL_API_KEY", "").strip()
70
+ if not api_key:
71
+ raise ValueError(
72
+ "WEB_SEARCH_BACKEND=firecrawl requires FIRECRAWL_API_KEY to be set."
73
+ )
74
+ return FirecrawlBackend(api_key=api_key)
75
+
76
+ if backend_name == "duckduckgo":
77
+ return DuckDuckGoBackend()
78
+
79
+ raise ValueError(
80
+ f"Unknown WEB_SEARCH_BACKEND={backend_name!r}. "
81
+ "Choose one of: duckduckgo, tavily, firecrawl, none."
82
+ )
@@ -0,0 +1,5 @@
1
+ from monkeybot.web_search.backends.duckduckgo import DuckDuckGoBackend
2
+ from monkeybot.web_search.backends.firecrawl import FirecrawlBackend
3
+ from monkeybot.web_search.backends.tavily import TavilyBackend
4
+
5
+ __all__ = ["DuckDuckGoBackend", "FirecrawlBackend", "TavilyBackend"]
@@ -0,0 +1,32 @@
1
+ """DuckDuckGo web search backend (zero-config default via the ``ddgs`` package)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ from monkeybot.web_search.protocol import SearchResult
8
+
9
+
10
+ class DuckDuckGoBackend:
11
+ name = "duckduckgo"
12
+
13
+ async def search(self, query: str, *, max_results: int = 5) -> list[SearchResult]:
14
+ try:
15
+ from ddgs import DDGS
16
+ except ImportError as exc:
17
+ raise ImportError(
18
+ "ddgs is required for the DuckDuckGo backend. "
19
+ "Install with: uv add ddgs"
20
+ ) from exc
21
+
22
+ raw = await asyncio.to_thread(
23
+ lambda: list(DDGS().text(query, max_results=max_results))
24
+ )
25
+ return [
26
+ SearchResult(
27
+ title=r.get("title", ""),
28
+ url=r.get("href", ""),
29
+ snippet=r.get("body", ""),
30
+ )
31
+ for r in raw
32
+ ]
@@ -0,0 +1,43 @@
1
+ """Firecrawl web search backend (requires FIRECRAWL_API_KEY)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from monkeybot.web_search.protocol import SearchResult
6
+
7
+ _API_URL = "https://api.firecrawl.dev/v1/search"
8
+
9
+
10
+ class FirecrawlBackend:
11
+ name = "firecrawl"
12
+
13
+ def __init__(self, *, api_key: str) -> None:
14
+ if not api_key:
15
+ raise ValueError("FIRECRAWL_API_KEY must be non-empty")
16
+ self._api_key = api_key
17
+
18
+ async def search(self, query: str, *, max_results: int = 5) -> list[SearchResult]:
19
+ try:
20
+ import httpx
21
+ except ImportError as exc:
22
+ raise ImportError(
23
+ "httpx is required for the Firecrawl backend. "
24
+ "Install with: uv add httpx"
25
+ ) from exc
26
+
27
+ payload = {"query": query, "limit": max_results}
28
+ headers = {"Authorization": f"Bearer {self._api_key}"}
29
+ async with httpx.AsyncClient(timeout=30) as client:
30
+ resp = await client.post(_API_URL, json=payload, headers=headers)
31
+ resp.raise_for_status()
32
+ data = resp.json()
33
+
34
+ results: list[SearchResult] = []
35
+ for r in data.get("data", []):
36
+ results.append(
37
+ SearchResult(
38
+ title=r.get("title", ""),
39
+ url=r.get("url", ""),
40
+ snippet=r.get("description", ""),
41
+ )
42
+ )
43
+ return results
@@ -0,0 +1,45 @@
1
+ """Tavily web search backend (requires TAVILY_API_KEY)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from monkeybot.web_search.protocol import SearchResult
6
+
7
+ _API_URL = "https://api.tavily.com/search"
8
+
9
+
10
+ class TavilyBackend:
11
+ name = "tavily"
12
+
13
+ def __init__(self, *, api_key: str) -> None:
14
+ if not api_key:
15
+ raise ValueError("TAVILY_API_KEY must be non-empty")
16
+ self._api_key = api_key
17
+
18
+ async def search(self, query: str, *, max_results: int = 5) -> list[SearchResult]:
19
+ try:
20
+ import httpx
21
+ except ImportError as exc:
22
+ raise ImportError(
23
+ "httpx is required for the Tavily backend. "
24
+ "Install with: uv add httpx"
25
+ ) from exc
26
+
27
+ payload = {
28
+ "query": query,
29
+ "max_results": max_results,
30
+ "api_key": self._api_key,
31
+ }
32
+ async with httpx.AsyncClient(timeout=30) as client:
33
+ resp = await client.post(_API_URL, json=payload)
34
+ resp.raise_for_status()
35
+ data = resp.json()
36
+
37
+ return [
38
+ SearchResult(
39
+ title=r.get("title", ""),
40
+ url=r.get("url", ""),
41
+ snippet=r.get("content", ""),
42
+ score=r.get("score"),
43
+ )
44
+ for r in data.get("results", [])
45
+ ]
@@ -0,0 +1,25 @@
1
+ """Shared types and protocol for pluggable web search backends."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Protocol, runtime_checkable
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class SearchResult:
11
+ """Normalised result returned by every backend."""
12
+
13
+ title: str
14
+ url: str
15
+ snippet: str
16
+ score: float | None = None
17
+
18
+
19
+ @runtime_checkable
20
+ class WebSearchBackend(Protocol):
21
+ """Protocol every web search backend must satisfy."""
22
+
23
+ name: str
24
+
25
+ async def search(self, query: str, *, max_results: int = 5) -> list[SearchResult]: ...
@@ -0,0 +1,56 @@
1
+ """CustomTool wrapper that exposes a WebSearchBackend as a harness tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+
8
+ from monkeybot.core.types.types_tools import ToolDef
9
+ from monkeybot.web_search.protocol import WebSearchBackend
10
+
11
+ _WEB_SEARCH_SCHEMA: dict[str, object] = {
12
+ "type": "object",
13
+ "properties": {
14
+ "query": {"type": "string", "description": "The search query."},
15
+ "max_results": {
16
+ "type": "integer",
17
+ "description": "Maximum number of results to return (default 5).",
18
+ },
19
+ },
20
+ "required": ["query"],
21
+ }
22
+
23
+
24
+ class WebSearchTool:
25
+ """Adapts a :class:`WebSearchBackend` to the :class:`~monkeybot.core.context.CustomTool` protocol."""
26
+
27
+ def __init__(self, backend: WebSearchBackend) -> None:
28
+ self._backend = backend
29
+ self.tool_def = ToolDef(
30
+ "web_search",
31
+ f"Search the web using {backend.name}. Returns titles, URLs, and text snippets.",
32
+ _WEB_SEARCH_SCHEMA,
33
+ )
34
+
35
+ async def execute(self, args: dict[str, object]) -> str:
36
+ query = str(args.get("query") or "").strip()
37
+ if not query:
38
+ return json.dumps({"ok": False, "error": "web_search requires a non-empty query."})
39
+
40
+ default_max = int(os.environ.get("WEB_SEARCH_MAX_RESULTS", "5"))
41
+ raw_max = args.get("max_results")
42
+ if isinstance(raw_max, (int, float, str)):
43
+ try:
44
+ max_results = int(raw_max)
45
+ except (TypeError, ValueError):
46
+ max_results = default_max
47
+ else:
48
+ max_results = default_max
49
+
50
+ results = await self._backend.search(query, max_results=max_results)
51
+ items = [
52
+ {"title": r.title, "url": r.url, "snippet": r.snippet}
53
+ | ({"score": r.score} if r.score is not None else {})
54
+ for r in results
55
+ ]
56
+ return json.dumps({"ok": True, "query": query, "results": items}, ensure_ascii=False)
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.4
2
+ Name: monkeybot
3
+ Version: 2.1.1
4
+ Summary: monkeybot agent harness — FastAPI SSE gateway, owned loop, SQLite, and MCP.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: aiosqlite>=0.20.0
9
+ Requires-Dist: fastapi>=0.115.0
10
+ Requires-Dist: google-genai>=1.0.0
11
+ Requires-Dist: httpx>=0.27.0
12
+ Requires-Dist: mcp>=1.0.0
13
+ Requires-Dist: pydantic>=2.10.0
14
+ Requires-Dist: python-dotenv>=1.0.0
15
+ Requires-Dist: python-ulid>=2.0.0
16
+ Requires-Dist: pyyaml>=6.0.2
17
+ Requires-Dist: uvicorn[standard]>=0.32.0
18
+ Provides-Extra: aws
19
+ Requires-Dist: boto3>=1.34.0; extra == 'aws'
20
+ Provides-Extra: bedrock
21
+ Requires-Dist: anthropic[bedrock]>=0.40.0; extra == 'bedrock'
22
+ Requires-Dist: boto3>=1.34.0; extra == 'bedrock'
23
+ Provides-Extra: claude
24
+ Requires-Dist: anthropic>=0.40.0; extra == 'claude'
25
+ Provides-Extra: cli
26
+ Requires-Dist: typer>=0.12.0; extra == 'cli'
27
+ Requires-Dist: websockets>=14.0; extra == 'cli'
28
+ Provides-Extra: cli-realtime
29
+ Requires-Dist: pyaudio>=0.2.11; extra == 'cli-realtime'
30
+ Requires-Dist: pynput>=1.7.6; extra == 'cli-realtime'
31
+ Requires-Dist: typer>=0.12.0; extra == 'cli-realtime'
32
+ Requires-Dist: websockets>=14.0; extra == 'cli-realtime'
33
+ Provides-Extra: council
34
+ Requires-Dist: google-cloud-storage>=2.18.0; extra == 'council'
35
+ Provides-Extra: evals
36
+ Requires-Dist: deepeval>=3.0.0; extra == 'evals'
37
+ Requires-Dist: langfuse>=3.0.0; extra == 'evals'
38
+ Provides-Extra: firestore
39
+ Requires-Dist: google-cloud-firestore>=2.16.0; extra == 'firestore'
40
+ Provides-Extra: gcs
41
+ Requires-Dist: google-cloud-storage>=2.18.0; extra == 'gcs'
42
+ Provides-Extra: gemini
43
+ Requires-Dist: google-genai>=1.0.0; extra == 'gemini'
44
+ Provides-Extra: huggingface
45
+ Requires-Dist: openai>=1.0; extra == 'huggingface'
46
+ Requires-Dist: tiktoken>=0.7.0; extra == 'huggingface'
47
+ Provides-Extra: nvidia
48
+ Requires-Dist: openai>=1.0; extra == 'nvidia'
49
+ Requires-Dist: tiktoken>=0.7.0; extra == 'nvidia'
50
+ Provides-Extra: observability
51
+ Requires-Dist: opentelemetry-api>=1.28; extra == 'observability'
52
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.28; extra == 'observability'
53
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.62b1; extra == 'observability'
54
+ Requires-Dist: opentelemetry-sdk>=1.28; extra == 'observability'
55
+ Provides-Extra: ollama
56
+ Requires-Dist: openai>=1.0; extra == 'ollama'
57
+ Requires-Dist: tiktoken>=0.7.0; extra == 'ollama'
58
+ Provides-Extra: openai
59
+ Requires-Dist: openai>=1.0; extra == 'openai'
60
+ Requires-Dist: tiktoken>=0.7.0; extra == 'openai'
61
+ Provides-Extra: postgres
62
+ Requires-Dist: asyncpg>=0.29.0; extra == 'postgres'
63
+ Provides-Extra: realtime
64
+ Requires-Dist: websockets>=14.0; extra == 'realtime'
65
+ Provides-Extra: realtime-gemini
66
+ Requires-Dist: google-genai>=1.0.0; extra == 'realtime-gemini'
67
+ Requires-Dist: websockets>=14.0; extra == 'realtime-gemini'
68
+ Provides-Extra: sandbox
69
+ Requires-Dist: opensandbox>=0.1.7; extra == 'sandbox'
70
+ Provides-Extra: scheduler
71
+ Requires-Dist: croniter>=2.0; extra == 'scheduler'
72
+ Provides-Extra: vertex
73
+ Requires-Dist: google-api-python-client>=2.150.0; extra == 'vertex'
74
+ Requires-Dist: google-auth>=2.35.0; extra == 'vertex'
75
+ Requires-Dist: google-genai>=1.0.0; extra == 'vertex'
76
+ Provides-Extra: vertex-claude
77
+ Requires-Dist: anthropic[vertex]>=0.40.0; extra == 'vertex-claude'
78
+ Provides-Extra: web-search
79
+ Requires-Dist: ddgs>=7.0; extra == 'web-search'
80
+ Description-Content-Type: text/markdown
81
+
82
+ # monkeybot
83
+
84
+ <div align="center">
85
+ <img src="logo.png" alt="monkeybot Logo" width="600" />
86
+
87
+ <br />
88
+
89
+ *Multi-cloud agent harness — GCP-first docs, portable runtime*
90
+
91
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
92
+
93
+ </div>
94
+
95
+ monkeybot is a thin **multi-cloud-capable** harness for tool-using LLM agents: FastAPI SSE gateway, pluggable storage (SQLite/Postgres/Firestore), MCP tools, skills, and memory. It runs locally with zero cloud dependencies and ships the same container image to any Docker host.
96
+
97
+ > [!TIP]
98
+ > **Positioning:** monkeybot is an owned agent runtime with a focused provider set — not a universal integration catalog. **Docs and examples are GCP-first** (Vertex, GCS, Cloud Run, Agent Engine); **AWS** paths (Bedrock, S3, AgentCore, ECS/Lambda) are shipped in the Pattern guides; Azure coverage is thinner. See [Cloud deployment — Positioning](docs/cloud-deployment-design.md#positioning).
99
+
100
+ > [!NOTE]
101
+ > Skills under `SKILLS_PATH` execute Python code; only add skills from trusted sources.
102
+
103
+ ## Installation
104
+
105
+ Install [uv](https://docs.astral.sh/uv/), then the global CLI (no repo clone required):
106
+
107
+ ```bash
108
+ curl -LsSf https://astral.sh/uv/install.sh | sh # if needed
109
+ uv tool install monkeybot-cli
110
+
111
+ monkeybot new --dest ./my-agent --provider openai --yes
112
+ cd my-agent
113
+ uv sync
114
+ cp .env.example .env # add provider keys
115
+ monkeybot doctor
116
+ monkeybot chat
117
+ ```
118
+
119
+ `monkeybot new` writes an agent `pyproject.toml` with `monkeybot[<provider>]` (plus any `--with` extras). Plain `uv sync` installs the harness into the agent `.venv`. Upgrade the CLI with `uv tool upgrade monkeybot-cli`.
120
+
121
+ Full walkthrough (config, `.env`, MCP, Docker): **[Getting Started](docs/getting-started.md)**.
122
+
123
+ ### Developing the harness (contributors)
124
+
125
+ Clone only if you are changing monkeybot itself:
126
+
127
+ ```bash
128
+ git clone https://github.com/human-plus-machine/monkeybot.git
129
+ cd monkeybot && uv sync
130
+ cd cli && uv sync
131
+ uv tool install --editable .
132
+ ```
133
+
134
+ For a self-contained example agent that depends on this checkout via an editable path, see **[`demo_agent/`](demo_agent/)**.
135
+
136
+ ## CLI (`monkeybot`)
137
+
138
+ The CLI scaffolds, validates, and talks to an agent from the terminal. Use `monkeybot chat` for the turn-based SSE gateway, or `monkeybot talk` for the realtime WebSocket gateway (audio/text).
139
+
140
+ ### Talk to an agent
141
+
142
+ ```bash
143
+ cd path/to/agent # the dir containing monkeybot_config/monkeybot.yaml
144
+ monkeybot chat # spawns the gateway, connects, cleans up on exit
145
+ ```
146
+
147
+ Type a message and press Enter. `/bye` exits (and stops the gateway if this command started it). Ctrl-C also exits.
148
+
149
+ ### Agent-first dependencies
150
+
151
+ The CLI is intentionally thin — it does **not** pull in provider/storage extras (`bedrock`, `postgres`, …) globally. Those extras are declared on the **agent project** (scaffolded `pyproject.toml` lists `monkeybot[…]`). `monkeybot run` and `monkeybot chat` spawn the gateway from the agent project's interpreter so the extras resolve correctly:
152
+
153
+ 1. `<agent>/.venv/bin/python` — used directly when a project venv exists.
154
+ 2. `uv run python -m monkeybot.gateway.main` — when `<agent>/pyproject.toml` exists but no `.venv`.
155
+ 3. `sys.executable` (the CLI's interpreter) — legacy fallback for config-only trees (just `monkeybot_config/`, no `pyproject.toml`); in this case extras must be installed in the CLI env.
156
+
157
+ `monkeybot doctor` checks provider extras and Python version in that same interpreter, and its `remediation` field points at the agent project (add `monkeybot[<extra>]` to `pyproject.toml` dependencies, then `uv sync`), not the CLI.
158
+
159
+ To attach to a gateway you started yourself (e.g. to watch its logs), run it separately and connect with `--attach`:
160
+
161
+ ```bash
162
+ monkeybot run # terminal 1: gateway with live logs
163
+ monkeybot chat --attach # terminal 2: connect to the running gateway
164
+ ```
165
+
166
+ ### Commands
167
+
168
+ | Command | Purpose |
169
+ |---|---|
170
+ | `monkeybot new` | Scaffold `monkeybot_config/`, workspace dirs, agent `pyproject.toml`, and `.env.example` |
171
+ | `monkeybot validate` | Check `monkeybot.yaml`, referenced paths, and MCP config (`--json` for machine output) |
172
+ | `monkeybot doctor` | Verify Python, provider extras, credentials, and port availability (`--json` for machine output) |
173
+ | `monkeybot run` | Start the SSE gateway in the foreground (keeps logs visible) |
174
+ | `monkeybot chat` | Talk to the agent (SSE); spawns the gateway by default (`--attach` to use a running one) |
175
+ | `monkeybot talk` | Realtime voice/text client (WebSocket); audio by default, `--text` for typed input |
176
+
177
+ Common flags: `--cwd` (agent root, defaults to the current directory), `--config` (explicit `monkeybot.yaml` path), `--port` / `--url` (override the config-derived gateway address). Secrets are read from the agent's `.env`; nothing is committed to `monkeybot.yaml`.
178
+
179
+ Realtime audio (`monkeybot talk` without `--text`) needs PortAudio plus `monkeybot[cli-realtime]` in the agent env (already included for `demo_agent`).
180
+
181
+ ## Agent skill
182
+
183
+ Install the onboarding skill for Cursor and other agents via [skills.sh](https://skills.sh):
184
+
185
+ ```bash
186
+ npx skills add human-plus-machine/monkeybot --skill monkeybot
187
+ ```
188
+
189
+ The skill walks through CLI install, scaffolding `monkeybot_config/`, configuration, and your first chat. Source: [`cli/skills/monkeybot/`](cli/skills/monkeybot/).
190
+
191
+ ## Requirements
192
+
193
+ Python 3.11+ · [uv](https://docs.astral.sh/uv/) · optional provider keys in `.env` (Gemini, OpenAI, Anthropic)
194
+
195
+ ## Key capabilities
196
+
197
+ ### Subagent task queue (optional)
198
+
199
+ When **`MONKEYBOT_TASK_QUEUE=1`**, the `task` tool enqueues subagent runs via `record_pending` instead of spawning inline. A storage backend (`DB_URL`) is **required** — queue mode without storage raises at enqueue time.
200
+
201
+ Run workers with `python -m monkeybot.subagents.worker` (production) or `MONKEYBOT_WORKER_POOL=1` on the gateway (development only). Worker tuning:
202
+
203
+ | Variable | Default | Purpose |
204
+ |---|---|---|
205
+ | `MONKEYBOT_WORKER_STALE_CLAIM_MS` | `600000` (10 min) | Reclaim `running` rows with no heartbeat after this window; another worker may re-execute the run |
206
+ | `MONKEYBOT_WORKER_POLL_INTERVAL_S` | `2` | Poll interval for `pending_runs()` |
207
+ | `MONKEYBOT_WORKER_CONCURRENCY` | `1` | Max concurrent claimed runs per worker |
208
+ | `MONKEYBOT_WORKER_ID` | auto | Worker identity for claim attribution |
209
+
210
+ There is no claim heartbeat yet — subagent runs longer than `MONKEYBOT_WORKER_STALE_CLAIM_MS` risk duplicate execution. Increase the limit for long LLM workloads or keep runs under the window.
211
+
212
+ **Docker:** baseline production-style image — [`docker/Dockerfile`](docker/Dockerfile) + [`docker-compose.yml`](docker-compose.yml). Optional Cloud Run helpers may live in gitignored `internal/` for private forks; see **Step 3** in that doc.
213
+ ---
214
+
215
+ **Config:** copy or scaffold **`monkeybot_config/monkeybot.yaml`** from **`monkeybot_config_example/monkeybot.example.yaml`** (or the packaged scaffold template). Secrets go in **`.env`** — see the YAML header for variable names.
216
+
217
+ ## Documentation
218
+
219
+ | Guide | Description |
220
+ |---|---|
221
+ | [Getting Started](docs/getting-started.md) | Install, configure the gateway, and exercise sessions + SSE from the command line |
222
+ | [SSE gateway and custom UI](docs/sse-gateway-ui.md) | HTTP + SSE endpoints, event types, CORS/proxy notes, and how to wire your own frontend |
223
+ | [Skills](docs/skills.md) | Skill directory layout and `SKILL.md` discovery |
224
+ | [Model Context Protocol](docs/mcp.md) | MCP configuration, env interpolation, OAuth2 flows, and diagnostics |
225
+ | [Cloud deployment](docs/cloud-deployment-design.md) | Multi-cloud Patterns A/B/C — **GCP-first guides**, AWS addenda, portable harness |
226
+
227
+ ## Integrations
228
+
229
+ Tables below reflect what **shipped in v2.0.0** (see [CHANGELOG](CHANGELOG.md)). Longer-term platform work lives in [BACKLOG.md](BACKLOG.md).
230
+
231
+ ### LLM providers
232
+
233
+ | Provider | Status | Install extra |
234
+ |---|---|---|
235
+ | **Google Vertex AI / Gemini** | Supported | `monkeybot[gemini]` or ADC |
236
+ | **OpenAI** | Supported | `monkeybot[openai]` |
237
+ | **Anthropic Claude** | Supported | `monkeybot[claude]` |
238
+ | **Anthropic on Vertex AI** | Supported | `monkeybot[vertex-claude]` |
239
+ | **AWS Bedrock** | Supported | `monkeybot[bedrock]` |
240
+ | **HuggingFace Inference** | Supported | `monkeybot[huggingface]` |
241
+ | **Ollama** | Supported | `monkeybot[ollama]` |
242
+ | **NVIDIA NIM** (build.nvidia.com) | Supported | `monkeybot[nvidia]` |
243
+
244
+ ### Storage & persistence
245
+
246
+ | Backend | Status | Install extra |
247
+ |---|---|---|
248
+ | **SQLite** | Default | — (SSE gateway session history) |
249
+ | **Postgres** | Supported | `monkeybot[postgres]` |
250
+ | **Google Cloud Firestore** | Supported | `monkeybot[firestore]` |
251
+ | **Local filesystem** | Default | `local://` memory URI |
252
+ | **Google Cloud Storage** | Supported | `monkeybot[gcs]` |
253
+ | **AWS S3** | Supported | `monkeybot[aws]` |
254
+
255
+ ### Platform & tooling
256
+
257
+ | Integration | Status | Notes |
258
+ |---|---|---|
259
+ | **FastAPI SSE gateway** | Shipped | Sessions, streaming, health |
260
+ | **MCP** (stdio + HTTP) | Shipped | `monkeybot_config/mcp.json` |
261
+ | **Browser MCP** | Shipped | `integrations/browser-mcp/` |
262
+ | **OpenSandbox** | Shipped | Optional sandbox (`monkeybot[sandbox]`) |
263
+ | **Web search** | Shipped | DuckDuckGo (default, `monkeybot[web-search]`); Tavily / Firecrawl / Vertex grounding |
264
+ | **OpenTelemetry** | Shipped | `monkeybot[observability]` |
265
+ | **Docker / Cloud Run** | Shipped | Pattern A — [deploy guide](docs/deploy-pattern-a-container.md) |
266
+ | **Pattern C adapters** | Shipped | Agent platforms — e.g. AWS Bedrock AgentCore (`examples/agentcore/`), Vertex AI Agent Engine |
267
+
268
+ ### Roadmap (near-term)
269
+
270
+ | Item | Notes |
271
+ |---|---|
272
+ | **Slack gateway** | Events API / socket mode — [BACKLOG](BACKLOG.md#1-chat-integration) |
273
+ | **Google Chat gateway** | Workspace webhook handler — [BACKLOG](BACKLOG.md#1-chat-integration) |
274
+ | **Harness evals** | Langfuse / deepeval traceability in the agent loop — in progress |
275
+ | **Scheduler in gateway** | Cron jobs for long-running deployments — design in [BACKLOG](BACKLOG.md#3-scheduler) |
276
+
277
+ Azure, Microsoft Teams, Telegram, DynamoDB, CosmosDB, and managed secret resolvers (AWS Secrets Manager, Azure Key Vault, full GCP Secret Manager wiring) are tracked under **Future platforms** in [BACKLOG.md](BACKLOG.md).
278
+
279
+ For provider and cloud wiring, start from **`monkeybot_config_example/monkeybot.example.yaml`** and the [Cloud deployment](docs/cloud-deployment-design.md) guide.
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ uv sync
285
+ uv run pytest
286
+ uv run ruff check .
287
+ uv run mypy src/
288
+ ```
289
+
290
+ ## Support
291
+
292
+ If you need help or hit a problem, search existing issues or open a new one in the [GitHub issue tracker](https://github.com/human-plus-machine/monkeybot/issues).
293
+
294
+ ## Contributing
295
+
296
+ Contributions are welcome. You can help by:
297
+
298
+ - Reporting bugs or gaps in the docs (via issues).
299
+ - Suggesting new providers, interfaces, or deployment patterns (feature requests welcome).
300
+ - Opening pull requests for fixes and improvements.
301
+
302
+ Run checks before submitting: `uv run pytest && uv run ruff check . && uv run mypy src/`
303
+
304
+ ### Releasing
305
+
306
+ `develop` is the working branch; `main` always reflects the latest release.
307
+
308
+ 1. Anyone runs the **Prepare release** workflow (Actions tab → Prepare release → Run workflow), choosing `core` or `cli` and a version bump. It bumps the package's `pyproject.toml`, moves the `CHANGELOG.md` `Unreleased` section into a dated entry on a `release/<package>-v<version>` branch, and opens a PR from that branch into `main` (`develop` itself is untouched until the PR merges, so an abandoned release PR leaves no trace).
309
+ 2. An admin reviews and **merges the PR** (branch protection on `main` restricts who can merge — see below). Use a regular merge commit, not squash, so `main`'s history and the release tag line up with what was reviewed.
310
+ 3. The **Publish release** workflow runs automatically on that merge: it tags the new version, creates a GitHub Release from the changelog entry, Trusted-Publishes the released package(s) to PyPI (core before CLI when both ship), and merges `main` back into `develop` so the two branches don't drift apart.
311
+
312
+ One-time setup (repo Settings → Branches → add rule for `main`): require a pull request before merging, and restrict who can push to matching branches to Admins. That's the only access control needed — anyone can prepare a release, only admins can promote it to `main`.
313
+
314
+ **Before relying on this:** `develop` and `main` currently have diverged history (independent commits on each side). Reconcile them once with a manual merge before running the first automated release, or the first release PR may show unrelated changes or conflicts. `CHANGELOG.md` is shared across both packages — `core` and `cli` versions are bumped independently but their release notes live in the same file. The current versions already on `main` (`core` 2.0.0, `cli` 0.1.7) predate this tooling and have no changelog entry, so the first `publish` run intentionally skips tagging them rather than creating a release with empty notes; tagging starts from the next real version bump.
315
+
316
+ ## License
317
+
318
+ You may use, modify, and distribute monkeybot under the MIT license. See the [`LICENSE`](LICENSE) file for details.