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,188 @@
1
+ """Crash-recoverable ProjectStore to host-journal projection.
2
+
3
+ The project ledger and host event journal are separate durable stores. A
4
+ ProjectStore transaction therefore stages immutable event data in its own
5
+ outbox first. This projector uses the journal's keyed append handshake, then
6
+ records the returned sequence in the project database. Replaying after a
7
+ crash can finish either side without creating a second host event.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import inspect
13
+ from typing import Any, Callable, Dict, Mapping, Optional
14
+
15
+ from apsimo.events.journal import event_record_request_digest
16
+ from apsimo.projects.store import ProjectStore
17
+
18
+
19
+ _RECEIPT_ACKNOWLEDGEMENT_FIELDS = frozenset({
20
+ "expected_seq",
21
+ "expected_event_id",
22
+ "expected_recorded_at",
23
+ "expected_request_digest",
24
+ })
25
+
26
+
27
+ class ProjectEventProjector:
28
+ """Drain immutable project events into the canonical host journal."""
29
+
30
+ def __init__(
31
+ self,
32
+ store: ProjectStore,
33
+ *,
34
+ journal_projector: Optional[Callable[..., Optional[Mapping[str, Any]]]] = None,
35
+ journal_acknowledger: Optional[Callable[..., bool]] = None,
36
+ ) -> None:
37
+ self.store = store
38
+ if journal_projector is None:
39
+ from apsimo.events.journal import append_event_record
40
+
41
+ journal_projector = append_event_record
42
+ if journal_acknowledger is None:
43
+ from apsimo.events.journal import acknowledge_event_record
44
+
45
+ journal_acknowledger = acknowledge_event_record
46
+ self._project = journal_projector
47
+ self._acknowledge = journal_acknowledger
48
+ self._acknowledge_with_receipt = self._supports_receipt_acknowledgement(
49
+ journal_acknowledger
50
+ )
51
+ if not self._acknowledge_with_receipt:
52
+ raise TypeError(
53
+ "project journal acknowledger must accept the exact projection "
54
+ "receipt fields"
55
+ )
56
+
57
+ @staticmethod
58
+ def _supports_receipt_acknowledgement(callback: Callable[..., bool]) -> bool:
59
+ """Require an acknowledger that validates the exact journal receipt."""
60
+
61
+ try:
62
+ parameters = inspect.signature(callback).parameters
63
+ except (TypeError, ValueError):
64
+ return False
65
+ if any(
66
+ parameter.kind is inspect.Parameter.VAR_KEYWORD
67
+ for parameter in parameters.values()
68
+ ):
69
+ return True
70
+ return _RECEIPT_ACKNOWLEDGEMENT_FIELDS.issubset(parameters)
71
+
72
+ def _validated_payload(self, row: Mapping[str, Any]) -> Dict[str, Any]:
73
+ payload_error = str(row.get("payload_error") or "")
74
+ payload = row.get("payload")
75
+ if payload_error:
76
+ raise ValueError(payload_error)
77
+ if not isinstance(payload, dict):
78
+ raise ValueError("project outbox payload is not an object")
79
+ expected = self.store.project_event_envelope_digest(
80
+ event_key=str(row.get("event_key") or ""),
81
+ event_type=str(row.get("event_type") or ""),
82
+ occurred_at=str(row.get("occurred_at") or ""),
83
+ payload=payload,
84
+ )
85
+ if expected != str(row.get("event_digest") or ""):
86
+ raise ValueError("project outbox envelope digest mismatch")
87
+ return payload
88
+
89
+ def _validated_projection_receipt(
90
+ self,
91
+ row: Mapping[str, Any],
92
+ payload: Mapping[str, Any],
93
+ ) -> Dict[str, Any]:
94
+ if row.get("state") != "projected":
95
+ raise ValueError("project outbox event is not projected")
96
+ expected = self.store.project_event_projection_receipt_digest(
97
+ event_key=str(row.get("event_key") or ""),
98
+ event_type=str(row.get("event_type") or ""),
99
+ event_digest=str(row.get("event_digest") or ""),
100
+ occurred_at=str(row.get("occurred_at") or ""),
101
+ journal_seq=row.get("journal_seq"),
102
+ journal_event_id=str(row.get("journal_event_id") or ""),
103
+ journal_recorded_at=str(row.get("journal_recorded_at") or ""),
104
+ )
105
+ if str(row.get("projection_receipt_digest") or "") != expected:
106
+ raise ValueError("project outbox projection receipt digest mismatch")
107
+ return {
108
+ "expected_seq": row["journal_seq"],
109
+ "expected_event_id": str(row["journal_event_id"]),
110
+ "expected_recorded_at": str(row["journal_recorded_at"]),
111
+ "expected_request_digest": event_record_request_digest(
112
+ str(row["event_type"]),
113
+ dict(payload),
114
+ str(row["occurred_at"]),
115
+ ),
116
+ }
117
+
118
+ def _acknowledge_row(self, row: Mapping[str, Any]) -> None:
119
+ key = str(row.get("event_key") or "")
120
+ payload = self._validated_payload(row)
121
+ receipt = self._validated_projection_receipt(row, payload)
122
+ acknowledged = self._acknowledge(key, **receipt)
123
+ if not acknowledged:
124
+ raise RuntimeError("journal event-key acknowledgement failed")
125
+ self.store.acknowledge_project_event(key)
126
+
127
+ def _cleanup_acknowledgements(self, *, limit: int) -> tuple[int, int]:
128
+ cleaned = 0
129
+ failed = 0
130
+ for row in self.store.unacknowledged_project_events(limit=limit):
131
+ key = str(row["event_key"])
132
+ try:
133
+ self._acknowledge_row(row)
134
+ cleaned += 1
135
+ except Exception as exc:
136
+ self.store.fail_project_event(key, str(exc))
137
+ failed += 1
138
+ return cleaned, failed
139
+
140
+ def run_once(self, *, limit: int = 100) -> Dict[str, Any]:
141
+ bound = max(1, min(500, int(limit)))
142
+ acknowledged, acknowledgement_failures = self._cleanup_acknowledgements(
143
+ limit=bound,
144
+ )
145
+ projected = 0
146
+ failed = 0
147
+ retained = 0
148
+ for row in self.store.pending_project_events(limit=bound):
149
+ key = str(row["event_key"])
150
+ try:
151
+ payload = self._validated_payload(row)
152
+ record = self._project(
153
+ str(row["event_type"]),
154
+ payload,
155
+ occurred_at=str(row["occurred_at"]),
156
+ event_key=key,
157
+ )
158
+ if not isinstance(record, Mapping):
159
+ raise RuntimeError("project event journal projection failed")
160
+ completed = self.store.complete_project_event(key, dict(record))
161
+ projected += 1
162
+ retained += int(bool(record.get("retained", True)))
163
+ except Exception as exc:
164
+ self.store.fail_project_event(key, str(exc))
165
+ failed += 1
166
+ continue
167
+ try:
168
+ self._acknowledge_row(completed)
169
+ acknowledged += 1
170
+ except Exception as exc:
171
+ self.store.fail_project_event(key, str(exc))
172
+ acknowledgement_failures += 1
173
+ status = self.store.project_event_outbox_status()
174
+ return {
175
+ "processed": projected + failed,
176
+ "projected": projected,
177
+ "failed": failed,
178
+ "retained": retained,
179
+ "acknowledged": acknowledged,
180
+ "acknowledgement_failures": acknowledgement_failures,
181
+ "outbox": status,
182
+ }
183
+
184
+ def status(self) -> Dict[str, Any]:
185
+ return self.store.project_event_outbox_status()
186
+
187
+
188
+ __all__ = ["ProjectEventProjector"]
@@ -0,0 +1,216 @@
1
+ """Project + Step data model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ import uuid
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ # Fixed step-action vocabulary. Each kind routes through an existing gated
13
+ # sub-path; anything outside this set is dropped at validation.
14
+ # analyze / research / internal -> sidecar reasoning turn (internal tools)
15
+ # directed -> DirectedActionService (dry_run/approval)
16
+ # deliver -> guarded proposal delivery
17
+ ACTION_KINDS = frozenset({"analyze", "research", "directed", "deliver", "internal"})
18
+
19
+ PROJECT_STATUSES = ("planning", "active", "blocked", "completed", "abandoned")
20
+ STEP_STATUSES = ("pending", "active", "done", "failed", "skipped")
21
+
22
+
23
+ def projects_mode() -> str:
24
+ from apsimo.util.autonomy_preset import resolve
25
+ return resolve("COLONY_PROJECTS_MODE", ("off", "shadow", "live"), "shadow")
26
+
27
+
28
+ def projects_max_steps() -> int:
29
+ try:
30
+ return max(1, min(50, int(os.environ.get("COLONY_PROJECTS_MAX_STEPS", "12"))))
31
+ except (TypeError, ValueError):
32
+ return 12
33
+
34
+
35
+ def projects_max_replans() -> int:
36
+ try:
37
+ return max(0, int(os.environ.get("COLONY_PROJECTS_MAX_REPLANS", "3")))
38
+ except (TypeError, ValueError):
39
+ return 3
40
+
41
+
42
+ def projects_review_secs() -> float:
43
+ try:
44
+ return max(30.0, float(os.environ.get("COLONY_PROJECTS_REVIEW_SECS", "900")))
45
+ except (TypeError, ValueError):
46
+ return 900.0
47
+
48
+
49
+ @dataclass
50
+ class Project:
51
+ title: str
52
+ objective: str = ""
53
+ source: str = "owner" # owner | thinker | directive
54
+ status: str = "planning"
55
+ outcome: str = "pending" # lifecycle status != evidence outcome
56
+ entity_ids: List[str] = field(default_factory=list)
57
+ reason: str = "" # abandon/complete/blocked reason
58
+ replans: int = 0
59
+ next_review_at: float = 0.0 # 0 -> due immediately
60
+ # P3 autonomous-goal provenance. Legacy/owner projects leave these
61
+ # empty; cognition-spine projects carry every upstream ID and scope into
62
+ # WorkOrder.context_refs without copying private context text.
63
+ concern_id: str = ""
64
+ source_event_refs: List[str] = field(default_factory=list)
65
+ thought_job_id: str = ""
66
+ thought_result_ref: str = ""
67
+ goal_proposal_id: str = ""
68
+ evidence_refs: List[str] = field(default_factory=list)
69
+ policy_decision_refs: List[str] = field(default_factory=list)
70
+ subject_person_id: str = ""
71
+ viewer_scope: str = "owner"
72
+ shareability: str = "owner_private"
73
+ capability_allowlist: List[str] = field(default_factory=list)
74
+ goal_fingerprint: str = ""
75
+ id: str = field(default_factory=lambda: f"proj-{uuid.uuid4().hex[:12]}")
76
+ created_at: float = field(default_factory=time.time)
77
+ updated_at: float = field(default_factory=time.time)
78
+
79
+ def subject_text(self) -> str:
80
+ """Boundary-checkable subject for this project."""
81
+ return f"{self.title} {self.objective}"
82
+
83
+ def provenance_refs(self) -> List[str]:
84
+ """Return bounded reference-only lineage for a WorkOrder."""
85
+
86
+ refs: List[str] = []
87
+ if self.concern_id:
88
+ refs.append(f"concern:{self.concern_id}")
89
+ refs.extend(self.source_event_refs)
90
+ if self.thought_job_id:
91
+ refs.append(f"thought-job:{self.thought_job_id}")
92
+ if self.thought_result_ref:
93
+ refs.append(self.thought_result_ref)
94
+ if self.goal_proposal_id:
95
+ refs.append(self.goal_proposal_id)
96
+ refs.extend(self.evidence_refs)
97
+ refs.extend(self.policy_decision_refs)
98
+ return list(dict.fromkeys(ref for ref in refs if ref))[:60]
99
+
100
+ def to_row(self) -> Dict[str, Any]:
101
+ return {
102
+ "id": self.id, "title": self.title, "objective": self.objective,
103
+ "source": self.source, "status": self.status,
104
+ "outcome": self.outcome,
105
+ "entity_ids": json.dumps(self.entity_ids), "reason": self.reason,
106
+ "replans": self.replans, "next_review_at": self.next_review_at,
107
+ "concern_id": self.concern_id,
108
+ "source_event_refs": json.dumps(self.source_event_refs),
109
+ "thought_job_id": self.thought_job_id,
110
+ "thought_result_ref": self.thought_result_ref,
111
+ "goal_proposal_id": self.goal_proposal_id,
112
+ "evidence_refs": json.dumps(self.evidence_refs),
113
+ "policy_decision_refs": json.dumps(self.policy_decision_refs),
114
+ "subject_person_id": self.subject_person_id,
115
+ "viewer_scope": self.viewer_scope,
116
+ "shareability": self.shareability,
117
+ "capability_allowlist": json.dumps(self.capability_allowlist),
118
+ "goal_fingerprint": self.goal_fingerprint,
119
+ "created_at": self.created_at, "updated_at": self.updated_at,
120
+ }
121
+
122
+ @classmethod
123
+ def from_row(cls, r: Dict[str, Any]) -> "Project":
124
+ def _list(name: str) -> List[str]:
125
+ try:
126
+ value = json.loads(r.get(name) or "[]")
127
+ return [str(item) for item in value] if isinstance(value, list) else []
128
+ except Exception:
129
+ return []
130
+
131
+ return cls(
132
+ id=r["id"], title=r.get("title", "") or "",
133
+ objective=r.get("objective", "") or "",
134
+ source=r.get("source", "owner") or "owner",
135
+ status=r.get("status", "planning") or "planning",
136
+ outcome=r.get("outcome", "pending") or "pending",
137
+ entity_ids=_list("entity_ids"),
138
+ reason=r.get("reason", "") or "",
139
+ replans=int(r.get("replans") or 0),
140
+ next_review_at=float(r.get("next_review_at") or 0.0),
141
+ concern_id=r.get("concern_id", "") or "",
142
+ source_event_refs=_list("source_event_refs"),
143
+ thought_job_id=r.get("thought_job_id", "") or "",
144
+ thought_result_ref=r.get("thought_result_ref", "") or "",
145
+ goal_proposal_id=r.get("goal_proposal_id", "") or "",
146
+ evidence_refs=_list("evidence_refs"),
147
+ policy_decision_refs=_list("policy_decision_refs"),
148
+ subject_person_id=r.get("subject_person_id", "") or "",
149
+ viewer_scope=r.get("viewer_scope", "owner") or "owner",
150
+ shareability=r.get("shareability", "owner_private") or "owner_private",
151
+ capability_allowlist=_list("capability_allowlist"),
152
+ goal_fingerprint=r.get("goal_fingerprint", "") or "",
153
+ created_at=float(r.get("created_at") or time.time()),
154
+ updated_at=float(r.get("updated_at") or time.time()),
155
+ )
156
+
157
+
158
+ @dataclass
159
+ class Step:
160
+ project_id: str
161
+ ordinal: int
162
+ description: str
163
+ action_kind: str = "analyze"
164
+ depends_on: List[int] = field(default_factory=list) # ordinals within project
165
+ status: str = "pending"
166
+ attempts: int = 0
167
+ result: str = ""
168
+ work_order_ref: str = "" # immutable project-ledger WorkOrder row
169
+ work_order_digest: str = "" # exact authority envelope digest
170
+ work_order_issued_at: float = 0.0 # stable first-dispatch authority time
171
+ result_ref: str = "" # logical ExecutionResultV1 ledger row
172
+ boundary_subject: str = "" # extra subject text for the guard
173
+ confidence: float = 0.6 # planner-stated (charter contract)
174
+ id: str = field(default_factory=lambda: f"step-{uuid.uuid4().hex[:12]}")
175
+ created_at: float = field(default_factory=time.time)
176
+ updated_at: float = field(default_factory=time.time)
177
+
178
+ def to_row(self) -> Dict[str, Any]:
179
+ return {
180
+ "id": self.id, "project_id": self.project_id, "ordinal": self.ordinal,
181
+ "description": self.description, "action_kind": self.action_kind,
182
+ "depends_on": json.dumps(self.depends_on), "status": self.status,
183
+ "attempts": self.attempts, "result": self.result,
184
+ "work_order_ref": self.work_order_ref,
185
+ "work_order_digest": self.work_order_digest,
186
+ "work_order_issued_at": self.work_order_issued_at,
187
+ "result_ref": self.result_ref,
188
+ "boundary_subject": self.boundary_subject,
189
+ "confidence": self.confidence,
190
+ "created_at": self.created_at, "updated_at": self.updated_at,
191
+ }
192
+
193
+ @classmethod
194
+ def from_row(cls, r: Dict[str, Any]) -> "Step":
195
+ try:
196
+ deps = json.loads(r.get("depends_on") or "[]")
197
+ deps = [int(d) for d in deps] if isinstance(deps, list) else []
198
+ except Exception:
199
+ deps = []
200
+ return cls(
201
+ id=r["id"], project_id=r.get("project_id", "") or "",
202
+ ordinal=int(r.get("ordinal") or 0),
203
+ description=r.get("description", "") or "",
204
+ action_kind=r.get("action_kind", "analyze") or "analyze",
205
+ depends_on=deps, status=r.get("status", "pending") or "pending",
206
+ attempts=int(r.get("attempts") or 0),
207
+ result=r.get("result", "") or "",
208
+ work_order_ref=r.get("work_order_ref", "") or "",
209
+ work_order_digest=r.get("work_order_digest", "") or "",
210
+ work_order_issued_at=float(r.get("work_order_issued_at") or 0.0),
211
+ result_ref=r.get("result_ref", "") or "",
212
+ boundary_subject=r.get("boundary_subject", "") or "",
213
+ confidence=float(r.get("confidence", 0.6) or 0.6),
214
+ created_at=float(r.get("created_at") or time.time()),
215
+ updated_at=float(r.get("updated_at") or time.time()),
216
+ )
@@ -0,0 +1,181 @@
1
+ """Project planner: objective -> validated, dependency-ordered Steps.
2
+
3
+ One LLM planning pass returns a STRICT JSON step list; deterministic code
4
+ re-validates everything (same discipline as directed intake): unknown
5
+ action_kinds are dropped, dependency cycles are broken, unknown/self deps are
6
+ stripped, and the step count is capped. The LLM proposes; code decides.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ import re
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from apsimo.projects.models import ACTION_KINDS, Step, projects_max_steps
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # The system prompt composes through the shared cognition charter
21
+ # (cognition/charter.py, role "planner"): shared doctrine + planner rules +
22
+ # injected self-model/boundaries/skills. This block only supplies the
23
+ # action-kind vocabulary the role rules reference.
24
+ _ACTION_KINDS_CONTEXT = """\
25
+ Allowed action kinds (choose the weakest kind that does the job):
26
+ - "analyze": reason over information the system already has (memory, world
27
+ model, configured read-only repo mirrors).
28
+ - "research": gather new information (web search, background research).
29
+ - "internal": internal bookkeeping (record findings, update memory).
30
+ - "directed": delegate scoped repo/code work to the directed-action pipeline
31
+ (it has its own approval gates). Use ONLY for work on the owner's
32
+ designated repositories.
33
+ - "deliver": report a finding/milestone to the owner."""
34
+
35
+
36
+ def _parse_array(content: str) -> List[dict]:
37
+ text = (content or "").strip()
38
+ if "```" in text:
39
+ m = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
40
+ if m:
41
+ text = m.group(1).strip()
42
+ if not text.startswith("["):
43
+ m = re.search(r"\[.*\]", text, re.DOTALL)
44
+ if not m:
45
+ return []
46
+ text = m.group(0)
47
+ try:
48
+ data = json.loads(text)
49
+ except json.JSONDecodeError:
50
+ logger.warning("project planner output was not valid JSON; dropping")
51
+ return []
52
+ return [d for d in data if isinstance(d, dict)] if isinstance(data, list) else []
53
+
54
+
55
+ def validate_steps(raw: List[dict], project_id: str = "",
56
+ max_steps: Optional[int] = None) -> List[Step]:
57
+ """Deterministic re-validation of an LLM-proposed step list.
58
+
59
+ Drops steps with unknown action_kind, strips unknown/self dependencies,
60
+ breaks dependency cycles (a dep that is not strictly ordinal-decreasing
61
+ after topological sorting is dropped), caps the count, and renumbers
62
+ ordinals contiguously.
63
+ """
64
+ cap = max_steps or projects_max_steps()
65
+
66
+ # 1. Shape + kind filter.
67
+ cleaned: List[Dict[str, Any]] = []
68
+ for d in raw or []:
69
+ desc = str(d.get("description", "")).strip()
70
+ kind = str(d.get("action_kind", "")).strip().lower()
71
+ if not desc or kind not in ACTION_KINDS:
72
+ continue
73
+ try:
74
+ ordinal = int(d.get("ordinal", len(cleaned) + 1))
75
+ except (TypeError, ValueError):
76
+ ordinal = len(cleaned) + 1
77
+ deps = []
78
+ for dep in (d.get("depends_on") or []):
79
+ try:
80
+ deps.append(int(dep))
81
+ except (TypeError, ValueError):
82
+ continue
83
+ try:
84
+ confidence = max(0.0, min(1.0, float(d.get("confidence", 0.6))))
85
+ except (TypeError, ValueError):
86
+ confidence = 0.6
87
+ cleaned.append({"ordinal": ordinal, "description": desc[:500],
88
+ "action_kind": kind, "depends_on": deps,
89
+ "confidence": confidence})
90
+
91
+ if not cleaned:
92
+ return []
93
+
94
+ # 2. Deduplicate ordinals (keep first) and drop unknown/self deps.
95
+ by_ord: Dict[int, Dict[str, Any]] = {}
96
+ for d in cleaned:
97
+ if d["ordinal"] not in by_ord:
98
+ by_ord[d["ordinal"]] = d
99
+ known = set(by_ord)
100
+ for d in by_ord.values():
101
+ d["depends_on"] = [x for x in d["depends_on"]
102
+ if x in known and x != d["ordinal"]]
103
+
104
+ # 3. Topological sort (Kahn); cycle-breaking: when no node is free, cut
105
+ # the remaining node with the fewest deps loose (drop its deps).
106
+ remaining = dict(by_ord)
107
+ ordered: List[Dict[str, Any]] = []
108
+ placed: set = set()
109
+ while remaining:
110
+ free = [o for o, d in remaining.items()
111
+ if all(dep in placed for dep in d["depends_on"])]
112
+ if not free:
113
+ victim = min(remaining.values(),
114
+ key=lambda d: (len(d["depends_on"]), d["ordinal"]))
115
+ logger.info("project planner: breaking dependency cycle at step %s",
116
+ victim["ordinal"])
117
+ victim["depends_on"] = [dep for dep in victim["depends_on"]
118
+ if dep in placed]
119
+ free = [victim["ordinal"]]
120
+ for o in sorted(free):
121
+ ordered.append(remaining.pop(o))
122
+ placed.add(o)
123
+
124
+ # 4. Cap, then drop deps pointing at capped-away steps.
125
+ ordered = ordered[:cap]
126
+ kept = {d["ordinal"] for d in ordered}
127
+ for d in ordered:
128
+ d["depends_on"] = [x for x in d["depends_on"] if x in kept]
129
+
130
+ # 5. Renumber contiguously (deps remapped).
131
+ remap = {d["ordinal"]: i + 1 for i, d in enumerate(ordered)}
132
+ steps: List[Step] = []
133
+ for d in ordered:
134
+ steps.append(Step(
135
+ project_id=project_id,
136
+ ordinal=remap[d["ordinal"]],
137
+ description=d["description"],
138
+ action_kind=d["action_kind"],
139
+ depends_on=sorted(remap[x] for x in d["depends_on"]),
140
+ confidence=d.get("confidence", 0.6),
141
+ ))
142
+ return steps
143
+
144
+
145
+ async def plan_project(
146
+ router: Any,
147
+ objective: str,
148
+ *,
149
+ project_id: str = "",
150
+ context: str = "",
151
+ skills_block: str = "",
152
+ self_brief: str = "",
153
+ boundaries: str = "",
154
+ max_steps: Optional[int] = None,
155
+ ) -> List[Step]:
156
+ """One LLM planning pass -> validated Steps. Empty list on any failure."""
157
+ if router is None or not (objective or "").strip():
158
+ return []
159
+ cap = max_steps or projects_max_steps()
160
+ user_parts = [f"OBJECTIVE:\n{objective.strip()[:1500]}"]
161
+ if context:
162
+ user_parts.append(f"CONTEXT:\n{str(context)[:2000]}")
163
+ try:
164
+ from apsimo.cognition.charter import build_system_prompt
165
+ system = build_system_prompt(
166
+ "planner",
167
+ self_brief=(self_brief or None),
168
+ boundaries=(boundaries or None),
169
+ skills=(skills_block or None),
170
+ extra=_ACTION_KINDS_CONTEXT,
171
+ max_steps=cap)
172
+ response = await router.complete(
173
+ [{"role": "system", "content": system},
174
+ {"role": "user", "content": "\n\n".join(user_parts)}],
175
+ context={"task": "project_planning"})
176
+ content = getattr(response, "content", "") or ""
177
+ except Exception as exc:
178
+ logger.warning("project planning LLM call failed: %s", exc)
179
+ return []
180
+ return validate_steps(_parse_array(content), project_id=project_id,
181
+ max_steps=cap)