apsimo 1.3.0__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 (614) hide show
  1. apsimo/__init__.py +38 -0
  2. apsimo/__main__.py +6 -0
  3. apsimo/agent/__init__.py +6 -0
  4. apsimo/agent/client.py +276 -0
  5. apsimo/agent/models.py +46 -0
  6. apsimo/agents/__init__.py +20 -0
  7. apsimo/agents/models.py +264 -0
  8. apsimo/agents/store.py +861 -0
  9. apsimo/agents/websocket.py +522 -0
  10. apsimo/api/__init__.py +1 -0
  11. apsimo/api/auth_telemetry.py +287 -0
  12. apsimo/api/authority.py +1203 -0
  13. apsimo/api/contact_grants.py +347 -0
  14. apsimo/api/middleware.py +483 -0
  15. apsimo/api/routers/__init__.py +1 -0
  16. apsimo/api/routers/commitment_work.py +265 -0
  17. apsimo/api/routers/context_gate.py +123 -0
  18. apsimo/api/routers/executions.py +140 -0
  19. apsimo/api/routers/followup_plans.py +147 -0
  20. apsimo/api/routers/governed_actions.py +162 -0
  21. apsimo/api/routers/host.py +14473 -0
  22. apsimo/api/routers/initiative_work.py +115 -0
  23. apsimo/api/routers/mining.py +104 -0
  24. apsimo/api/routers/observations.py +110 -0
  25. apsimo/api/routers/social_state.py +225 -0
  26. apsimo/api/routers/task_queue.py +2715 -0
  27. apsimo/api/routers/temporal_followups.py +251 -0
  28. apsimo/api/routers/transport.py +110 -0
  29. apsimo/api/routers/transport_ingress_api.py +240 -0
  30. apsimo/api/schemas/__init__.py +1 -0
  31. apsimo/api/schemas/host.py +1949 -0
  32. apsimo/autonomy/cli.py +110 -0
  33. apsimo/autonomy/condition_worker.py +437 -0
  34. apsimo/autonomy/config.py +424 -0
  35. apsimo/autonomy/loop.py +4316 -0
  36. apsimo/autonomy/registry.py +339 -0
  37. apsimo/autonomy/scheduler.py +1822 -0
  38. apsimo/autonomy/synthesis.py +449 -0
  39. apsimo/backup.py +962 -0
  40. apsimo/beliefs/__init__.py +23 -0
  41. apsimo/beliefs/contradictions.py +109 -0
  42. apsimo/beliefs/decay.py +61 -0
  43. apsimo/beliefs/engine.py +479 -0
  44. apsimo/beliefs/models.py +67 -0
  45. apsimo/beliefs/promotion.py +41 -0
  46. apsimo/beliefs/resolve.py +58 -0
  47. apsimo/beliefs/source_claims.py +690 -0
  48. apsimo/beliefs/source_projection.py +883 -0
  49. apsimo/beliefs/source_time.py +208 -0
  50. apsimo/beliefs/store.py +133 -0
  51. apsimo/briefings/aggregators.py +824 -0
  52. apsimo/briefings/composer.py +420 -0
  53. apsimo/briefings/config.py +55 -0
  54. apsimo/briefings/delivery.py +439 -0
  55. apsimo/briefings/engagement.py +97 -0
  56. apsimo/briefings/engine.py +274 -0
  57. apsimo/briefings/enhancer.py +99 -0
  58. apsimo/briefings/models.py +183 -0
  59. apsimo/briefings/scheduler.py +382 -0
  60. apsimo/briefings/store.py +435 -0
  61. apsimo/chain/__init__.py +48 -0
  62. apsimo/chain/block.py +100 -0
  63. apsimo/chain/cli.py +704 -0
  64. apsimo/chain/genesis.py +443 -0
  65. apsimo/chain/identity.py +416 -0
  66. apsimo/chain/keys.py +1025 -0
  67. apsimo/chain/local_keys.py +187 -0
  68. apsimo/chain/manager.py +290 -0
  69. apsimo/chain/node.py +163 -0
  70. apsimo/chain/plugin_transactions.py +371 -0
  71. apsimo/chain/protocol.py +220 -0
  72. apsimo/chain/state_machine.py +676 -0
  73. apsimo/chain/storage.py +503 -0
  74. apsimo/chain/transactions.py +250 -0
  75. apsimo/chain/validation.py +397 -0
  76. apsimo/channels/__init__.py +1 -0
  77. apsimo/channels/manifest.py +31 -0
  78. apsimo/channels/migrations/001_channels_schema.sql +12 -0
  79. apsimo/channels/phone_gateways.py +42 -0
  80. apsimo/channels/presence.py +188 -0
  81. apsimo/channels/router.py +235 -0
  82. apsimo/channels/store.py +231 -0
  83. apsimo/cli.py +2688 -0
  84. apsimo/cognition/__init__.py +11 -0
  85. apsimo/cognition/charter.py +398 -0
  86. apsimo/cognition/drive_governance.py +3530 -0
  87. apsimo/cognition/evidence_pipeline.py +1627 -0
  88. apsimo/cognition/external_events.py +932 -0
  89. apsimo/cognition/goal_spine.py +3488 -0
  90. apsimo/cognition/introspection.py +214 -0
  91. apsimo/cognition/prompt.py +150 -0
  92. apsimo/cognition/runtime.py +108 -0
  93. apsimo/cognition/trigger.py +154 -0
  94. apsimo/commitments/__init__.py +18 -0
  95. apsimo/commitments/local_work.py +355 -0
  96. apsimo/commitments/store.py +1052 -0
  97. apsimo/commitments/work.py +91 -0
  98. apsimo/compat.py +53 -0
  99. apsimo/compression/__init__.py +467 -0
  100. apsimo/connectors/__init__.py +21 -0
  101. apsimo/connectors/base.py +152 -0
  102. apsimo/connectors/caldav_calendar.py +125 -0
  103. apsimo/connectors/fs_documents.py +85 -0
  104. apsimo/connectors/imap_email.py +138 -0
  105. apsimo/connectors/manager.py +218 -0
  106. apsimo/connectors/webhook_pull.py +88 -0
  107. apsimo/contacts/__init__.py +33 -0
  108. apsimo/contacts/comms.py +357 -0
  109. apsimo/contacts/config.py +79 -0
  110. apsimo/contacts/exporters/__init__.py +1 -0
  111. apsimo/contacts/exporters/vcard.py +71 -0
  112. apsimo/contacts/identity_links.py +251 -0
  113. apsimo/contacts/importer.py +280 -0
  114. apsimo/contacts/importers/__init__.py +1 -0
  115. apsimo/contacts/importers/batch.py +43 -0
  116. apsimo/contacts/importers/macos_contacts.py +101 -0
  117. apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
  118. apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
  119. apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
  120. apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
  121. apsimo/contacts/migrations/005_identity_links.sql +27 -0
  122. apsimo/contacts/models.py +308 -0
  123. apsimo/contacts/scoring.py +16 -0
  124. apsimo/contacts/store.py +1623 -0
  125. apsimo/contacts/transport_ingress.py +252 -0
  126. apsimo/contacts/world_bridge.py +314 -0
  127. apsimo/contextgate/__init__.py +69 -0
  128. apsimo/contextgate/chunker.py +169 -0
  129. apsimo/contextgate/estimate.py +54 -0
  130. apsimo/contextgate/gate.py +313 -0
  131. apsimo/contextgate/retrieve.py +115 -0
  132. apsimo/delivery/__init__.py +16 -0
  133. apsimo/delivery/bridge.py +1260 -0
  134. apsimo/delivery/channels.py +526 -0
  135. apsimo/delivery/classification.py +50 -0
  136. apsimo/delivery/rate_limiter.py +268 -0
  137. apsimo/delivery/reachout_policy.py +206 -0
  138. apsimo/directed/__init__.py +22 -0
  139. apsimo/directed/audit.py +167 -0
  140. apsimo/directed/intake.py +95 -0
  141. apsimo/directed/models.py +191 -0
  142. apsimo/directed/service.py +509 -0
  143. apsimo/directives/__init__.py +25 -0
  144. apsimo/directives/evidence.py +87 -0
  145. apsimo/directives/extractor.py +188 -0
  146. apsimo/directives/guard.py +364 -0
  147. apsimo/directives/models.py +206 -0
  148. apsimo/directives/service.py +372 -0
  149. apsimo/directives/store.py +167 -0
  150. apsimo/doctor.py +2173 -0
  151. apsimo/environment.py +43 -0
  152. apsimo/events/__init__.py +33 -0
  153. apsimo/events/broadcaster.py +98 -0
  154. apsimo/events/bus.py +217 -0
  155. apsimo/events/journal.py +863 -0
  156. apsimo/events/stream.py +131 -0
  157. apsimo/events/types.py +150 -0
  158. apsimo/execution_results.py +357 -0
  159. apsimo/feedback/__init__.py +5 -0
  160. apsimo/feedback/store.py +76 -0
  161. apsimo/feeds/__init__.py +19 -0
  162. apsimo/feeds/cli.py +84 -0
  163. apsimo/feeds/engine.py +437 -0
  164. apsimo/feeds/example-feed.yaml +77 -0
  165. apsimo/feeds/hermes_cron.py +126 -0
  166. apsimo/feeds/manager.py +235 -0
  167. apsimo/feeds/spec.py +250 -0
  168. apsimo/feeds/template.py +202 -0
  169. apsimo/gate/__init__.py +18 -0
  170. apsimo/gate/audit.py +61 -0
  171. apsimo/gate/communication_policy.py +166 -0
  172. apsimo/gate/config.py +72 -0
  173. apsimo/gate/context_provenance.py +170 -0
  174. apsimo/gate/env_risk.py +226 -0
  175. apsimo/gate/guard_audit.py +353 -0
  176. apsimo/gate/layers/__init__.py +1 -0
  177. apsimo/gate/layers/base.py +15 -0
  178. apsimo/gate/layers/l1_recipient.py +66 -0
  179. apsimo/gate/layers/l2_pii.py +134 -0
  180. apsimo/gate/layers/l3_cross_context.py +50 -0
  181. apsimo/gate/layers/l4_trust_tier.py +78 -0
  182. apsimo/gate/layers/l5_injection.py +199 -0
  183. apsimo/gate/layers/l6_review.py +86 -0
  184. apsimo/gate/layers/l7_delay.py +100 -0
  185. apsimo/gate/layers/tom2_epistemic.py +185 -0
  186. apsimo/gate/models.py +64 -0
  187. apsimo/gate/pending_dispatch.py +5 -0
  188. apsimo/gate/pipeline.py +206 -0
  189. apsimo/gate/rejection.py +259 -0
  190. apsimo/gate/response_guard.py +700 -0
  191. apsimo/gate/rulesets/injection_v1.yaml +51 -0
  192. apsimo/gate/surface_policy.py +189 -0
  193. apsimo/gate/taint.py +226 -0
  194. apsimo/genesis.json +9 -0
  195. apsimo/goals/__init__.py +100 -0
  196. apsimo/goals/config.py +38 -0
  197. apsimo/goals/decomposer.py +421 -0
  198. apsimo/goals/engine.py +617 -0
  199. apsimo/goals/inference.py +354 -0
  200. apsimo/goals/models.py +302 -0
  201. apsimo/goals/priority.py +270 -0
  202. apsimo/goals/queue_bridge.py +149 -0
  203. apsimo/goals/replan.py +450 -0
  204. apsimo/goals/schema.sql +89 -0
  205. apsimo/goals/store.py +692 -0
  206. apsimo/governed_actions.py +1708 -0
  207. apsimo/harness_integration/__init__.py +45 -0
  208. apsimo/harness_integration/context.py +41 -0
  209. apsimo/harness_integration/skills.py +231 -0
  210. apsimo/identity/__init__.py +26 -0
  211. apsimo/identity/participants.py +181 -0
  212. apsimo/identity/resolver.py +329 -0
  213. apsimo/identity_bootstrap/__init__.py +5 -0
  214. apsimo/identity_bootstrap/builder.py +208 -0
  215. apsimo/identity_bootstrap/corpus.py +443 -0
  216. apsimo/identity_bootstrap/models.py +54 -0
  217. apsimo/identity_bootstrap/runner.py +353 -0
  218. apsimo/identity_bootstrap/seeders/__init__.py +25 -0
  219. apsimo/identity_bootstrap/seeders/briefings.py +109 -0
  220. apsimo/identity_bootstrap/seeders/chain.py +57 -0
  221. apsimo/identity_bootstrap/seeders/goals.py +128 -0
  222. apsimo/identity_bootstrap/seeders/memory.py +191 -0
  223. apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
  224. apsimo/identity_bootstrap/seeders/relationship.py +152 -0
  225. apsimo/identity_bootstrap/seeders/sessions.py +67 -0
  226. apsimo/identity_bootstrap/seeders/skills.py +92 -0
  227. apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
  228. apsimo/identity_bootstrap/seeders/world_model.py +143 -0
  229. apsimo/identity_bootstrap/self_query.py +92 -0
  230. apsimo/identity_bootstrap/self_reflection.py +155 -0
  231. apsimo/identity_bootstrap/skill.py +37 -0
  232. apsimo/identity_bootstrap/verifier.py +436 -0
  233. apsimo/initiatives/__init__.py +20 -0
  234. apsimo/initiatives/action_registry.py +454 -0
  235. apsimo/initiatives/approval_authority.py +2105 -0
  236. apsimo/initiatives/approval_policy.py +123 -0
  237. apsimo/initiatives/assignment.py +263 -0
  238. apsimo/initiatives/backup_evidence.py +100 -0
  239. apsimo/initiatives/context_freshness.py +103 -0
  240. apsimo/initiatives/models.py +318 -0
  241. apsimo/initiatives/native_work.py +270 -0
  242. apsimo/initiatives/standing_approvals.py +232 -0
  243. apsimo/initiatives/store.py +1081 -0
  244. apsimo/initiatives/temporal_followup.py +410 -0
  245. apsimo/intelligence/__init__.py +1 -0
  246. apsimo/intelligence/cognition/__init__.py +24 -0
  247. apsimo/intelligence/cognition/gap_detector.py +148 -0
  248. apsimo/intelligence/cognition/metalearner.py +547 -0
  249. apsimo/intelligence/cognition/metrics_collector.py +217 -0
  250. apsimo/intelligence/cognition/performance_index.py +299 -0
  251. apsimo/intelligence/cognition/registry.py +192 -0
  252. apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
  253. apsimo/intelligence/cognition/types.py +16 -0
  254. apsimo/intelligence/components/__init__.py +66 -0
  255. apsimo/intelligence/components/anomaly_detector.py +413 -0
  256. apsimo/intelligence/components/initiative_engine.py +2643 -0
  257. apsimo/intelligence/components/preference_learner.py +521 -0
  258. apsimo/intelligence/components/research_orchestrator.py +358 -0
  259. apsimo/intelligence/components/self_directed_thinker.py +221 -0
  260. apsimo/intelligence/components/self_reflector.py +252 -0
  261. apsimo/intelligence/components/session_continuity.py +154 -0
  262. apsimo/intelligence/components/task_planner.py +320 -0
  263. apsimo/intelligence/components/tool_learner.py +217 -0
  264. apsimo/intelligence/graph/__init__.py +79 -0
  265. apsimo/intelligence/graph/client.py +2483 -0
  266. apsimo/intelligence/graph/consolidator.py +405 -0
  267. apsimo/intelligence/graph/distiller.py +312 -0
  268. apsimo/intelligence/graph/migrations.py +129 -0
  269. apsimo/intelligence/graph/queries.py +248 -0
  270. apsimo/intelligence/graph/recall.py +281 -0
  271. apsimo/intelligence/graph/reconciler.py +144 -0
  272. apsimo/intelligence/graph/schema.py +337 -0
  273. apsimo/intelligence/graph/selection.py +252 -0
  274. apsimo/intelligence/learning/__init__.py +17 -0
  275. apsimo/intelligence/learning/continuous_learner.py +245 -0
  276. apsimo/intelligence/learning/feedback_store.py +321 -0
  277. apsimo/intelligence/mind_model/__init__.py +1 -0
  278. apsimo/intelligence/mind_model/graph_baseline.py +136 -0
  279. apsimo/intelligence/mind_model/signal_collector.py +361 -0
  280. apsimo/intelligence/relationships/__init__.py +11 -0
  281. apsimo/intelligence/relationships/profiler.py +389 -0
  282. apsimo/intelligence/relationships/scorer.py +560 -0
  283. apsimo/intelligence/relationships/signal_floor.py +66 -0
  284. apsimo/intelligence/relationships/trust_tiers.py +300 -0
  285. apsimo/intelligence/synthesis/__init__.py +40 -0
  286. apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
  287. apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
  288. apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
  289. apsimo/intelligence/synthesis/insight_store.py +79 -0
  290. apsimo/intelligence/synthesis/insight_validator.py +183 -0
  291. apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
  292. apsimo/intelligence/turn_middleware/__init__.py +15 -0
  293. apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
  294. apsimo/mcp/__init__.py +41 -0
  295. apsimo/mcp/__main__.py +6 -0
  296. apsimo/mcp/config.py +287 -0
  297. apsimo/mcp/server.py +501 -0
  298. apsimo/migrations.py +187 -0
  299. apsimo/mining/__init__.py +27 -0
  300. apsimo/mining/corpus.py +239 -0
  301. apsimo/mining/escalations.py +289 -0
  302. apsimo/mining/models.py +169 -0
  303. apsimo/mining/store.py +210 -0
  304. apsimo/models/__init__.py +30 -0
  305. apsimo/models/memory.py +80 -0
  306. apsimo/models/mesh.py +72 -0
  307. apsimo/models/person.py +104 -0
  308. apsimo/models/signal.py +108 -0
  309. apsimo/observations/__init__.py +15 -0
  310. apsimo/observations/store.py +277 -0
  311. apsimo/patterns/__init__.py +6 -0
  312. apsimo/patterns/extract.py +187 -0
  313. apsimo/patterns/store.py +227 -0
  314. apsimo/persona/__init__.py +1 -0
  315. apsimo/persona/engine.py +611 -0
  316. apsimo/persona/manifest.py +140 -0
  317. apsimo/projects/__init__.py +28 -0
  318. apsimo/projects/engine.py +1681 -0
  319. apsimo/projects/event_outbox.py +188 -0
  320. apsimo/projects/models.py +216 -0
  321. apsimo/projects/planner.py +181 -0
  322. apsimo/projects/store.py +1446 -0
  323. apsimo/proposals/__init__.py +12 -0
  324. apsimo/proposals/engine.py +114 -0
  325. apsimo/proposals/models.py +207 -0
  326. apsimo/qualification/__init__.py +1 -0
  327. apsimo/qualification/cases.py +75 -0
  328. apsimo/qualification/cli.py +51 -0
  329. apsimo/qualification/memory_cases.py +209 -0
  330. apsimo/qualification/records.py +92 -0
  331. apsimo/qualification/report.py +87 -0
  332. apsimo/qualification/runner.py +311 -0
  333. apsimo/qualification/structured_cases.py +131 -0
  334. apsimo/reasoning/__init__.py +13 -0
  335. apsimo/reasoning/executor.py +506 -0
  336. apsimo/reasoning/loop.py +373 -0
  337. apsimo/reasoning/native_tools/__init__.py +16 -0
  338. apsimo/reasoning/native_tools/calculate.py +141 -0
  339. apsimo/reasoning/native_tools/file_ops.py +150 -0
  340. apsimo/reasoning/native_tools/web_search.py +49 -0
  341. apsimo/reasoning/tool_policy.py +182 -0
  342. apsimo/redact/__init__.py +176 -0
  343. apsimo/repos/__init__.py +5 -0
  344. apsimo/repos/mirrors.py +204 -0
  345. apsimo/research/__init__.py +41 -0
  346. apsimo/research/artifact.py +482 -0
  347. apsimo/research/gatherer.py +387 -0
  348. apsimo/research/pipeline.py +513 -0
  349. apsimo/research/search/__init__.py +7 -0
  350. apsimo/research/search/base.py +41 -0
  351. apsimo/research/search/brave.py +59 -0
  352. apsimo/research/search/cache.py +51 -0
  353. apsimo/research/search/duckduckgo.py +103 -0
  354. apsimo/research/search/orchestrator.py +119 -0
  355. apsimo/research/search/serpapi.py +59 -0
  356. apsimo/research/search/tavily.py +59 -0
  357. apsimo/research/synthesizer.py +309 -0
  358. apsimo/router/__init__.py +30 -0
  359. apsimo/router/complexity_scorer.py +148 -0
  360. apsimo/router/endpoints.py +153 -0
  361. apsimo/router/fallback.py +58 -0
  362. apsimo/router/functions.py +243 -0
  363. apsimo/router/native_policy.py +52 -0
  364. apsimo/router/router.py +762 -0
  365. apsimo/router/self_learning.py +174 -0
  366. apsimo/router/tiers.py +677 -0
  367. apsimo/sandbox/__init__.py +21 -0
  368. apsimo/sandbox/backend.py +195 -0
  369. apsimo/sandbox/manager.py +173 -0
  370. apsimo/scope_bounds.py +7 -0
  371. apsimo/secrets/__init__.py +6 -0
  372. apsimo/secrets/backends/__init__.py +8 -0
  373. apsimo/secrets/backends/base.py +42 -0
  374. apsimo/secrets/backends/env.py +110 -0
  375. apsimo/secrets/backends/keyring.py +72 -0
  376. apsimo/secrets/backends/onepassword.py +232 -0
  377. apsimo/secrets/cli.py +191 -0
  378. apsimo/secrets/manager.py +160 -0
  379. apsimo/secrets/migration.py +101 -0
  380. apsimo/secrets/types.py +98 -0
  381. apsimo/seed.py +41 -0
  382. apsimo/self_model/__init__.py +37 -0
  383. apsimo/self_model/appraisals.py +673 -0
  384. apsimo/self_model/benchmark.py +1314 -0
  385. apsimo/self_model/brief.py +40 -0
  386. apsimo/self_model/event_concerns.py +1128 -0
  387. apsimo/self_model/execution_forecasts.py +353 -0
  388. apsimo/self_model/expectations.py +1595 -0
  389. apsimo/self_model/experiments.py +1150 -0
  390. apsimo/self_model/journal.py +148 -0
  391. apsimo/self_model/judgments.py +705 -0
  392. apsimo/self_model/native_outcomes.py +55 -0
  393. apsimo/self_model/params.py +220 -0
  394. apsimo/self_model/perspective.py +246 -0
  395. apsimo/self_model/reconcile.py +183 -0
  396. apsimo/self_model/reply_forecasts.py +381 -0
  397. apsimo/self_model/runtime_forecasts.py +296 -0
  398. apsimo/self_model/runtime_models.py +67 -0
  399. apsimo/self_model/settlement.py +207 -0
  400. apsimo/self_model/situation.py +1731 -0
  401. apsimo/self_model/store.py +883 -0
  402. apsimo/self_model/supervised.py +137 -0
  403. apsimo/self_model/thinker.py +99 -0
  404. apsimo/self_model/trust.py +388 -0
  405. apsimo/self_model/workspace.py +2388 -0
  406. apsimo/server.py +4197 -0
  407. apsimo/services/__init__.py +1 -0
  408. apsimo/services/agent_bridge.py +474 -0
  409. apsimo/services/initiative_executor.py +914 -0
  410. apsimo/services/instance.py +297 -0
  411. apsimo/sessions/__init__.py +22 -0
  412. apsimo/sessions/config.py +13 -0
  413. apsimo/sessions/context_loader.py +88 -0
  414. apsimo/sessions/federation_session.py +75 -0
  415. apsimo/sessions/isolated_session.py +98 -0
  416. apsimo/sessions/reports.py +84 -0
  417. apsimo/sessions/store.py +148 -0
  418. apsimo/setup.py +2818 -0
  419. apsimo/setup_hermes.py +879 -0
  420. apsimo/setup_local_work.py +218 -0
  421. apsimo/setup_native_goals.py +134 -0
  422. apsimo/setup_native_reviews.py +115 -0
  423. apsimo/skills/__init__.py +10 -0
  424. apsimo/skills/base.py +108 -0
  425. apsimo/skills/budget.py +28 -0
  426. apsimo/skills/executor.py +493 -0
  427. apsimo/skills/executors/__init__.py +1 -0
  428. apsimo/skills/executors/behavioral_correction.py +75 -0
  429. apsimo/skills/executors/capability_gap.py +38 -0
  430. apsimo/skills/executors/data_quality.py +163 -0
  431. apsimo/skills/executors/knowledge_acquisition.py +41 -0
  432. apsimo/skills/executors/operational_hygiene.py +185 -0
  433. apsimo/skills/executors/subsystem_health.py +169 -0
  434. apsimo/skills/hermes_export.py +431 -0
  435. apsimo/skills/index.py +123 -0
  436. apsimo/skills/learning/__init__.py +21 -0
  437. apsimo/skills/learning/novelty_detector.py +206 -0
  438. apsimo/skills/learning/pattern_extractor.py +199 -0
  439. apsimo/skills/learning/triggers.py +159 -0
  440. apsimo/skills/loader.py +246 -0
  441. apsimo/skills/migrations/002_progressive_loading.sql +6 -0
  442. apsimo/skills/migrations/backfill_triggers.py +20 -0
  443. apsimo/skills/models.py +202 -0
  444. apsimo/skills/packager.py +128 -0
  445. apsimo/skills/protocols.py +70 -0
  446. apsimo/skills/registry.py +191 -0
  447. apsimo/skills/runtime.py +58 -0
  448. apsimo/skills/sandbox_runner.py +229 -0
  449. apsimo/skills/scheduler.py +129 -0
  450. apsimo/skills/schema.py +79 -0
  451. apsimo/skills/security/__init__.py +12 -0
  452. apsimo/skills/security/guards.py +53 -0
  453. apsimo/skills/security/scanner.py +223 -0
  454. apsimo/skills_memory/__init__.py +26 -0
  455. apsimo/skills_memory/distill.py +159 -0
  456. apsimo/skills_memory/models.py +85 -0
  457. apsimo/skills_memory/retrieve.py +62 -0
  458. apsimo/skills_memory/store.py +172 -0
  459. apsimo/surprise/__init__.py +6 -0
  460. apsimo/surprise/accumulation.py +57 -0
  461. apsimo/surprise/scorer.py +102 -0
  462. apsimo/surprise/store.py +203 -0
  463. apsimo/task_queue/__init__.py +69 -0
  464. apsimo/task_queue/action_receipts.py +148 -0
  465. apsimo/task_queue/approval_relay_canary.py +108 -0
  466. apsimo/task_queue/config.py +85 -0
  467. apsimo/task_queue/contract.py +361 -0
  468. apsimo/task_queue/events.py +130 -0
  469. apsimo/task_queue/governor.py +1031 -0
  470. apsimo/task_queue/handlers/__init__.py +16 -0
  471. apsimo/task_queue/handlers/base.py +37 -0
  472. apsimo/task_queue/handlers/inference.py +640 -0
  473. apsimo/task_queue/handlers/monitoring.py +116 -0
  474. apsimo/task_queue/handlers/registry.py +75 -0
  475. apsimo/task_queue/handlers/subtask_handler.py +173 -0
  476. apsimo/task_queue/handlers/system_maintenance.py +147 -0
  477. apsimo/task_queue/mesh_integration.py +111 -0
  478. apsimo/task_queue/models.py +317 -0
  479. apsimo/task_queue/queue_manager.py +8286 -0
  480. apsimo/task_queue/routing.py +287 -0
  481. apsimo/task_queue/scheduler.py +252 -0
  482. apsimo/task_queue/schema.sql +197 -0
  483. apsimo/task_queue/work_control.py +342 -0
  484. apsimo/task_queue/worker.py +993 -0
  485. apsimo/telemetry.py +145 -0
  486. apsimo/tom/__init__.py +6 -0
  487. apsimo/tom/affect.py +387 -0
  488. apsimo/tom/approvals.py +171 -0
  489. apsimo/tom/arcs.py +896 -0
  490. apsimo/tom/asymmetry.py +131 -0
  491. apsimo/tom/eligibility.py +248 -0
  492. apsimo/tom/engagement.py +214 -0
  493. apsimo/tom/exposure.py +214 -0
  494. apsimo/tom/extractor.py +306 -0
  495. apsimo/tom/fact_adapters.py +144 -0
  496. apsimo/tom/facts.py +326 -0
  497. apsimo/tom/integration.py +592 -0
  498. apsimo/tom/leveled.py +118 -0
  499. apsimo/tom/levels.py +247 -0
  500. apsimo/tom/recipient_audit.py +995 -0
  501. apsimo/tom/recipient_simulator.py +593 -0
  502. apsimo/tom/source_lineage.py +93 -0
  503. apsimo/tom/tom2.py +277 -0
  504. apsimo/tom/visibility.py +559 -0
  505. apsimo/tom/visibility_store.py +414 -0
  506. apsimo/tools/__init__.py +0 -0
  507. apsimo/tools/definitions.py +740 -0
  508. apsimo/tools/handlers.py +943 -0
  509. apsimo/toolsmith/__init__.py +26 -0
  510. apsimo/toolsmith/authority.py +166 -0
  511. apsimo/toolsmith/engine.py +559 -0
  512. apsimo/toolsmith/integrity.py +100 -0
  513. apsimo/toolsmith/miner.py +145 -0
  514. apsimo/toolsmith/policy.py +110 -0
  515. apsimo/toolsmith/registry.py +635 -0
  516. apsimo/turns/__init__.py +17 -0
  517. apsimo/turns/audio.py +134 -0
  518. apsimo/turns/documents.py +235 -0
  519. apsimo/turns/executions.py +486 -0
  520. apsimo/turns/hermes_history.py +245 -0
  521. apsimo/turns/hermes_kanban.py +268 -0
  522. apsimo/turns/hermes_work.py +96 -0
  523. apsimo/turns/idempotency.py +752 -0
  524. apsimo/turns/local_work.py +115 -0
  525. apsimo/turns/media.py +581 -0
  526. apsimo/turns/reported_workers.py +196 -0
  527. apsimo/turns/source_annotations.py +283 -0
  528. apsimo/turns/source_attribution.py +154 -0
  529. apsimo/turns/source_read.py +351 -0
  530. apsimo/turns/source_vectors.py +263 -0
  531. apsimo/turns/video.py +210 -0
  532. apsimo/util/autonomy_preset.py +220 -0
  533. apsimo/util/instance.py +92 -0
  534. apsimo/util/model_output.py +25 -0
  535. apsimo/util/quiet_hours.py +27 -0
  536. apsimo/util/session_safety.py +37 -0
  537. apsimo/util/temporal.py +343 -0
  538. apsimo/vector/__init__.py +75 -0
  539. apsimo/vector/backfill.py +171 -0
  540. apsimo/vector/caption.py +114 -0
  541. apsimo/vector/collections.py +51 -0
  542. apsimo/vector/config.py +102 -0
  543. apsimo/vector/embedder.py +670 -0
  544. apsimo/vector/image_preprocess.py +406 -0
  545. apsimo/vector/image_store.py +296 -0
  546. apsimo/vector/indexes.py +162 -0
  547. apsimo/vector/migrate.py +334 -0
  548. apsimo/vector/multimodal_provider.py +417 -0
  549. apsimo/vector/multimodal_types.py +87 -0
  550. apsimo/vector/openai_provider.py +119 -0
  551. apsimo/vector/query.py +49 -0
  552. apsimo/vector/reranker.py +565 -0
  553. apsimo/vector/safety_image.py +159 -0
  554. apsimo/vector/scanner.py +197 -0
  555. apsimo/vector/setup.py +289 -0
  556. apsimo/vector/store.py +533 -0
  557. apsimo/vector/tiers.py +263 -0
  558. apsimo/work_orders.py +925 -0
  559. apsimo/workers/__init__.py +21 -0
  560. apsimo/workers/agent_bridge.py +640 -0
  561. apsimo/workers/colony_worker.py +382 -0
  562. apsimo/workers/queue_worker.py +441 -0
  563. apsimo/workers/skills_sync.py +152 -0
  564. apsimo/world_model/__init__.py +71 -0
  565. apsimo/world_model/causal_maintenance.py +131 -0
  566. apsimo/world_model/causal_policy.py +43 -0
  567. apsimo/world_model/causal_query.py +125 -0
  568. apsimo/world_model/confidence.py +54 -0
  569. apsimo/world_model/config.py +64 -0
  570. apsimo/world_model/constants.py +97 -0
  571. apsimo/world_model/entities.py +145 -0
  572. apsimo/world_model/expectation_resolvers.py +177 -0
  573. apsimo/world_model/extraction/__init__.py +7 -0
  574. apsimo/world_model/extraction/base.py +62 -0
  575. apsimo/world_model/extraction/conversation_extractor.py +262 -0
  576. apsimo/world_model/extraction/detector.py +74 -0
  577. apsimo/world_model/extraction/document_extractor.py +78 -0
  578. apsimo/world_model/extraction/formats/__init__.py +24 -0
  579. apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
  580. apsimo/world_model/extraction/formats/html_fmt.py +72 -0
  581. apsimo/world_model/extraction/formats/json_fmt.py +68 -0
  582. apsimo/world_model/extraction/formats/pdf.py +43 -0
  583. apsimo/world_model/extraction/formats/text.py +27 -0
  584. apsimo/world_model/extraction/llm_extractor.py +164 -0
  585. apsimo/world_model/extraction/pipeline.py +73 -0
  586. apsimo/world_model/integrations/__init__.py +5 -0
  587. apsimo/world_model/integrations/mind_model_bridge.py +115 -0
  588. apsimo/world_model/integrations/social_intel_bridge.py +120 -0
  589. apsimo/world_model/jobs/__init__.py +4 -0
  590. apsimo/world_model/jobs/extraction_job.py +168 -0
  591. apsimo/world_model/llm_extract.py +572 -0
  592. apsimo/world_model/neo4j/__init__.py +5 -0
  593. apsimo/world_model/neo4j/backend.py +654 -0
  594. apsimo/world_model/observations.py +155 -0
  595. apsimo/world_model/populator.py +307 -0
  596. apsimo/world_model/postgres/__init__.py +1 -0
  597. apsimo/world_model/postgres/backend.py +683 -0
  598. apsimo/world_model/relationships.py +25 -0
  599. apsimo/world_model/resolution/__init__.py +13 -0
  600. apsimo/world_model/resolution/entity_resolver.py +232 -0
  601. apsimo/world_model/resolution/merge_audit.py +16 -0
  602. apsimo/world_model/resolution/merge_workflow.py +117 -0
  603. apsimo/world_model/source_reports.py +121 -0
  604. apsimo/world_model/sqlite/__init__.py +4 -0
  605. apsimo/world_model/sqlite/backend.py +855 -0
  606. apsimo/world_model/sqlite/schema.sql +132 -0
  607. apsimo/world_model/store.py +545 -0
  608. apsimo-1.3.0.dist-info/METADATA +78 -0
  609. apsimo-1.3.0.dist-info/RECORD +614 -0
  610. apsimo-1.3.0.dist-info/WHEEL +5 -0
  611. apsimo-1.3.0.dist-info/entry_points.txt +11 -0
  612. apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
  613. apsimo-1.3.0.dist-info/top_level.txt +2 -0
  614. colony_sidecar/__init__.py +4 -0
