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,382 @@
1
+ """Colony worker daemon -- claims typed jobs and executes them with an LLM
2
+ (cognition program, Phase B item 5).
3
+
4
+ This is the installable, capability-typed worker half of the Colony queue.
5
+ It authenticates to the sidecar, advertises its capabilities, claims eligible
6
+ jobs, reasons over each with an OpenAI-compatible LLM using a READ/ANALYSE
7
+ tool posture ONLY, and posts a structured report the sidecar audits.
8
+
9
+ Trust model: the worker is UNTRUSTED. It never performs mutations itself and
10
+ never reports one -- the sidecar's WorkerGovernor re-checks capability
11
+ coverage and owner boundaries at claim time and audits the report at
12
+ completion (a mutation on a read-only job is a server-side violation). Keeping
13
+ the worker read/analyse-only is what makes "never trust the client" cheap:
14
+ there is nothing dangerous for it to do.
15
+
16
+ Deliberately stdlib-only (like the other workers/ modules) so it runs on hosts
17
+ that have the agent but not the full sidecar dependency stack. The report
18
+ schema (summary, operations, files_touched, commits, branch, confidence)
19
+ matches what governor.audit_report expects.
20
+
21
+ Native alternative (preferred where available): rather than this bespoke
22
+ executor, a deployment can drive work through Hermes kanban (dispatcher +
23
+ worker subprocess) or the gateway's structured-runs API (/v1/runs + SSE +
24
+ approval), and let this governor enforce/audit the same way. This daemon is
25
+ the dependency-light reference path; see the private deployment layer for the
26
+ host/LLM/capability placement.
27
+
28
+ Environment:
29
+ COLONY_URL sidecar base URL (default http://127.0.0.1:7777)
30
+ COLONY_API_KEY sidecar API key (default dev-mode-no-key)
31
+ COLONY_WORKER_NODE_ID worker identity (default <agent>-worker)
32
+ COLONY_WORKER_CAPABILITIES csv capabilities (default research,analyst,read)
33
+ COLONY_WORKER_JOB_TYPES csv job types (default research)
34
+ COLONY_WORKER_MAX_JOBS jobs per cycle (default 1)
35
+ COLONY_WORKER_POLL_SECS seconds between polls (default 30; daemon mode)
36
+ COLONY_WORKER_HEARTBEAT_SECS heartbeat cadence while running (default 20)
37
+ COLONY_WORKER_LLM_BASE_URL OpenAI-compatible base (default OPENAI_BASE_URL)
38
+ COLONY_WORKER_LLM_MODEL model name (default gpt-4o-mini)
39
+ COLONY_WORKER_LLM_API_KEY LLM key (default OPENAI_API_KEY)
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import argparse
45
+ import json
46
+ import os
47
+ import threading
48
+ import time
49
+ import urllib.request
50
+ from typing import Any, Dict, List, Optional
51
+
52
+ # Compact worker doctrine (mirrors cognition/charter.py role="worker"; inlined
53
+ # to keep this daemon stdlib-only and runnable without the sidecar package).
54
+ _WORKER_SYSTEM = (
55
+ "You are an Apsimo worker agent executing ONE claimed job with a read and "
56
+ "analysis posture only. You never modify files, commit, push, deploy, or "
57
+ "send anything -- you observe, reason, and report. Rules: stay strictly "
58
+ "inside the job's stated scope; verify before asserting; if the job cannot "
59
+ "be done, say so plainly with the reason; never fabricate a result. "
60
+ "Respond with ONLY a JSON object (no prose, no markdown fences):\n"
61
+ '{"summary": str (outcome first, then evidence), '
62
+ '"operations": [str] (only from: analyze, read, search), '
63
+ '"files_touched": [] (always empty -- you do not touch files), '
64
+ '"commits": 0, "branch": "", '
65
+ '"confidence": float 0.0-1.0 (calibrated: your honesty here trains earned '
66
+ 'autonomy), "remaining_work": str}'
67
+ )
68
+
69
+
70
+ def load_config() -> Dict[str, Any]:
71
+ """Resolve worker config from the environment (at call time)."""
72
+ node_id = os.environ.get("COLONY_WORKER_NODE_ID") or (
73
+ os.environ.get("COLONY_AGENT_NAME", "colony").strip().lower().replace(" ", "-")
74
+ + "-worker"
75
+ )
76
+ caps = [c.strip() for c in os.environ.get(
77
+ "COLONY_WORKER_CAPABILITIES", "research,analyst,read").split(",") if c.strip()]
78
+ job_types = [t.strip() for t in os.environ.get(
79
+ "COLONY_WORKER_JOB_TYPES", "research").split(",") if t.strip()]
80
+ if os.environ.get(
81
+ "COLONY_AGENT_JOB_CLAIMS_ENABLED", "true"
82
+ ).strip().lower() not in {"1", "true", "yes", "on"}:
83
+ job_types = [item for item in job_types if item != "agent_action"]
84
+ return {
85
+ "colony_url": os.environ.get("COLONY_URL", "http://127.0.0.1:7777").rstrip("/"),
86
+ "api_key": os.environ.get("COLONY_API_KEY", "dev-mode-no-key"),
87
+ "node_id": node_id,
88
+ "capabilities": caps,
89
+ "job_types": job_types,
90
+ "max_jobs": int(os.environ.get("COLONY_WORKER_MAX_JOBS", "1")),
91
+ "poll_secs": float(os.environ.get("COLONY_WORKER_POLL_SECS", "30")),
92
+ "heartbeat_secs": max(
93
+ 1.0,
94
+ float(os.environ.get("COLONY_WORKER_HEARTBEAT_SECS", "20")),
95
+ ),
96
+ "llm_base_url": (os.environ.get("COLONY_WORKER_LLM_BASE_URL")
97
+ or os.environ.get("OPENAI_BASE_URL", "")).rstrip("/"),
98
+ "llm_model": os.environ.get("COLONY_WORKER_LLM_MODEL", "gpt-4o-mini"),
99
+ "llm_api_key": (os.environ.get("COLONY_WORKER_LLM_API_KEY")
100
+ or os.environ.get("OPENAI_API_KEY", "")),
101
+ }
102
+
103
+
104
+ def _headers(cfg: Dict[str, Any]) -> Dict[str, str]:
105
+ return {"X-API-Key": cfg["api_key"], "Content-Type": "application/json"}
106
+
107
+
108
+ def _post(cfg: Dict[str, Any], url: str, body: dict, timeout: int = 30) -> dict:
109
+ req = urllib.request.Request(
110
+ url, data=json.dumps(body).encode("utf-8"),
111
+ headers=_headers(cfg), method="POST")
112
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
113
+ raw = resp.read().decode("utf-8")
114
+ return json.loads(raw) if raw else {}
115
+
116
+
117
+ def register_worker(cfg: Dict[str, Any]) -> None:
118
+ """Registration is ephemeral (lost on sidecar restart) -- re-register."""
119
+ _post(cfg, f"{cfg['colony_url']}/v1/host/queue/workers/register", {
120
+ "node_id": cfg["node_id"],
121
+ "capabilities": cfg["capabilities"],
122
+ "job_types": cfg["job_types"],
123
+ "max_concurrent": 1,
124
+ "available": True,
125
+ "load": 0.0,
126
+ })
127
+
128
+
129
+ def claim_job(cfg: Dict[str, Any]) -> Optional[dict]:
130
+ return _post(cfg, f"{cfg['colony_url']}/v1/host/queue/jobs/claim", {
131
+ "node_id": cfg["node_id"],
132
+ "capabilities": cfg["capabilities"],
133
+ "job_types": cfg["job_types"],
134
+ }) or None
135
+
136
+
137
+ def build_llm_messages(job: dict) -> List[Dict[str, str]]:
138
+ """Compose the worker LLM messages for a claimed job (pure -- testable)."""
139
+ payload = job.get("payload") or {}
140
+ parts = []
141
+ for k in ("description", "action_hint", "domain"):
142
+ v = payload.get(k)
143
+ if v:
144
+ parts.append(f"{k}: {v}")
145
+ ctx = payload.get("context")
146
+ if ctx:
147
+ parts.append("context: " + json.dumps(ctx, default=str)[:2000])
148
+ user = "JOB:\n" + "\n".join(parts) if parts else "JOB: (no description)"
149
+ return [{"role": "system", "content": _WORKER_SYSTEM},
150
+ {"role": "user", "content": user}]
151
+
152
+
153
+ def _parse_report(content: str) -> Dict[str, Any]:
154
+ """Parse the LLM's JSON report; sanitise to the read/analyse contract."""
155
+ text = (content or "").strip()
156
+ if "```" in text:
157
+ import re
158
+ m = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
159
+ if m:
160
+ text = m.group(1).strip()
161
+ if not text.startswith("{"):
162
+ import re
163
+ m = re.search(r"\{.*\}", text, re.DOTALL)
164
+ text = m.group(0) if m else "{}"
165
+ parsed = False
166
+ try:
167
+ data = json.loads(text)
168
+ parsed = isinstance(data, dict) and bool(
169
+ str(data.get("summary") or "").strip()
170
+ )
171
+ except Exception:
172
+ data = {"summary": (content or "")[:600]}
173
+ allowed_ops = {"analyze", "read", "search"}
174
+ ops = [str(o).lower() for o in (data.get("operations") or [])
175
+ if str(o).lower() in allowed_ops] or ["analyze"]
176
+ conf = data.get("confidence")
177
+ try:
178
+ conf = max(0.0, min(1.0, float(conf)))
179
+ except (TypeError, ValueError):
180
+ conf = 0.5
181
+ # Read/analyse posture is enforced client-side too: never report a mutation.
182
+ report = {
183
+ "summary": str(data.get("summary", ""))[:2000],
184
+ "operations": ops,
185
+ "files_touched": [],
186
+ "commits": 0,
187
+ "branch": "",
188
+ "confidence": conf,
189
+ "remaining_work": str(data.get("remaining_work", ""))[:600],
190
+ }
191
+ if parsed:
192
+ report.update({
193
+ "status": "completed",
194
+ "action_plane": {"state": "completed"},
195
+ })
196
+ return report
197
+
198
+
199
+ def _heartbeat_loop(
200
+ cfg: Dict[str, Any],
201
+ job_id: str,
202
+ claim_attempt_id: str,
203
+ stop: threading.Event,
204
+ ) -> None:
205
+ """Maintain the exact RUNNING lease around the blocking LLM request."""
206
+
207
+ interval = float(cfg.get("heartbeat_secs") or 20.0)
208
+ while not stop.wait(interval):
209
+ try:
210
+ _post(
211
+ cfg,
212
+ f"{cfg['colony_url']}/v1/host/queue/jobs/{job_id}/heartbeat",
213
+ {"claim_attempt_id": claim_attempt_id},
214
+ timeout=10,
215
+ )
216
+ except Exception as exc:
217
+ print(f"Heartbeat failed for job {job_id}: {exc}")
218
+
219
+
220
+ def call_llm(cfg: Dict[str, Any], messages: List[Dict[str, str]]) -> Dict[str, Any]:
221
+ """Call the OpenAI-compatible endpoint; return a parsed report."""
222
+ if not cfg["llm_base_url"]:
223
+ raise RuntimeError("no COLONY_WORKER_LLM_BASE_URL configured")
224
+ body = {"model": cfg["llm_model"], "messages": messages, "temperature": 0.2}
225
+ req = urllib.request.Request(
226
+ f"{cfg['llm_base_url']}/chat/completions",
227
+ data=json.dumps(body).encode("utf-8"),
228
+ headers={"Content-Type": "application/json",
229
+ "Authorization": f"Bearer {cfg['llm_api_key']}"},
230
+ method="POST")
231
+ with urllib.request.urlopen(req, timeout=120) as resp:
232
+ data = json.loads(resp.read().decode("utf-8"))
233
+ content = (data.get("choices") or [{}])[0].get("message", {}).get("content", "")
234
+ return _parse_report(content)
235
+
236
+
237
+ def execute_job(cfg: Dict[str, Any], job: dict) -> bool:
238
+ """Run one claimed job to completion (report) or failure."""
239
+ job_id = job.get("job_id") or job.get("id")
240
+ claim_attempt_id = job.get("claim_attempt_id")
241
+ try:
242
+ started = _post(
243
+ cfg,
244
+ f"{cfg['colony_url']}/v1/host/queue/jobs/{job_id}/start",
245
+ {"claim_attempt_id": claim_attempt_id},
246
+ )
247
+ if not isinstance(started, dict) or started.get("success") is not True:
248
+ raise RuntimeError("start transition was not accepted")
249
+ except Exception as exc:
250
+ print(f"Start failed for job {job_id}: {exc}")
251
+ try:
252
+ _post(
253
+ cfg,
254
+ f"{cfg['colony_url']}/v1/host/queue/jobs/{job_id}/release",
255
+ {"claim_attempt_id": claim_attempt_id},
256
+ )
257
+ except Exception:
258
+ pass
259
+ return False
260
+ heartbeat_stop = threading.Event()
261
+ heartbeat = threading.Thread(
262
+ target=_heartbeat_loop,
263
+ args=(cfg, str(job_id), str(claim_attempt_id), heartbeat_stop),
264
+ daemon=True,
265
+ name=f"colony-heartbeat-{job_id}",
266
+ )
267
+ try:
268
+ # Confirm this exact attempt is still authoritative before spending
269
+ # inference time, then keep it alive during the blocking request.
270
+ _post(
271
+ cfg,
272
+ f"{cfg['colony_url']}/v1/host/queue/jobs/{job_id}/heartbeat",
273
+ {"claim_attempt_id": claim_attempt_id},
274
+ timeout=10,
275
+ )
276
+ heartbeat.start()
277
+ report = call_llm(cfg, build_llm_messages(job))
278
+ except Exception as exc:
279
+ try:
280
+ _post(cfg, f"{cfg['colony_url']}/v1/host/queue/jobs/{job_id}/fail",
281
+ {"error": f"worker execution failed: {exc}"[:400],
282
+ "claim_attempt_id": claim_attempt_id})
283
+ except Exception:
284
+ pass
285
+ return False
286
+ finally:
287
+ heartbeat_stop.set()
288
+ if heartbeat.is_alive():
289
+ heartbeat.join(timeout=2.0)
290
+ try:
291
+ resp = _post(cfg, f"{cfg['colony_url']}/v1/host/queue/jobs/{job_id}/complete",
292
+ {"output": report, "claim_attempt_id": claim_attempt_id})
293
+ verdict = resp.get("verdict", "?")
294
+ semantic_success = (
295
+ resp.get("transitioned") is True
296
+ and resp.get("job_status") == "completed"
297
+ and resp.get("governor_outcome") == "success"
298
+ )
299
+ print(f"Completed job {job_id} (verdict={verdict}, "
300
+ f"status={resp.get('job_status', '?')}, "
301
+ f"confidence={report['confidence']})")
302
+ return semantic_success
303
+ except Exception as exc:
304
+ print(f"Complete post failed for {job_id}: {exc}")
305
+ return False
306
+
307
+
308
+ def run_cycle(cfg: Dict[str, Any]) -> int:
309
+ """One poll cycle: register, claim up to max_jobs, execute each."""
310
+ if not cfg.get("job_types"):
311
+ print("No enabled worker job types.")
312
+ return 0
313
+ try:
314
+ register_worker(cfg)
315
+ except Exception as exc:
316
+ print(f"Worker registration failed (sidecar down?): {exc}")
317
+ return 0
318
+ done = 0
319
+ for _ in range(cfg["max_jobs"]):
320
+ try:
321
+ job = claim_job(cfg)
322
+ except Exception as exc:
323
+ print(f"Claim failed: {exc}")
324
+ break
325
+ if not job:
326
+ break
327
+ if execute_job(cfg, job):
328
+ done += 1
329
+ if not done:
330
+ print("No eligible jobs.")
331
+ return done
332
+
333
+
334
+ def run_forever(cfg: Dict[str, Any]) -> None:
335
+ print(f"colony-worker {cfg['node_id']} polling {cfg['colony_url']} "
336
+ f"every {cfg['poll_secs']}s (caps={cfg['capabilities']}, "
337
+ f"types={cfg['job_types']})")
338
+ while True:
339
+ try:
340
+ run_cycle(cfg)
341
+ except Exception as exc:
342
+ print(f"cycle error: {exc}")
343
+ time.sleep(cfg["poll_secs"])
344
+
345
+
346
+ def main(argv: Optional[list] = None) -> int:
347
+ # Under launchd/systemd stdout is a pipe (block-buffered): without line
348
+ # buffering every print sits invisible in the buffer and the daemon's
349
+ # log file stays empty for hours. Flush per line.
350
+ import sys
351
+ try:
352
+ sys.stdout.reconfigure(line_buffering=True)
353
+ sys.stderr.reconfigure(line_buffering=True)
354
+ except Exception:
355
+ pass
356
+ parser = argparse.ArgumentParser(
357
+ prog="colony-worker",
358
+ description="Claim typed Colony jobs and execute them read/analyse-only.")
359
+ parser.add_argument("--dry-run", action="store_true",
360
+ help="print resolved config and exit (no network)")
361
+ parser.add_argument("--once", action="store_true",
362
+ help="run a single poll cycle and exit")
363
+ args = parser.parse_args(argv)
364
+
365
+ cfg = load_config()
366
+ if args.dry_run:
367
+ safe = dict(cfg)
368
+ safe["api_key"] = "***" if cfg["api_key"] else ""
369
+ safe["llm_api_key"] = "***" if cfg["llm_api_key"] else ""
370
+ print("colony-worker (dry run -- no network calls):")
371
+ for k, v in safe.items():
372
+ print(f" {k}: {v}")
373
+ return 0
374
+ if args.once:
375
+ run_cycle(cfg)
376
+ return 0
377
+ run_forever(cfg)
378
+ return 0
379
+
380
+
381
+ if __name__ == "__main__":
382
+ raise SystemExit(main())