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,1949 @@
1
+ """Host API schemas — Pydantic models for the /v1/host surface.
2
+
3
+ Mirrors the TypeScript types in colony's src/types.ts.
4
+ The sidecar is the source of truth for these schemas.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os as _os
10
+ from typing import Any, Dict, List, Literal, Optional, Union
11
+
12
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
13
+
14
+ from apsimo.gate.communication_policy import CommunicationPolicyContextV1
15
+ from apsimo.gate.surface_policy import ResponseGuardSurfaceName
16
+
17
+ MAX_NAME_LEN = 256
18
+
19
+
20
+ # --- Identity ---------------------------------------------------------------
21
+
22
+ class HostIdentity(BaseModel):
23
+ host_id: str
24
+ host_version: Optional[str] = None
25
+ plugin_version: Optional[str] = None
26
+ instance_id: Optional[str] = None
27
+ colony_id: Optional[str] = None
28
+ node_id: Optional[str] = None
29
+ node_cert_fingerprint: Optional[str] = None
30
+ trust_tier: Optional[Literal["REGULAR", "TRUSTED", "PRIVILEGED", "GENESIS"]] = None
31
+
32
+
33
+ class HostTurnContext(BaseModel):
34
+ session_id: str
35
+ contact_id: str
36
+ channel_id: Optional[str] = None
37
+ turn_id: Optional[str] = None
38
+ locale: Optional[str] = None
39
+ timezone: Optional[str] = None # per-communication tz override (v0.21.0)
40
+ metadata: Optional[Dict[str, Any]] = None
41
+
42
+
43
+ # --- Messages ---------------------------------------------------------------
44
+
45
+ class HostMessage(BaseModel):
46
+ role: Literal["user", "assistant", "system", "tool"]
47
+ content: str
48
+ name: Optional[str] = None
49
+ tool_call_id: Optional[str] = None
50
+ metadata: Optional[Dict[str, Any]] = None
51
+
52
+
53
+ # --- Health -----------------------------------------------------------------
54
+
55
+ class TemporalMetrics(BaseModel):
56
+ started_at: Optional[str] = None
57
+ last_sync_at: Optional[str] = None
58
+ last_tick_at: Optional[str] = None
59
+ last_initiative_at: Optional[str] = None
60
+ last_prefetch_at: Optional[str] = None
61
+ silence_hours: Dict[str, Optional[float]] = Field(default_factory=dict)
62
+ stale_flags: List[str] = Field(default_factory=list)
63
+ #: "ok" normally; "unknown" when persisted telemetry could not be restored
64
+ state: Optional[str] = None
65
+
66
+
67
+ class HostHealthResponse(BaseModel):
68
+ status: Literal["ok", "degraded", "starting", "stopping"]
69
+ api_version: str = "1.0.0"
70
+ capabilities: List[str] = []
71
+ notes: Optional[Dict[str, str]] = None
72
+ temporal: Optional[TemporalMetrics] = None
73
+
74
+
75
+ # --- Memory -----------------------------------------------------------------
76
+
77
+ class MemoryEntry(BaseModel):
78
+ id: str
79
+ content: str
80
+ type: Optional[str] = None
81
+ strength: Optional[float] = None
82
+ effective_confidence: Optional[float] = None
83
+ epistemic_state: Optional[str] = None
84
+ source_type: Optional[str] = None
85
+ source_uri: Optional[str] = None
86
+ source_version: Optional[str] = None
87
+ content_hash: Optional[str] = None
88
+ protected: Optional[bool] = None
89
+ person_id: Optional[str] = None
90
+ entities: Optional[List[str]] = None
91
+ tags: Optional[List[str]] = None
92
+ created_at: Optional[str] = None
93
+ score: Optional[float] = None
94
+
95
+
96
+ class MemoryReadRequest(BaseModel):
97
+ identity: HostIdentity
98
+ memory_id: Optional[str] = None
99
+ person_id: Optional[str] = None
100
+ audience: Optional[Literal["viewer", "owner", "shared", "global"]] = None
101
+ limit: Optional[int] = None
102
+ source_id: Optional[str] = Field(default=None, min_length=1, max_length=256)
103
+ source_version: Optional[str] = Field(default=None, pattern='^[0-9a-f]{64}$')
104
+ session_id: Optional[str] = Field(default=None, min_length=1, max_length=256)
105
+ source_view: Literal['source', 'assertions', 'image', 'document', 'video'] = 'source'
106
+ asset_hash: Optional[str] = Field(default=None, pattern='^[0-9a-f]{64}$')
107
+ page: Optional[int] = Field(default=None, ge=1, strict=True)
108
+ requested_ms: Optional[int] = Field(default=None, ge=0, le=30000, strict=True)
109
+ claim_id: Optional[str] = Field(default=None, min_length=1, max_length=256)
110
+ offset: int = Field(default=0, ge=0, le=10000000)
111
+ read_revision: Optional[str] = Field(default=None, pattern='^[0-9a-f]{64}$')
112
+
113
+ @model_validator(mode='after')
114
+ def canonical_read_selector(self):
115
+ if self.source_id:
116
+ if not self.source_version or not self.session_id or self.memory_id:
117
+ raise ValueError('canonical reads require source version/session and no graph memory ID')
118
+ if (self.source_view == 'assertions') != bool(self.claim_id):
119
+ raise ValueError('assertion history requires its source claim ID')
120
+ if (self.source_view in {'image', 'document', 'video'}) != bool(self.asset_hash):
121
+ raise ValueError('image, document and video reads require an exact asset hash')
122
+ if self.source_view in {'image', 'video'} and self.offset:
123
+ raise ValueError('image and video reads take no page offset')
124
+ if (self.source_view == 'video') != (self.requested_ms is not None):
125
+ raise ValueError('video reads require a clip-relative requested_ms')
126
+ if (self.source_view == 'document') != (self.page is not None):
127
+ raise ValueError('document reads require an original page number')
128
+ if self.offset and not self.read_revision:
129
+ raise ValueError('continuation requires the preceding read revision')
130
+ elif self.source_version or self.claim_id or self.asset_hash or self.page is not None or self.requested_ms is not None or self.offset or self.read_revision or self.source_view != 'source':
131
+ raise ValueError('canonical read fields require a source ID')
132
+ return self
133
+
134
+
135
+ class MemoryReadResponse(BaseModel):
136
+ entries: List[MemoryEntry] = []
137
+ source: Optional[Dict[str, Any]] = None
138
+
139
+
140
+ class MemoryWriteRequest(BaseModel):
141
+ identity: HostIdentity
142
+ context: Optional[HostTurnContext] = None
143
+ content: str
144
+ type: Optional[str] = None
145
+ person_id: Optional[str] = None
146
+ audience: Optional[Literal["viewer", "owner", "shared", "global"]] = None
147
+ entities: Optional[List[str]] = None
148
+ tags: Optional[List[str]] = None
149
+ strength: Optional[float] = None
150
+ source_type: Optional[str] = "inference"
151
+ source_uri: Optional[str] = None
152
+ source_version: Optional[str] = None
153
+ content_hash: Optional[str] = None
154
+
155
+
156
+ class MemoryWriteResponse(BaseModel):
157
+ id: str
158
+ accepted: bool
159
+
160
+
161
+ class MemorySearchRequest(BaseModel):
162
+ identity: HostIdentity
163
+ query: str
164
+ limit: Optional[int] = None
165
+ min_score: Optional[float] = None
166
+ min_confidence: Optional[float] = 0.1
167
+ person_id: Optional[str] = None
168
+ audience: Optional[Literal["viewer", "owner", "shared", "global"]] = None
169
+ types: Optional[List[str]] = None
170
+ tags: Optional[List[str]] = None
171
+
172
+
173
+ class RerankRequest(BaseModel):
174
+ identity: HostIdentity
175
+ query: str
176
+ documents: List[str]
177
+ top_k: Optional[int] = 10
178
+
179
+
180
+ class RerankResult(BaseModel):
181
+ index: int
182
+ score: float
183
+ text: str
184
+
185
+
186
+ class RerankResponse(BaseModel):
187
+ results: List[RerankResult] = []
188
+ model: str = ""
189
+
190
+
191
+ class MemorySearchResponse(BaseModel):
192
+ entries: List[MemoryEntry] = []
193
+
194
+
195
+ class MemoryReconcileRequest(BaseModel):
196
+ identity: HostIdentity
197
+ dry_run: Optional[bool] = False
198
+
199
+
200
+ class MemoryReconcileResponse(BaseModel):
201
+ files_checked: int = 0
202
+ memories_verified: int = 0
203
+ memories_staled: int = 0
204
+ memories_superseded: int = 0
205
+ errors: List[str] = []
206
+
207
+
208
+ class MemoryConflictEntry(BaseModel):
209
+ memory_id_a: str
210
+ memory_id_b: str
211
+ entity_name: str
212
+ reason: str
213
+ detected_at: Optional[str] = None
214
+
215
+
216
+ class MemoryConflictsResponse(BaseModel):
217
+ conflicts: List[MemoryConflictEntry] = []
218
+ total: int = 0
219
+
220
+
221
+ class MemoryVerifyRequest(BaseModel):
222
+ identity: HostIdentity
223
+ memory_id: str
224
+
225
+
226
+ class MemoryVerifyResponse(BaseModel):
227
+ memory_id: str
228
+ verified: bool
229
+ effective_confidence: float = 0.0
230
+
231
+
232
+ class MemoryStatsResponse(BaseModel):
233
+ by_state: Dict[str, int] = Field(default_factory=dict)
234
+ by_source: Dict[str, int] = Field(default_factory=dict)
235
+ total_active: int = 0
236
+ total_archived: int = 0
237
+ protected_count: int = 0
238
+
239
+
240
+ # --- Context ----------------------------------------------------------------
241
+
242
+ class ContextAssembleRequest(BaseModel):
243
+ identity: HostIdentity
244
+ context: HostTurnContext
245
+ audience: Optional[Literal["viewer", "owner", "shared", "global"]] = None
246
+ incoming_message: HostMessage
247
+ available_tools: Optional[List[str]] = None
248
+ citations_mode: Optional[Literal["off", "inline", "appendix"]] = None
249
+ include_initiatives: Optional[bool] = None # v0.13.0
250
+ projection_policy: Optional[
251
+ Literal["scoped_viewer_required"]
252
+ ] = None
253
+
254
+
255
+ class ContextSection(BaseModel):
256
+ id: str
257
+ title: Optional[str] = None
258
+ body: str
259
+ priority: Optional[int] = None
260
+ citations: Optional[List[Dict[str, Any]]] = None
261
+
262
+
263
+ class ContextProjectionAttestation(BaseModel):
264
+ """Server-sealed viewer/projection posture for one context response."""
265
+
266
+ model_config = ConfigDict(populate_by_name=True)
267
+ schema_name: Literal["ContextProjectionAttestationV1"] = Field(
268
+ default="ContextProjectionAttestationV1",
269
+ alias="schema",
270
+ )
271
+ version: Literal[1] = 1
272
+ viewer_person_id: str = ""
273
+ viewer_attested: bool = False
274
+ viewer_is_owner: bool = False
275
+ p8_mode: Literal["off", "shadow", "live"] = "off"
276
+ projection_backend: Literal["unavailable", "p8", "canonical_sources"] = "unavailable"
277
+ scoped_projection_ready: bool = False
278
+ legacy_global_allowed: bool = False
279
+
280
+
281
+ class ContextAssembleResponse(BaseModel):
282
+ sections: List[ContextSection] = []
283
+ notices: Optional[List[str]] = None
284
+ projection_attestation: Optional[ContextProjectionAttestation] = None
285
+ source_erasure_watermark: Optional[int] = None
286
+
287
+
288
+ class MemoryFlushRequest(BaseModel):
289
+ identity: HostIdentity
290
+ reason: Optional[str] = None
291
+
292
+
293
+ class MemoryFlushResponse(BaseModel):
294
+ accepted: bool
295
+ job_id: Optional[str] = None
296
+
297
+
298
+ class MemoryEmbedRequest(BaseModel):
299
+ identity: HostIdentity
300
+ inputs: List[str] # Kept for backward compat — use texts instead
301
+ texts: Optional[List[str]] = None # Preferred: list of texts to embed
302
+ model: Optional[str] = None
303
+
304
+
305
+ class MemoryEmbedResponse(BaseModel):
306
+ model: str
307
+ vectors: List[List[float]]
308
+
309
+
310
+ class EmbedHealthResponse(BaseModel):
311
+ provider: str = ""
312
+ model: str = ""
313
+ dims: int = 0
314
+ latency_ms: float = 0.0
315
+ status: str = "unknown"
316
+ error: Optional[str] = None
317
+ modalities: List[str] = ["text"]
318
+ multimodal_enabled: bool = False
319
+
320
+
321
+ class BackfillRequest(BaseModel):
322
+ identity: HostIdentity
323
+ collection: Optional[str] = None
324
+ batch_size: int = 64
325
+
326
+
327
+ class BackfillResponse(BaseModel):
328
+ task_id: str = ""
329
+ status: str = "started" # started | completed | failed
330
+ total: int = 0
331
+ processed: int = 0
332
+ failed: int = 0
333
+ skipped: int = 0
334
+ duration_s: float = 0.0
335
+ errors: List[str] = []
336
+
337
+
338
+ class MigrateRequest(BaseModel):
339
+ identity: HostIdentity
340
+ old_model_id: Optional[str] = None
341
+ batch_size: int = 64
342
+
343
+
344
+ class MigrateResponse(BaseModel):
345
+ task_id: str = ""
346
+ status: str = "started" # started | completed | failed
347
+ collections_migrated: int = 0
348
+ vectors_migrated: int = 0
349
+ vectors_failed: int = 0
350
+ duration_s: float = 0.0
351
+ errors: List[str] = []
352
+ generation_id: str = ""
353
+ fingerprint: str = ""
354
+
355
+
356
+ class IndexRequest(BaseModel):
357
+ identity: HostIdentity
358
+ items: List[dict] # [{text, collection, id, metadata?}] or [{image, mime_type, caption, collection, id}]
359
+
360
+
361
+ class IndexResponse(BaseModel):
362
+ indexed: int = 0
363
+ failed: int = 0
364
+ model: str = ""
365
+
366
+
367
+ class ImageEmbedRequest(BaseModel):
368
+ identity: HostIdentity
369
+ image: Optional[str] = None # Base64-encoded image
370
+ image_url: Optional[str] = None # URL to image
371
+ image_path: Optional[str] = None # Local file path
372
+ mime_type: Optional[str] = None
373
+ caption: Optional[str] = None
374
+ collection: Optional[str] = None
375
+ id: Optional[str] = None
376
+
377
+
378
+ class ImageEmbedResponse(BaseModel):
379
+ model: str
380
+ vector: List[float]
381
+ image_hash: str = ""
382
+ image_ref: str = ""
383
+ thumbnail_ref: str = ""
384
+ caption: str = ""
385
+ width: int = 0
386
+ height: int = 0
387
+ modality: str = "image"
388
+
389
+
390
+ class ImageBatchEmbedRequest(BaseModel):
391
+ identity: HostIdentity
392
+ images: List[dict] # [{image, image_url, image_path, mime_type, caption}]
393
+ collection: Optional[str] = None
394
+
395
+
396
+ class ImageBatchEmbedResponse(BaseModel):
397
+ model: str
398
+ results: List[dict] # [{vector, image_hash, caption, ...}]
399
+
400
+
401
+ class MultimodalSearchRequest(BaseModel):
402
+ identity: HostIdentity
403
+ query: Optional[str] = None # Text query
404
+ query_image: Optional[str] = None # Base64 image for image-based search
405
+ collection: Optional[str] = None
406
+ filter_modality: Optional[str] = None # "text" or "image"
407
+ limit: int = 10
408
+ min_score: float = 0.0
409
+
410
+
411
+ class MultimodalSearchResponse(BaseModel):
412
+ results: List[dict]
413
+ model: str = ""
414
+
415
+
416
+ # --- Reasoning --------------------------------------------------------------
417
+
418
+ class ReasoningTurnRequest(BaseModel):
419
+ identity: HostIdentity
420
+ context: HostTurnContext
421
+ messages: List[HostMessage]
422
+ available_tools: List[str] = Field(default_factory=list)
423
+ model_override: Optional[str] = Field(None, max_length=MAX_NAME_LEN)
424
+
425
+
426
+ class ReasoningToolCall(BaseModel):
427
+ id: str
428
+ name: str
429
+ arguments: Dict[str, Any] = Field(default_factory=dict)
430
+
431
+
432
+ class ReasoningTurnResponse(BaseModel):
433
+ status: Literal["completed", "needs_tool", "error"]
434
+ message: Optional[HostMessage] = None
435
+ tool_calls: List[ReasoningToolCall] = Field(default_factory=list)
436
+ usage: Dict[str, Any] = Field(default_factory=dict)
437
+ error: Optional[str] = None
438
+
439
+
440
+ class ToolInvokeRequest(BaseModel):
441
+ identity: HostIdentity
442
+ name: str = Field(..., max_length=MAX_NAME_LEN)
443
+ arguments: Dict[str, Any] = Field(default_factory=dict)
444
+
445
+
446
+ class ToolInvokeResponse(BaseModel):
447
+ result: str = ""
448
+ available: bool = True
449
+ error: Optional[str] = None
450
+
451
+
452
+ class SkillExecuteRequest(BaseModel):
453
+ identity: HostIdentity
454
+ arguments: Dict[str, Any] = Field(default_factory=dict)
455
+ context: Optional[HostTurnContext] = None
456
+
457
+
458
+ class SkillExecuteResponse(BaseModel):
459
+ status: Literal["success", "failed", "timeout", "violated"]
460
+ output: Optional[Any] = None
461
+ error: Optional[str] = None
462
+ execution_id: Optional[str] = None
463
+ duration_ms: Optional[int] = None
464
+
465
+
466
+ # --- Sender identity ---------------------------------------------------------
467
+
468
+ class HostSender(BaseModel):
469
+ """WHO produced the user side of this turn (docs/RELATIONSHIPS.md).
470
+
471
+ Per-message sender identity so the sidecar attributes group traffic to
472
+ the real speaker server-side, independent of any client-side contact
473
+ caching. Optional and additive: hosts that cannot supply it keep the
474
+ legacy context.contact_id behavior."""
475
+ platform: str = Field(..., max_length=64)
476
+ user_id: str = Field(..., max_length=256)
477
+ display_name: str = Field(default="", max_length=256)
478
+ group_id: str = Field(default="", max_length=256)
479
+
480
+
481
+ # --- Signals ----------------------------------------------------------------
482
+
483
+ class SignalIngestRequest(BaseModel):
484
+ identity: HostIdentity
485
+ context: HostTurnContext
486
+ sender: Optional[HostSender] = None
487
+ incoming_message: Optional[HostMessage] = None
488
+ outgoing_message: Optional[HostMessage] = None
489
+ tool_calls: List[ReasoningToolCall] = Field(default_factory=list)
490
+ correction: Optional[str] = None
491
+ signals: List[Dict[str, Any]] = Field(default_factory=list)
492
+
493
+
494
+ class SignalIngestResponse(BaseModel):
495
+ accepted: bool
496
+ signals_recorded: int
497
+
498
+
499
+ # --- Turns ------------------------------------------------------------------
500
+
501
+ class CheckpointMessage(BaseModel):
502
+ """Direct source content; injected API context is not part of this schema."""
503
+ model_config = ConfigDict(extra="forbid")
504
+ role: Literal["user", "assistant"]
505
+ content: Union[str, List[Dict[str, Any]]]
506
+
507
+ @model_validator(mode="after")
508
+ def nonempty_content(self):
509
+ if not self.content or isinstance(self.content, str) and not self.content.strip():
510
+ raise ValueError("checkpoint messages must contain source content")
511
+ return self
512
+
513
+
514
+ class TurnMessage(HostMessage):
515
+ """Direct source blocks are retained; ordinary cognition receives text only."""
516
+ content: Union[str, List[Dict[str, Any]]]
517
+
518
+ def text_content(self) -> str:
519
+ if isinstance(self.content, str):
520
+ return self.content
521
+ return "\n".join(block["text"] for block in self.content
522
+ if block.get("type") in {"text", "input_text", "output_text"}
523
+ and isinstance(block.get("text"), str))
524
+
525
+
526
+ class SourceReference(BaseModel):
527
+ """A supplied canonical revision, not a generated citation or truth claim."""
528
+ model_config = ConfigDict(extra="forbid")
529
+ source_id: str = Field(min_length=1, max_length=256)
530
+ source_version: str = Field(pattern=r"^[0-9a-f]{64}$")
531
+
532
+
533
+ class SourceInputReference(BaseModel):
534
+ """Exact admitted input before canonical media normalization."""
535
+ model_config = ConfigDict(extra="forbid")
536
+ source_id: str = Field(min_length=1, max_length=256)
537
+ input_message_hash: str = Field(pattern=r"^[0-9a-f]{64}$")
538
+
539
+
540
+ class TurnSyncRequest(BaseModel):
541
+ identity: HostIdentity
542
+ context: HostTurnContext
543
+ sender: Optional[HostSender] = None
544
+ topics: List[str] = Field(default_factory=list)
545
+ entities: List[str] = Field(default_factory=list)
546
+ pending_tasks: List[str] = Field(default_factory=list)
547
+ tools_used: List[str] = Field(default_factory=list)
548
+ summary: Optional[str] = None
549
+ # Raw message fields — populated by Hermes provider and MCP tools.
550
+ # When structured fields are empty but raw messages are present,
551
+ # the sidecar runs extraction from the raw messages.
552
+ user_message: Optional[Union[HostMessage, TurnMessage]] = None
553
+ assistant_message: Optional[Union[HostMessage, TurnMessage]] = None
554
+ assistant_source_refs: Optional[List[SourceReference]] = Field(default=None, min_length=1)
555
+ assistant_input_refs: Optional[List[SourceInputReference]] = Field(default=None, min_length=1)
556
+ source_only: Optional[Literal[True]] = None
557
+ # Model that produced the assistant side of this turn (optional, additive).
558
+ # Lets the mining layer detect provider escalations / cloud failovers from
559
+ # real per-turn metadata instead of guessing from text.
560
+ model: Optional[str] = None
561
+ # Evidence-only checkpoints never trigger ordinary turn/relationship effects.
562
+ checkpoint_messages: Optional[List[CheckpointMessage]] = Field(
563
+ default=None, min_length=1, max_length=20_000,
564
+ )
565
+
566
+ @model_validator(mode="after")
567
+ def bounded_checkpoint(self):
568
+ if len(self.model_dump_json().encode("utf-8")) > 8 * 1024 * 1024:
569
+ raise ValueError("turn exceeds the 8 MiB source limit")
570
+ if self.checkpoint_messages is not None:
571
+ if self.sender is not None or self.user_message is not None or self.assistant_message is not None:
572
+ raise ValueError("checkpoint cannot also represent an ordinary turn")
573
+ if not self.context.turn_id:
574
+ raise ValueError("checkpoint requires an idempotent turn id")
575
+ def nonempty(message):
576
+ return message is not None and bool(message.content.strip() if isinstance(message.content, str) else message.content)
577
+ if self.source_only and (self.checkpoint_messages is not None or not any(
578
+ nonempty(message) for message in (self.user_message, self.assistant_message))):
579
+ raise ValueError('source-only delivery requires nonempty direct source messages')
580
+ if (self.assistant_source_refs or self.assistant_input_refs) and not any(message is not None and message.role == 'assistant' and nonempty(message)
581
+ for message in [self.assistant_message, *(self.checkpoint_messages or [])]):
582
+ raise ValueError('source references require an assistant message')
583
+ if self.assistant_input_refs and (not self.context.turn_id or self.checkpoint_messages is not None):
584
+ raise ValueError('input parents require an identified direct assistant source')
585
+ return self
586
+
587
+
588
+ class TurnSyncResponse(BaseModel):
589
+ accepted: bool
590
+ continuity_updated: bool
591
+ skipped_reason: Optional[str] = None
592
+ errors: Optional[List[str]] = None
593
+ source_recorded: bool = False
594
+
595
+
596
+ # --- Safety -----------------------------------------------------------------
597
+
598
+ class SafetyCheckRequest(BaseModel):
599
+ identity: HostIdentity
600
+ context: HostTurnContext
601
+ response_text: str
602
+ incoming_message_text: Optional[str] = None
603
+ target_gateway: Optional[str] = None
604
+ trust_tier: Optional[str] = None
605
+ mentioned_entities: Optional[List[str]] = None
606
+
607
+
608
+ class SafetyCheckResponse(BaseModel):
609
+ # "unavailable" (with HTTP 503) = the gate did not evaluate; never
610
+ # reported as "pass".
611
+ decision: Literal["pass", "block", "pending", "unavailable"]
612
+ blocked: bool
613
+ blocking_layer: Optional[int] = None
614
+ reason: Optional[str] = None
615
+ flagged_excerpt: Optional[str] = None
616
+ layer_results: Optional[Dict[str, Any]] = None
617
+
618
+
619
+ # --- Events -----------------------------------------------------------------
620
+
621
+ class HostEvent(BaseModel):
622
+ type: str
623
+ occurred_at: str
624
+ payload: Dict[str, Any] = Field(default_factory=dict)
625
+
626
+
627
+ # --- Goals ------------------------------------------------------------------
628
+
629
+ class GoalCreateRequest(BaseModel):
630
+ identity: HostIdentity
631
+ context: Optional[HostTurnContext] = None
632
+ title: str
633
+ description: Optional[str] = None
634
+ priority: Optional[str] = "medium"
635
+ parent_goal_id: Optional[str] = None
636
+ person_id: Optional[str] = None
637
+
638
+
639
+ class GoalUpdateRequest(BaseModel):
640
+ identity: HostIdentity
641
+ status: Optional[str] = None
642
+ progress: Optional[float] = None
643
+ notes: Optional[str] = None
644
+ # When blocking: an external condition the autonomy loop polls; the goal
645
+ # auto-unblocks when it's met (email_reply | deployment_health |
646
+ # delivery_status | api_response | custom).
647
+ condition_type: Optional[str] = None
648
+ condition_params: Optional[Dict[str, Any]] = None
649
+
650
+
651
+ class GoalResponse(BaseModel):
652
+ id: str
653
+ title: str
654
+ description: Optional[str] = None
655
+ status: str = "active"
656
+ priority: str = "medium"
657
+ progress: float = 0.0
658
+ parent_goal_id: Optional[str] = None
659
+ person_id: Optional[str] = None
660
+ created_at: Optional[str] = None
661
+ updated_at: Optional[str] = None
662
+ dispatch_unavailable: Optional[str] = None
663
+ completion_basis: Optional[str] = None
664
+
665
+
666
+ class GoalListResponse(BaseModel):
667
+ goals: List[GoalResponse] = []
668
+
669
+
670
+ # --- Contacts ---------------------------------------------------------------
671
+
672
+ class ContactResponse(BaseModel):
673
+ contact_id: str
674
+ display_name: Optional[str] = None
675
+ given_name: Optional[str] = None
676
+ family_name: Optional[str] = None
677
+ organization: Optional[str] = None
678
+ relationship_score: float = 0.0
679
+ trust_tier: Optional[str] = None
680
+ interaction_allowed: bool = True
681
+ tags: List[str] = Field(default_factory=list)
682
+ privacy_level: Optional[str] = None
683
+ person_node_id: Optional[str] = None
684
+ notes: Optional[str] = None
685
+ import_source: Optional[str] = None
686
+ first_seen_at: Optional[str] = None
687
+ last_interaction_at: Optional[str] = None
688
+ interaction_count: int = 0
689
+ enrichment_source: List[str] = Field(default_factory=list)
690
+ enrichment_last_at: Optional[str] = None
691
+ deleted_at: Optional[str] = None
692
+ created_at: Optional[str] = None
693
+ updated_at: Optional[str] = None
694
+ timezone: Optional[str] = None # IANA tz for this contact (v0.21.0)
695
+ introduced_by: Optional[str] = None # contact_id of whoever introduced them
696
+ met_via: Optional[Dict[str, Any]] = None # {channel, scope_id, ...} provenance
697
+
698
+
699
+ class ContactListResponse(BaseModel):
700
+ contacts: List[ContactResponse] = []
701
+ source_filter: Optional[str] = None
702
+ total: int = 0
703
+
704
+
705
+ # --- Temporal awareness (v0.21.0) -------------------------------------------
706
+
707
+ class TemporalConfigRequest(BaseModel):
708
+ agent_timezone: Optional[str] = None # set the agent's home tz
709
+ default_contact_timezone: Optional[str] = None # fallback tz for contacts
710
+ clear_default_contact_timezone: bool = False # explicitly clear the fallback
711
+
712
+
713
+ class TemporalConfigResponse(BaseModel):
714
+ agent_timezone: str
715
+ default_contact_timezone: Optional[str] = None
716
+ now_utc: str
717
+ now_agent_local: str
718
+ agent_local_clock: str
719
+
720
+
721
+ class ContactTimezoneRequest(BaseModel):
722
+ timezone: Optional[str] = None # None clears it
723
+
724
+
725
+ class TimelineEvent(BaseModel):
726
+ seq: int
727
+ type: str
728
+ at: str # ISO timestamp the event was recorded
729
+ when: str # humanized, e.g. "3h ago"
730
+ bucket: str # coarse, e.g. "today"
731
+ summary: Optional[str] = None
732
+ contact_id: Optional[str] = None
733
+ data: Dict[str, Any] = Field(default_factory=dict)
734
+
735
+
736
+ class TimelineResponse(BaseModel):
737
+ since: str
738
+ count: int
739
+ digest: str # human-readable rollup for the agent
740
+ events: List[TimelineEvent] = Field(default_factory=list)
741
+ has_more: bool = False
742
+
743
+
744
+ class TemporalContact(BaseModel):
745
+ contact_id: str
746
+ name: str
747
+ timezone: Optional[str] = None
748
+ last_interaction_at: Optional[str] = None
749
+ days_since: float
750
+ cadence_days: float
751
+ overdue: bool
752
+ overdue_ratio: float
753
+
754
+
755
+ class TemporalContactsResponse(BaseModel):
756
+ now: str
757
+ count: int
758
+ contacts: List[TemporalContact] = Field(default_factory=list)
759
+
760
+
761
+ class ContactHandleIn(BaseModel):
762
+ gateway: str
763
+ address: str
764
+ is_primary: bool = False
765
+ verified: bool = False
766
+
767
+
768
+ class ContactCreateRequest(BaseModel):
769
+ display_name: str
770
+ given_name: Optional[str] = None
771
+ family_name: Optional[str] = None
772
+ organization: Optional[str] = None
773
+ trust_tier: str = "regular"
774
+ tags: Optional[List[str]] = None
775
+ notes: Optional[str] = None
776
+ handles: List[ContactHandleIn] = []
777
+
778
+
779
+ class ContactIntroRequest(BaseModel):
780
+ """Capture an organic introduction: the agent met/learned of a new person.
781
+
782
+ Creates (or annotates) a PROVISIONAL contact with introduction provenance.
783
+ A provisional contact is inert by design — trust_tier defaults to 'unknown'
784
+ and interaction_allowed is forced false — so capturing an intro never grants
785
+ anyone outreach standing; promotion/merge reconciles them later.
786
+ """
787
+ name: str
788
+ gateway: Optional[str] = None # optional handle to attach
789
+ address: Optional[str] = None
790
+ introduced_by: Optional[str] = None # contact_id of the introducer
791
+ met_via: Optional[Dict[str, Any]] = None # {channel, scope_id, ...}
792
+ note: Optional[str] = None
793
+ trust_tier: str = "unknown" # provisional; never grants 1:1 rights
794
+
795
+
796
+ class ContactIntroResponse(BaseModel):
797
+ contact: ContactResponse
798
+ created: bool # True if a new provisional contact was made; False if annotated existing
799
+
800
+
801
+ class ContactStyleRequest(BaseModel):
802
+ identity: HostIdentity
803
+ person_id: str
804
+
805
+
806
+ class ContactStyleResponse(BaseModel):
807
+ person_id: str
808
+ formality: Optional[str] = None
809
+ tone: Optional[str] = None
810
+ notes: Optional[Dict[str, Any]] = None
811
+
812
+
813
+ # --- Briefings --------------------------------------------------------------
814
+
815
+ class BriefingResponse(BaseModel):
816
+ id: str
817
+ title: Optional[str] = None
818
+ body: str
819
+ briefing_type: Optional[str] = None
820
+ created_at: Optional[str] = None
821
+
822
+
823
+ class BriefingListResponse(BaseModel):
824
+ briefings: List[BriefingResponse] = []
825
+
826
+
827
+ # --- World Model ------------------------------------------------------------
828
+
829
+ class EntityResponse(BaseModel):
830
+ id: str
831
+ entity_type: str
832
+ name: str
833
+ properties: Optional[Dict[str, Any]] = None
834
+
835
+
836
+ class EntityListResponse(BaseModel):
837
+ entities: List[EntityResponse] = []
838
+
839
+
840
+ class EntityQueryRequest(BaseModel):
841
+ identity: HostIdentity
842
+ query: str
843
+ entity_type: Optional[str] = None # None or "all" = every type
844
+ limit: Optional[int] = 10
845
+
846
+
847
+ class ExtractionRequest(BaseModel):
848
+ identity: HostIdentity
849
+ content: str # Base64-encoded document content
850
+ filename: Optional[str] = None
851
+ mime_type: Optional[str] = None
852
+ metadata: Optional[Dict[str, Any]] = None
853
+
854
+
855
+ class ExtractedEntityResponse(BaseModel):
856
+ name: str
857
+ entity_type: str
858
+ attributes: Optional[Dict[str, Any]] = None
859
+ confidence: float = 1.0
860
+
861
+
862
+ class ExtractionResponse(BaseModel):
863
+ format_detected: str
864
+ entities: List[ExtractedEntityResponse] = []
865
+ text_length: int = 0
866
+
867
+
868
+ # --- Cognition --------------------------------------------------------------
869
+
870
+ class CognitionCycleRequest(BaseModel):
871
+ identity: HostIdentity
872
+ context: Optional[HostTurnContext] = None
873
+
874
+
875
+ class CognitivePerformanceIndex(BaseModel):
876
+ overall: float = 0.0
877
+ memory: float = 0.0
878
+ reasoning: float = 0.0
879
+ social: float = 0.0
880
+ autonomy: float = 0.0
881
+ domains: Optional[Dict[str, float]] = None
882
+
883
+
884
+ class CognitionGap(BaseModel):
885
+ gap_id: str
886
+ domain: str
887
+ severity: float
888
+ description: Optional[str] = None
889
+
890
+
891
+ class CognitionCycleResponse(BaseModel):
892
+ cpi: Optional[CognitivePerformanceIndex] = None
893
+ gaps: List[CognitionGap] = []
894
+ adjustments: List[Dict[str, Any]] = []
895
+
896
+
897
+ # --- Research ---------------------------------------------------------------
898
+
899
+ class ResearchStartRequest(BaseModel):
900
+ identity: HostIdentity
901
+ topic: str
902
+ depth: Optional[str] = "standard" # quick | standard | deep
903
+ person_id: Optional[str] = None
904
+
905
+
906
+ class ResearchRunResponse(BaseModel):
907
+ run_id: str
908
+ topic: str
909
+ status: str
910
+ stages_completed: List[str] = []
911
+ artifact: Optional[Dict[str, Any]] = None
912
+ created_at: Optional[str] = None
913
+
914
+
915
+ class ResearchListResponse(BaseModel):
916
+ runs: List[ResearchRunResponse] = []
917
+
918
+
919
+ # --- Delivery ---------------------------------------------------------------
920
+
921
+ class DeliveryListResponse(BaseModel):
922
+ pending: List[Dict[str, Any]] = []
923
+
924
+
925
+ class DeliveryMarkRequest(BaseModel):
926
+ identity: HostIdentity
927
+ delivery_id: str
928
+
929
+
930
+ # --- Synthesis --------------------------------------------------------------
931
+
932
+ class SynthesisDiscoverRequest(BaseModel):
933
+ identity: HostIdentity
934
+ context: Optional[HostTurnContext] = None
935
+ person_id: Optional[str] = None
936
+ min_novelty: Optional[float] = 0.3
937
+
938
+
939
+ class SynthesisConnection(BaseModel):
940
+ id: str
941
+ connection_type: str
942
+ entities: List[str] = []
943
+ novelty: float = 0.0
944
+ description: Optional[str] = None
945
+
946
+
947
+ class SynthesisDiscoverResponse(BaseModel):
948
+ connections: List[SynthesisConnection] = []
949
+
950
+
951
+ # --- Learning ---------------------------------------------------------------
952
+
953
+ class LearningCorrectionRequest(BaseModel):
954
+ identity: HostIdentity
955
+ context: HostTurnContext
956
+ original: str
957
+ correction: str
958
+ component: Optional[str] = None
959
+ correction_type: Literal[
960
+ "factual", "tone", "action", "preference"
961
+ ] = "factual"
962
+ # Exact reference of the corrected outbound response. The benchmark
963
+ # later joins this to a receipt-backed CommsLog row; an absent or unknown
964
+ # reference remains useful learning feedback but is not causal evidence.
965
+ external_ref: Optional[str] = None
966
+ # Callers that retry can supply a stable id. FeedbackStore makes the
967
+ # resulting persistence idempotent across process restarts.
968
+ correction_id: Optional[str] = None
969
+ # Explicit owner control of one current agent judgment. Ordinary feedback
970
+ # retains its existing behavior when these fields are absent.
971
+ judgment_id: Optional[int] = Field(default=None, strict=True, gt=0)
972
+ judgment_action: Optional[Literal["withdraw", "reconsider"]] = None
973
+ source_id: Optional[str] = None
974
+
975
+
976
+ class LearningEngagementRequest(BaseModel):
977
+ identity: HostIdentity
978
+ briefing_id: str
979
+ action: str # opened | dismissed | clicked | saved
980
+ dwell_seconds: Optional[float] = None
981
+
982
+
983
+ class LearningWeightsResponse(BaseModel):
984
+ weights: Dict[str, float] = {}
985
+ stats: Dict[str, int] = {}
986
+
987
+
988
+ # --- Skills -----------------------------------------------------------------
989
+
990
+ class SkillSummary(BaseModel):
991
+ id: str
992
+ name: str
993
+ description: Optional[str] = None
994
+ version: Optional[str] = None
995
+ triggers: List[str] = []
996
+
997
+
998
+ class SkillDetailResponse(BaseModel):
999
+ id: str
1000
+ name: str
1001
+ description: Optional[str] = None
1002
+ version: Optional[str] = None
1003
+ triggers: List[str] = []
1004
+ input_schema: Optional[Dict[str, Any]] = None
1005
+ permissions: Optional[Dict[str, Any]] = None
1006
+
1007
+
1008
+ class SkillsListResponse(BaseModel):
1009
+ skills: List[SkillSummary] = []
1010
+
1011
+
1012
+ # --- Insights ---------------------------------------------------------------
1013
+
1014
+ class InsightResponse(BaseModel):
1015
+ id: str
1016
+ title: str
1017
+ body: str
1018
+ insight_type: Optional[str] = None
1019
+ novelty: float = 0.0
1020
+ entities: List[str] = []
1021
+ created_at: Optional[str] = None
1022
+ dismissed: bool = False
1023
+
1024
+
1025
+ class InsightsListResponse(BaseModel):
1026
+ insights: List[InsightResponse] = []
1027
+
1028
+
1029
+ # --- Enriched Context -------------------------------------------------------
1030
+
1031
+ class EnrichedContextRequest(BaseModel):
1032
+ identity: HostIdentity
1033
+ context: HostTurnContext
1034
+ audience: Optional[Literal["viewer", "owner", "shared", "global"]] = None
1035
+ message: str
1036
+ features: Optional[Dict[str, bool]] = None
1037
+ compression: Optional[Literal["off", "conservative", "balanced", "aggressive"]] = None
1038
+
1039
+
1040
+ class EnrichedContextResponse(BaseModel):
1041
+ sections: List[ContextSection] = []
1042
+ contact_id: Optional[str] = None
1043
+ metadata: Optional[Dict[str, Any]] = None
1044
+
1045
+
1046
+ # --- Chain / Identity -------------------------------------------------------
1047
+
1048
+ class IdentityStatusResponse(BaseModel):
1049
+ colony_id: Optional[str] = None
1050
+ public_key: Optional[str] = None
1051
+ node_id: Optional[str] = None
1052
+ node_public_key: Optional[str] = None
1053
+ node_cert_fingerprint: Optional[str] = None
1054
+ initialized: bool = False
1055
+ keys_configured: bool = False
1056
+ is_genesis: bool = False
1057
+ trust_tier: Optional[Literal["REGULAR", "TRUSTED", "PRIVILEGED", "GENESIS"]] = None
1058
+ trust_anchor_verified: bool = False
1059
+
1060
+
1061
+ class IdentityInitRequest(BaseModel):
1062
+ identity: HostIdentity
1063
+ force: bool = False
1064
+
1065
+
1066
+ class ChainVerifyRequest(BaseModel):
1067
+ identity: HostIdentity
1068
+ data: str
1069
+ signature: Optional[str] = None
1070
+
1071
+
1072
+ class ChainVerifyResponse(BaseModel):
1073
+ valid: bool
1074
+ colony_id: Optional[str] = None
1075
+ signed_attestation: Optional[str] = None
1076
+ attested_at: Optional[str] = None
1077
+ signer_public_key: Optional[str] = None
1078
+
1079
+
1080
+ # --- Secrets ----------------------------------------------------------------
1081
+
1082
+ class SecretListRequest(BaseModel):
1083
+ identity: HostIdentity
1084
+ prefix: Optional[str] = None
1085
+
1086
+
1087
+ class SecretListResponse(BaseModel):
1088
+ keys: List[str] = []
1089
+
1090
+
1091
+ class SecretGetRequest(BaseModel):
1092
+ identity: HostIdentity
1093
+ key: str
1094
+
1095
+
1096
+ class SecretGetResponse(BaseModel):
1097
+ key: str
1098
+ value: Optional[str] = None
1099
+ exists: bool = False
1100
+
1101
+
1102
+ class SecretSetRequest(BaseModel):
1103
+ identity: HostIdentity
1104
+ key: str
1105
+ value: str
1106
+ secret_type: Optional[str] = None
1107
+
1108
+
1109
+ class SecretSetResponse(BaseModel):
1110
+ key: str
1111
+ stored: bool
1112
+
1113
+
1114
+ class SecretDeleteRequest(BaseModel):
1115
+ identity: HostIdentity
1116
+ key: str
1117
+
1118
+
1119
+ class SecretDeleteResponse(BaseModel):
1120
+ key: str
1121
+ deleted: bool
1122
+
1123
+
1124
+ # --- Autonomy ---------------------------------------------------------------
1125
+
1126
+ class AutonomyStatusResponse(BaseModel):
1127
+ running: bool = False
1128
+ mode: str = "reactive"
1129
+ timezone: str = "UTC"
1130
+ in_quiet_hours: bool = False
1131
+ ticks: int = 0
1132
+ events_processed: int = 0
1133
+ goals_checked: int = 0
1134
+ initiatives_generated: int = 0
1135
+ actions_executed: int = 0
1136
+ errors: int = 0
1137
+ phases_cancelled: int = 0
1138
+ last_cancelled_phase: Optional[str] = None
1139
+ # Per-phase wall-clock seconds (last run / worst run) and the phase
1140
+ # running right now, so a tick that overruns its budget is attributable.
1141
+ phases: Optional[Dict[str, Any]] = None
1142
+ config: Optional[Dict[str, Any]] = None
1143
+
1144
+
1145
+
1146
+ # --- Configure (Host LLM Config) -------------------------------------------
1147
+
1148
+ class LLMModelsConfig(BaseModel):
1149
+ # Each tier is either a bare model string or an object spec (per-tier
1150
+ # endpoint/priority/useful-context overrides — see build_tiers_from_host).
1151
+ small: Optional[Union[str, Dict[str, Any]]] = None
1152
+ medium: Optional[Union[str, Dict[str, Any]]] = None
1153
+ large: Optional[Union[str, Dict[str, Any]]] = None
1154
+
1155
+
1156
+ class HostConfigureRequest(BaseModel):
1157
+ identity: HostIdentity
1158
+ llm: Optional[Dict[str, Any]] = Field(None, description="LLM provider config from host")
1159
+
1160
+
1161
+ class HostConfigureResponse(BaseModel):
1162
+ configured: bool = True
1163
+ provider: Optional[str] = None
1164
+ # Values may be bare model strings or per-tier object specs, so the echo
1165
+ # must accept both (a str-only type 500s on multi-endpoint configs).
1166
+ models: Optional[Dict[str, Any]] = None
1167
+ routing: Optional[Dict[str, Any]] = None
1168
+
1169
+
1170
+ # --- Models (local LLM discovery) -------------------------------------------
1171
+
1172
+ class ModelInfo(BaseModel):
1173
+ id: str
1174
+ provider: Optional[str] = None
1175
+ size: Optional[int] = None
1176
+ owned_by: Optional[str] = None
1177
+
1178
+
1179
+ class ModelListResponse(BaseModel):
1180
+ provider: str = ""
1181
+ base_url: Optional[str] = None
1182
+ models: List[ModelInfo] = []
1183
+ discovered: bool = False
1184
+ error: Optional[str] = None
1185
+ routing: Optional[Dict[str, Any]] = None
1186
+
1187
+
1188
+ # ---------------------------------------------------------------------------
1189
+ # Commitment Tracking
1190
+ # ---------------------------------------------------------------------------
1191
+
1192
+ class CommitmentCreateRequest(BaseModel):
1193
+ person_id: str = Field(..., min_length=1)
1194
+ description: str = Field(..., min_length=1, max_length=1000)
1195
+ due_at: Optional[str] = None
1196
+ priority: int = Field(default=50, ge=0, le=100)
1197
+ # "introspection": a same-interaction owed follow-up surfaced by the per-turn
1198
+ # introspection reflex (e.g. "text me the result"), as opposed to a durable
1199
+ # future promise ("cognition"). Such a commitment carries a deliverable tag in
1200
+ # `metadata`: {"kind": "deliverable", "content": <what to send>,
1201
+ # "channel_hint": "sms"|"dm"|..., "delivered": bool}. The autonomy loop turns an
1202
+ # undelivered deliverable into an agent_action so the host actually sends it.
1203
+ source_type: Literal["manual", "autonomy", "cognition", "introspection"] = "manual"
1204
+ source_context: Optional[str] = None
1205
+ # When true, an open commitment for the same person that already says the
1206
+ # same thing (normalized match) is returned instead of creating a twin.
1207
+ # Agent/tool callers should set this; the raw API default stays off.
1208
+ dedupe: bool = False
1209
+ metadata: Optional[Dict[str, Any]] = None
1210
+
1211
+
1212
+ class CommitmentUpdateRequest(BaseModel):
1213
+ status: Optional[Literal["fulfilled", "cancelled"]] = None
1214
+ fulfilled_at: Optional[str] = None
1215
+ description: Optional[str] = Field(None, min_length=1, max_length=1000)
1216
+ due_at: Optional[str] = None
1217
+ priority: Optional[int] = Field(None, ge=0, le=100)
1218
+ metadata: Optional[Dict[str, Any]] = None
1219
+ # Resolution semantics: WHY the item is being settled, so the system can
1220
+ # learn from it. When `outcome` is set, `status` may be omitted — it is
1221
+ # derived (done -> fulfilled, everything else -> cancelled) and the
1222
+ # resolution {outcome, note, by, at} is recorded in metadata.
1223
+ outcome: Optional[Literal["done", "invalid", "duplicate", "wont_do", "obsolete"]] = None
1224
+ reason: Optional[str] = Field(None, max_length=300)
1225
+ resolved_by: Optional[str] = Field(None, max_length=60)
1226
+
1227
+
1228
+ class CommitmentResponse(BaseModel):
1229
+ id: str
1230
+ person_id: str
1231
+ description: str
1232
+ made_at: str
1233
+ due_at: Optional[str] = None
1234
+ fulfilled_at: Optional[str] = None
1235
+ status: str
1236
+ source_type: str
1237
+ source_context: Optional[str] = None
1238
+ priority: int
1239
+ metadata: Optional[Dict[str, Any]] = None
1240
+ # True when a dedupe-create returned an existing open item.
1241
+ deduped: bool = False
1242
+
1243
+
1244
+ class CommitmentListResponse(BaseModel):
1245
+ commitments: List[CommitmentResponse] = []
1246
+ total: int
1247
+ limit: int
1248
+ offset: int
1249
+
1250
+
1251
+ class ConcernResolveRequest(BaseModel):
1252
+ """Owner/agent resolution of a workspace concern. `outcome` says why:
1253
+ done means handled, the rest are flavors of "stop tracking this".
1254
+ With cascade on (default), sources the concern was raised from (e.g. an
1255
+ overdue commitment) are settled too — otherwise the ingest loop re-raises
1256
+ the concern from the still-open source and the resolve is cosmetic."""
1257
+ note: str = Field("resolved by owner", max_length=300)
1258
+ outcome: Literal["done", "invalid", "duplicate", "wont_do", "obsolete"] = "done"
1259
+ cascade: bool = True
1260
+ resolved_by: str = Field("owner", max_length=60)
1261
+
1262
+
1263
+ # ---------------------------------------------------------------------------
1264
+ # Cognition Substrate
1265
+ # ---------------------------------------------------------------------------
1266
+
1267
+ class CognitionTriggerRequest(BaseModel):
1268
+ trigger_type: Literal["turn_sync", "signal_ingest", "anomaly", "manual"]
1269
+ context: Dict[str, Any]
1270
+ priority: Literal["high", "normal", "low"] = "normal"
1271
+
1272
+
1273
+ class CognitionTriggerResponse(BaseModel):
1274
+ accepted: bool = True
1275
+ message: str = "Cognition trigger accepted"
1276
+ throttle_seconds: Optional[int] = None
1277
+
1278
+
1279
+ # ---------------------------------------------------------------------------
1280
+ # Theory of Mind — Affect
1281
+ # ---------------------------------------------------------------------------
1282
+
1283
+ class AffectEventCreateRequest(BaseModel):
1284
+ contact_id: str
1285
+ valence: float = Field(..., ge=-1.0, le=1.0)
1286
+ arousal: float = Field(0.5, ge=0.0, le=1.0)
1287
+ source: Literal["explicit", "inferred", "signal"] = "explicit"
1288
+ trigger: Optional[str] = None
1289
+ session_id: Optional[str] = None
1290
+
1291
+
1292
+ class AffectEventResponse(BaseModel):
1293
+ id: str
1294
+ contact_id: str
1295
+ valence: float
1296
+ arousal: float
1297
+ source: str
1298
+ trigger: Optional[str] = None
1299
+ timestamp: str
1300
+ session_id: Optional[str] = None
1301
+ source_lineage: Optional[Dict[str, Any]] = None
1302
+ evidence_basis: str = "unlinked_observation"
1303
+
1304
+
1305
+ class AffectStateResponse(BaseModel):
1306
+ contact_id: str
1307
+ current_valence: float = 0.0
1308
+ current_arousal: float = 0.3
1309
+ trend: str = "stable"
1310
+ last_event_id: Optional[str] = None
1311
+ last_updated: Optional[str] = None
1312
+ event_count: int = 0
1313
+
1314
+
1315
+ class AffectEventListResponse(BaseModel):
1316
+ events: List[AffectEventResponse] = []
1317
+ total: int
1318
+ limit: int
1319
+ offset: int
1320
+
1321
+
1322
+ # ---------------------------------------------------------------------------
1323
+ # Theory of Mind — Shared Facts
1324
+ # ---------------------------------------------------------------------------
1325
+
1326
+ class SharedFactCreateRequest(BaseModel):
1327
+ contact_id: str
1328
+ fact: str
1329
+ source: Literal["told_by_contact", "told_to_contact", "shared_context", "inferred"] = "shared_context"
1330
+ confidence: float = Field(0.8, ge=0.0, le=1.0)
1331
+ expires_at: Optional[str] = None
1332
+ metadata: Optional[Dict[str, Any]] = None
1333
+
1334
+
1335
+ class SharedFactUpdateRequest(BaseModel):
1336
+ fact: Optional[str] = None
1337
+ confidence: Optional[float] = Field(None, ge=0.0, le=1.0)
1338
+ expires_at: Optional[str] = None
1339
+ metadata: Optional[Dict[str, Any]] = None
1340
+
1341
+
1342
+ class SharedFactResponse(BaseModel):
1343
+ id: str
1344
+ contact_id: str
1345
+ fact: str
1346
+ source: str
1347
+ confidence: float
1348
+ created_at: str
1349
+ expires_at: Optional[str] = None
1350
+ metadata: Optional[Dict[str, Any]] = None
1351
+
1352
+
1353
+ class SharedFactListResponse(BaseModel):
1354
+ facts: List[SharedFactResponse] = []
1355
+ total: int
1356
+ limit: int
1357
+ offset: int
1358
+
1359
+
1360
+ # ---------------------------------------------------------------------------
1361
+ # Pattern Extraction
1362
+ # ---------------------------------------------------------------------------
1363
+
1364
+ class PatternCreateRequest(BaseModel):
1365
+ pattern_type: Literal["entity_cooccurrence", "relation_frequency", "temporal_sequence", "attribute_cluster"]
1366
+ description: str
1367
+ pattern_key: str
1368
+ frequency: int = 1
1369
+ confidence: float = Field(0.5, ge=0.0, le=1.0)
1370
+ metadata: Optional[Dict[str, Any]] = None
1371
+ source: Literal["extraction", "manual", "inferred"] = "extraction"
1372
+
1373
+
1374
+ class PatternResponse(BaseModel):
1375
+ id: str
1376
+ pattern_type: str
1377
+ description: str
1378
+ pattern_key: str
1379
+ frequency: int
1380
+ last_seen: str
1381
+ first_seen: str
1382
+ confidence: float
1383
+ metadata: Optional[Dict[str, Any]] = None
1384
+ source: str
1385
+ active: bool = True
1386
+
1387
+
1388
+ class PatternListResponse(BaseModel):
1389
+ patterns: List[PatternResponse] = []
1390
+ total: int
1391
+ limit: int
1392
+ offset: int
1393
+
1394
+
1395
+ class PatternUpdateRequest(BaseModel):
1396
+ description: Optional[str] = None
1397
+ confidence: Optional[float] = Field(None, ge=0.0, le=1.0)
1398
+ metadata: Optional[Dict[str, Any]] = None
1399
+ active: Optional[bool] = None
1400
+
1401
+
1402
+ class PatternExtractResponse(BaseModel):
1403
+ new: int = 0
1404
+ updated: int = 0
1405
+ total: int = 0
1406
+ reason: Optional[str] = None
1407
+
1408
+
1409
+ # ---------------------------------------------------------------------------
1410
+ # Surprise Engine
1411
+ # ---------------------------------------------------------------------------
1412
+
1413
+ class SurpriseCreateRequest(BaseModel):
1414
+ observation: str
1415
+ expected: Optional[str] = None
1416
+ surprise_score: Optional[float] = Field(None, ge=0.0, le=1.0)
1417
+ pattern_id: Optional[str] = None
1418
+ context: Optional[Dict[str, Any]] = None
1419
+ auto_score: bool = False
1420
+
1421
+
1422
+ class SurpriseResponse(BaseModel):
1423
+ id: str
1424
+ observation: str
1425
+ expected: Optional[str] = None
1426
+ surprise_score: float
1427
+ pattern_id: Optional[str] = None
1428
+ context: Optional[Dict[str, Any]] = None
1429
+ timestamp: str
1430
+ resolved: bool = False
1431
+ resolution: Optional[str] = None
1432
+
1433
+
1434
+ class SurpriseListResponse(BaseModel):
1435
+ surprises: List[SurpriseResponse] = []
1436
+ total: int
1437
+ limit: int
1438
+ offset: int
1439
+
1440
+
1441
+ class SurpriseResolveRequest(BaseModel):
1442
+ resolution: Optional[str] = None
1443
+
1444
+
1445
+ # ---------------------------------------------------------------------------
1446
+ # ToM LLM Extraction
1447
+ # ---------------------------------------------------------------------------
1448
+
1449
+ class TomExtractRequest(BaseModel):
1450
+ conversation_text: str
1451
+ contact_id: str
1452
+ session_id: Optional[str] = None
1453
+ extract_affect: bool = True
1454
+ extract_facts: bool = True
1455
+
1456
+
1457
+ class TomExtractResponse(BaseModel):
1458
+ affect: Optional[Dict[str, Any]] = None
1459
+ facts: List[Dict[str, Any]] = []
1460
+ throttled: bool = False
1461
+
1462
+
1463
+ # ---------------------------------------------------------------------------
1464
+ # World Model — Entities
1465
+ # ---------------------------------------------------------------------------
1466
+
1467
+ class WorldEntityCreateRequest(BaseModel):
1468
+ name: str
1469
+ entity_type: str
1470
+ aliases: Optional[List[str]] = []
1471
+ external_ids: Optional[Dict[str, str]] = {}
1472
+ confidence: float = 0.5
1473
+ properties: Optional[Dict[str, Any]] = {}
1474
+
1475
+
1476
+ class WorldEntityUpdateRequest(BaseModel):
1477
+ name: Optional[str] = None
1478
+ confidence: Optional[float] = None
1479
+ properties: Optional[Dict[str, Any]] = None
1480
+ aliases: Optional[List[str]] = None
1481
+
1482
+
1483
+ class WorldEntityDetailResponse(BaseModel):
1484
+ id: str
1485
+ name: str
1486
+ entity_type: str
1487
+ aliases: List[str] = []
1488
+ external_ids: Dict[str, str] = {}
1489
+ confidence: float = 0.5
1490
+ properties: Dict[str, Any] = {}
1491
+ first_seen: Optional[str] = None
1492
+ last_seen: Optional[str] = None
1493
+ created_at: Optional[str] = None
1494
+ updated_at: Optional[str] = None
1495
+
1496
+
1497
+ class WorldEntityListResponse(BaseModel):
1498
+ entities: List[WorldEntityDetailResponse] = []
1499
+ total: int = 0
1500
+
1501
+
1502
+ # ---------------------------------------------------------------------------
1503
+ # World Model — Relationships
1504
+ # ---------------------------------------------------------------------------
1505
+
1506
+ class WorldRelationshipCreateRequest(BaseModel):
1507
+ source_id: str
1508
+ target_id: str
1509
+ relationship_type: str
1510
+ confidence: float = 0.5
1511
+ valid_from: Optional[str] = None
1512
+ properties: Optional[Dict[str, Any]] = {}
1513
+
1514
+
1515
+ class WorldRelationshipUpdateRequest(BaseModel):
1516
+ confidence: Optional[float] = None
1517
+ valid_to: Optional[str] = None
1518
+ properties: Optional[Dict[str, Any]] = None
1519
+
1520
+
1521
+ class WorldRelationshipResponse(BaseModel):
1522
+ id: str
1523
+ source_id: str
1524
+ target_id: str
1525
+ relationship_type: str
1526
+ confidence: float = 0.5
1527
+ valid_from: Optional[str] = None
1528
+ valid_to: Optional[str] = None
1529
+ properties: Dict[str, Any] = {}
1530
+ is_active: bool = True
1531
+ created_at: Optional[str] = None
1532
+
1533
+
1534
+ class WorldRelationshipListResponse(BaseModel):
1535
+ relationships: List[WorldRelationshipResponse] = []
1536
+ total: int = 0
1537
+
1538
+
1539
+ # ---------------------------------------------------------------------------
1540
+ # World Model — Graph Traversal
1541
+ # ---------------------------------------------------------------------------
1542
+
1543
+ class WorldNeighborhoodResponse(BaseModel):
1544
+ center: Optional[WorldEntityDetailResponse] = None
1545
+ reachable: List[WorldEntityDetailResponse] = []
1546
+ edges: List[WorldRelationshipResponse] = []
1547
+ hop_counts: Dict[str, int] = {}
1548
+ truncated: bool = False
1549
+
1550
+
1551
+ class WorldPathResponse(BaseModel):
1552
+ source_id: str
1553
+ target_id: str
1554
+ path: Optional[List[WorldRelationshipResponse]] = None
1555
+ found: bool = False
1556
+
1557
+
1558
+ class WorldStatsResponse(BaseModel):
1559
+ total_entities: int = 0
1560
+ entities_by_type: Dict[str, int] = {}
1561
+ total_relationships: int = 0
1562
+ active_relationships: int = 0
1563
+ total_observations: int = 0
1564
+ merge_proposals_pending: int = 0
1565
+
1566
+
1567
+ # ---------------------------------------------------------------------------
1568
+ # Multi-Agent — Agent Management (v0.7.0)
1569
+ # ---------------------------------------------------------------------------
1570
+
1571
+ class AgentInviteRequest(BaseModel):
1572
+ expires_in_seconds: int = Field(default=900, ge=60, le=86400)
1573
+ max_uses: int = Field(default=1, ge=1, le=100)
1574
+ granted_capabilities: List[str] = Field(default_factory=lambda: ["messaging"])
1575
+ granted_is_primary: bool = False
1576
+ granted_max_concurrent: int = Field(default=5, ge=1, le=100)
1577
+ label: Optional[str] = None
1578
+
1579
+
1580
+ class AgentInviteResponse(BaseModel):
1581
+ code: str
1582
+ expires_at: str
1583
+ max_uses: int
1584
+ setup_command: str
1585
+
1586
+
1587
+ class AgentConnectRequest(BaseModel):
1588
+ setup_code: str
1589
+ node_id: Optional[str] = None
1590
+ node_public_key: str
1591
+ name: str
1592
+ capabilities: List[str] = Field(default_factory=list)
1593
+ metadata: Dict[str, Any] = Field(default_factory=dict)
1594
+
1595
+
1596
+ class AgentNodeCert(BaseModel):
1597
+ colony_id: str
1598
+ node_id: str
1599
+ public_key: str
1600
+ signature: str
1601
+ issued_at: str
1602
+
1603
+
1604
+ class AgentConnectResponse(BaseModel):
1605
+ agent_id: str
1606
+ node_id: str
1607
+ colony_id: str
1608
+ node_cert: AgentNodeCert
1609
+ websocket_url: str
1610
+ capabilities: List[str]
1611
+ is_primary: bool
1612
+ max_concurrent: int
1613
+
1614
+
1615
+ class AgentRegisterRequest(BaseModel):
1616
+ agent_id: Optional[str] = None
1617
+ node_id: Optional[str] = None
1618
+ name: str
1619
+ connection_mode: Literal["local", "remote"] = "local"
1620
+ gateway_url: Optional[str] = None
1621
+ capabilities: List[str] = Field(default_factory=list)
1622
+ is_primary: bool = False
1623
+ priority: int = Field(default=0, ge=0, le=100)
1624
+ max_concurrent: int = Field(default=5, ge=1, le=100)
1625
+ excluded_types: List[str] = Field(default_factory=list)
1626
+ metadata: Dict[str, Any] = Field(default_factory=dict)
1627
+
1628
+
1629
+ class AgentRegisterResponse(BaseModel):
1630
+ agent_id: str
1631
+ node_id: str
1632
+ colony_id: str
1633
+ websocket_url: Optional[str] = None
1634
+
1635
+
1636
+ class AgentHeartbeatRequest(BaseModel):
1637
+ status: Literal["online", "busy", "offline"] = "online"
1638
+ current_assignments: int = Field(default=0, ge=0)
1639
+ metadata: Dict[str, Any] = Field(default_factory=dict)
1640
+
1641
+
1642
+ class AgentMetadataSchema(BaseModel):
1643
+ hostname: Optional[str] = None
1644
+ platform: Optional[str] = None
1645
+ version: Optional[str] = None
1646
+ harness: Optional[str] = None
1647
+ timezone: Optional[str] = None
1648
+
1649
+
1650
+ class AgentResponse(BaseModel):
1651
+ agent_id: str
1652
+ node_id: Optional[str] = None
1653
+ name: str
1654
+ colony_id: str
1655
+ connection_mode: Literal["local", "remote"]
1656
+ gateway_url: Optional[str] = None
1657
+ capabilities: List[str]
1658
+ is_primary: bool
1659
+ priority: int
1660
+ max_concurrent: int
1661
+ excluded_types: List[str]
1662
+ status: Literal["online", "busy", "offline", "suspended", "revoked"]
1663
+ current_assignments: int
1664
+ metadata: AgentMetadataSchema = AgentMetadataSchema()
1665
+ registered_at: str
1666
+ last_seen_at: Optional[str] = None
1667
+
1668
+
1669
+ class AgentListResponse(BaseModel):
1670
+ agents: List[AgentResponse]
1671
+ total: int
1672
+
1673
+
1674
+ class AgentHealthResponse(BaseModel):
1675
+ agents: List[Dict[str, Any]]
1676
+ websocket_endpoint: str
1677
+
1678
+
1679
+ class AgentUpdateRequest(BaseModel):
1680
+ name: Optional[str] = None
1681
+ capabilities: Optional[List[str]] = None
1682
+ is_primary: Optional[bool] = None
1683
+ priority: Optional[int] = None
1684
+ max_concurrent: Optional[int] = None
1685
+ excluded_types: Optional[List[str]] = None
1686
+
1687
+
1688
+ # ---------------------------------------------------------------------------
1689
+ # Multi-Agent — Initiative Management (v0.7.0)
1690
+ # ---------------------------------------------------------------------------
1691
+
1692
+ class InitiativeCreateRequest(BaseModel):
1693
+ initiative_type: Literal[
1694
+ "PROACTIVE_MESSAGE", "CALENDAR_REMINDER", "TASK_SUGGESTION",
1695
+ "RESEARCH_DEEP_DIVE", "SKILL_EVICT", "CODING"
1696
+ ]
1697
+ title: str
1698
+ description: str
1699
+ priority: int = Field(default=0, ge=0, le=100)
1700
+ timeout_seconds: int = Field(default=3600, ge=60, le=86400)
1701
+ context: Dict[str, Any] = Field(default_factory=dict)
1702
+ entity_id: Optional[str] = None
1703
+ target_agent_id: Optional[str] = None
1704
+ dedup_key: Optional[str] = None
1705
+
1706
+
1707
+ class InitiativeResponse(BaseModel):
1708
+ id: str
1709
+ initiative_type: str
1710
+ title: str
1711
+ description: str
1712
+ priority: int
1713
+ status: Literal[
1714
+ "pending", "assigned", "acknowledged", "in_progress",
1715
+ "completed", "failed", "cancelled",
1716
+ ]
1717
+ timeout_seconds: int
1718
+ context: Dict[str, Any]
1719
+ # v0.16.0: durability contract for the context snapshot —
1720
+ # 'durable' (safe to act on as-is) or 'volatile' (check
1721
+ # context_captured_at against the type's freshness TTL first).
1722
+ context_durability: Optional[str] = None
1723
+ # The SUBJECT of the initiative (a person, a PR, a commitment) —
1724
+ # distinct from target/assigned agent (who EXECUTES it).
1725
+ entity_id: Optional[str] = None
1726
+ target_agent_id: Optional[str]
1727
+ assigned_agent_id: Optional[str]
1728
+ dedup_key: Optional[str]
1729
+ result: Optional[Dict[str, Any]]
1730
+ error_message: Optional[str]
1731
+ created_at: str
1732
+ acknowledged_at: Optional[str]
1733
+ completed_at: Optional[str]
1734
+ failed_at: Optional[str]
1735
+ expires_at: Optional[str]
1736
+
1737
+
1738
+ class InitiativeListResponse(BaseModel):
1739
+ initiatives: List[InitiativeResponse]
1740
+ total: int
1741
+
1742
+
1743
+ class InitiativeClaimRequest(BaseModel):
1744
+ agent_id: str
1745
+
1746
+
1747
+ class InitiativeCompleteRequest(BaseModel):
1748
+ agent_id: str
1749
+ result: Dict[str, Any] = Field(default_factory=dict)
1750
+
1751
+
1752
+ class InitiativeFailRequest(BaseModel):
1753
+ agent_id: str
1754
+ error_message: str
1755
+
1756
+
1757
+ class InitiativeDelegateRequest(BaseModel):
1758
+ target_agent_id: str
1759
+ reason: Optional[str] = None
1760
+
1761
+
1762
+ class InitiativePriorityRequest(BaseModel):
1763
+ priority: int = Field(ge=0, le=100)
1764
+
1765
+
1766
+ # --- Agent Snapshot ---------------------------------------------------------
1767
+
1768
+ class AgentSnapshotInitiative(BaseModel):
1769
+ id: str
1770
+ type: str
1771
+ description: str
1772
+ priority: float
1773
+ status: str
1774
+ rationale: Optional[str] = None
1775
+ action_hint: Optional[str] = None
1776
+ entity_id: Optional[str] = None
1777
+ dedup_key: Optional[str] = None
1778
+ created_at: str
1779
+ expires_at: Optional[str] = None
1780
+ assigned_agent_id: Optional[str] = None
1781
+ acknowledged_at: Optional[str] = None
1782
+ completed_at: Optional[str] = None
1783
+ failed_at: Optional[str] = None
1784
+ failed_reason: Optional[str] = None
1785
+
1786
+
1787
+ class AgentSnapshotResponse(BaseModel):
1788
+ timestamp: str
1789
+ telemetry: Dict[str, Any]
1790
+ pending_initiatives: List[AgentSnapshotInitiative]
1791
+ pending_count: int
1792
+ assigned_count: int
1793
+ failed_count: int
1794
+ recently_completed: List[AgentSnapshotInitiative]
1795
+ autonomy_mode: str
1796
+ autonomy_running: bool
1797
+ last_tick_age_minutes: Optional[float] = None
1798
+ flags: List[str]
1799
+
1800
+
1801
+ class RecordOutreachRequest(BaseModel):
1802
+ # Agent identity is deployment-specific — configure COLONY_AGENT_NAME
1803
+ # rather than relying on this generic fallback.
1804
+ agent_id: str = Field(
1805
+ default_factory=lambda: _os.environ.get("COLONY_AGENT_NAME", "agent")
1806
+ )
1807
+ channel: str = "whatsapp"
1808
+ contact_id: Optional[str] = None
1809
+ reason: Optional[str] = None
1810
+
1811
+
1812
+ class RecordOutreachResponse(BaseModel):
1813
+ recorded_at: str
1814
+ last_agent_outreach_at: str
1815
+
1816
+
1817
+ # --- Session Reports & Context Digest -----------------------------------------
1818
+
1819
+ class AgentSnapshotSystemState(BaseModel):
1820
+ """Reusable system-state component for context digest."""
1821
+
1822
+ autonomy_running: bool
1823
+ mode: str
1824
+ last_tick_age_minutes: Optional[float] = None
1825
+ silence_hours: Dict[str, Any] = Field(default_factory=dict)
1826
+ stale_flags: List[str] = Field(default_factory=list)
1827
+
1828
+
1829
+ class SessionReportRequest(BaseModel):
1830
+ session_id: str
1831
+ contact_id: str
1832
+ started_at: str # ISO datetime
1833
+ ended_at: Optional[str] = None
1834
+ summary: str
1835
+ topics: List[str] = Field(default_factory=list)
1836
+ resolutions: List[str] = Field(default_factory=list)
1837
+ pending: List[str] = Field(default_factory=list)
1838
+ notified_user: bool = False
1839
+ metadata: Dict[str, Any] = Field(default_factory=dict)
1840
+
1841
+
1842
+ class SessionReportResponse(BaseModel):
1843
+ stored: bool
1844
+ report_id: str
1845
+
1846
+
1847
+ class ContextDigestSessionReport(BaseModel):
1848
+ report_id: str
1849
+ started_at: str
1850
+ ended_at: Optional[str] = None
1851
+ summary: str
1852
+ topics: List[str] = Field(default_factory=list)
1853
+ resolutions: List[str] = Field(default_factory=list)
1854
+ pending: List[str] = Field(default_factory=list)
1855
+ notified_user: bool = False
1856
+
1857
+
1858
+ class ContextDigestResponse(BaseModel):
1859
+ generated_at: str
1860
+ contact_id: Optional[str] = None
1861
+ session_reports: List[ContextDigestSessionReport] = Field(default_factory=list)
1862
+ pending_initiatives: List[AgentSnapshotInitiative] = Field(default_factory=list)
1863
+ system_state: AgentSnapshotSystemState
1864
+ last_outreach: Dict[str, Any] = Field(default_factory=dict)
1865
+
1866
+
1867
+ # ── Trust scopes (context-scoped authorization) ──────────────────────────────
1868
+
1869
+ class ScopeAuthzResponse(BaseModel):
1870
+ """Whether a sender is authorized WITHIN a scope (group). Says nothing about 1:1."""
1871
+ authorized: bool
1872
+ scope_id: Optional[str] = None
1873
+ granted_tier: Optional[str] = None
1874
+ contact_id: Optional[str] = None
1875
+ active: bool = False
1876
+
1877
+
1878
+ class ScopeMemberIn(BaseModel):
1879
+ gateway: Optional[str] = None # handle gateway (resolve / auto-create a shadow)
1880
+ address: Optional[str] = None # handle address
1881
+ contact_id: Optional[str] = None # or a known contact directly
1882
+ name: Optional[str] = None # display name for an auto-created shadow
1883
+ role: str = "member"
1884
+
1885
+
1886
+ class ScopeCreateRequest(BaseModel):
1887
+ platform: str
1888
+ external_id: str # the group/conversation id on the platform
1889
+ label: Optional[str] = None
1890
+ scope_type: str = "group"
1891
+ granted_tier: str = "group_guest"
1892
+ created_by: str = "agent"
1893
+ members: List[ScopeMemberIn] = Field(default_factory=list)
1894
+
1895
+
1896
+ class ScopeResponse(BaseModel):
1897
+ scope_id: str
1898
+ scope_type: str
1899
+ platform: Optional[str] = None
1900
+ external_id: Optional[str] = None
1901
+ label: Optional[str] = None
1902
+ granted_tier: str
1903
+ active: bool
1904
+ members: List[str] = Field(default_factory=list) # contact_ids
1905
+
1906
+
1907
+ class ScopeDeactivateRequest(BaseModel):
1908
+ scope_id: Optional[str] = None
1909
+ platform: Optional[str] = None
1910
+ external_id: Optional[str] = None
1911
+
1912
+
1913
+ class ScopePromoteRequest(BaseModel):
1914
+ contact_id: str
1915
+ to_tier: str = "regular"
1916
+
1917
+
1918
+ class ResponseGuardCheckRequest(BaseModel):
1919
+ """A host asks the gate to evaluate an outbound reply before sending it."""
1920
+ surface: ResponseGuardSurfaceName
1921
+ response_text: str
1922
+ incoming_message_text: Optional[str] = None
1923
+ trust_tier: Optional[str] = None
1924
+ target_contact_id: Optional[str] = None
1925
+ target_gateway: Optional[str] = None
1926
+ session_id: Optional[str] = None
1927
+ turn_id: Optional[str] = None
1928
+ conversation_key: Optional[str] = None
1929
+ mentioned_entities: Optional[List[str]] = None
1930
+ mode: Optional[Literal["shadow", "enforce"]] = None
1931
+ # Optional, exact host-owned purpose/disclosure binding. This narrows the
1932
+ # candidate's permitted use; its literal-false capability fields cannot
1933
+ # grant authority or select owner-private context.
1934
+ communication_policy: Optional[CommunicationPolicyContextV1] = None
1935
+ # A request may strengthen shadow to enforce. It cannot weaken a server
1936
+ # configured for enforce; ResponseGuardSurfacePolicyV1 resolves the mode.
1937
+ # Legacy compatibility input. The host API never treats a caller-declared
1938
+ # boolean as owner authority; trusted in-process paths derive that fact from
1939
+ # server-owned identity state.
1940
+ authorized: bool = False
1941
+
1942
+ @model_validator(mode="after")
1943
+ def _communication_policy_matches_target(self) -> "ResponseGuardCheckRequest":
1944
+ policy = self.communication_policy
1945
+ if policy is not None and self.target_contact_id != policy.target_contact_id:
1946
+ raise ValueError(
1947
+ "communication policy target must match target_contact_id"
1948
+ )
1949
+ return self