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,635 @@
1
+ """Persisted registry for self-built tools (Mind M1).
2
+
3
+ SQLite metadata plus an on-disk tool directory per tool (source, manifest,
4
+ test). Deliberately independent of the runtime `skills/` executor registry
5
+ (which serves initiative executors and is load-bearing elsewhere); this one
6
+ owns the LLM-callable-tool artifact lifecycle only.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import logging
14
+ import os
15
+ import re
16
+ import sqlite3
17
+ import threading
18
+ import time
19
+ import uuid
20
+ from dataclasses import asdict, dataclass, field
21
+ from typing import Any, Dict, List, Optional
22
+
23
+ from apsimo.toolsmith.authority import (
24
+ GraduationAuthorityError,
25
+ GraduationAuthorityV1,
26
+ )
27
+ from apsimo.toolsmith.integrity import (
28
+ PURE_CAPABILITY_MANIFEST,
29
+ artifact_digest as compute_artifact_digest,
30
+ digest_json,
31
+ )
32
+ from apsimo.tools.definitions import STATIC_TOOL_NAMES
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ _NAME_RE = re.compile(r"^[a-z][a-z0-9_]{2,48}$")
37
+
38
+
39
+ class ToolStatus:
40
+ DRAFT = "draft" # written, not yet verified
41
+ VERIFIED = "verified" # passed sandbox replay, not yet advertised
42
+ SHADOW = "shadow" # advertised as shadow (simulated, journaled)
43
+ LIVE = "live" # advertised and executed for real
44
+ RETIRED = "retired" # demoted (unused or failing)
45
+ REJECTED = "rejected" # failed verification or owner-rejected
46
+
47
+ ALL = (DRAFT, VERIFIED, SHADOW, LIVE, RETIRED, REJECTED)
48
+
49
+
50
+ @dataclass
51
+ class Tool:
52
+ tool_id: str
53
+ name: str
54
+ description: str
55
+ status: str
56
+ source_code: str
57
+ input_schema: Dict[str, Any] = field(default_factory=dict)
58
+ checksum_sha256: str = ""
59
+ candidate_digest: str = ""
60
+ artifact_digest: str = ""
61
+ capability_manifest: Dict[str, Any] = field(
62
+ default_factory=lambda: dict(PURE_CAPABILITY_MANIFEST))
63
+ origin_kind: str = "mined" # mined | requested
64
+ evidence: List[str] = field(default_factory=list)
65
+ test_source: str = ""
66
+ verify_detail: Dict[str, Any] = field(default_factory=dict)
67
+ invocations: int = 0
68
+ failures: int = 0
69
+ shadow_runs: int = 0
70
+ last_used_at: Optional[float] = None
71
+ created_at: float = 0.0
72
+ updated_at: float = 0.0
73
+
74
+ def public(self) -> Dict[str, Any]:
75
+ d = asdict(self)
76
+ d.pop("source_code", None)
77
+ d.pop("test_source", None)
78
+ return d
79
+
80
+
81
+ class ToolRegistry:
82
+ def __init__(self, db_path: str, library_root: str) -> None:
83
+ self._conn = sqlite3.connect(db_path, check_same_thread=False)
84
+ self._conn.row_factory = sqlite3.Row
85
+ self._conn.execute("PRAGMA journal_mode=WAL")
86
+ self._lock = threading.Lock()
87
+ self._library = library_root
88
+ os.makedirs(self._library, exist_ok=True)
89
+ self._conn.executescript(
90
+ """
91
+ CREATE TABLE IF NOT EXISTS tools (
92
+ tool_id TEXT PRIMARY KEY,
93
+ name TEXT NOT NULL,
94
+ description TEXT NOT NULL,
95
+ status TEXT NOT NULL,
96
+ source_code TEXT NOT NULL,
97
+ input_schema TEXT,
98
+ checksum_sha256 TEXT,
99
+ candidate_digest TEXT,
100
+ artifact_digest TEXT,
101
+ capability_manifest TEXT,
102
+ origin_kind TEXT,
103
+ evidence TEXT,
104
+ test_source TEXT,
105
+ verify_detail TEXT,
106
+ invocations INTEGER DEFAULT 0,
107
+ failures INTEGER DEFAULT 0,
108
+ shadow_runs INTEGER DEFAULT 0,
109
+ last_used_at REAL,
110
+ created_at REAL NOT NULL,
111
+ updated_at REAL NOT NULL
112
+ );
113
+ CREATE INDEX IF NOT EXISTS idx_tools_status ON tools(status);
114
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tools_name ON tools(name);
115
+ CREATE TABLE IF NOT EXISTS toolsmith_shadow_comparisons (
116
+ comparison_id TEXT PRIMARY KEY,
117
+ tool_id TEXT NOT NULL,
118
+ capture_id TEXT NOT NULL,
119
+ capture_source TEXT NOT NULL,
120
+ principal_id TEXT NOT NULL,
121
+ artifact_digest TEXT NOT NULL,
122
+ input_digest TEXT NOT NULL,
123
+ incumbent_output_digest TEXT NOT NULL,
124
+ candidate_output_digest TEXT NOT NULL,
125
+ repeat_output_digest TEXT NOT NULL,
126
+ deterministic INTEGER NOT NULL,
127
+ matched INTEGER NOT NULL,
128
+ success INTEGER NOT NULL,
129
+ created_at REAL NOT NULL,
130
+ UNIQUE(tool_id, capture_id)
131
+ );
132
+ CREATE INDEX IF NOT EXISTS idx_toolsmith_shadow_tool
133
+ ON toolsmith_shadow_comparisons(tool_id, created_at);
134
+ CREATE TABLE IF NOT EXISTS toolsmith_graduations (
135
+ authority_id TEXT PRIMARY KEY,
136
+ decision_id TEXT NOT NULL UNIQUE,
137
+ tool_id TEXT NOT NULL,
138
+ candidate_digest TEXT NOT NULL,
139
+ artifact_digest TEXT NOT NULL,
140
+ authority_digest TEXT NOT NULL,
141
+ principal_id TEXT NOT NULL,
142
+ owner_person_id TEXT NOT NULL,
143
+ issued_at TEXT NOT NULL,
144
+ expires_at TEXT NOT NULL,
145
+ max_uses INTEGER NOT NULL,
146
+ status TEXT NOT NULL,
147
+ created_at REAL NOT NULL
148
+ );
149
+ CREATE INDEX IF NOT EXISTS idx_toolsmith_graduations_tool
150
+ ON toolsmith_graduations(tool_id, created_at);
151
+ """
152
+ )
153
+ self._ensure_column("tools", "candidate_digest", "TEXT")
154
+ self._ensure_column("tools", "artifact_digest", "TEXT")
155
+ self._ensure_column("tools", "capability_manifest", "TEXT")
156
+ self._backfill_integrity_digests()
157
+ self._conn.commit()
158
+
159
+ # -- helpers ----------------------------------------------------------
160
+ @staticmethod
161
+ def valid_name(name: str) -> bool:
162
+ return bool(_NAME_RE.match(name or ""))
163
+
164
+ def tool_dir(self, tool_id: str) -> str:
165
+ return os.path.join(self._library, tool_id)
166
+
167
+ def _ensure_column(self, table: str, column: str, declaration: str) -> None:
168
+ columns = {
169
+ str(row["name"])
170
+ for row in self._conn.execute(f"PRAGMA table_info({table})")
171
+ }
172
+ if column not in columns:
173
+ self._conn.execute(
174
+ f"ALTER TABLE {table} ADD COLUMN {column} {declaration}"
175
+ )
176
+
177
+ @staticmethod
178
+ def _artifact_digest_for(tool: Tool) -> str:
179
+ return compute_artifact_digest(
180
+ name=tool.name,
181
+ description=tool.description,
182
+ source_code=tool.source_code,
183
+ input_schema=tool.input_schema,
184
+ test_source=tool.test_source,
185
+ origin_kind=tool.origin_kind,
186
+ evidence=tool.evidence,
187
+ candidate_digest_value=tool.candidate_digest,
188
+ capability_manifest=tool.capability_manifest,
189
+ )
190
+
191
+ def _backfill_integrity_digests(self) -> None:
192
+ rows = self._conn.execute("SELECT * FROM tools").fetchall()
193
+ for row in rows:
194
+ tool = self._row_to_tool(row)
195
+ candidate = tool.candidate_digest or digest_json({
196
+ "legacy_tool_id": tool.tool_id,
197
+ "origin_kind": tool.origin_kind,
198
+ "evidence": tool.evidence,
199
+ })
200
+ tool.candidate_digest = candidate
201
+ # Only fill fields introduced by this migration. Once an
202
+ # artifact digest exists, never silently recompute/bless changed
203
+ # code during startup; artifact_intact() must expose the mismatch.
204
+ artifact = row["artifact_digest"] or self._artifact_digest_for(tool)
205
+ manifest = row["capability_manifest"] or json.dumps(
206
+ PURE_CAPABILITY_MANIFEST, sort_keys=True)
207
+ if (
208
+ not row["candidate_digest"]
209
+ or not row["artifact_digest"]
210
+ or not row["capability_manifest"]
211
+ ):
212
+ self._conn.execute(
213
+ "UPDATE tools SET candidate_digest=?, artifact_digest=?,"
214
+ " capability_manifest=? "
215
+ "WHERE tool_id=?",
216
+ (candidate, artifact, manifest, tool.tool_id),
217
+ )
218
+
219
+ def _row_to_tool(self, r: sqlite3.Row) -> Tool:
220
+ return Tool(
221
+ tool_id=r["tool_id"], name=r["name"], description=r["description"],
222
+ status=r["status"], source_code=r["source_code"],
223
+ input_schema=json.loads(r["input_schema"] or "{}"),
224
+ checksum_sha256=r["checksum_sha256"] or "",
225
+ candidate_digest=r["candidate_digest"] or "",
226
+ artifact_digest=r["artifact_digest"] or "",
227
+ capability_manifest=json.loads(
228
+ r["capability_manifest"] or json.dumps(PURE_CAPABILITY_MANIFEST)),
229
+ origin_kind=r["origin_kind"] or "mined",
230
+ evidence=json.loads(r["evidence"] or "[]"),
231
+ test_source=r["test_source"] or "",
232
+ verify_detail=json.loads(r["verify_detail"] or "{}"),
233
+ invocations=r["invocations"] or 0, failures=r["failures"] or 0,
234
+ shadow_runs=r["shadow_runs"] or 0, last_used_at=r["last_used_at"],
235
+ created_at=r["created_at"], updated_at=r["updated_at"])
236
+
237
+ # -- writes -----------------------------------------------------------
238
+ def create_draft(self, *, name: str, description: str, source_code: str,
239
+ input_schema: Dict[str, Any], test_source: str,
240
+ origin_kind: str = "mined",
241
+ evidence: Optional[List[str]] = None,
242
+ candidate_digest: str = "") -> Optional[Tool]:
243
+ if not self.valid_name(name):
244
+ logger.warning("toolsmith: invalid tool name %r", name)
245
+ return None
246
+ if name in STATIC_TOOL_NAMES:
247
+ logger.warning("toolsmith: reserved first-party tool name %r", name)
248
+ return None
249
+ if self.get_by_name(name) is not None:
250
+ logger.info("toolsmith: tool named %r already exists", name)
251
+ return None
252
+ tool_id = f"tool-{uuid.uuid4().hex[:12]}"
253
+ now = time.time()
254
+ checksum = hashlib.sha256(source_code.encode()).hexdigest()
255
+ provenance_digest = candidate_digest or digest_json({
256
+ "origin_kind": origin_kind,
257
+ "evidence": evidence or [],
258
+ "name": name,
259
+ })
260
+ tool = Tool(
261
+ tool_id=tool_id, name=name, description=description,
262
+ status=ToolStatus.DRAFT, source_code=source_code,
263
+ input_schema=input_schema or {}, checksum_sha256=checksum,
264
+ candidate_digest=provenance_digest,
265
+ capability_manifest=dict(PURE_CAPABILITY_MANIFEST),
266
+ origin_kind=origin_kind, evidence=evidence or [],
267
+ test_source=test_source, created_at=now, updated_at=now)
268
+ tool.artifact_digest = self._artifact_digest_for(tool)
269
+ self._persist_files(tool)
270
+ with self._lock:
271
+ self._conn.execute(
272
+ "INSERT INTO tools (tool_id,name,description,status,"
273
+ "source_code,input_schema,checksum_sha256,candidate_digest,"
274
+ "artifact_digest,capability_manifest,origin_kind,evidence,"
275
+ "test_source,verify_detail,created_at,updated_at) "
276
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
277
+ (tool_id, name, description, ToolStatus.DRAFT, source_code,
278
+ json.dumps(input_schema or {}), checksum, provenance_digest,
279
+ tool.artifact_digest,
280
+ json.dumps(tool.capability_manifest, sort_keys=True),
281
+ origin_kind, json.dumps(evidence or []), test_source, "{}",
282
+ now, now))
283
+ self._conn.commit()
284
+ return tool
285
+
286
+ def _persist_files(self, tool: Tool) -> None:
287
+ d = self.tool_dir(tool.tool_id)
288
+ os.makedirs(d, exist_ok=True)
289
+ with open(os.path.join(d, "tool.py"), "w") as f:
290
+ f.write(tool.source_code)
291
+ with open(os.path.join(d, "test_tool.py"), "w") as f:
292
+ f.write(tool.test_source)
293
+ with open(os.path.join(d, "manifest.json"), "w") as f:
294
+ json.dump({"tool_id": tool.tool_id, "name": tool.name,
295
+ "description": tool.description,
296
+ "input_schema": tool.input_schema,
297
+ "checksum_sha256": tool.checksum_sha256,
298
+ "candidate_digest": tool.candidate_digest,
299
+ "artifact_digest": tool.artifact_digest,
300
+ "capability_manifest": tool.capability_manifest,
301
+ "origin_kind": tool.origin_kind}, f, indent=2)
302
+
303
+ def set_status(self, tool_id: str, status: str, *,
304
+ verify_detail: Optional[Dict[str, Any]] = None) -> bool:
305
+ if status not in ToolStatus.ALL:
306
+ return False
307
+ # LIVE is a capability publication, not a generic lifecycle edit. It
308
+ # must be committed atomically with a one-shot authority receipt.
309
+ if status == ToolStatus.LIVE:
310
+ return False
311
+ with self._lock:
312
+ if verify_detail is not None:
313
+ self._conn.execute(
314
+ "UPDATE tools SET status=?, verify_detail=?, updated_at=?"
315
+ " WHERE tool_id=?",
316
+ (status, json.dumps(verify_detail), time.time(), tool_id))
317
+ else:
318
+ self._conn.execute(
319
+ "UPDATE tools SET status=?, updated_at=? WHERE tool_id=?",
320
+ (status, time.time(), tool_id))
321
+ self._conn.commit()
322
+ return True
323
+
324
+ def artifact_intact(self, tool: Tool) -> bool:
325
+ return bool(
326
+ tool.artifact_digest
327
+ and tool.artifact_digest == self._artifact_digest_for(tool)
328
+ )
329
+
330
+ def record_shadow_comparison(
331
+ self,
332
+ *,
333
+ tool: Tool,
334
+ capture_id: str,
335
+ capture_source: str,
336
+ principal_id: str,
337
+ input_digest: str,
338
+ incumbent_output_digest: str,
339
+ candidate_output_digest: str,
340
+ repeat_output_digest: str,
341
+ deterministic: bool,
342
+ matched: bool,
343
+ ) -> Dict[str, Any]:
344
+ """Persist one immutable same-input comparison exactly once."""
345
+
346
+ if tool.status != ToolStatus.SHADOW:
347
+ raise ValueError("tool is not in shadow state")
348
+ if not self.artifact_intact(tool):
349
+ raise ValueError("tool artifact digest mismatch")
350
+ success = bool(deterministic and matched)
351
+ payload = {
352
+ "tool_id": tool.tool_id,
353
+ "capture_id": capture_id,
354
+ "capture_source": capture_source,
355
+ "principal_id": principal_id,
356
+ "artifact_digest": tool.artifact_digest,
357
+ "input_digest": input_digest,
358
+ "incumbent_output_digest": incumbent_output_digest,
359
+ "candidate_output_digest": candidate_output_digest,
360
+ "repeat_output_digest": repeat_output_digest,
361
+ "deterministic": bool(deterministic),
362
+ "matched": bool(matched),
363
+ "success": success,
364
+ }
365
+ comparison_id = "cmp_" + digest_json(payload)
366
+ now = time.time()
367
+ with self._lock:
368
+ self._conn.execute("BEGIN IMMEDIATE")
369
+ try:
370
+ current_row = self._conn.execute(
371
+ "SELECT * FROM tools WHERE tool_id=?", (tool.tool_id,)
372
+ ).fetchone()
373
+ if current_row is None:
374
+ raise ValueError("tool was not found")
375
+ current = self._row_to_tool(current_row)
376
+ if current.status != ToolStatus.SHADOW:
377
+ raise ValueError("tool is not in shadow state")
378
+ if (
379
+ current.artifact_digest != tool.artifact_digest
380
+ or not self.artifact_intact(current)
381
+ ):
382
+ raise ValueError("tool artifact digest mismatch")
383
+ existing = self._conn.execute(
384
+ "SELECT * FROM toolsmith_shadow_comparisons "
385
+ "WHERE tool_id=? AND capture_id=?",
386
+ (tool.tool_id, capture_id),
387
+ ).fetchone()
388
+ if existing is not None:
389
+ if existing["comparison_id"] != comparison_id:
390
+ raise ValueError(
391
+ "shadow capture replayed with conflicting content"
392
+ )
393
+ self._conn.commit()
394
+ return {
395
+ **payload,
396
+ "comparison_id": comparison_id,
397
+ "replayed": True,
398
+ }
399
+ self._conn.execute(
400
+ "INSERT INTO toolsmith_shadow_comparisons ("
401
+ "comparison_id,tool_id,capture_id,capture_source,principal_id,"
402
+ "artifact_digest,input_digest,incumbent_output_digest,"
403
+ "candidate_output_digest,repeat_output_digest,deterministic,"
404
+ "matched,success,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
405
+ (
406
+ comparison_id,
407
+ tool.tool_id,
408
+ capture_id,
409
+ capture_source,
410
+ principal_id,
411
+ tool.artifact_digest,
412
+ input_digest,
413
+ incumbent_output_digest,
414
+ candidate_output_digest,
415
+ repeat_output_digest,
416
+ int(deterministic),
417
+ int(matched),
418
+ int(success),
419
+ now,
420
+ ),
421
+ )
422
+ self._conn.execute(
423
+ "UPDATE tools SET shadow_runs=shadow_runs+1,"
424
+ " failures=failures+?, last_used_at=?, updated_at=?"
425
+ " WHERE tool_id=?",
426
+ (0 if success else 1, now, now, tool.tool_id),
427
+ )
428
+ self._conn.commit()
429
+ except Exception:
430
+ self._conn.rollback()
431
+ raise
432
+ return {**payload, "comparison_id": comparison_id, "replayed": False}
433
+
434
+ def graduate_with_authority(
435
+ self,
436
+ authority: GraduationAuthorityV1,
437
+ *,
438
+ shadow_min: int,
439
+ ) -> Dict[str, Any]:
440
+ """Atomically publish one exact artifact and consume one authority."""
441
+
442
+ authority.assert_current()
443
+ authority_digest = authority.authority_digest
444
+ with self._lock:
445
+ self._conn.execute("BEGIN IMMEDIATE")
446
+ try:
447
+ prior = self._conn.execute(
448
+ "SELECT * FROM toolsmith_graduations WHERE authority_id=? "
449
+ "OR decision_id=?",
450
+ (authority.authority_id, authority.decision_id),
451
+ ).fetchone()
452
+ if prior is not None:
453
+ if (
454
+ prior["authority_digest"] != authority_digest
455
+ or prior["tool_id"] != authority.tool_id
456
+ ):
457
+ raise GraduationAuthorityError(
458
+ "authority_replay",
459
+ "authority_id or decision_id was used for another decision",
460
+ )
461
+ self._conn.commit()
462
+ return {
463
+ "graduated": authority.tool_id,
464
+ "authority_id": authority.authority_id,
465
+ "authority_digest": authority_digest,
466
+ "replayed": True,
467
+ }
468
+
469
+ row = self._conn.execute(
470
+ "SELECT * FROM tools WHERE tool_id=?", (authority.tool_id,)
471
+ ).fetchone()
472
+ if row is None:
473
+ raise GraduationAuthorityError(
474
+ "tool_not_found", "tool was not found"
475
+ )
476
+ tool = self._row_to_tool(row)
477
+ if tool.name in STATIC_TOOL_NAMES:
478
+ raise GraduationAuthorityError(
479
+ "tool_name_reserved",
480
+ "tool name collides with a first-party capability",
481
+ )
482
+ if tool.status != ToolStatus.SHADOW:
483
+ raise GraduationAuthorityError(
484
+ "tool_not_shadow", "tool is not in shadow state"
485
+ )
486
+ if not self.artifact_intact(tool):
487
+ raise GraduationAuthorityError(
488
+ "artifact_integrity_failure", "tool artifact digest mismatch"
489
+ )
490
+ if tool.candidate_digest != authority.candidate_digest:
491
+ raise GraduationAuthorityError(
492
+ "candidate_digest_mismatch", "candidate digest changed"
493
+ )
494
+ if tool.artifact_digest != authority.artifact_digest:
495
+ raise GraduationAuthorityError(
496
+ "artifact_digest_mismatch", "artifact digest changed"
497
+ )
498
+ clean_row = self._conn.execute(
499
+ "SELECT COUNT(*) AS n FROM toolsmith_shadow_comparisons "
500
+ "WHERE tool_id=? AND success=1",
501
+ (tool.tool_id,),
502
+ ).fetchone()
503
+ clean_comparisons = int(clean_row["n"] if clean_row else 0)
504
+ if clean_comparisons < shadow_min or tool.failures:
505
+ raise GraduationAuthorityError(
506
+ "shadow_evidence_insufficient",
507
+ "tool lacks the required clean same-input comparisons",
508
+ )
509
+ now = time.time()
510
+ self._conn.execute(
511
+ "INSERT INTO toolsmith_graduations (authority_id,decision_id,"
512
+ "tool_id,candidate_digest,artifact_digest,authority_digest,"
513
+ "principal_id,owner_person_id,issued_at,expires_at,max_uses,"
514
+ "status,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
515
+ (
516
+ authority.authority_id,
517
+ authority.decision_id,
518
+ authority.tool_id,
519
+ authority.candidate_digest,
520
+ authority.artifact_digest,
521
+ authority_digest,
522
+ authority.principal_id,
523
+ authority.owner_person_id,
524
+ authority.issued_at,
525
+ authority.expires_at,
526
+ authority.max_uses,
527
+ "consumed",
528
+ now,
529
+ ),
530
+ )
531
+ changed = self._conn.execute(
532
+ "UPDATE tools SET status=?, updated_at=? "
533
+ "WHERE tool_id=? AND status=?",
534
+ (ToolStatus.LIVE, now, tool.tool_id, ToolStatus.SHADOW),
535
+ ).rowcount
536
+ if changed != 1:
537
+ raise GraduationAuthorityError(
538
+ "graduation_conflict", "tool state changed during graduation"
539
+ )
540
+ self._conn.commit()
541
+ except Exception:
542
+ self._conn.rollback()
543
+ raise
544
+ return {
545
+ "graduated": authority.tool_id,
546
+ "authority_id": authority.authority_id,
547
+ "authority_digest": authority_digest,
548
+ "replayed": False,
549
+ }
550
+
551
+ def audit_projection(self, tool_id: str) -> Dict[str, Any]:
552
+ """Return digest-only comparison and publication receipts."""
553
+
554
+ with self._lock:
555
+ comparisons = self._conn.execute(
556
+ "SELECT comparison_id,capture_id,capture_source,principal_id,"
557
+ "artifact_digest,input_digest,incumbent_output_digest,"
558
+ "candidate_output_digest,repeat_output_digest,deterministic,"
559
+ "matched,success,created_at FROM toolsmith_shadow_comparisons "
560
+ "WHERE tool_id=? ORDER BY created_at ASC",
561
+ (tool_id,),
562
+ ).fetchall()
563
+ graduations = self._conn.execute(
564
+ "SELECT authority_id,decision_id,candidate_digest,artifact_digest,"
565
+ "authority_digest,principal_id,owner_person_id,issued_at,expires_at,"
566
+ "max_uses,status,created_at FROM toolsmith_graduations "
567
+ "WHERE tool_id=? ORDER BY created_at ASC",
568
+ (tool_id,),
569
+ ).fetchall()
570
+ return {
571
+ "shadow_comparisons": [dict(row) for row in comparisons],
572
+ "graduations": [dict(row) for row in graduations],
573
+ }
574
+
575
+ def clean_comparison_count(self, tool_id: str) -> int:
576
+ """Count only receipt-backed successful comparisons, never old counters."""
577
+
578
+ with self._lock:
579
+ row = self._conn.execute(
580
+ "SELECT COUNT(*) AS n FROM toolsmith_shadow_comparisons "
581
+ "WHERE tool_id=? AND success=1",
582
+ (tool_id,),
583
+ ).fetchone()
584
+ return int(row["n"] if row is not None else 0)
585
+
586
+ def record_invocation(self, tool_id: str, *, success: bool,
587
+ shadow: bool = False) -> None:
588
+ with self._lock:
589
+ if shadow:
590
+ self._conn.execute(
591
+ "UPDATE tools SET shadow_runs=shadow_runs+1,"
592
+ " failures=failures+?, last_used_at=?, updated_at=?"
593
+ " WHERE tool_id=?",
594
+ (0 if success else 1, time.time(), time.time(), tool_id))
595
+ else:
596
+ self._conn.execute(
597
+ "UPDATE tools SET invocations=invocations+1,"
598
+ " failures=failures+?, last_used_at=?, updated_at=?"
599
+ " WHERE tool_id=?",
600
+ (0 if success else 1, time.time(), time.time(), tool_id))
601
+ self._conn.commit()
602
+
603
+ # -- reads ------------------------------------------------------------
604
+ def get(self, tool_id: str) -> Optional[Tool]:
605
+ with self._lock:
606
+ r = self._conn.execute(
607
+ "SELECT * FROM tools WHERE tool_id=?", (tool_id,)).fetchone()
608
+ return self._row_to_tool(r) if r else None
609
+
610
+ def get_by_name(self, name: str) -> Optional[Tool]:
611
+ with self._lock:
612
+ r = self._conn.execute(
613
+ "SELECT * FROM tools WHERE name=?", (name,)).fetchone()
614
+ return self._row_to_tool(r) if r else None
615
+
616
+ def list(self, status: Optional[str] = None,
617
+ limit: int = 200) -> List[Tool]:
618
+ q = "SELECT * FROM tools"
619
+ params: List[Any] = []
620
+ if status:
621
+ q += " WHERE status=?"
622
+ params.append(status)
623
+ q += " ORDER BY updated_at DESC LIMIT ?"
624
+ params.append(limit)
625
+ with self._lock:
626
+ rows = self._conn.execute(q, params).fetchall()
627
+ return [self._row_to_tool(r) for r in rows]
628
+
629
+ def advertised(self) -> List[Tool]:
630
+ """Tools the reasoning loop should see: shadow + live."""
631
+ with self._lock:
632
+ rows = self._conn.execute(
633
+ "SELECT * FROM tools WHERE status IN (?,?)",
634
+ (ToolStatus.SHADOW, ToolStatus.LIVE)).fetchall()
635
+ return [self._row_to_tool(r) for r in rows]
@@ -0,0 +1,17 @@
1
+ """Stable turn-ingestion primitives."""
2
+
3
+ from .idempotency import (
4
+ Reservation,
5
+ ReservationOutcome,
6
+ TurnIdempotencyLedger,
7
+ canonical_turn_digest,
8
+ get_turn_idempotency_ledger,
9
+ )
10
+
11
+ __all__ = [
12
+ "Reservation",
13
+ "ReservationOutcome",
14
+ "TurnIdempotencyLedger",
15
+ "canonical_turn_digest",
16
+ "get_turn_idempotency_ledger",
17
+ ]