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,559 @@
1
+ """Toolsmith engine: draft -> verify -> shadow -> graduate (Mind M1).
2
+
3
+ Each stage composes shipped infrastructure:
4
+ draft LLM router writes source + input schema + a self-contained test
5
+ verify the test runs inside the egress-none Docker sandbox (replay)
6
+ shadow incumbent/candidate comparisons on bounded captured inputs
7
+ graduate one-shot owner authority publishes one exact artifact
8
+ retire unused or failing tools demote automatically
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import json
15
+ import logging
16
+ import os
17
+ import re
18
+ from typing import Any, Dict, List, Optional, Tuple
19
+
20
+ from apsimo.toolsmith.authority import GraduationAuthorityV1
21
+ from apsimo.toolsmith.integrity import (
22
+ MAX_CAPTURE_BYTES,
23
+ IntegrityError,
24
+ candidate_digest,
25
+ canonical_json,
26
+ digest_json,
27
+ )
28
+ from apsimo.toolsmith.miner import ToolCandidate, ToolsmithMiner
29
+ from apsimo.toolsmith.policy import validate_pure_tool
30
+ from apsimo.toolsmith.registry import Tool, ToolRegistry, ToolStatus
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ TRUST_DOMAIN = "toolsmith"
35
+ _VERDICT_RE = re.compile(r"@@VERDICT@@\s*(\{.*\})", re.DOTALL)
36
+ _JSON_RE = re.compile(r"\{.*\}", re.DOTALL)
37
+ _CAPTURE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:@/-]{7,127}$")
38
+ _CAPTURE_SOURCES = frozenset({"real", "captured", "operator_canary", "production_trace"})
39
+
40
+
41
+ def toolsmith_enabled() -> bool:
42
+ return os.environ.get(
43
+ "COLONY_TOOLSMITH", "off").strip().lower() in ("shadow", "live")
44
+
45
+
46
+ def toolsmith_mode() -> str:
47
+ m = os.environ.get("COLONY_TOOLSMITH", "off").strip().lower()
48
+ return m if m in ("off", "shadow", "live") else "off"
49
+
50
+
51
+ def _shadow_min() -> int:
52
+ try:
53
+ return max(1, min(100, int(os.environ.get(
54
+ "COLONY_TOOLSMITH_SHADOW_MIN", "5"))))
55
+ except ValueError:
56
+ return 5
57
+
58
+
59
+ _DRAFT_SYSTEM = """You write small, dependency-free Python tools for an agent.
60
+
61
+ Given a recurring procedure the agent performs, output ONE JSON object (no
62
+ prose) with exactly these keys:
63
+ name snake_case identifier, 3-48 chars, [a-z][a-z0-9_]*
64
+ description one sentence: what the tool does and when to call it
65
+ input_schema JSON-Schema "properties" object for the tool's arguments
66
+ source_code a Python module defining `def run(**kwargs) -> dict:`
67
+ test_source Python that imports nothing external, calls run() with
68
+ representative inputs, and asserts on the result
69
+
70
+ Hard rules:
71
+ - Standard library only. No network, no file writes, no subprocess, no
72
+ environment access. The tool runs in a locked-down sandbox with no egress.
73
+ - `run` must be pure and deterministic given its inputs.
74
+ - The test must exercise run() and raise AssertionError on wrong output.
75
+ - If the procedure cannot be expressed as a safe pure function, output
76
+ {"name": null} and nothing else."""
77
+
78
+
79
+ class Toolsmith:
80
+ def __init__(self, registry: ToolRegistry, *, miner: Optional[ToolsmithMiner] = None,
81
+ sandbox: Any = None, self_model: Any = None,
82
+ router: Any = None) -> None:
83
+ self.registry = registry
84
+ self.miner = miner or ToolsmithMiner(registry=registry)
85
+ self._sandbox = sandbox
86
+ self._self_model = self_model
87
+ self._router = router
88
+
89
+ # -- lazy wiring ------------------------------------------------------
90
+ def _sb(self) -> Any:
91
+ if self._sandbox is not None:
92
+ return self._sandbox
93
+ try:
94
+ from apsimo.api.routers import host
95
+ return getattr(host, "_sandbox", None)
96
+ except Exception:
97
+ return None
98
+
99
+ def _sm(self) -> Any:
100
+ if self._self_model is not None:
101
+ return self._self_model
102
+ try:
103
+ from apsimo.api.routers import host
104
+ return getattr(host, "_self_model", None)
105
+ except Exception:
106
+ return None
107
+
108
+ def _llm(self) -> Any:
109
+ if self._router is not None:
110
+ return self._router
111
+ try:
112
+ from apsimo.api.routers import host
113
+ return getattr(host, "_llm_router", None)
114
+ except Exception:
115
+ return None
116
+
117
+ def _journal(self, desc: str, *, decision: str, outcome: str = "",
118
+ ref: str = "") -> None:
119
+ sm = self._sm()
120
+ journal = getattr(sm, "journal", None) if sm is not None else None
121
+ if journal is None:
122
+ return
123
+ try:
124
+ journal.record(TRUST_DOMAIN, desc, decision=decision,
125
+ outcome=outcome, ref=ref)
126
+ except Exception:
127
+ logger.debug("toolsmith journal write failed", exc_info=True)
128
+
129
+ # -- draft ------------------------------------------------------------
130
+ async def draft(self, candidate: ToolCandidate) -> Optional[Tool]:
131
+ router = self._llm()
132
+ if router is None:
133
+ return None
134
+ samples = "\n".join(f"- {d}" for d in candidate.sample_descriptions[:5])
135
+ prompt = (
136
+ f"Recurring procedure in domain '{candidate.domain}', observed "
137
+ f"{candidate.occurrences} times. Representative descriptions:\n"
138
+ f"{samples}\n\nWrite the tool.")
139
+ try:
140
+ resp = await router.complete(
141
+ [{"role": "system", "content": _DRAFT_SYSTEM},
142
+ {"role": "user", "content": prompt}],
143
+ context={"task": "toolsmith_draft"})
144
+ spec = self._parse_spec(getattr(resp, "content", "") or "")
145
+ except Exception as exc:
146
+ logger.warning("toolsmith draft LLM failed: %s", exc)
147
+ return None
148
+ if not spec or not spec.get("name"):
149
+ return None
150
+ static_verdict = validate_pure_tool(
151
+ spec["source_code"], spec.get("test_source", ""))
152
+ if not static_verdict.allowed:
153
+ self._journal(
154
+ "rejected drafted tool at static policy",
155
+ decision="blocked",
156
+ outcome="failure",
157
+ ref=candidate_digest(candidate),
158
+ )
159
+ return None
160
+ tool = self.registry.create_draft(
161
+ name=spec["name"], description=spec.get("description", "")[:300],
162
+ source_code=spec["source_code"],
163
+ input_schema=spec.get("input_schema") or {},
164
+ test_source=spec.get("test_source", ""),
165
+ origin_kind=candidate and "mined" or "requested",
166
+ evidence=candidate.evidence if candidate else [],
167
+ candidate_digest=candidate_digest(candidate))
168
+ if tool is not None:
169
+ self._journal(
170
+ f"drafted tool {tool.name} from {candidate.occurrences} "
171
+ f"occurrences", decision="acted", ref=tool.tool_id)
172
+ return tool
173
+
174
+ @staticmethod
175
+ def _parse_spec(content: str) -> Optional[Dict[str, Any]]:
176
+ m = _JSON_RE.search(content)
177
+ if not m:
178
+ return None
179
+ try:
180
+ spec = json.loads(m.group(0))
181
+ except json.JSONDecodeError:
182
+ return None
183
+ if spec.get("name") in (None, "", "null"):
184
+ return None
185
+ for k in ("source_code", "test_source"):
186
+ if not isinstance(spec.get(k), str) or not spec[k].strip():
187
+ return None
188
+ if not ToolRegistry.valid_name(spec["name"]):
189
+ return None
190
+ return spec
191
+
192
+ # -- verify -----------------------------------------------------------
193
+ async def verify(self, tool: Tool) -> Tuple[bool, Dict[str, Any]]:
194
+ """Run the tool's test inside the sandbox. Verified -> shadow."""
195
+ sandbox = self._sb()
196
+ if sandbox is None:
197
+ return False, {"reason": "sandbox_unavailable"}
198
+ if not self.registry.artifact_intact(tool):
199
+ return False, {"reason": "artifact_integrity_failure"}
200
+ static_verdict = validate_pure_tool(tool.source_code, tool.test_source)
201
+ if not static_verdict.allowed:
202
+ detail = {
203
+ "reason": "static_policy_blocked",
204
+ "policy_reasons": list(static_verdict.reasons),
205
+ "candidate_digest": tool.candidate_digest,
206
+ "artifact_digest": tool.artifact_digest,
207
+ }
208
+ self.registry.set_status(
209
+ tool.tool_id, ToolStatus.REJECTED, verify_detail=detail)
210
+ return False, detail
211
+ script = self._verify_script(tool)
212
+ try:
213
+ # This is an autonomous, contained read-only evaluation. It is
214
+ # fast-path eligible because the sandbox has no egress/secrets or
215
+ # writable host mount, not because Toolsmith impersonates owner
216
+ # direction.
217
+ res = await asyncio.to_thread(
218
+ sandbox.run, script, "python",
219
+ purpose=f"toolsmith verify {tool.name}",
220
+ owner_directed=False,
221
+ read_only=True)
222
+ except Exception as exc:
223
+ return False, {"reason": f"sandbox_error: {exc}"}
224
+ if not res.get("ran"):
225
+ return False, {"reason": res.get("reason", "not_run"),
226
+ "detail": res.get("detail")}
227
+ result = res.get("result") or {}
228
+ stdout = result.get("stdout", "")
229
+ verdict = self._parse_verdict(stdout)
230
+ passed = bool(verdict.get("passed")) and result.get("exit_code") == 0
231
+ detail = {"passed": passed, "exit_code": result.get("exit_code"),
232
+ "verdict": verdict, "stderr": (result.get("stderr") or "")[:500]}
233
+ detail.update({
234
+ "candidate_digest": tool.candidate_digest,
235
+ "artifact_digest": tool.artifact_digest,
236
+ "evidence_kind": "self_test_only",
237
+ })
238
+ self.registry.set_status(
239
+ tool.tool_id,
240
+ ToolStatus.SHADOW if passed else ToolStatus.REJECTED,
241
+ verify_detail=detail)
242
+ self._journal(
243
+ f"verify tool {tool.name}: {'passed' if passed else 'failed'}",
244
+ decision="acted", outcome="success" if passed else "failure",
245
+ ref=tool.tool_id)
246
+ return passed, detail
247
+
248
+ @staticmethod
249
+ def _verify_script(tool: Tool) -> str:
250
+ return (
251
+ "import json, traceback\n"
252
+ "_ok = True\n_err = None\n"
253
+ "try:\n"
254
+ + _indent(tool.source_code) + "\n"
255
+ + _indent(tool.test_source) + "\n"
256
+ "except AssertionError as e:\n"
257
+ " _ok = False; _err = 'assertion: ' + str(e)\n"
258
+ "except Exception as e:\n"
259
+ " _ok = False; _err = type(e).__name__ + ': ' + str(e)\n"
260
+ "print('@@VERDICT@@ ' + json.dumps({'passed': _ok, 'error': _err}))\n"
261
+ )
262
+
263
+ @staticmethod
264
+ def _parse_verdict(stdout: str) -> Dict[str, Any]:
265
+ m = _VERDICT_RE.search(stdout or "")
266
+ if not m:
267
+ return {"passed": False, "error": "no verdict emitted"}
268
+ try:
269
+ return json.loads(m.group(1))
270
+ except json.JSONDecodeError:
271
+ return {"passed": False, "error": "verdict parse failed"}
272
+
273
+ # -- shadow accumulation ----------------------------------------------
274
+ async def verify_shadow_run(
275
+ self,
276
+ tool: Tool,
277
+ *,
278
+ captured_input: Dict[str, Any],
279
+ incumbent_output: Any,
280
+ capture_id: str,
281
+ capture_source: str = "captured",
282
+ principal_id: str = "internal-capture",
283
+ ) -> Tuple[bool, Dict[str, Any]]:
284
+ """Compare incumbent and candidate on one exact captured input.
285
+
286
+ The candidate runs twice to prove determinism. Only a deterministic
287
+ output equal to the captured incumbent output is clean graduation
288
+ evidence. Inputs and outputs are bounded canonical JSON; the durable
289
+ audit projection stores their digests, not potentially sensitive data.
290
+ """
291
+ sandbox = self._sb()
292
+ if sandbox is None:
293
+ return False, {"reason": "sandbox_unavailable"}
294
+ if tool.status != ToolStatus.SHADOW:
295
+ return False, {"reason": "tool_not_shadow"}
296
+ if not self.registry.artifact_intact(tool):
297
+ return False, {"reason": "artifact_integrity_failure"}
298
+ capture_id = str(capture_id or "").strip()
299
+ capture_source = str(capture_source or "").strip().lower()
300
+ principal_id = str(principal_id or "").strip()
301
+ if not _CAPTURE_ID_RE.fullmatch(capture_id):
302
+ return False, {"reason": "invalid_capture_id"}
303
+ if capture_source not in _CAPTURE_SOURCES:
304
+ return False, {"reason": "invalid_capture_source"}
305
+ if not principal_id:
306
+ return False, {"reason": "capture_principal_required"}
307
+ try:
308
+ self._validate_captured_input(tool, captured_input)
309
+ input_digest = digest_json(
310
+ captured_input, max_bytes=MAX_CAPTURE_BYTES)
311
+ incumbent_digest = digest_json(
312
+ incumbent_output, max_bytes=MAX_CAPTURE_BYTES)
313
+ except IntegrityError as exc:
314
+ return False, {"reason": "invalid_capture", "detail": str(exc)}
315
+ script = self._call_script(tool, captured_input)
316
+
317
+ async def one_run() -> Tuple[bool, Any, Dict[str, Any]]:
318
+ res = await asyncio.to_thread(
319
+ sandbox.run,
320
+ script,
321
+ "python",
322
+ purpose=f"toolsmith compare {tool.name}",
323
+ owner_directed=False,
324
+ read_only=True,
325
+ )
326
+ if not res.get("ran"):
327
+ return False, None, {
328
+ "reason": res.get("reason", "not_run"),
329
+ "detail": res.get("detail"),
330
+ }
331
+ raw_result = res.get("result") or {}
332
+ verdict = self._parse_verdict(raw_result.get("stdout", ""))
333
+ ok = bool(verdict.get("passed")) and raw_result.get("exit_code") == 0
334
+ return ok, verdict.get("result"), verdict
335
+
336
+ try:
337
+ first_ok, first_output, first_detail = await one_run()
338
+ second_ok, second_output, second_detail = await one_run()
339
+ except Exception as exc:
340
+ return False, {"reason": f"sandbox_error: {exc}"}
341
+ try:
342
+ first_digest = digest_json(first_output, max_bytes=MAX_CAPTURE_BYTES)
343
+ second_digest = digest_json(second_output, max_bytes=MAX_CAPTURE_BYTES)
344
+ except IntegrityError:
345
+ # Preserve an auditable failure without accepting an unbounded or
346
+ # non-JSON candidate output as comparison evidence.
347
+ first_digest = digest_json({"invalid_output": True, "run": 1})
348
+ second_digest = digest_json({"invalid_output": True, "run": 2})
349
+ first_ok = second_ok = False
350
+ deterministic = bool(first_ok and second_ok and first_digest == second_digest)
351
+ matched = bool(deterministic and first_digest == incumbent_digest)
352
+ evidence = self.registry.record_shadow_comparison(
353
+ tool=tool,
354
+ capture_id=capture_id,
355
+ capture_source=capture_source,
356
+ principal_id=principal_id,
357
+ input_digest=input_digest,
358
+ incumbent_output_digest=incumbent_digest,
359
+ candidate_output_digest=first_digest,
360
+ repeat_output_digest=second_digest,
361
+ deterministic=deterministic,
362
+ matched=matched,
363
+ )
364
+ passed = bool(evidence["success"])
365
+ if not evidence["replayed"]:
366
+ sm = self._sm()
367
+ if sm is not None:
368
+ try:
369
+ sm.record(
370
+ TRUST_DOMAIN,
371
+ "success" if passed else "failure",
372
+ shadow=True,
373
+ )
374
+ except Exception:
375
+ logger.debug("toolsmith shadow record failed", exc_info=True)
376
+ return passed, {
377
+ **evidence,
378
+ "passed": passed,
379
+ "first_error": first_detail.get("error"),
380
+ "second_error": second_detail.get("error"),
381
+ }
382
+
383
+ @staticmethod
384
+ def _validate_captured_input(tool: Tool, captured_input: Dict[str, Any]) -> None:
385
+ if not isinstance(captured_input, dict):
386
+ raise IntegrityError("captured_input must be a JSON object")
387
+ canonical_json(captured_input, max_bytes=MAX_CAPTURE_BYTES)
388
+ declared = tool.input_schema or {}
389
+ if not isinstance(declared, dict):
390
+ raise IntegrityError("tool input schema is malformed")
391
+ unknown = set(captured_input) - set(declared)
392
+ if declared and unknown:
393
+ raise IntegrityError("captured_input contains undeclared fields")
394
+
395
+ # -- shadow invocation ------------------------------------------------
396
+ def record_live(self, tool_id: str, success: bool) -> None:
397
+ self.registry.record_invocation(tool_id, success=success)
398
+ sm = self._sm()
399
+ if sm is not None:
400
+ try:
401
+ sm.record(TRUST_DOMAIN,
402
+ "success" if success else "failure",
403
+ violation=not success)
404
+ except Exception:
405
+ logger.debug("toolsmith live record failed", exc_info=True)
406
+
407
+ # -- graduation -------------------------------------------------------
408
+ def graduation_candidates(self) -> List[Tool]:
409
+ """Shadow tools with enough receipt-backed clean comparisons."""
410
+ floor = _shadow_min()
411
+ return [t for t in self.registry.list(status=ToolStatus.SHADOW)
412
+ if self.registry.clean_comparison_count(t.tool_id) >= floor
413
+ and t.failures == 0]
414
+
415
+ def trust_stage(self) -> str:
416
+ sm = self._sm()
417
+ trust = getattr(sm, "trust", None) if sm is not None else None
418
+ if trust is None:
419
+ return "shadow"
420
+ try:
421
+ return trust.stage(TRUST_DOMAIN, default="shadow")
422
+ except Exception:
423
+ return "shadow"
424
+
425
+ def graduate(
426
+ self,
427
+ tool_id: str,
428
+ *,
429
+ authority: GraduationAuthorityV1,
430
+ ) -> Dict[str, Any]:
431
+ """Publish one exact artifact using one bounded owner authority."""
432
+
433
+ if authority.tool_id != tool_id:
434
+ raise ValueError("graduation authority is bound to another tool")
435
+ tool = self.registry.get(tool_id)
436
+ if tool is None:
437
+ raise ValueError("tool was not found")
438
+ static_verdict = validate_pure_tool(tool.source_code, tool.test_source)
439
+ if not static_verdict.allowed:
440
+ raise ValueError("tool violates the pure capability manifest")
441
+ result = self.registry.graduate_with_authority(
442
+ authority, shadow_min=_shadow_min())
443
+ tool = self.registry.get(tool_id)
444
+ assert tool is not None
445
+ self._journal(f"graduated tool {tool.name} to live",
446
+ decision="acted", outcome="success",
447
+ ref=result["authority_digest"])
448
+ return result
449
+
450
+ def retire(self, tool_id: str, reason: str = "") -> bool:
451
+ tool = self.registry.get(tool_id)
452
+ if tool is None:
453
+ return False
454
+ self.registry.set_status(tool_id, ToolStatus.RETIRED)
455
+ self._journal(f"retired tool {tool.name}: {reason}"[:200],
456
+ decision="acted", ref=tool_id)
457
+ return True
458
+
459
+ # -- live invocation (from the reasoning loop) ------------------------
460
+ async def invoke_live(self, tool_id: str,
461
+ kwargs: Dict[str, Any]) -> Dict[str, Any]:
462
+ """Run a graduated tool's run(**kwargs) inside the sandbox and return
463
+ its result. Records the outcome against trust."""
464
+ tool = self.registry.get(tool_id)
465
+ if tool is None or tool.status != ToolStatus.LIVE:
466
+ return {"error": "tool not live"}
467
+ static_verdict = validate_pure_tool(tool.source_code, tool.test_source)
468
+ if not static_verdict.allowed:
469
+ self.record_live(tool_id, success=False)
470
+ return {"error": "tool violates pure capability manifest"}
471
+ sandbox = self._sb()
472
+ if sandbox is None:
473
+ return {"error": "sandbox unavailable"}
474
+ try:
475
+ self._validate_captured_input(tool, kwargs)
476
+ script = self._call_script(tool, kwargs)
477
+ except IntegrityError as exc:
478
+ return {"error": f"invalid tool input: {exc}"}
479
+ try:
480
+ res = await asyncio.to_thread(
481
+ sandbox.run, script, "python",
482
+ purpose=f"toolsmith invoke {tool.name}",
483
+ owner_directed=False,
484
+ read_only=True)
485
+ except Exception as exc:
486
+ self.record_live(tool_id, success=False)
487
+ return {"error": f"sandbox_error: {exc}"}
488
+ result = res.get("result") or {}
489
+ out = self._parse_verdict(result.get("stdout", ""))
490
+ success = bool(out.get("passed")) and result.get("exit_code") == 0
491
+ if success:
492
+ try:
493
+ canonical_json(out.get("result"), max_bytes=MAX_CAPTURE_BYTES)
494
+ except IntegrityError:
495
+ success = False
496
+ out["error"] = "tool output is not bounded finite JSON"
497
+ self.record_live(tool_id, success=success)
498
+ if not success:
499
+ return {"error": out.get("error") or "tool failed",
500
+ "stderr": (result.get("stderr") or "")[:300]}
501
+ return {"result": out.get("result")}
502
+
503
+ @staticmethod
504
+ def _call_script(tool: Tool, kwargs: Dict[str, Any]) -> str:
505
+ encoded_args = canonical_json(kwargs, max_bytes=MAX_CAPTURE_BYTES)
506
+ return (
507
+ "import json\n_ok=True\n_err=None\n_res=None\n"
508
+ "try:\n"
509
+ + _indent(tool.source_code) + "\n"
510
+ + _indent(f"_res = run(**json.loads({encoded_args!r}))") + "\n"
511
+ "except Exception as e:\n"
512
+ " _ok=False; _err=type(e).__name__+': '+str(e)\n"
513
+ "print('@@VERDICT@@ ' + json.dumps({'passed': _ok, "
514
+ "'error': _err, 'result': _res}))\n"
515
+ )
516
+
517
+ def build_dynamic_provider(self):
518
+ """Return a callable for ToolExecutor.set_dynamic_provider that maps
519
+ every LIVE tool to its (openai_definition, async_handler)."""
520
+ def provider() -> Dict[str, Any]:
521
+ out: Dict[str, Any] = {}
522
+ for tool in self.registry.list(status=ToolStatus.LIVE):
523
+ definition = {
524
+ "type": "function",
525
+ "function": {
526
+ "name": tool.name,
527
+ "description": tool.description,
528
+ "parameters": {
529
+ "type": "object",
530
+ "properties": tool.input_schema or {},
531
+ },
532
+ },
533
+ }
534
+
535
+ def make_handler(tid: str):
536
+ async def _handler(args: Dict[str, Any]) -> str:
537
+ r = await self.invoke_live(tid, args or {})
538
+ return json.dumps(r)
539
+ return _handler
540
+
541
+ out[tool.name] = (definition, make_handler(tool.tool_id))
542
+ return out
543
+ return provider
544
+
545
+ def retirement_candidates(self) -> List[Tool]:
546
+ """Live tools failing repeatedly, or shadow tools stuck failing."""
547
+ out: List[Tool] = []
548
+ for t in self.registry.list(status=ToolStatus.LIVE):
549
+ if t.invocations >= 3 and t.failures >= max(2, t.invocations // 2):
550
+ out.append(t)
551
+ for t in self.registry.list(status=ToolStatus.SHADOW):
552
+ if t.failures >= 3:
553
+ out.append(t)
554
+ return out
555
+
556
+
557
+ def _indent(code: str, spaces: int = 4) -> str:
558
+ pad = " " * spaces
559
+ return "\n".join(pad + line for line in code.splitlines())
@@ -0,0 +1,100 @@
1
+ """Canonical digests for Toolsmith artifacts and comparison captures.
2
+
3
+ The helpers in this module deliberately accept JSON-shaped values only. A
4
+ Toolsmith artifact or shadow input must have one stable byte representation so
5
+ that audit records cannot be rebound to different code or data later.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ from typing import Any, Mapping
13
+
14
+
15
+ MAX_CAPTURE_BYTES = 32 * 1024
16
+ PURE_CAPABILITY_MANIFEST = {
17
+ "version": "toolsmith.capability-manifest.v1",
18
+ "entrypoint": "run",
19
+ "effects": "none",
20
+ "filesystem": "none",
21
+ "network": "none",
22
+ "subprocess": False,
23
+ "environment": False,
24
+ "deterministic": True,
25
+ }
26
+
27
+
28
+ class IntegrityError(ValueError):
29
+ """A value cannot be represented by the bounded canonical contract."""
30
+
31
+
32
+ def canonical_json(value: Any, *, max_bytes: int | None = None) -> str:
33
+ """Return deterministic JSON, rejecting non-JSON and oversized values."""
34
+
35
+ try:
36
+ rendered = json.dumps(
37
+ value,
38
+ ensure_ascii=False,
39
+ allow_nan=False,
40
+ sort_keys=True,
41
+ separators=(",", ":"),
42
+ )
43
+ except (TypeError, ValueError) as exc:
44
+ raise IntegrityError("value must be finite JSON") from exc
45
+ size = len(rendered.encode("utf-8"))
46
+ if max_bytes is not None and size > max_bytes:
47
+ raise IntegrityError(f"canonical value exceeds {max_bytes} bytes")
48
+ return rendered
49
+
50
+
51
+ def digest_text(value: str) -> str:
52
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
53
+
54
+
55
+ def digest_json(value: Any, *, max_bytes: int | None = None) -> str:
56
+ return digest_text(canonical_json(value, max_bytes=max_bytes))
57
+
58
+
59
+ def candidate_digest(candidate: Any) -> str:
60
+ """Bind a draft to the exact mined/requested evidence it came from."""
61
+
62
+ payload = {
63
+ "signature": str(getattr(candidate, "signature", "") or ""),
64
+ "domain": str(getattr(candidate, "domain", "") or ""),
65
+ "description": str(getattr(candidate, "description", "") or ""),
66
+ "occurrences": int(getattr(candidate, "occurrences", 0) or 0),
67
+ "evidence": list(getattr(candidate, "evidence", []) or []),
68
+ "sample_descriptions": list(
69
+ getattr(candidate, "sample_descriptions", []) or []
70
+ ),
71
+ }
72
+ return digest_json(payload)
73
+
74
+
75
+ def artifact_digest(
76
+ *,
77
+ name: str,
78
+ description: str,
79
+ source_code: str,
80
+ input_schema: Mapping[str, Any],
81
+ test_source: str,
82
+ origin_kind: str,
83
+ evidence: list[str],
84
+ candidate_digest_value: str,
85
+ capability_manifest: Mapping[str, Any] | None = None,
86
+ ) -> str:
87
+ """Bind all executable and provenance-bearing fields of one tool."""
88
+
89
+ return digest_json({
90
+ "name": name,
91
+ "description": description,
92
+ "source_code": source_code,
93
+ "input_schema": dict(input_schema),
94
+ "test_source": test_source,
95
+ "origin_kind": origin_kind,
96
+ "evidence": list(evidence),
97
+ "candidate_digest": candidate_digest_value,
98
+ "capability_manifest": dict(
99
+ capability_manifest or PURE_CAPABILITY_MANIFEST),
100
+ })