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,286 @@
1
+ """Durable subprocess run tracking in SQLite."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from dataclasses import asdict, dataclass
8
+ from pathlib import Path
9
+ from typing import cast
10
+
11
+ import aiosqlite
12
+
13
+ _SUBAGENT_COLUMNS: tuple[str, ...] = (
14
+ "run_id",
15
+ "parent_run_id",
16
+ "script",
17
+ "envelope_json",
18
+ "status",
19
+ "result_json",
20
+ "error_json",
21
+ "started_at",
22
+ "finished_at",
23
+ "scratch_dir",
24
+ "worker_id",
25
+ "claimed_at",
26
+ )
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class SubagentRunRow:
31
+ """One row from the ``subagent_runs`` table."""
32
+
33
+ run_id: str
34
+ parent_run_id: str | None
35
+ script: str
36
+ envelope_json: str
37
+ status: str
38
+ result_json: str | None
39
+ error_json: str | None
40
+ started_at: int
41
+ finished_at: int | None
42
+ scratch_dir: str
43
+ worker_id: str | None = None
44
+ claimed_at: int | None = None
45
+
46
+
47
+ @dataclass
48
+ class SubagentEnvelope:
49
+ """Minimal envelope persisted with each subagent run."""
50
+
51
+ task: str
52
+ context: str
53
+ memory_storage_uri: str
54
+ parent_run_id: str
55
+ model: str = "gemini-2.5-flash"
56
+ traceparent: str | None = None
57
+ agent_md: str | None = None
58
+ subagent_type: str | None = None
59
+
60
+ def to_json(self) -> str:
61
+ """Serialize envelope to JSON text."""
62
+ data = asdict(self)
63
+ if data.get("traceparent") is None:
64
+ data.pop("traceparent", None)
65
+ if data.get("agent_md") is None:
66
+ data.pop("agent_md", None)
67
+ if data.get("subagent_type") is None:
68
+ data.pop("subagent_type", None)
69
+ return json.dumps(data, sort_keys=True)
70
+
71
+ @classmethod
72
+ def from_json(cls, raw: str) -> SubagentEnvelope:
73
+ """Deserialize JSON text; raises ``ValueError`` on invalid input."""
74
+ try:
75
+ data = json.loads(raw)
76
+ except json.JSONDecodeError as exc:
77
+ raise ValueError("Invalid envelope JSON") from exc
78
+ if not isinstance(data, dict):
79
+ raise ValueError("Envelope JSON must be an object")
80
+ base_required = {"task", "context", "parent_run_id"}
81
+ missing = base_required - data.keys()
82
+ if missing:
83
+ raise ValueError(f"Envelope missing fields: {sorted(missing)}")
84
+ from monkeybot.core.subagents.subagent_proto import _memory_storage_uri_from_dict
85
+
86
+ uri = _memory_storage_uri_from_dict(data)
87
+ model = data.get("model", "gemini-2.5-flash")
88
+ if not isinstance(model, str):
89
+ raise ValueError("model must be a string when present")
90
+ for key in ("task", "context", "parent_run_id"):
91
+ if not isinstance(data[key], str):
92
+ raise ValueError(f"{key} must be a string")
93
+ traceparent = data.get("traceparent")
94
+ if traceparent is not None and not isinstance(traceparent, str):
95
+ raise ValueError("traceparent must be a string when present")
96
+ agent_md = data.get("agent_md")
97
+ if agent_md is not None and not isinstance(agent_md, str):
98
+ raise ValueError("agent_md must be a string when present")
99
+ subagent_type = data.get("subagent_type")
100
+ if subagent_type is not None and not isinstance(subagent_type, str):
101
+ raise ValueError("subagent_type must be a string when present")
102
+ return cls(
103
+ task=data["task"],
104
+ context=data["context"],
105
+ memory_storage_uri=uri.strip(),
106
+ parent_run_id=data["parent_run_id"],
107
+ model=model,
108
+ traceparent=traceparent.strip() if isinstance(traceparent, str) and traceparent.strip() else None,
109
+ agent_md=agent_md.strip() if isinstance(agent_md, str) and agent_md.strip() else None,
110
+ subagent_type=(
111
+ subagent_type.strip()
112
+ if isinstance(subagent_type, str) and subagent_type.strip()
113
+ else None
114
+ ),
115
+ )
116
+
117
+
118
+ def _tuple_to_run_row(row: tuple[object, ...]) -> SubagentRunRow:
119
+ d = dict(zip(_SUBAGENT_COLUMNS, row, strict=True))
120
+ return SubagentRunRow(
121
+ run_id=str(d["run_id"]),
122
+ parent_run_id=str(d["parent_run_id"]) if d["parent_run_id"] is not None else None,
123
+ script=str(d["script"]),
124
+ envelope_json=str(d["envelope_json"]),
125
+ status=str(d["status"]),
126
+ result_json=str(d["result_json"]) if d["result_json"] is not None else None,
127
+ error_json=str(d["error_json"]) if d["error_json"] is not None else None,
128
+ started_at=int(cast(int, d["started_at"])),
129
+ finished_at=int(cast(int, d["finished_at"])) if d["finished_at"] is not None else None,
130
+ scratch_dir=str(d["scratch_dir"]),
131
+ worker_id=str(d["worker_id"]) if d["worker_id"] is not None else None,
132
+ claimed_at=int(cast(int, d["claimed_at"])) if d["claimed_at"] is not None else None,
133
+ )
134
+
135
+
136
+ class SQLiteRunStore:
137
+ """Persist lifecycle rows for subprocess subagents."""
138
+
139
+ def __init__(self, conn: aiosqlite.Connection) -> None:
140
+ self._conn = conn
141
+
142
+ async def record_pending(
143
+ self,
144
+ run_id: str,
145
+ parent_run_id: str | None,
146
+ script: str,
147
+ envelope: SubagentEnvelope,
148
+ scratch_dir: Path,
149
+ ) -> None:
150
+ """Insert a ``pending`` row for worker-pool consumption."""
151
+ await self._record_run("pending", run_id, parent_run_id, script, envelope, scratch_dir)
152
+
153
+ async def record_started(
154
+ self,
155
+ run_id: str,
156
+ parent_run_id: str | None,
157
+ script: str,
158
+ envelope: SubagentEnvelope,
159
+ scratch_dir: Path,
160
+ ) -> None:
161
+ """Insert a ``running`` row with envelope metadata."""
162
+ await self._record_run("running", run_id, parent_run_id, script, envelope, scratch_dir)
163
+
164
+ async def _record_run(
165
+ self,
166
+ status: str,
167
+ run_id: str,
168
+ parent_run_id: str | None,
169
+ script: str,
170
+ envelope: SubagentEnvelope,
171
+ scratch_dir: Path,
172
+ ) -> None:
173
+ now_ms = int(time.time() * 1000)
174
+ await self._conn.execute(
175
+ """
176
+ INSERT INTO subagent_runs(
177
+ run_id, parent_run_id, script, envelope_json,
178
+ status, result_json, error_json, started_at, finished_at, scratch_dir,
179
+ worker_id, claimed_at
180
+ )
181
+ VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, NULL, ?, NULL, NULL)
182
+ """,
183
+ (
184
+ run_id,
185
+ parent_run_id,
186
+ script,
187
+ envelope.to_json(),
188
+ status,
189
+ now_ms,
190
+ str(scratch_dir),
191
+ ),
192
+ )
193
+ await self._conn.commit()
194
+
195
+ async def claim(self, run_id: str, worker_id: str) -> bool:
196
+ """Atomically transition ``pending`` -> ``running``; return True if this caller won."""
197
+ now_ms = int(time.time() * 1000)
198
+ cursor = await self._conn.execute(
199
+ """
200
+ UPDATE subagent_runs
201
+ SET status = 'running',
202
+ worker_id = ?,
203
+ claimed_at = ?
204
+ WHERE run_id = ? AND status = 'pending'
205
+ """,
206
+ (worker_id, now_ms, run_id),
207
+ )
208
+ await self._conn.commit()
209
+ return int(cursor.rowcount) == 1
210
+
211
+ async def reset_stale_claims(self, stale_after_ms: int) -> int:
212
+ """Reset ``running`` rows with stale ``claimed_at`` back to ``pending``."""
213
+ cutoff = int(time.time() * 1000) - stale_after_ms
214
+ cursor = await self._conn.execute(
215
+ """
216
+ UPDATE subagent_runs
217
+ SET status = 'pending',
218
+ worker_id = NULL,
219
+ claimed_at = NULL
220
+ WHERE status = 'running'
221
+ AND claimed_at IS NOT NULL
222
+ AND claimed_at < ?
223
+ """,
224
+ (cutoff,),
225
+ )
226
+ await self._conn.commit()
227
+ return int(cursor.rowcount)
228
+
229
+ async def record_completed(self, run_id: str, result_json: str) -> None:
230
+ """Mark run completed with payload."""
231
+ now_ms = int(time.time() * 1000)
232
+ await self._conn.execute(
233
+ """
234
+ UPDATE subagent_runs
235
+ SET status = 'completed',
236
+ result_json = ?,
237
+ finished_at = ?,
238
+ error_json = NULL
239
+ WHERE run_id = ?
240
+ """,
241
+ (result_json, now_ms, run_id),
242
+ )
243
+ await self._conn.commit()
244
+
245
+ async def record_failed(self, run_id: str, error: str) -> None:
246
+ """Mark run failed with JSON ``{\"message\": ...}`` error."""
247
+ now_ms = int(time.time() * 1000)
248
+ err_payload = json.dumps({"message": error})
249
+ await self._conn.execute(
250
+ """
251
+ UPDATE subagent_runs
252
+ SET status = 'failed',
253
+ error_json = ?,
254
+ finished_at = ?
255
+ WHERE run_id = ?
256
+ """,
257
+ (err_payload, now_ms, run_id),
258
+ )
259
+ await self._conn.commit()
260
+
261
+ async def pending_runs(self) -> list[SubagentRunRow]:
262
+ """Return ``pending`` runs oldest-first (worker pool claim candidates)."""
263
+ columns = ", ".join(_SUBAGENT_COLUMNS)
264
+ cursor = await self._conn.execute(
265
+ f"""
266
+ SELECT {columns} FROM subagent_runs
267
+ WHERE status = 'pending'
268
+ ORDER BY started_at ASC
269
+ """
270
+ )
271
+ rows = await cursor.fetchall()
272
+ await cursor.close()
273
+ return [_tuple_to_run_row(tuple(row)) for row in rows]
274
+
275
+ async def get_run(self, run_id: str) -> SubagentRunRow | None:
276
+ """Return full row or ``None``."""
277
+ columns = ", ".join(_SUBAGENT_COLUMNS)
278
+ cursor = await self._conn.execute(
279
+ f"SELECT {columns} FROM subagent_runs WHERE run_id = ?",
280
+ (run_id,),
281
+ )
282
+ row = await cursor.fetchone()
283
+ await cursor.close()
284
+ if row is None:
285
+ return None
286
+ return _tuple_to_run_row(tuple(row))