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,943 @@
1
+ """Tool handlers for Colony-native server-side execution.
2
+
3
+ Each handler is an async function that receives the tool arguments
4
+ and returns a string result. Handlers have access to the SubsystemRegistry
5
+ for calling Colony's intelligence systems.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ if TYPE_CHECKING:
15
+ from apsimo.autonomy.registry import SubsystemRegistry
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def _as_int(value: Any, default: int) -> int:
21
+ """Coerce a tool-supplied value to int.
22
+
23
+ LLM tool calls frequently deliver numbers as strings ("5") or floats
24
+ (10.0). Downstream stores (Neo4j LIMIT, list slicing) require a real int,
25
+ so normalise here rather than crashing deep in a query.
26
+ """
27
+ try:
28
+ return int(value)
29
+ except (TypeError, ValueError):
30
+ return default
31
+
32
+
33
+ async def handle_memory_search(
34
+ args: dict[str, Any],
35
+ registry: SubsystemRegistry,
36
+ ) -> str:
37
+ """Search Colony's memory graph."""
38
+ query = args.get("query", "")
39
+ person_id = args.get("person_id")
40
+ limit = _as_int(args.get("limit", 5), 5)
41
+
42
+ try:
43
+ graph = registry.graph
44
+ if graph is None:
45
+ return json.dumps({"error": "Memory graph not wired", "status": "unavailable"})
46
+
47
+ # ColonyGraph exposes semantic memory retrieval as recall(); there is
48
+ # no search() method (the old name was API drift). recall does its own
49
+ # ANN + strength decay and returns dicts annotated with relevance.
50
+ results = await graph.recall(
51
+ query=query,
52
+ limit=limit,
53
+ person_id=person_id,
54
+ )
55
+
56
+ def _ts(v: Any) -> Any:
57
+ # Neo4j hydration returns neo4j.time.DateTime, which json can't
58
+ # serialise. Normalise any date-like value to an ISO string.
59
+ return v.isoformat() if hasattr(v, "isoformat") else v
60
+
61
+ memories = [
62
+ {
63
+ "content": (m.get("content") or "")[:200],
64
+ "timestamp": _ts(m.get("created_at") or m.get("timestamp")),
65
+ "relevance": m.get("relevance", m.get("score", 0)),
66
+ }
67
+ for m in results[:limit]
68
+ ]
69
+
70
+ # default=str is a belt-and-suspenders guard for any other non-JSON
71
+ # types (e.g. stray DateTime/Decimal) surfacing from the graph.
72
+ return json.dumps({
73
+ "query": query,
74
+ "count": len(memories),
75
+ "memories": memories,
76
+ }, default=str)
77
+ except Exception as e:
78
+ logger.error("colony_memory_search failed: %s", e)
79
+ return json.dumps({"error": str(e), "status": "error"})
80
+
81
+
82
+ async def handle_get_relationship(
83
+ args: dict[str, Any],
84
+ registry: SubsystemRegistry,
85
+ ) -> str:
86
+ """Get relationship info for a contact."""
87
+ contact_id = args.get("contact_id", "")
88
+
89
+ try:
90
+ contacts = registry.contacts
91
+ if contacts is None:
92
+ return json.dumps({"error": "Contacts store not wired", "status": "unavailable"})
93
+
94
+ contact = await contacts.get(contact_id)
95
+ if contact is None:
96
+ return json.dumps({
97
+ "contact_id": contact_id,
98
+ "status": "not_found",
99
+ "tier": "stranger",
100
+ "score": 0,
101
+ })
102
+
103
+ return json.dumps({
104
+ "contact_id": contact_id,
105
+ "name": contact.get("name"),
106
+ "tier": contact.get("tier", "stranger"),
107
+ "score": contact.get("score", 0),
108
+ "interaction_count": contact.get("interaction_count", 0),
109
+ "last_interaction": contact.get("last_interaction"),
110
+ })
111
+ except Exception as e:
112
+ logger.error("colony_get_relationship failed: %s", e)
113
+ return json.dumps({"error": str(e), "status": "error"})
114
+
115
+
116
+ async def handle_list_goals(
117
+ args: dict[str, Any],
118
+ registry: SubsystemRegistry,
119
+ ) -> str:
120
+ """List the user's goals."""
121
+ person_id = args.get("person_id")
122
+ status = args.get("status", "active")
123
+
124
+ try:
125
+ goals = registry.goals
126
+ if goals is None:
127
+ return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
128
+
129
+ # GoalEngine is sync and exposes list_goals(status, limit, offset)
130
+ # returning Goal objects — there is no `list` method and no person_id
131
+ # filter (the old call raised AttributeError on every invocation).
132
+ goal_list = goals.list_goals(status=status, limit=50)
133
+
134
+ return json.dumps({
135
+ "count": len(goal_list or []),
136
+ "goals": [
137
+ {
138
+ "id": getattr(g, "goal_id", None),
139
+ "title": getattr(g, "title", ""),
140
+ "status": getattr(getattr(g, "status", None), "value",
141
+ str(getattr(g, "status", ""))),
142
+ "progress": float(getattr(g, "progress_pct", 0.0) or 0.0),
143
+ }
144
+ for g in (goal_list or [])
145
+ ],
146
+ })
147
+ except Exception as e:
148
+ logger.error("colony_list_goals failed: %s", e)
149
+ return json.dumps({"error": str(e), "status": "error"})
150
+
151
+
152
+ async def handle_get_briefing(
153
+ args: dict[str, Any],
154
+ registry: SubsystemRegistry,
155
+ ) -> str:
156
+ """Get a briefing for a contact."""
157
+ contact_id = args.get("contact_id", "")
158
+
159
+ try:
160
+ briefings = registry.briefings
161
+ if briefings is None:
162
+ return json.dumps({"error": "Briefings engine not wired", "status": "unavailable"})
163
+
164
+ # API drift fix: the engine has no generate(); expose recent + pending
165
+ # briefings (read-only, no LLM cost, no delivery side effects).
166
+ from dataclasses import asdict, is_dataclass
167
+
168
+ def _plain(b):
169
+ try:
170
+ if is_dataclass(b):
171
+ return asdict(b)
172
+ if hasattr(b, "model_dump"):
173
+ return b.model_dump()
174
+ return b.__dict__
175
+ except Exception:
176
+ return {"repr": str(b)}
177
+
178
+ recent = [_plain(b) for b in briefings.get_recent(limit=3)]
179
+ pending = [_plain(b) for b in briefings.get_pending()]
180
+ return json.dumps(
181
+ {"recent": recent, "pending": pending, "contact_id": contact_id},
182
+ default=str,
183
+ )
184
+ except Exception as e:
185
+ logger.error("colony_get_briefing failed: %s", e)
186
+ return json.dumps({"error": str(e), "status": "error"})
187
+
188
+
189
+ async def handle_record_insight(
190
+ args: dict[str, Any],
191
+ registry: SubsystemRegistry,
192
+ ) -> str:
193
+ """Record an insight to memory."""
194
+ insight_type = args.get("insight_type", "fact")
195
+ content = args.get("content", "")
196
+ confidence = args.get("confidence", 0.7)
197
+ person_id = args.get("person_id")
198
+
199
+ try:
200
+ graph = registry.graph
201
+ if graph is None:
202
+ return json.dumps({"error": "Memory graph not wired", "status": "unavailable"})
203
+
204
+ insight_id = await graph.record_insight(
205
+ insight_type=insight_type,
206
+ content=content,
207
+ confidence=confidence,
208
+ person_id=person_id,
209
+ )
210
+
211
+ return json.dumps({
212
+ "status": "recorded",
213
+ "insight_id": insight_id,
214
+ "type": insight_type,
215
+ })
216
+ except Exception as e:
217
+ logger.error("colony_record_insight failed: %s", e)
218
+ return json.dumps({"error": str(e), "status": "error"})
219
+
220
+
221
+ async def handle_query_entities(
222
+ args: dict[str, Any],
223
+ registry: SubsystemRegistry,
224
+ ) -> str:
225
+ """Query the world model for entities."""
226
+ query = args.get("query", "")
227
+ entity_type = args.get("entity_type", "all")
228
+ limit = _as_int(args.get("limit", 10), 10)
229
+
230
+ try:
231
+ world = registry.world_model
232
+ if world is None:
233
+ return json.dumps({"error": "World model not wired", "status": "unavailable"})
234
+
235
+ # WorldModelStore searches entities via find_entities() (there is no
236
+ # query() method -- API drift). "all" (the tool default) means no type
237
+ # filter. Results are BaseEntity dataclasses, so read fields by
238
+ # attribute (with a dict fallback) rather than .get().
239
+ etype = None if entity_type in (None, "", "all") else entity_type
240
+ entities = await world.find_entities(
241
+ query=query,
242
+ entity_type=etype,
243
+ limit=limit,
244
+ )
245
+
246
+ def _field(e: Any, name: str, default: Any = None) -> Any:
247
+ if isinstance(e, dict):
248
+ return e.get(name, default)
249
+ return getattr(e, name, default)
250
+
251
+ return json.dumps({
252
+ "count": len(entities),
253
+ "entities": [
254
+ {
255
+ "id": _field(e, "id"),
256
+ "name": _field(e, "name"),
257
+ "type": _field(e, "entity_type"),
258
+ }
259
+ for e in entities
260
+ ],
261
+ }, default=str)
262
+ except Exception as e:
263
+ logger.error("colony_query_entities failed: %s", e)
264
+ return json.dumps({"error": str(e), "status": "error"})
265
+
266
+
267
+ async def handle_start_research(
268
+ args: dict[str, Any],
269
+ registry: SubsystemRegistry,
270
+ ) -> str:
271
+ """Start a background research task."""
272
+ topic = args.get("topic", "")
273
+ depth = args.get("depth", "standard")
274
+
275
+ try:
276
+ research = registry.research
277
+ if research is None:
278
+ return json.dumps({"error": "Research pipeline not wired", "status": "unavailable"})
279
+
280
+ # The ResearchPipeline runs its stages inline and returns a finished
281
+ # artifact (the previous `.start()` method never existed -> API drift).
282
+ run = await research.run(goal=topic)
283
+ artifact = getattr(run, "artifact", None)
284
+ content = getattr(artifact, "content", "") if artifact else ""
285
+ status = getattr(run, "status", "completed")
286
+ status = getattr(status, "value", None) or str(status)
287
+ return json.dumps({
288
+ "status": status,
289
+ "run_id": getattr(run, "id", None),
290
+ "topic": topic,
291
+ "finding": (content or "")[:1500],
292
+ "word_count": getattr(artifact, "word_count", 0) if artifact else 0,
293
+ })
294
+ except Exception as e:
295
+ logger.error("colony_start_research failed: %s", e)
296
+ return json.dumps({"error": str(e), "status": "error"})
297
+
298
+
299
+ async def handle_discover_connections(
300
+ args: dict[str, Any],
301
+ registry: SubsystemRegistry,
302
+ ) -> str:
303
+ """Discover non-obvious connections."""
304
+ entity_id = args.get("entity_id")
305
+ min_novelty = args.get("min_novelty", 0.3)
306
+
307
+ try:
308
+ # The real accessor is connection_discoverer (registry.synthesis never
309
+ # existed -> API drift), and the method is discover_connections().
310
+ discoverer = registry.connection_discoverer
311
+ if discoverer is None:
312
+ return json.dumps({"error": "Connection discoverer not wired", "status": "unavailable"})
313
+
314
+ connections = await discoverer.discover_connections(
315
+ person_id=entity_id,
316
+ min_confidence=float(min_novelty),
317
+ )
318
+
319
+ def _f(c, *names, default=None):
320
+ for n in names:
321
+ v = c.get(n) if isinstance(c, dict) else getattr(c, n, None)
322
+ if v is not None:
323
+ return v
324
+ return default
325
+
326
+ return json.dumps({
327
+ "count": len(connections),
328
+ "connections": [
329
+ {
330
+ "from": _f(c, "source", "from", "source_id"),
331
+ "to": _f(c, "target", "to", "target_id"),
332
+ "type": _f(c, "connection_type", "type", "relationship_type"),
333
+ "confidence": _f(c, "confidence", "novelty", default=0),
334
+ }
335
+ for c in connections[:20]
336
+ ],
337
+ }, default=str)
338
+ except Exception as e:
339
+ logger.error("colony_discover_connections failed: %s", e)
340
+ return json.dumps({"error": str(e), "status": "error"})
341
+
342
+
343
+ def _iso_ts(epoch: Any) -> str:
344
+ try:
345
+ from datetime import datetime, timezone
346
+ return datetime.fromtimestamp(float(epoch), timezone.utc).isoformat()
347
+ except Exception:
348
+ return str(epoch)
349
+
350
+
351
+ async def handle_list_boundaries(
352
+ args: dict[str, Any],
353
+ registry: SubsystemRegistry,
354
+ ) -> str:
355
+ """List the owner's active standing directives / boundaries."""
356
+ try:
357
+ dm = registry.directives
358
+ if dm is None:
359
+ return json.dumps({"error": "Directives not wired", "status": "unavailable"})
360
+ active = dm.active()
361
+ return json.dumps({
362
+ "count": len(active),
363
+ "boundaries": [
364
+ {
365
+ "id": d.id,
366
+ "polarity": d.polarity.value, # prohibit | require | prefer
367
+ "subject": d.subject,
368
+ "stated": d.raw_text or d.subject,
369
+ "since": _iso_ts(d.created_at),
370
+ }
371
+ for d in active
372
+ ],
373
+ }, default=str)
374
+ except Exception as e:
375
+ logger.error("colony_list_boundaries failed: %s", e)
376
+ return json.dumps({"error": str(e), "status": "error"})
377
+
378
+
379
+ async def handle_recent_boundary_blocks(
380
+ args: dict[str, Any],
381
+ registry: SubsystemRegistry,
382
+ ) -> str:
383
+ """What autonomous actions were recently refused, and which boundary refused them."""
384
+ limit = _as_int(args.get("limit", 10), 10)
385
+ try:
386
+ dm = registry.directives
387
+ if dm is None:
388
+ return json.dumps({"error": "Directives not wired", "status": "unavailable"})
389
+ blocks = dm.guard.recent_blocks(limit=limit)
390
+ return json.dumps({
391
+ "count": len(blocks),
392
+ "blocks": [
393
+ {
394
+ "when": _iso_ts(b.get("ts")),
395
+ "action_kind": b.get("action_kind"),
396
+ "action": b.get("action_summary"),
397
+ "refused_because": b.get("directives"),
398
+ "directive_ids": b.get("directive_ids"),
399
+ }
400
+ for b in blocks
401
+ ],
402
+ }, default=str)
403
+ except Exception as e:
404
+ logger.error("colony_recent_boundary_blocks failed: %s", e)
405
+ return json.dumps({"error": str(e), "status": "error"})
406
+
407
+
408
+ async def handle_flag_boundary_concern(
409
+ args: dict[str, Any],
410
+ registry: SubsystemRegistry,
411
+ ) -> str:
412
+ """Surface a CRITICAL finding about a boundaried subject (once, guarded).
413
+
414
+ Use ONLY when reflection over a subject the owner told you to leave alone
415
+ reveals something critical (security vulnerability, data loss, financial
416
+ risk). It is delivered at most once per boundary, clearly marked as
417
+ boundary-respecting.
418
+ """
419
+ subject = args.get("subject", "")
420
+ finding = args.get("finding", "")
421
+ try:
422
+ severity = float(args.get("severity", 0.9))
423
+ except (TypeError, ValueError):
424
+ severity = 0.9
425
+ try:
426
+ dm = registry.directives
427
+ if dm is None:
428
+ return json.dumps({"error": "Directives not wired", "status": "unavailable"})
429
+ if not subject or not finding:
430
+ return json.dumps({"error": "subject and finding required", "status": "error"})
431
+ out = await dm.flag_critical(subject, finding, severity=severity)
432
+ return json.dumps(out, default=str)
433
+ except Exception as e:
434
+ logger.error("colony_flag_boundary_concern failed: %s", e)
435
+ return json.dumps({"error": str(e), "status": "error"})
436
+
437
+
438
+ async def handle_repo_list_files(
439
+ args: dict[str, Any],
440
+ registry: SubsystemRegistry,
441
+ ) -> str:
442
+ """List files in an owner-designated read-only repo mirror."""
443
+ try:
444
+ mirrors = registry.repo_mirrors
445
+ if mirrors is None:
446
+ return json.dumps({"error": "No repo mirrors configured", "status": "unavailable"})
447
+ return json.dumps(mirrors.list_files(
448
+ args.get("repo", ""), args.get("path", ""),
449
+ limit=_as_int(args.get("limit", 200), 200)), default=str)
450
+ except Exception as e:
451
+ logger.error("repo_list_files failed: %s", e)
452
+ return json.dumps({"error": str(e), "status": "error"})
453
+
454
+
455
+ async def handle_repo_read_file(
456
+ args: dict[str, Any],
457
+ registry: SubsystemRegistry,
458
+ ) -> str:
459
+ """Read a file from an owner-designated read-only repo mirror."""
460
+ try:
461
+ mirrors = registry.repo_mirrors
462
+ if mirrors is None:
463
+ return json.dumps({"error": "No repo mirrors configured", "status": "unavailable"})
464
+ return json.dumps(mirrors.read_file(
465
+ args.get("repo", ""), args.get("path", "")), default=str)
466
+ except Exception as e:
467
+ logger.error("repo_read_file failed: %s", e)
468
+ return json.dumps({"error": str(e), "status": "error"})
469
+
470
+
471
+ async def handle_repo_search(
472
+ args: dict[str, Any],
473
+ registry: SubsystemRegistry,
474
+ ) -> str:
475
+ """Search an owner-designated read-only repo mirror (git grep)."""
476
+ try:
477
+ mirrors = registry.repo_mirrors
478
+ if mirrors is None:
479
+ return json.dumps({"error": "No repo mirrors configured", "status": "unavailable"})
480
+ return json.dumps(mirrors.search(
481
+ args.get("repo", ""), args.get("query", ""),
482
+ glob=args.get("glob", "")), default=str)
483
+ except Exception as e:
484
+ logger.error("repo_search failed: %s", e)
485
+ return json.dumps({"error": str(e), "status": "error"})
486
+
487
+
488
+ async def handle_task_complete(
489
+ args: dict[str, Any],
490
+ registry: SubsystemRegistry,
491
+ ) -> str:
492
+ """Mark a task/goal as completed."""
493
+ task_id = args.get("task_id", "")
494
+ try:
495
+ goals = registry.goals
496
+ if goals is None:
497
+ return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
498
+ await goals.complete(task_id)
499
+ return json.dumps({"status": "completed", "task_id": task_id})
500
+ except Exception as e:
501
+ logger.error("colony_task_complete failed: %s", e)
502
+ return json.dumps({"error": str(e), "status": "error"})
503
+
504
+
505
+ async def handle_task_snooze(
506
+ args: dict[str, Any],
507
+ registry: SubsystemRegistry,
508
+ ) -> str:
509
+ """Snooze a task for N hours."""
510
+ task_id = args.get("task_id", "")
511
+ hours = min(args.get("hours", 24), 168)
512
+ reason = args.get("reason", "")
513
+ try:
514
+ goals = registry.goals
515
+ if goals is None:
516
+ return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
517
+ await goals.snooze(task_id, hours=hours, reason=reason)
518
+ return json.dumps({"status": "snoozed", "task_id": task_id, "hours": hours})
519
+ except Exception as e:
520
+ logger.error("colony_task_snooze failed: %s", e)
521
+ return json.dumps({"error": str(e), "status": "error"})
522
+
523
+
524
+ async def handle_task_dismiss(
525
+ args: dict[str, Any],
526
+ registry: SubsystemRegistry,
527
+ ) -> str:
528
+ """Dismiss a task as no longer relevant."""
529
+ task_id = args.get("task_id", "")
530
+ reason = args.get("reason", "stale")
531
+ try:
532
+ goals = registry.goals
533
+ if goals is None:
534
+ return json.dumps({"error": "Goals store not wired", "status": "unavailable"})
535
+ await goals.dismiss(task_id, reason=reason)
536
+ return json.dumps({"status": "dismissed", "task_id": task_id, "reason": reason})
537
+ except Exception as e:
538
+ logger.error("colony_task_dismiss failed: %s", e)
539
+ return json.dumps({"error": str(e), "status": "error"})
540
+
541
+
542
+ async def handle_initiative_feedback(
543
+ args: dict[str, Any],
544
+ registry: SubsystemRegistry,
545
+ ) -> str:
546
+ """Record feedback on an initiative (also drives per-type priority feedback)."""
547
+ initiative_id = args.get("initiative_id", "")
548
+ action = args.get("action", "acknowledged")
549
+ details = args.get("details", {})
550
+ try:
551
+ # Outcome-driven feedback (item 3b): nudge the initiative's TYPE
552
+ # multiplier so classes the owner acts on rise and dismissed ones decay.
553
+ try:
554
+ fb = getattr(registry, "feedback_store", None)
555
+ store = getattr(registry, "initiative_store", None)
556
+ itype = args.get("initiative_type") or details.get("type")
557
+ if not itype and store is not None:
558
+ init = store.get(initiative_id) if hasattr(store, "get") else None
559
+ itype = getattr(init, "type", None)
560
+ if fb is not None and itype:
561
+ fb.record(itype, action)
562
+ except Exception:
563
+ logger.debug("type feedback record failed", exc_info=True)
564
+ return json.dumps({
565
+ "status": "recorded",
566
+ "initiative_id": initiative_id,
567
+ "action": action,
568
+ })
569
+ except Exception as e:
570
+ logger.error("colony_initiative_feedback failed: %s", e)
571
+ return json.dumps({"error": str(e), "status": "error"})
572
+
573
+
574
+ # --- Cognition program handlers (items 1/3/4/7 + Amendment 1) ---
575
+
576
+ async def handle_list_projects(
577
+ args: dict[str, Any],
578
+ registry: SubsystemRegistry,
579
+ ) -> str:
580
+ status = args.get("status", "all")
581
+ try:
582
+ engine = registry.project_engine
583
+ if engine is None:
584
+ return json.dumps({"error": "Projects not wired", "status": "unavailable"})
585
+ items = engine.store.list_projects(
586
+ status=None if status in ("", "all") else status, limit=30)
587
+ return json.dumps({
588
+ "count": len(items),
589
+ "projects": [
590
+ {"id": p.id, "title": p.title, "status": p.status,
591
+ "source": p.source, "reason": p.reason}
592
+ for p in items
593
+ ],
594
+ }, default=str)
595
+ except Exception as e:
596
+ logger.error("list_projects failed: %s", e)
597
+ return json.dumps({"error": str(e), "status": "error"})
598
+
599
+
600
+ async def handle_project_status(
601
+ args: dict[str, Any],
602
+ registry: SubsystemRegistry,
603
+ ) -> str:
604
+ try:
605
+ engine = registry.project_engine
606
+ if engine is None:
607
+ return json.dumps({"error": "Projects not wired", "status": "unavailable"})
608
+ out = engine.project_status(args.get("project_id", ""))
609
+ return json.dumps(out or {"error": "not_found"}, default=str)
610
+ except Exception as e:
611
+ logger.error("project_status failed: %s", e)
612
+ return json.dumps({"error": str(e), "status": "error"})
613
+
614
+
615
+ async def handle_create_project(
616
+ args: dict[str, Any],
617
+ registry: SubsystemRegistry,
618
+ ) -> str:
619
+ try:
620
+ engine = registry.project_engine
621
+ if engine is None:
622
+ return json.dumps({"error": "Projects not wired", "status": "unavailable"})
623
+ project, reason = engine.create_project(
624
+ args.get("objective", ""), title=args.get("title", ""),
625
+ source="owner")
626
+ if project is None:
627
+ return json.dumps({"created": False, "reason": reason})
628
+ return json.dumps({"created": True, "project_id": project.id,
629
+ "title": project.title,
630
+ "note": "planned and pursued on the autonomy loop"})
631
+ except Exception as e:
632
+ logger.error("create_project failed: %s", e)
633
+ return json.dumps({"error": str(e), "status": "error"})
634
+
635
+
636
+ async def handle_abandon_project(
637
+ args: dict[str, Any],
638
+ registry: SubsystemRegistry,
639
+ ) -> str:
640
+ try:
641
+ engine = registry.project_engine
642
+ if engine is None:
643
+ return json.dumps({"error": "Projects not wired", "status": "unavailable"})
644
+ project = engine.abandon(args.get("project_id", ""),
645
+ reason=args.get("reason", "owner_request"))
646
+ return json.dumps({"abandoned": project is not None,
647
+ "status": getattr(project, "status", None)})
648
+ except Exception as e:
649
+ logger.error("abandon_project failed: %s", e)
650
+ return json.dumps({"error": str(e), "status": "error"})
651
+
652
+
653
+ async def handle_recall_skills(
654
+ args: dict[str, Any],
655
+ registry: SubsystemRegistry,
656
+ ) -> str:
657
+ try:
658
+ store = registry.skill_store
659
+ if store is None:
660
+ return json.dumps({"error": "Skills memory not wired", "status": "unavailable"})
661
+ from apsimo.skills_memory import relevant_skills
662
+ skills = relevant_skills(store, args.get("situation", ""), k=3)
663
+ return json.dumps({
664
+ "count": len(skills),
665
+ "skills": [
666
+ {"title": s.title, "situation": s.situation,
667
+ "steps": s.steps, "gotchas": s.gotchas, "domain": s.domain}
668
+ for s in skills
669
+ ],
670
+ }, default=str)
671
+ except Exception as e:
672
+ logger.error("recall_skills failed: %s", e)
673
+ return json.dumps({"error": str(e), "status": "error"})
674
+
675
+
676
+ async def handle_self_status(
677
+ args: dict[str, Any],
678
+ registry: SubsystemRegistry,
679
+ ) -> str:
680
+ try:
681
+ sm = registry.self_model
682
+ if sm is None:
683
+ return json.dumps({"error": "Self-model not wired", "status": "unavailable"})
684
+ out = sm.status()
685
+ out["brief"] = sm.brief()
686
+ return json.dumps(out, default=str)
687
+ except Exception as e:
688
+ logger.error("self_status failed: %s", e)
689
+ return json.dumps({"error": str(e), "status": "error"})
690
+
691
+
692
+ async def handle_action_journal(
693
+ args: dict[str, Any],
694
+ registry: SubsystemRegistry,
695
+ ) -> str:
696
+ try:
697
+ sm = registry.self_model
698
+ journal = getattr(sm, "journal", None) if sm is not None else None
699
+ if journal is None:
700
+ return json.dumps({"error": "Action journal not wired", "status": "unavailable"})
701
+ entries = journal.recent(limit=_as_int(args.get("limit", 20), 20),
702
+ domain=args.get("domain") or None)
703
+ return json.dumps({
704
+ "count": len(entries),
705
+ "entries": [
706
+ {"when": _iso_ts(e.get("ts")), "domain": e.get("domain"),
707
+ "action": e.get("description"), "decision": e.get("decision"),
708
+ "reasoning": e.get("reasoning"),
709
+ "confidence": e.get("confidence"),
710
+ "outcome": e.get("outcome")}
711
+ for e in entries
712
+ ],
713
+ }, default=str)
714
+ except Exception as e:
715
+ logger.error("action_journal failed: %s", e)
716
+ return json.dumps({"error": str(e), "status": "error"})
717
+
718
+
719
+ async def handle_belief_conflicts(
720
+ args: dict[str, Any],
721
+ registry: SubsystemRegistry,
722
+ ) -> str:
723
+ try:
724
+ engine = registry.belief_engine
725
+ if engine is None:
726
+ return json.dumps({"error": "Belief engine not wired", "status": "unavailable"})
727
+ status = args.get("status", "all")
728
+ items = engine.conflicts(status=None if status in ("", "all") else status,
729
+ limit=30)
730
+ return json.dumps({"count": len(items), "conflicts": items}, default=str)
731
+ except Exception as e:
732
+ logger.error("belief_conflicts failed: %s", e)
733
+ return json.dumps({"error": str(e), "status": "error"})
734
+
735
+
736
+ async def _resolve_one_contact(store, who: str):
737
+ """Resolve who -> a single contact_id, or return (None, error_json)."""
738
+ who = (who or "").strip()
739
+ if who.startswith("cid-"):
740
+ return who, None
741
+ matches = await store.find_by_name(who, threshold=0.6)
742
+ if not matches:
743
+ return None, json.dumps({"error": f"no contact matching {who!r}",
744
+ "status": "not_found"})
745
+ if len(matches) > 1:
746
+ return None, json.dumps({
747
+ "status": "ambiguous",
748
+ "candidates": [{"contact_id": m.contact_id,
749
+ "display_name": m.display_name}
750
+ for m in matches[:5]]})
751
+ return matches[0].contact_id, None
752
+
753
+
754
+ async def handle_link_contact(
755
+ args: dict[str, Any],
756
+ registry: SubsystemRegistry,
757
+ ) -> str:
758
+ """Attach a channel handle to a person (owner curation)."""
759
+ try:
760
+ from apsimo.api.routers.host import _contacts_store
761
+ if _contacts_store is None:
762
+ return json.dumps({"error": "contact store not wired",
763
+ "status": "unavailable"})
764
+ cid, err = await _resolve_one_contact(_contacts_store, args.get("who", ""))
765
+ if err:
766
+ return err
767
+ gw = str(args.get("gateway", "")).strip().lower()
768
+ addr = str(args.get("address", "")).strip()
769
+ if not gw or not addr:
770
+ return json.dumps({"error": "gateway and address required",
771
+ "status": "error"})
772
+ await _contacts_store.add_handle(cid, gw, addr, verified=True,
773
+ source="owner")
774
+ return json.dumps({"linked": True, "contact_id": cid,
775
+ "gateway": gw, "address": addr})
776
+ except ValueError as e:
777
+ return json.dumps({"error": str(e), "status": "conflict"})
778
+ except Exception as e:
779
+ logger.error("link_contact failed: %s", e)
780
+ return json.dumps({"error": str(e), "status": "error"})
781
+
782
+
783
+ async def handle_merge_contacts(
784
+ args: dict[str, Any],
785
+ registry: SubsystemRegistry,
786
+ ) -> str:
787
+ """Merge two contacts that are the same person (owner curation)."""
788
+ try:
789
+ from apsimo.api.routers.host import _contacts_store
790
+ if _contacts_store is None:
791
+ return json.dumps({"error": "contact store not wired",
792
+ "status": "unavailable"})
793
+ keep, err = await _resolve_one_contact(_contacts_store, args.get("keep", ""))
794
+ if err:
795
+ return err
796
+ merge, err = await _resolve_one_contact(_contacts_store, args.get("merge", ""))
797
+ if err:
798
+ return err
799
+ kept = await _contacts_store.merge_contacts(keep, merge, performed_by="owner")
800
+ return json.dumps({"merged": True, "kept_contact_id": keep,
801
+ "merged_contact_id": merge,
802
+ "interaction_count": getattr(kept, "interaction_count", None)},
803
+ default=str)
804
+ except ValueError as e:
805
+ return json.dumps({"error": str(e), "status": "error"})
806
+ except Exception as e:
807
+ logger.error("merge_contacts failed: %s", e)
808
+ return json.dumps({"error": str(e), "status": "error"})
809
+
810
+
811
+ async def handle_pending_contact_proposals(
812
+ args: dict[str, Any],
813
+ registry: SubsystemRegistry,
814
+ ) -> str:
815
+ """List handle-link proposals awaiting owner review."""
816
+ try:
817
+ from apsimo.api.routers.host import _contacts_store
818
+ if _contacts_store is None:
819
+ return json.dumps({"error": "contact store not wired",
820
+ "status": "unavailable"})
821
+ props = await _contacts_store.list_handle_proposals(limit=50)
822
+ return json.dumps({"count": len(props), "proposals": props}, default=str)
823
+ except Exception as e:
824
+ logger.error("pending_contact_proposals failed: %s", e)
825
+ return json.dumps({"error": str(e), "status": "error"})
826
+
827
+
828
+ async def handle_relationship_brief(
829
+ args: dict[str, Any],
830
+ registry: SubsystemRegistry,
831
+ ) -> str:
832
+ """Standing + psyche + approach brief for a person (docs/RELATIONSHIPS.md)."""
833
+ try:
834
+ from apsimo.api.routers.host import (
835
+ _contacts_store, _relationship_profiler,
836
+ )
837
+ if _relationship_profiler is None:
838
+ return json.dumps({"error": "relationship profiler not wired",
839
+ "status": "unavailable"})
840
+ who = str(args.get("who", "")).strip()
841
+ if not who:
842
+ return json.dumps({"error": "who is required", "status": "error"})
843
+ contact_id = who
844
+ if not who.startswith("cid-") and _contacts_store is not None:
845
+ matches = await _contacts_store.find_by_name(who, threshold=0.6)
846
+ if not matches:
847
+ return json.dumps({"error": f"no contact matching {who!r}",
848
+ "status": "not_found"})
849
+ if len(matches) > 1:
850
+ return json.dumps({
851
+ "status": "ambiguous",
852
+ "candidates": [
853
+ {"contact_id": m.contact_id,
854
+ "display_name": m.display_name}
855
+ for m in matches[:5]],
856
+ })
857
+ contact_id = matches[0].contact_id
858
+ brief = (None if args.get("refresh")
859
+ else _relationship_profiler.cached(contact_id))
860
+ if brief is None:
861
+ brief = await _relationship_profiler.profile(contact_id)
862
+ if brief is None:
863
+ return json.dumps({"error": f"no profile for {contact_id!r}",
864
+ "status": "not_found"})
865
+ return json.dumps({"brief": brief.to_dict(),
866
+ "rendered": brief.render()}, default=str)
867
+ except Exception as e:
868
+ logger.error("relationship_brief failed: %s", e)
869
+ return json.dumps({"error": str(e), "status": "error"})
870
+
871
+
872
+ async def handle_sandbox_run(
873
+ args: dict[str, Any],
874
+ registry: SubsystemRegistry,
875
+ ) -> str:
876
+ try:
877
+ sandbox = registry.sandbox
878
+ if sandbox is None:
879
+ return json.dumps({"error": "Sandbox not wired", "status": "unavailable"})
880
+ # Agent-invoked runs are NOT owner-directed: they are flagged for owner
881
+ # approval (the owner auto-runs via the authenticated API). The agent
882
+ # cannot self-grant owner authority here.
883
+ out = sandbox.run(
884
+ args.get("script", ""),
885
+ lang=args.get("lang", "python"),
886
+ purpose=args.get("purpose", ""),
887
+ owner_directed=False,
888
+ )
889
+ return json.dumps(out, default=str)
890
+ except Exception as e:
891
+ logger.error("sandbox_run failed: %s", e)
892
+ return json.dumps({"error": str(e), "status": "error"})
893
+
894
+
895
+ async def handle_sandbox_status(
896
+ args: dict[str, Any],
897
+ registry: SubsystemRegistry,
898
+ ) -> str:
899
+ try:
900
+ sandbox = registry.sandbox
901
+ if sandbox is None:
902
+ return json.dumps({"error": "Sandbox not wired", "status": "unavailable"})
903
+ return json.dumps(sandbox.status(), default=str)
904
+ except Exception as e:
905
+ logger.error("sandbox_status failed: %s", e)
906
+ return json.dumps({"error": str(e), "status": "error"})
907
+
908
+
909
+ # Handler registry -- maps tool name to handler function
910
+ TOOL_HANDLERS: dict[str, callable] = {
911
+ "colony_memory_search": handle_memory_search,
912
+ "colony_get_relationship": handle_get_relationship,
913
+ "colony_list_goals": handle_list_goals,
914
+ "colony_get_briefing": handle_get_briefing,
915
+ "colony_record_insight": handle_record_insight,
916
+ "colony_query_entities": handle_query_entities,
917
+ "colony_start_research": handle_start_research,
918
+ "colony_discover_connections": handle_discover_connections,
919
+ "colony_task_complete": handle_task_complete,
920
+ "colony_task_snooze": handle_task_snooze,
921
+ "colony_task_dismiss": handle_task_dismiss,
922
+ "colony_initiative_feedback": handle_initiative_feedback,
923
+ "colony_list_boundaries": handle_list_boundaries,
924
+ "colony_recent_boundary_blocks": handle_recent_boundary_blocks,
925
+ "repo_list_files": handle_repo_list_files,
926
+ "repo_read_file": handle_repo_read_file,
927
+ "repo_search": handle_repo_search,
928
+ "colony_flag_boundary_concern": handle_flag_boundary_concern,
929
+ "list_projects": handle_list_projects,
930
+ "project_status": handle_project_status,
931
+ "create_project": handle_create_project,
932
+ "abandon_project": handle_abandon_project,
933
+ "recall_skills": handle_recall_skills,
934
+ "self_status": handle_self_status,
935
+ "action_journal": handle_action_journal,
936
+ "belief_conflicts": handle_belief_conflicts,
937
+ "link_contact": handle_link_contact,
938
+ "merge_contacts": handle_merge_contacts,
939
+ "pending_contact_proposals": handle_pending_contact_proposals,
940
+ "relationship_brief": handle_relationship_brief,
941
+ "sandbox_run": handle_sandbox_run,
942
+ "sandbox_status": handle_sandbox_status,
943
+ }