@@ -0,0 +1,373 @@
1
+ """ReasoningLoop — orchestrates model calls and tool execution.
2
+
3
+ This is the core of Colony's reasoning capability. It replaces the
4
+ tightly-coupled reasoning loop in ``run_agent.py`` with a clean,
5
+ host-agnostic interface that can be driven via the ``/v1/host/reasoning/turn``
6
+ HTTP endpoint.
7
+
8
+ The loop works like this:
9
+
10
+ 1. Receive messages + optional system prompt from the host
11
+ 2. Call the LLM via LLMRouter
12
+ 3. If the LLM returns tool calls, execute them and append results
13
+ 4. Repeat until the LLM responds without tool calls (or budget exhausted)
14
+ 5. Return the final response + any pending tool calls
15
+
16
+ Key differences from run_agent.py:
17
+
18
+ - No session persistence (hosts manage their own sessions)
19
+ - No streaming callbacks (hosts handle streaming themselves)
20
+ - No display/print logic (hosts handle display)
21
+ - No memory auto-recall (that's the context engine's job)
22
+ - Tool execution is delegated to ToolExecutor (pluggable)
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import asyncio
28
+ import json
29
+ import logging
30
+ import uuid
31
+ from dataclasses import dataclass, field
32
+ from typing import Any
33
+
34
+ from apsimo.reasoning.executor import ToolExecutor, ToolRegistryError
35
+ from apsimo.reasoning.tool_policy import ToolActorPolicy
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ @dataclass
41
+ class ReasoningConfig:
42
+ """Tunable parameters for the reasoning loop."""
43
+
44
+ max_iterations: int = 10
45
+ tool_delay_seconds: float = 0.0 # Delay between tool call batches
46
+ force_tier: str | None = None # Force a specific LLM tier
47
+
48
+
49
+ @dataclass
50
+ class ReasoningResult:
51
+ """Result of a single reasoning turn."""
52
+
53
+ status: str # "completed" | "needs_tool" | "error"
54
+ message: dict[str, Any] | None = None # HostMessage-shaped
55
+ tool_calls: list[dict[str, Any]] = field(default_factory=list)
56
+ usage: dict[str, Any] = field(default_factory=dict)
57
+ error: str | None = None
58
+ model_provenance: dict[str, Any] = field(default_factory=dict)
59
+
60
+
61
+ def _messages_to_dicts(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
62
+ """Normalise messages for LiteLLM consumption.
63
+
64
+ Ensures every message has at least ``role`` and ``content`` keys.
65
+ Strips None content, replacing with empty string.
66
+ """
67
+ result = []
68
+ for m in messages:
69
+ d = dict(m)
70
+ if d.get("content") is None:
71
+ d["content"] = ""
72
+ result.append(d)
73
+ return result
74
+
75
+
76
+ class ReasoningLoop:
77
+ """Orchestrate model calls and tool execution for a reasoning turn.
78
+
79
+ Parameters
80
+ ----------
81
+ model :
82
+ An object with an ``async complete(messages, *, tools, force_tier, context)``
83
+ method — typically an :class:`~apsimo.router.router.LLMRouter`.
84
+ tools :
85
+ A :class:`ToolExecutor` that can dispatch tool calls.
86
+ config :
87
+ Optional tuning parameters.
88
+ """
89
+
90
+ def __init__(
91
+ self,
92
+ model: Any,
93
+ tools: ToolExecutor | None = None,
94
+ config: ReasoningConfig | None = None,
95
+ ) -> None:
96
+ self._model = model
97
+ self._tools = tools or ToolExecutor()
98
+ self._config = config or ReasoningConfig()
99
+
100
+ async def run_turn(
101
+ self,
102
+ *,
103
+ session_id: str,
104
+ messages: list[dict[str, Any]],
105
+ available_tools: list[str] | None = None,
106
+ model_override: str | None = None,
107
+ system_prompt: str | None = None,
108
+ context: dict[str, Any] | None = None,
109
+ actor_policy: ToolActorPolicy | None = None,
110
+ ) -> ReasoningResult:
111
+ """Run a single reasoning turn with tool iteration.
112
+
113
+ This is the main entry point called by the host router.
114
+
115
+ Parameters
116
+ ----------
117
+ session_id :
118
+ Identifier for the reasoning session (for logging / tracing).
119
+ messages :
120
+ OpenAI-format message list (role + content).
121
+ available_tools :
122
+ Tool names the host has made available.
123
+ model_override :
124
+ If set, passed as ``force_tier`` hint to the LLMRouter.
125
+ system_prompt :
126
+ Optional system prompt prepended to messages.
127
+ context :
128
+ Optional routing hints for the LLMRouter.
129
+
130
+ Returns
131
+ -------
132
+ ReasoningResult
133
+ The final response, or a needs_tool signal if the budget
134
+ ran out mid-iteration.
135
+ """
136
+ turn_id = str(uuid.uuid4())[:8]
137
+ log_prefix = f"[reasoning:{turn_id}]"
138
+
139
+ # Build the working message list
140
+ working = []
141
+ if system_prompt:
142
+ working.append({"role": "system", "content": system_prompt})
143
+ working.extend(_messages_to_dicts(messages))
144
+
145
+ # Build tool definitions for the LLM call
146
+ try:
147
+ tool_defs = self._tools.get_definitions(available_tools)
148
+ except ToolRegistryError as exc:
149
+ logger.error(
150
+ "%s tool registry failed closed: %s: %s",
151
+ log_prefix,
152
+ exc.code,
153
+ exc,
154
+ )
155
+ return ReasoningResult(
156
+ status="error",
157
+ error=f"{exc.code}: {exc}",
158
+ )
159
+ allowed_tool_names = (
160
+ None if available_tools is None else frozenset(available_tools)
161
+ )
162
+
163
+ iterations = 0
164
+ last_usage: dict[str, Any] = {}
165
+ cumulative_usage: dict[str, int] = {
166
+ "prompt_tokens": 0,
167
+ "completion_tokens": 0,
168
+ "total_tokens": 0,
169
+ }
170
+ model_provenance: dict[str, Any] = {}
171
+
172
+ while iterations < self._config.max_iterations:
173
+ iterations += 1
174
+ logger.debug(
175
+ "%s iteration %d/%d — %d messages, %d tools",
176
+ log_prefix,
177
+ iterations,
178
+ self._config.max_iterations,
179
+ len(working),
180
+ len(tool_defs),
181
+ )
182
+
183
+ # Call the LLM
184
+ try:
185
+ tier_hint = model_override or self._config.force_tier
186
+ if isinstance(tier_hint, str):
187
+ from apsimo.router.tiers import ModelTier
188
+ try:
189
+ tier_hint = ModelTier(tier_hint)
190
+ except ValueError:
191
+ tier_hint = None
192
+ response = await self._model.complete(
193
+ working,
194
+ tools=tool_defs if tool_defs else None,
195
+ force_tier=tier_hint,
196
+ context=context or {},
197
+ )
198
+ except Exception as exc:
199
+ logger.error("%s LLM call failed: %s", log_prefix, exc)
200
+ return ReasoningResult(
201
+ status="error",
202
+ error=f"LLM call failed: {exc}",
203
+ model_provenance=model_provenance,
204
+ )
205
+
206
+ # Accumulate usage
207
+ used = getattr(response, 'tier_used', None)
208
+ model_provenance = {'model_role': getattr(response, 'function_role', '') or getattr(used, 'value', str(used or 'unknown')),
209
+ 'model_id': getattr(response, 'model_id', 'unknown'),
210
+ 'model_revision': getattr(response, 'model_revision', 'unknown'),
211
+ 'config_revision': getattr(response, 'config_revision', '') or 'unknown',
212
+ 'basis': 'last_successful_model_response'}
213
+ last_usage = dict(response.usage)
214
+ for k in cumulative_usage:
215
+ cumulative_usage[k] += last_usage.get(k, 0)
216
+
217
+ # Extract the assistant's response
218
+ raw = response.raw
219
+ assistant_content = response.content or ""
220
+ tool_calls_raw = self._extract_tool_calls(raw)
221
+
222
+ if not tool_calls_raw:
223
+ # No tool calls — the LLM is done
224
+ return ReasoningResult(
225
+ status="completed",
226
+ message={
227
+ "role": "assistant",
228
+ "content": assistant_content,
229
+ },
230
+ usage=cumulative_usage,
231
+ model_provenance=model_provenance,
232
+ )
233
+
234
+ # Tool calls — execute them
235
+ logger.debug(
236
+ "%s LLM requested %d tool calls",
237
+ log_prefix,
238
+ len(tool_calls_raw),
239
+ )
240
+
241
+ # Add assistant message with tool calls to working history
242
+ working.append(self._build_assistant_message(raw, assistant_content, tool_calls_raw))
243
+
244
+ # Execute tool calls
245
+ tool_delay = self._config.tool_delay_seconds
246
+ if tool_delay > 0:
247
+ await asyncio.sleep(tool_delay)
248
+
249
+ try:
250
+ tool_results = await self._tools.execute_batch(
251
+ tool_calls_raw,
252
+ session_id=session_id,
253
+ allowed_tools=allowed_tool_names,
254
+ actor_policy=actor_policy,
255
+ )
256
+ except Exception as exc:
257
+ logger.error("%s tool execution failed: %s", log_prefix, exc)
258
+ # Return what we have — the host can decide what to do
259
+ return ReasoningResult(
260
+ status="error",
261
+ message={
262
+ "role": "assistant",
263
+ "content": assistant_content or f"Tool execution error: {exc}",
264
+ },
265
+ tool_calls=tool_calls_raw,
266
+ usage=cumulative_usage,
267
+ error=f"Tool execution failed: {exc}",
268
+ model_provenance=model_provenance,
269
+ )
270
+
271
+ # Append tool results to working history
272
+ for result in tool_results:
273
+ working.append({
274
+ "role": "tool",
275
+ "tool_call_id": result["tool_call_id"],
276
+ "content": result["content"],
277
+ })
278
+
279
+ # Budget exhausted — return needs_tool with pending calls
280
+ logger.warning(
281
+ "%s budget exhausted after %d iterations",
282
+ log_prefix,
283
+ iterations,
284
+ )
285
+ return ReasoningResult(
286
+ status="needs_tool",
287
+ message={
288
+ "role": "assistant",
289
+ "content": assistant_content,
290
+ },
291
+ tool_calls=tool_calls_raw,
292
+ usage=cumulative_usage,
293
+ model_provenance=model_provenance,
294
+ )
295
+
296
+ @staticmethod
297
+ def _extract_tool_calls(raw_response: Any) -> list[dict[str, Any]]:
298
+ """Extract tool calls from a LiteLLM response object.
299
+
300
+ Returns a list of dicts with keys: id, name, arguments.
301
+ """
302
+ if raw_response is None:
303
+ return []
304
+
305
+ # LiteLLM / OpenAI response format
306
+ choices = getattr(raw_response, "choices", None)
307
+ if not choices:
308
+ return []
309
+
310
+ message = getattr(choices[0], "message", None)
311
+ if message is None:
312
+ return []
313
+
314
+ raw_calls = getattr(message, "tool_calls", None)
315
+ if not raw_calls:
316
+ return []
317
+
318
+ result = []
319
+ for tc in raw_calls:
320
+ tc_id = getattr(tc, "id", None) or str(uuid.uuid4())
321
+ func = getattr(tc, "function", None)
322
+ if func is None:
323
+ continue
324
+ name = getattr(func, "name", "unknown")
325
+ try:
326
+ arguments = json.loads(getattr(func, "arguments", "{}"))
327
+ except (json.JSONDecodeError, TypeError):
328
+ arguments = {}
329
+
330
+ result.append({
331
+ "id": tc_id,
332
+ "name": name,
333
+ "arguments": arguments,
334
+ })
335
+
336
+ return result
337
+
338
+ @staticmethod
339
+ def _build_assistant_message(
340
+ raw_response: Any,
341
+ content: str,
342
+ tool_calls: list[dict[str, Any]],
343
+ ) -> dict[str, Any]:
344
+ """Build an assistant message dict that includes tool calls.
345
+
346
+ This preserves the tool call structure in the conversation
347
+ history so the LLM can see its own prior tool calls.
348
+ """
349
+ msg: dict[str, Any] = {"role": "assistant"}
350
+
351
+ if content:
352
+ msg["content"] = content
353
+ else:
354
+ msg["content"] = None
355
+
356
+ if tool_calls:
357
+ msg["tool_calls"] = [
358
+ {
359
+ "id": tc["id"],
360
+ "type": "function",
361
+ "function": {
362
+ "name": tc["name"],
363
+ "arguments": (
364
+ tc["arguments"]
365
+ if isinstance(tc["arguments"], str)
366
+ else json.dumps(tc["arguments"])
367
+ ),
368
+ },
369
+ }
370
+ for tc in tool_calls
371
+ ]
372
+
373
+ return msg
@@ -0,0 +1,16 @@
1
+ """Native tool handlers for Colony's reasoning loop.
2
+
3
+ These tools run inside Colony without requiring a host harness roundtrip.
4
+ """
5
+
6
+ from apsimo.reasoning.native_tools.calculate import CalculateTool
7
+ from apsimo.reasoning.native_tools.web_search import WebSearchTool
8
+ from apsimo.reasoning.native_tools.file_ops import ReadFileTool, WriteFileTool, ListDirectoryTool
9
+
10
+ __all__ = [
11
+ "CalculateTool",
12
+ "WebSearchTool",
13
+ "ReadFileTool",
14
+ "WriteFileTool",
15
+ "ListDirectoryTool",
16
+ ]
@@ -0,0 +1,141 @@
1
+ """Safe math evaluation tool — no eval(), no exec()."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import logging
7
+ import operator
8
+ from typing import Any, Dict
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ ALLOWED_NAMES = {
13
+ "abs": abs, "round": round, "min": min, "max": max,
14
+ "sum": sum, "len": len, "int": int, "float": float,
15
+ "pow": pow, "divmod": divmod, "sorted": sorted,
16
+ "True": True, "False": False, "None": None,
17
+ }
18
+
19
+ BIN_OPS = {
20
+ ast.Add: operator.add, ast.Sub: operator.sub,
21
+ ast.Mult: operator.mul, ast.Div: operator.truediv,
22
+ ast.Mod: operator.mod, ast.Pow: operator.pow,
23
+ ast.FloorDiv: operator.floordiv,
24
+ }
25
+
26
+ COMPARE_OPS = {
27
+ ast.Eq: operator.eq, ast.NotEq: operator.ne,
28
+ ast.Lt: operator.lt, ast.LtE: operator.le,
29
+ ast.Gt: operator.gt, ast.GtE: operator.ge,
30
+ }
31
+
32
+ UNARY_OPS = {
33
+ ast.USub: operator.neg,
34
+ ast.UAdd: operator.pos,
35
+ ast.Not: operator.not_,
36
+ }
37
+
38
+
39
+ class CalculateTool:
40
+ """Safe math expression evaluator using AST parsing.
41
+
42
+ No eval(), no exec(). Only numeric operations allowed.
43
+ """
44
+
45
+ @property
46
+ def name(self) -> str:
47
+ return "calculate"
48
+
49
+ @property
50
+ def schema(self) -> dict:
51
+ return {
52
+ "type": "object",
53
+ "properties": {
54
+ "expression": {
55
+ "type": "string",
56
+ "description": "Math expression to evaluate (e.g., '2 + 2', 'abs(-5) * 3')",
57
+ },
58
+ },
59
+ "required": ["expression"],
60
+ }
61
+
62
+ async def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
63
+ expression = args.get("expression", "")
64
+ if not expression:
65
+ return {"error": True, "message": "No expression provided"}
66
+
67
+ try:
68
+ tree = ast.parse(expression, mode="eval")
69
+ result = self._eval_node(tree.body)
70
+ return {"result": result, "expression": expression}
71
+ except (ValueError, TypeError, SyntaxError, ZeroDivisionError) as e:
72
+ return {"error": True, "message": str(e), "expression": expression}
73
+ except Exception as e:
74
+ return {"error": True, "message": f"Cannot evaluate: {e}", "expression": expression}
75
+
76
+ def _eval_node(self, node: ast.AST) -> Any:
77
+ # Constants (numbers, strings)
78
+ if isinstance(node, ast.Constant):
79
+ return node.value
80
+
81
+ # Names (functions, constants)
82
+ if isinstance(node, ast.Name):
83
+ if node.id in ALLOWED_NAMES:
84
+ return ALLOWED_NAMES[node.id]
85
+ raise ValueError(f"Name not allowed: {node.id}")
86
+
87
+ # Binary operations
88
+ if isinstance(node, ast.BinOp):
89
+ left = self._eval_node(node.left)
90
+ right = self._eval_node(node.right)
91
+ op_type = type(node.op)
92
+ if op_type in BIN_OPS:
93
+ return BIN_OPS[op_type](left, right)
94
+ raise ValueError(f"Binary operation not allowed: {op_type.__name__}")
95
+
96
+ # Unary operations
97
+ if isinstance(node, ast.UnaryOp):
98
+ operand = self._eval_node(node.operand)
99
+ op_type = type(node.op)
100
+ if op_type in UNARY_OPS:
101
+ return UNARY_OPS[op_type](operand)
102
+ raise ValueError(f"Unary operation not allowed: {op_type.__name__}")
103
+
104
+ # Comparisons
105
+ if isinstance(node, ast.Compare):
106
+ left = self._eval_node(node.left)
107
+ for op, comparator in zip(node.ops, node.comparators):
108
+ right = self._eval_node(comparator)
109
+ op_type = type(op)
110
+ if op_type in COMPARE_OPS:
111
+ if not COMPARE_OPS[op_type](left, right):
112
+ return False
113
+ left = right
114
+ else:
115
+ raise ValueError(f"Comparison not allowed: {op_type.__name__}")
116
+ return True
117
+
118
+ # Function calls (abs, round, min, max, etc.)
119
+ if isinstance(node, ast.Call):
120
+ func = self._eval_node(node.func)
121
+ call_args = [self._eval_node(a) for a in node.args]
122
+ # Reject keyword arguments for safety
123
+ if node.keywords:
124
+ raise ValueError("Keyword arguments not allowed")
125
+ return func(*call_args)
126
+
127
+ # Subscripts (list[0])
128
+ if isinstance(node, ast.Subscript):
129
+ value = self._eval_node(node.value)
130
+ slice_val = self._eval_node(node.slice) if isinstance(node.slice, ast.AST) else node.slice
131
+ return value[slice_val]
132
+
133
+ # Lists
134
+ if isinstance(node, ast.List):
135
+ return [self._eval_node(e) for e in node.elts]
136
+
137
+ # Tuples
138
+ if isinstance(node, ast.Tuple):
139
+ return tuple(self._eval_node(e) for e in node.elts)
140
+
141
+ raise ValueError(f"Operation not allowed: {type(node).__name__}")
@@ -0,0 +1,150 @@
1
+ """Sandboxed file operation tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class ReadFileTool:
13
+ """Read files from a sandboxed directory."""
14
+
15
+ def __init__(self, sandbox_dir: str = ""):
16
+ self._sandbox = Path(sandbox_dir).resolve() if sandbox_dir else None
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ return "read_file"
21
+
22
+ @property
23
+ def schema(self) -> dict:
24
+ return {
25
+ "type": "object",
26
+ "properties": {
27
+ "path": {"type": "string", "description": "File path relative to sandbox"},
28
+ },
29
+ "required": ["path"],
30
+ }
31
+
32
+ async def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
33
+ if not self._sandbox:
34
+ return {"error": True, "message": "File sandbox not configured"}
35
+
36
+ path = args.get("path", "")
37
+ try:
38
+ resolved = self._resolve_safe(path)
39
+ if not resolved.exists():
40
+ return {"error": True, "message": f"File not found: {path}"}
41
+ if resolved.is_dir():
42
+ return {"error": True, "message": f"Path is a directory: {path}"}
43
+ content = resolved.read_text(errors="replace")
44
+ return {"content": content, "path": str(resolved.relative_to(self._sandbox)), "size": len(content)}
45
+ except ValueError as e:
46
+ return {"error": True, "message": str(e)}
47
+
48
+ def _resolve_safe(self, path: str) -> Path:
49
+ """Resolve path within sandbox — reject path traversal."""
50
+ if not self._sandbox:
51
+ raise ValueError("Sandbox not configured")
52
+ resolved = (self._sandbox / path).resolve()
53
+ if not str(resolved).startswith(str(self._sandbox)):
54
+ raise ValueError("Path traversal detected")
55
+ return resolved
56
+
57
+
58
+ class WriteFileTool:
59
+ """Write files to a sandboxed directory."""
60
+
61
+ def __init__(self, sandbox_dir: str = ""):
62
+ self._sandbox = Path(sandbox_dir).resolve() if sandbox_dir else None
63
+
64
+ @property
65
+ def name(self) -> str:
66
+ return "write_file"
67
+
68
+ @property
69
+ def schema(self) -> dict:
70
+ return {
71
+ "type": "object",
72
+ "properties": {
73
+ "path": {"type": "string", "description": "File path relative to sandbox"},
74
+ "content": {"type": "string", "description": "Content to write"},
75
+ },
76
+ "required": ["path", "content"],
77
+ }
78
+
79
+ async def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
80
+ if not self._sandbox:
81
+ return {"error": True, "message": "File sandbox not configured"}
82
+
83
+ path = args.get("path", "")
84
+ content = args.get("content", "")
85
+ try:
86
+ resolved = self._resolve_safe(path)
87
+ resolved.parent.mkdir(parents=True, exist_ok=True)
88
+ resolved.write_text(content)
89
+ return {"path": str(resolved.relative_to(self._sandbox)), "size": len(content), "written": True}
90
+ except ValueError as e:
91
+ return {"error": True, "message": str(e)}
92
+
93
+ def _resolve_safe(self, path: str) -> Path:
94
+ if not self._sandbox:
95
+ raise ValueError("Sandbox not configured")
96
+ resolved = (self._sandbox / path).resolve()
97
+ if not str(resolved).startswith(str(self._sandbox)):
98
+ raise ValueError("Path traversal detected")
99
+ return resolved
100
+
101
+
102
+ class ListDirectoryTool:
103
+ """List files in a sandboxed directory."""
104
+
105
+ def __init__(self, sandbox_dir: str = ""):
106
+ self._sandbox = Path(sandbox_dir).resolve() if sandbox_dir else None
107
+
108
+ @property
109
+ def name(self) -> str:
110
+ return "list_directory"
111
+
112
+ @property
113
+ def schema(self) -> dict:
114
+ return {
115
+ "type": "object",
116
+ "properties": {
117
+ "path": {"type": "string", "description": "Directory path relative to sandbox (default: root)", "default": ""},
118
+ },
119
+ }
120
+
121
+ async def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
122
+ if not self._sandbox:
123
+ return {"error": True, "message": "File sandbox not configured"}
124
+
125
+ path = args.get("path", "")
126
+ try:
127
+ resolved = self._resolve_safe(path)
128
+ if not resolved.exists():
129
+ return {"error": True, "message": f"Directory not found: {path}"}
130
+ if not resolved.is_dir():
131
+ return {"error": True, "message": f"Not a directory: {path}"}
132
+
133
+ entries = []
134
+ for p in sorted(resolved.iterdir()):
135
+ entries.append({
136
+ "name": p.name,
137
+ "type": "directory" if p.is_dir() else "file",
138
+ "size": p.stat().st_size if p.is_file() else None,
139
+ })
140
+ return {"path": path or "/", "entries": entries, "count": len(entries)}
141
+ except ValueError as e:
142
+ return {"error": True, "message": str(e)}
143
+
144
+ def _resolve_safe(self, path: str) -> Path:
145
+ if not self._sandbox:
146
+ raise ValueError("Sandbox not configured")
147
+ resolved = (self._sandbox / path).resolve() if path else self._sandbox
148
+ if not str(resolved).startswith(str(self._sandbox)):
149
+ raise ValueError("Path traversal detected")
150
+ return resolved