topos-node 1.0.2__tar.gz → 1.0.5__tar.gz

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 (413) hide show
  1. {topos_node-1.0.2 → topos_node-1.0.5}/PKG-INFO +11 -1
  2. {topos_node-1.0.2 → topos_node-1.0.5}/README.md +10 -0
  3. {topos_node-1.0.2 → topos_node-1.0.5}/pyproject.toml +1 -1
  4. {topos_node-1.0.2 → topos_node-1.0.5}/topos/__version__.py +2 -2
  5. topos_node-1.0.5/topos/api/engine_run.py +43 -0
  6. {topos_node-1.0.2 → topos_node-1.0.5}/topos/app.py +2 -0
  7. {topos_node-1.0.2 → topos_node-1.0.5}/topos/config/settings.py +15 -3
  8. {topos_node-1.0.2 → topos_node-1.0.5}/topos/disclosure/privacy_layer.py +7 -7
  9. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/__init__.py +9 -0
  10. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/huggingface.py +133 -76
  11. topos_node-1.0.5/topos/engine/client.py +85 -0
  12. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/engine.py +28 -3
  13. topos_node-1.0.5/topos/engine/memory_utils.py +57 -0
  14. topos_node-1.0.5/topos/engine/model_cache.py +211 -0
  15. topos_node-1.0.5/topos/engine/pipeline_memory.py +26 -0
  16. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/queue_manager.py +22 -1
  17. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/registration.py +15 -0
  18. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/_engine_runner.py +6 -3
  19. topos_node-1.0.5/topos/enrichment/jobs/canonical/emo_27_job.py +111 -0
  20. topos_node-1.0.5/topos/enrichment/models/manager.py +37 -0
  21. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/orchestrator.py +25 -2
  22. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/canonical_pipeline.py +6 -1
  23. {topos_node-1.0.2 → topos_node-1.0.5}/topos/observability/metrics.py +9 -0
  24. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/inference.py +4 -4
  25. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/manifest_validation.py +51 -6
  26. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sanitization/nsfw_classifier.py +7 -12
  27. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sanitization/privacy_filter.py +10 -13
  28. {topos_node-1.0.2 → topos_node-1.0.5}/topos_node.egg-info/PKG-INFO +11 -1
  29. {topos_node-1.0.2 → topos_node-1.0.5}/topos_node.egg-info/SOURCES.txt +5 -0
  30. topos_node-1.0.2/topos/enrichment/jobs/canonical/emo_27_job.py +0 -99
  31. topos_node-1.0.2/topos/enrichment/models/manager.py +0 -8
  32. {topos_node-1.0.2 → topos_node-1.0.5}/LICENSE +0 -0
  33. {topos_node-1.0.2 → topos_node-1.0.5}/setup.cfg +0 -0
  34. {topos_node-1.0.2 → topos_node-1.0.5}/shared/__init__.py +0 -0
  35. {topos_node-1.0.2 → topos_node-1.0.5}/shared/filtering.py +0 -0
  36. {topos_node-1.0.2 → topos_node-1.0.5}/shared/schema_registry.py +0 -0
  37. {topos_node-1.0.2 → topos_node-1.0.5}/tests/test_llm_integrations_storage.py +0 -0
  38. {topos_node-1.0.2 → topos_node-1.0.5}/topos/__init__.py +0 -0
  39. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/__init__.py +0 -0
  40. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/duckdb_adapter.py +0 -0
  41. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/messenger_communities.py +0 -0
  42. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/messenger_graph.py +0 -0
  43. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/messenger_labels.py +0 -0
  44. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/profiles.py +0 -0
  45. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/query_engine.py +0 -0
  46. {topos_node-1.0.2 → topos_node-1.0.5}/topos/analytics/raw_queries.py +0 -0
  47. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/__init__.py +0 -0
  48. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/analytics.py +0 -0
  49. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/app_registry.py +0 -0
  50. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/backup.py +0 -0
  51. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/compute_remote.py +0 -0
  52. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/data_commit.py +0 -0
  53. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/data_explorer_table_prefs.py +0 -0
  54. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/database_explorer.py +0 -0
  55. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/db.py +0 -0
  56. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/device.py +0 -0
  57. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/enrichment.py +0 -0
  58. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/filter_lab.py +0 -0
  59. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/health.py +0 -0
  60. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/home_chat.py +0 -0
  61. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/ingestion_api.py +0 -0
  62. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/ingestion_compat.py +0 -0
  63. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/ingestion_sources.py +0 -0
  64. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/llm.py +0 -0
  65. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/local_mcp.py +0 -0
  66. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/messenger_analytics.py +0 -0
  67. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/privacy_disclose.py +0 -0
  68. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/query_api.py +0 -0
  69. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/runtime_bootstrap.py +0 -0
  70. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/sanitization_ollama_config.py +0 -0
  71. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/signal.py +0 -0
  72. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/signal_extraction_config.py +0 -0
  73. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/source_install.py +0 -0
  74. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/source_scrub.py +0 -0
  75. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/sources.py +0 -0
  76. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/sources_overview.py +0 -0
  77. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/sync.py +0 -0
  78. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/ui_config.py +0 -0
  79. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/uma_data.py +0 -0
  80. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/usage.py +0 -0
  81. {topos_node-1.0.2 → topos_node-1.0.5}/topos/api/user_identity.py +0 -0
  82. {topos_node-1.0.2 → topos_node-1.0.5}/topos/auth.py +0 -0
  83. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/__init__.py +0 -0
  84. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/__init__.py +0 -0
  85. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/base.py +0 -0
  86. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/browser_activity_mapper.py +0 -0
  87. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/chatgpt_mapper.py +0 -0
  88. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/demo_mappers.py +0 -0
  89. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/grok_mapper.py +0 -0
  90. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/mappers/messenger_mapper.py +0 -0
  91. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/models.py +0 -0
  92. {topos_node-1.0.2 → topos_node-1.0.5}/topos/canonicalization/resolver.py +0 -0
  93. {topos_node-1.0.2 → topos_node-1.0.5}/topos/cli/__init__.py +0 -0
  94. {topos_node-1.0.2 → topos_node-1.0.5}/topos/cli/__main__.py +0 -0
  95. {topos_node-1.0.2 → topos_node-1.0.5}/topos/cli/commands.py +0 -0
  96. {topos_node-1.0.2 → topos_node-1.0.5}/topos/config/__init__.py +0 -0
  97. {topos_node-1.0.2 → topos_node-1.0.5}/topos/config/sanitization_ollama.py +0 -0
  98. {topos_node-1.0.2 → topos_node-1.0.5}/topos/config/signal_extraction.py +0 -0
  99. {topos_node-1.0.2 → topos_node-1.0.5}/topos/contacts/__init__.py +0 -0
  100. {topos_node-1.0.2 → topos_node-1.0.5}/topos/contacts/identity.py +0 -0
  101. {topos_node-1.0.2 → topos_node-1.0.5}/topos/control_plane_client.py +0 -0
  102. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/__init__.py +0 -0
  103. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/api_models.py +0 -0
  104. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/connection_resilience.py +0 -0
  105. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/device_helpers.py +0 -0
  106. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/errors.py +0 -0
  107. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/events.py +0 -0
  108. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/handlers.py +0 -0
  109. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/logging.py +0 -0
  110. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/metrics.py +0 -0
  111. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/routine_access.py +0 -0
  112. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/startup_banner.py +0 -0
  113. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/state.py +0 -0
  114. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/table_layers.py +0 -0
  115. {topos_node-1.0.2 → topos_node-1.0.5}/topos/core/types.py +0 -0
  116. {topos_node-1.0.2 → topos_node-1.0.5}/topos/data_explorer_row_delete.py +0 -0
  117. {topos_node-1.0.2 → topos_node-1.0.5}/topos/data_explorer_table_prefs.py +0 -0
  118. {topos_node-1.0.2 → topos_node-1.0.5}/topos/data_explorer_tables.py +0 -0
  119. {topos_node-1.0.2 → topos_node-1.0.5}/topos/disclosure/__init__.py +0 -0
  120. {topos_node-1.0.2 → topos_node-1.0.5}/topos/disclosure/canonical_writer.py +0 -0
  121. {topos_node-1.0.2 → topos_node-1.0.5}/topos/disclosure/content_policy.py +0 -0
  122. {topos_node-1.0.2 → topos_node-1.0.5}/topos/disclosure/field_registry.py +0 -0
  123. {topos_node-1.0.2 → topos_node-1.0.5}/topos/disclosure/tier.py +0 -0
  124. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/__init__.py +0 -0
  125. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/base.py +0 -0
  126. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/generative_prompts.py +0 -0
  127. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/generative_response.py +0 -0
  128. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/ollama.py +0 -0
  129. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/openai.py +0 -0
  130. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/openai_compatible.py +0 -0
  131. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/redpill.py +0 -0
  132. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/backends/stub.py +0 -0
  133. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/intake.py +0 -0
  134. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/result_formatter.py +0 -0
  135. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/router.py +0 -0
  136. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/scoped_token.py +0 -0
  137. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/tasks.py +0 -0
  138. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/transport.py +0 -0
  139. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/usage_guard.py +0 -0
  140. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/usage_observation.py +0 -0
  141. {topos_node-1.0.2 → topos_node-1.0.5}/topos/engine/validator.py +0 -0
  142. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/__init__.py +0 -0
  143. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/derived_tables.py +0 -0
  144. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/job_writer.py +0 -0
  145. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/__init__.py +0 -0
  146. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/base.py +0 -0
  147. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/__init__.py +0 -0
  148. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/_batch_limits.py +0 -0
  149. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/availability_scores_job.py +0 -0
  150. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/brief_fallback.py +0 -0
  151. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/dimension_summary_job.py +0 -0
  152. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/embeddings_job.py +0 -0
  153. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/entities_job.py +0 -0
  154. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/goal_extraction_job.py +0 -0
  155. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/pii_redaction_job.py +0 -0
  156. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/relationship_edges_job.py +0 -0
  157. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/sentiment_job.py +0 -0
  158. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/topic_clusters_job.py +0 -0
  159. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/topics_job.py +0 -0
  160. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/url_classification_core.py +0 -0
  161. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/url_classification_enrichment_job.py +0 -0
  162. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/canonical/url_classification_signal_job.py +0 -0
  163. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/raw/__init__.py +0 -0
  164. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/raw/attachments_job.py +0 -0
  165. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/raw/language_job.py +0 -0
  166. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/raw/time_normalization_job.py +0 -0
  167. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/jobs/raw/tool_calls_job.py +0 -0
  168. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/models/__init__.py +0 -0
  169. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/models/mvp_defaults.py +0 -0
  170. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/models/registry.py +0 -0
  171. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/models/versioning.py +0 -0
  172. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/processor.py +0 -0
  173. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/progress_bar.py +0 -0
  174. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/registry_contract.py +0 -0
  175. {topos_node-1.0.2 → topos_node-1.0.5}/topos/enrichment/website_classifier.py +0 -0
  176. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/fit/evaluator.py +0 -0
  177. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/__init__.py +0 -0
  178. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/brief_canonical_loader.py +0 -0
  179. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/brief_ontology.py +0 -0
  180. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/brief_renderer.py +0 -0
  181. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/brief_schemas.py +0 -0
  182. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/chunking.py +0 -0
  183. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/data_health.py +0 -0
  184. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/dimension_briefs.py +0 -0
  185. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/dimension_definition_loader.py +0 -0
  186. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/dimension_profiles.py +0 -0
  187. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/dimension_registry.py +0 -0
  188. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/extraction/artifact_router.py +0 -0
  189. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/extraction/artifact_store.py +0 -0
  190. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/extraction/rule_extractors.py +0 -0
  191. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/graph_sanitize.py +0 -0
  192. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/hybrid_search.py +0 -0
  193. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/query_embed_cache.py +0 -0
  194. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/schemas.py +0 -0
  195. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/service.py +0 -0
  196. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/signal_object_store.py +0 -0
  197. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/source_hydration.py +0 -0
  198. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/structured_signal.py +0 -0
  199. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/topic_clustering.py +0 -0
  200. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/typed_stores/aggregates.py +0 -0
  201. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/typed_stores/scope_materializer.py +0 -0
  202. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/vector_codec.py +0 -0
  203. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/vector_math.py +0 -0
  204. {topos_node-1.0.2 → topos_node-1.0.5}/topos/features/signal/vector_settings.py +0 -0
  205. {topos_node-1.0.2 → topos_node-1.0.5}/topos/filter_lab/__init__.py +0 -0
  206. {topos_node-1.0.2 → topos_node-1.0.5}/topos/filter_lab/bundles.py +0 -0
  207. {topos_node-1.0.2 → topos_node-1.0.5}/topos/filter_lab/schema.py +0 -0
  208. {topos_node-1.0.2 → topos_node-1.0.5}/topos/filter_lab/service.py +0 -0
  209. {topos_node-1.0.2 → topos_node-1.0.5}/topos/filter_lab/store.py +0 -0
  210. {topos_node-1.0.2 → topos_node-1.0.5}/topos/filter_lab/worker.py +0 -0
  211. {topos_node-1.0.2 → topos_node-1.0.5}/topos/home_chat/__init__.py +0 -0
  212. {topos_node-1.0.2 → topos_node-1.0.5}/topos/home_chat/schema.py +0 -0
  213. {topos_node-1.0.2 → topos_node-1.0.5}/topos/home_chat/store.py +0 -0
  214. {topos_node-1.0.2 → topos_node-1.0.5}/topos/hosted_pool_lease.py +0 -0
  215. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/__init__.py +0 -0
  216. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/checkpoints/__init__.py +0 -0
  217. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/checkpoints/checkpoint_store.py +0 -0
  218. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/checkpoints/sqlite_checkpoint_store.py +0 -0
  219. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/inbox_crypto.py +0 -0
  220. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/inbox_drain.py +0 -0
  221. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/ingest_helpers.py +0 -0
  222. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/jobs.py +0 -0
  223. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/journal_location_fanout.py +0 -0
  224. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/journal_time_log_normalize.py +0 -0
  225. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/local_sync.py +0 -0
  226. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/log_preview.py +0 -0
  227. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/manager.py +0 -0
  228. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parser.py +0 -0
  229. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/__init__.py +0 -0
  230. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/base.py +0 -0
  231. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/browser_parser.py +0 -0
  232. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/calendar_parser.py +0 -0
  233. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/chatgpt_conversation_flattener.py +0 -0
  234. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/chatgpt_parser.py +0 -0
  235. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/demo_file_parsers.py +0 -0
  236. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/grok_parser.py +0 -0
  237. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/messenger_parser.py +0 -0
  238. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/parsers/voxterm_parser.py +0 -0
  239. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/progress.py +0 -0
  240. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/reprocess.py +0 -0
  241. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/__init__.py +0 -0
  242. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/base.py +0 -0
  243. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/calendar.py +0 -0
  244. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/chatgpt.py +0 -0
  245. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/contact_importers.py +0 -0
  246. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/grok.py +0 -0
  247. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/imessage_reader.py +0 -0
  248. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/signal_export_parser.py +0 -0
  249. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/sources/signal_reader.py +0 -0
  250. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/state_machine.py +0 -0
  251. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/triggers/__init__.py +0 -0
  252. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/triggers/file_trigger.py +0 -0
  253. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/triggers/sqlite_trigger.py +0 -0
  254. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/usage_inbox_dedupe.py +0 -0
  255. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/validation/__init__.py +0 -0
  256. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/validation/base.py +0 -0
  257. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/validation/schema_registry.py +0 -0
  258. {topos_node-1.0.2 → topos_node-1.0.5}/topos/ingestion/validation/schema_validator.py +0 -0
  259. {topos_node-1.0.2 → topos_node-1.0.5}/topos/lineage/__init__.py +0 -0
  260. {topos_node-1.0.2 → topos_node-1.0.5}/topos/lineage/provenance.py +0 -0
  261. {topos_node-1.0.2 → topos_node-1.0.5}/topos/lineage/tracker.py +0 -0
  262. {topos_node-1.0.2 → topos_node-1.0.5}/topos/llm_integrations_storage.py +0 -0
  263. {topos_node-1.0.2 → topos_node-1.0.5}/topos/mcp_stdio_proxy.py +0 -0
  264. {topos_node-1.0.2 → topos_node-1.0.5}/topos/observability/__init__.py +0 -0
  265. {topos_node-1.0.2 → topos_node-1.0.5}/topos/observability/alerts.py +0 -0
  266. {topos_node-1.0.2 → topos_node-1.0.5}/topos/observability/tracing.py +0 -0
  267. {topos_node-1.0.2 → topos_node-1.0.5}/topos/openai_client.py +0 -0
  268. {topos_node-1.0.2 → topos_node-1.0.5}/topos/pipeline/__init__.py +0 -0
  269. {topos_node-1.0.2 → topos_node-1.0.5}/topos/pipeline/audit.py +0 -0
  270. {topos_node-1.0.2 → topos_node-1.0.5}/topos/pipeline/envelope.py +0 -0
  271. {topos_node-1.0.2 → topos_node-1.0.5}/topos/pipeline/stages.py +0 -0
  272. {topos_node-1.0.2 → topos_node-1.0.5}/topos/pipeline/stub_enqueue.py +0 -0
  273. {topos_node-1.0.2 → topos_node-1.0.5}/topos/projections/__init__.py +0 -0
  274. {topos_node-1.0.2 → topos_node-1.0.5}/topos/projections/vector_index/__init__.py +0 -0
  275. {topos_node-1.0.2 → topos_node-1.0.5}/topos/projections/vector_index/base.py +0 -0
  276. {topos_node-1.0.2 → topos_node-1.0.5}/topos/projections/vector_index/builders.py +0 -0
  277. {topos_node-1.0.2 → topos_node-1.0.5}/topos/projections/vector_index/health_checks.py +0 -0
  278. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/__init__.py +0 -0
  279. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/audit.py +0 -0
  280. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/disclosure.py +0 -0
  281. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/fingerprint.py +0 -0
  282. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/game_layer.py +0 -0
  283. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/intent.py +0 -0
  284. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/manifest.py +0 -0
  285. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/pipeline.py +0 -0
  286. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/retrieval.py +0 -0
  287. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/runtime.py +0 -0
  288. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/scope_registry_loader.py +0 -0
  289. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/session.py +0 -0
  290. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/session_utils.py +0 -0
  291. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/source_generation.py +0 -0
  292. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/turn_classifier.py +0 -0
  293. {topos_node-1.0.2 → topos_node-1.0.5}/topos/query/types.py +0 -0
  294. {topos_node-1.0.2 → topos_node-1.0.5}/topos/rate_limit.py +0 -0
  295. {topos_node-1.0.2 → topos_node-1.0.5}/topos/routines/__init__.py +0 -0
  296. {topos_node-1.0.2 → topos_node-1.0.5}/topos/routines/schema.py +0 -0
  297. {topos_node-1.0.2 → topos_node-1.0.5}/topos/routines/store.py +0 -0
  298. {topos_node-1.0.2 → topos_node-1.0.5}/topos/runtime_update.py +0 -0
  299. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sanitization/__init__.py +0 -0
  300. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sanitization/ollama_transforms.py +0 -0
  301. {topos_node-1.0.2 → topos_node-1.0.5}/topos/scope_resolution.py +0 -0
  302. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/__init__.py +0 -0
  303. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/container.py +0 -0
  304. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/embeddings/__init__.py +0 -0
  305. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/embeddings/base.py +0 -0
  306. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/embeddings/local.py +0 -0
  307. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/embeddings/remote.py +0 -0
  308. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/interfaces.py +0 -0
  309. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/llm/__init__.py +0 -0
  310. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/llm/base.py +0 -0
  311. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/llm/openai.py +0 -0
  312. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/local.py +0 -0
  313. {topos_node-1.0.2 → topos_node-1.0.5}/topos/services/postgres.py +0 -0
  314. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/__init__.py +0 -0
  315. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/bundled_canonical_triples.py +0 -0
  316. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/canonical_signal_defaults.py +0 -0
  317. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/definitions.py +0 -0
  318. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/install_service.py +0 -0
  319. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/registry.py +0 -0
  320. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/runtime_install.py +0 -0
  321. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/scrub_attribution.py +0 -0
  322. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sources/scrub_service.py +0 -0
  323. {topos_node-1.0.2 → topos_node-1.0.5}/topos/startup_banner.py +0 -0
  324. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/__init__.py +0 -0
  325. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/__init__.py +0 -0
  326. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/factory.py +0 -0
  327. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/fakes.py +0 -0
  328. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/postgres/stores.py +0 -0
  329. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/protocols.py +0 -0
  330. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/sqlite/__init__.py +0 -0
  331. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/sqlite/stores.py +0 -0
  332. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/adapters/sqlite/vector_search.py +0 -0
  333. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/__init__.py +0 -0
  334. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/activity_tables.py +0 -0
  335. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/ai_chat/__init__.py +0 -0
  336. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/ai_chat/canonicalizer.py +0 -0
  337. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/ai_chat/mapper.py +0 -0
  338. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/ai_chat/model.py +0 -0
  339. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/ai_chat/tables.py +0 -0
  340. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/calendar_contacts_tables.py +0 -0
  341. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/canonical_store.py +0 -0
  342. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/conversations_tables.py +0 -0
  343. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/mapping_store.py +0 -0
  344. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/canonical/postgres.py +0 -0
  345. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/__init__.py +0 -0
  346. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/client.py +0 -0
  347. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/__init__.py +0 -0
  348. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/canonical_disclosure_v1.py +0 -0
  349. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/canonical_nsfw_v1.py +0 -0
  350. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/extraction_artifacts.py +0 -0
  351. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/journal_entries_duration_v1.py +0 -0
  352. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/journal_entries_ends_at_v1.py +0 -0
  353. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/journal_entries_people_v1.py +0 -0
  354. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/journal_entries_place_name_v1.py +0 -0
  355. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/journal_entries_starts_at_v1.py +0 -0
  356. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/remediation_person_model.py +0 -0
  357. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/scope_source_generation.py +0 -0
  358. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/signal_dimension_harness.py +0 -0
  359. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/signal_objects.py +0 -0
  360. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/signal_objects_updated_by_v1.py +0 -0
  361. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/stage9_column_renames.py +0 -0
  362. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/vector_storage_v1.py +0 -0
  363. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/vector_storage_v2.py +0 -0
  364. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/vector_storage_v3.py +0 -0
  365. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_phase0.py +0 -0
  366. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_phase1.py +0 -0
  367. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_phase4_messages_cutover.py +0 -0
  368. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_phase5_topic_clusters.py +0 -0
  369. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_phase6_dimension_briefs.py +0 -0
  370. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_query_quality.py +0 -0
  371. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/migrations/wiki_mvp_topic_clusters_coordination.py +0 -0
  372. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/paths.py +0 -0
  373. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/postgres.py +0 -0
  374. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/schema.py +0 -0
  375. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/db/storage_breakdown.py +0 -0
  376. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/enrichment/__init__.py +0 -0
  377. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/enrichment/canonical_enrichment_store.py +0 -0
  378. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/enrichment/raw_enrichment_store.py +0 -0
  379. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/normalized/__init__.py +0 -0
  380. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/normalized/normalized_store.py +0 -0
  381. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/oplog/__init__.py +0 -0
  382. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/oplog/decision.py +0 -0
  383. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/oplog/oplog_store.py +0 -0
  384. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/oplog/postgres.py +0 -0
  385. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/person/identity_resolver.py +0 -0
  386. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/projections/__init__.py +0 -0
  387. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/projections/index_ops_store.py +0 -0
  388. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/projections/vector_index_store.py +0 -0
  389. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/raw/__init__.py +0 -0
  390. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/raw/browser_flat_tables.py +0 -0
  391. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/raw/file_store.py +0 -0
  392. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/raw/raw_store.py +0 -0
  393. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/raw/raw_tables_manager.py +0 -0
  394. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/raw/sqlite_raw_store.py +0 -0
  395. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/security/encryption.py +0 -0
  396. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/signal_identity.py +0 -0
  397. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/source_settings.py +0 -0
  398. {topos_node-1.0.2 → topos_node-1.0.5}/topos/storage/user_identity.py +0 -0
  399. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sync/__init__.py +0 -0
  400. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sync/client.py +0 -0
  401. {topos_node-1.0.2 → topos_node-1.0.5}/topos/sync_handlers.py +0 -0
  402. {topos_node-1.0.2 → topos_node-1.0.5}/topos/testing/__init__.py +0 -0
  403. {topos_node-1.0.2 → topos_node-1.0.5}/topos/testing/lifespan.py +0 -0
  404. {topos_node-1.0.2 → topos_node-1.0.5}/topos/uma_contact_enrichment.py +0 -0
  405. {topos_node-1.0.2 → topos_node-1.0.5}/topos/uma_filters.py +0 -0
  406. {topos_node-1.0.2 → topos_node-1.0.5}/topos/uma_resource_id.py +0 -0
  407. {topos_node-1.0.2 → topos_node-1.0.5}/topos/uma_rpt.py +0 -0
  408. {topos_node-1.0.2 → topos_node-1.0.5}/topos/utils/base_object.py +0 -0
  409. {topos_node-1.0.2 → topos_node-1.0.5}/topos/websocket_client.py +0 -0
  410. {topos_node-1.0.2 → topos_node-1.0.5}/topos_node.egg-info/dependency_links.txt +0 -0
  411. {topos_node-1.0.2 → topos_node-1.0.5}/topos_node.egg-info/entry_points.txt +0 -0
  412. {topos_node-1.0.2 → topos_node-1.0.5}/topos_node.egg-info/requires.txt +0 -0
  413. {topos_node-1.0.2 → topos_node-1.0.5}/topos_node.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topos-node
3
- Version: 1.0.2
3
+ Version: 1.0.5
4
4
  Summary: Topos personal AI engine (FastAPI): local data, sync, control plane WebSocket client
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://topos.dialogues.ai
@@ -195,6 +195,16 @@ uv sync --extra engine
195
195
  just run
196
196
  ```
197
197
 
198
+ ### Engine memory (local dev)
199
+
200
+ ML models are cached inside the Engine with LRU eviction. For lighter local runs (especially inside Cursor's integrated terminal), see [Engine memory management](topos/docs/ml-manager-v2/MEMORY_MANAGEMENT.md).
201
+
202
+ ```bash
203
+ # Default: ENGINE_MAX_RESIDENT_MODELS=3; pipeline flush is automatic
204
+ # Override only if needed, e.g. lower RAM: export ENGINE_MAX_RESIDENT_MODELS=2
205
+ export PRIVACY_FILTER_DEVICE=cpu
206
+ ```
207
+
198
208
  Run tests:
199
209
 
200
210
  ```bash
@@ -139,6 +139,16 @@ uv sync --extra engine
139
139
  just run
140
140
  ```
141
141
 
142
+ ### Engine memory (local dev)
143
+
144
+ ML models are cached inside the Engine with LRU eviction. For lighter local runs (especially inside Cursor's integrated terminal), see [Engine memory management](topos/docs/ml-manager-v2/MEMORY_MANAGEMENT.md).
145
+
146
+ ```bash
147
+ # Default: ENGINE_MAX_RESIDENT_MODELS=3; pipeline flush is automatic
148
+ # Override only if needed, e.g. lower RAM: export ENGINE_MAX_RESIDENT_MODELS=2
149
+ export PRIVACY_FILTER_DEVICE=cpu
150
+ ```
151
+
142
152
  Run tests:
143
153
 
144
154
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "topos-node"
7
- version = "1.0.2"
7
+ version = "1.0.5"
8
8
  description = "Topos personal AI engine (FastAPI): local data, sync, control plane WebSocket client"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  """Topos Control Plane Version Information."""
2
2
 
3
- __version__ = "1.0.2"
4
- __version_info__ = (1, 0, 2)
3
+ __version__ = "1.0.5"
4
+ __version_info__ = (1, 0, 5)
5
5
 
6
6
  __all__ = ["__version__", "__version_info__"]
@@ -0,0 +1,43 @@
1
+ """Engine task execution HTTP endpoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, status
8
+
9
+ from ..auth import require_api_key
10
+ from ..engine.engine import Engine
11
+ from ..engine.tasks import ProcessingResult, ProcessingTask
12
+
13
+ router = APIRouter(tags=["engine-run"])
14
+
15
+
16
+ @router.post("/v1/engine/run", dependencies=[Depends(require_api_key)])
17
+ async def engine_run(body: Dict[str, Any]) -> Dict[str, Any]:
18
+ """Execute a ProcessingTask synchronously and return ProcessingResult JSON."""
19
+ try:
20
+ task = ProcessingTask.model_validate(body)
21
+ except Exception as exc:
22
+ raise HTTPException(
23
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
24
+ detail=f"Invalid ProcessingTask: {exc}",
25
+ ) from exc
26
+ result: ProcessingResult = Engine().run(task)
27
+ return {"result": result.model_dump(mode="json")}
28
+
29
+
30
+ @router.get("/v1/engine/memory", dependencies=[Depends(require_api_key)])
31
+ async def engine_memory() -> Dict[str, Any]:
32
+ from ..engine.memory_utils import get_process_rss_mb, mps_available, torch_available
33
+ from ..engine.model_cache import get_model_cache
34
+
35
+ cache = get_model_cache()
36
+ return {
37
+ "rss_mb": get_process_rss_mb(),
38
+ "resident_slots": cache.resident_slots(),
39
+ "max_resident": cache.max_resident,
40
+ "evictions_total": cache.evictions_total,
41
+ "torch_available": torch_available(),
42
+ "mps_available": mps_available(),
43
+ }
@@ -15,6 +15,7 @@ from .api import (
15
15
  app_registry as app_registry_routes,
16
16
  backup as backup_routes,
17
17
  compute_remote as compute_remote_routes,
18
+ engine_run as engine_run_routes,
18
19
  data_commit as data_commit_routes,
19
20
  db as db_routes,
20
21
  device as device_routes,
@@ -119,6 +120,7 @@ app.include_router(filter_lab_routes.router)
119
120
  app.include_router(home_chat_routes.router)
120
121
  app.include_router(privacy_disclose_routes.router)
121
122
  app.include_router(compute_remote_routes.router)
123
+ app.include_router(engine_run_routes.router)
122
124
  app.include_router(data_commit_routes.router)
123
125
 
124
126
 
@@ -5,7 +5,7 @@ import os
5
5
  from pathlib import Path
6
6
  from typing import List, Optional
7
7
 
8
- from pydantic import Field, model_validator
8
+ from pydantic import AliasChoices, Field, model_validator
9
9
  from pydantic_settings import BaseSettings, SettingsConfigDict
10
10
 
11
11
  # Set before any huggingface_hub import during app/route loading.
@@ -34,8 +34,14 @@ class Settings(BaseSettings):
34
34
 
35
35
  openai_base_url: str = Field("https://api.openai.com/v1", env="OPENAI_BASE_URL")
36
36
  openai_model: str = Field("gpt-4o-mini", env="OPENAI_MODEL")
37
- red_pill_api_key: Optional[str] = Field(None, env="RED_PILL_API_KEY")
38
- red_pill_api_base: str = Field("https://api.redpill.ai/v1", env="RED_PILL_API_BASE")
37
+ red_pill_api_key: Optional[str] = Field(
38
+ None,
39
+ validation_alias=AliasChoices("RED_PILL_API_KEY", "REDPILL_API_KEY"),
40
+ )
41
+ red_pill_api_base: str = Field(
42
+ "https://api.redpill.ai/v1",
43
+ validation_alias=AliasChoices("RED_PILL_API_BASE", "REDPILL_API_BASE"),
44
+ )
39
45
 
40
46
  gt_cloud_api_key: Optional[str] = Field(None, env="GT_CLOUD_API_KEY")
41
47
  griptape_nodes_api_base_url: str = Field(
@@ -95,6 +101,12 @@ class Settings(BaseSettings):
95
101
  ollama_query_model: str = Field("llama3.2:latest", env="TOPOS_OLLAMA_QUERY_MODEL")
96
102
  engine_default_provider: str = Field("huggingface", env="ENGINE_DEFAULT_PROVIDER")
97
103
 
104
+ engine_max_resident_models: int = Field(3, env="ENGINE_MAX_RESIDENT_MODELS")
105
+ engine_model_idle_ttl_sec: int = Field(0, env="ENGINE_MODEL_IDLE_TTL_SEC")
106
+ engine_memory_rss_soft_limit_mb: int = Field(0, env="ENGINE_MEMORY_RSS_SOFT_LIMIT_MB")
107
+ engine_flush_after_task: bool = Field(False, env="ENGINE_FLUSH_AFTER_TASK")
108
+ engine_ml_device: Optional[str] = Field(None, env="ENGINE_ML_DEVICE")
109
+
98
110
  # Sanitization field-transforms via Ollama (see topos.config.sanitization_ollama + engine_config overrides)
99
111
  sanitization_ollama_enabled: bool = Field(False, env="SANITIZATION_OLLAMA_ENABLED")
100
112
  sanitization_ollama_host: Optional[str] = Field(None, env="SANITIZATION_OLLAMA_HOST")
@@ -69,13 +69,13 @@ class PrivacyLayerClient:
69
69
  *,
70
70
  transform_id: str,
71
71
  ) -> Dict[str, Any]:
72
- from ..engine import Engine
72
+ from ..engine.client import get_engine_client_or_local
73
73
  from ..enrichment.jobs.canonical._engine_runner import run_engine_task
74
74
 
75
- engine = self._engine or Engine()
75
+ client = get_engine_client_or_local(self._engine)
76
76
  record_ids = [str(i.get("id") or "") for i in items]
77
77
  result = await run_engine_task(
78
- engine,
78
+ client,
79
79
  task_id=f"privacy_{record_ids[0] or 'batch'}",
80
80
  subtype="privacy_disclosure",
81
81
  source_id=None,
@@ -127,15 +127,15 @@ class PrivacyLayerClient:
127
127
  return await self._classify_nsfw_via_engine(items)
128
128
 
129
129
  async def _classify_nsfw_via_engine(self, items: List[Dict[str, Any]]) -> Dict[str, Any]:
130
- from ..engine import Engine
131
- from ..enrichment.jobs.canonical._engine_runner import run_engine_task
132
130
  from ..config.settings import settings
131
+ from ..engine.client import get_engine_client_or_local
132
+ from ..enrichment.jobs.canonical._engine_runner import run_engine_task
133
133
 
134
- engine = self._engine or Engine()
134
+ client = get_engine_client_or_local(self._engine)
135
135
  model = getattr(settings, "nsfw_classifier_model", "michellejieli/NSFW_text_classifier")
136
136
  record_ids = [str(i.get("id") or "") for i in items]
137
137
  result = await run_engine_task(
138
- engine,
138
+ client,
139
139
  task_id=f"nsfw_{record_ids[0] or 'batch'}",
140
140
  subtype="content_nsfw_classification",
141
141
  source_id=None,
@@ -1,6 +1,8 @@
1
1
  """Topos Engine: unified runtime for ML/LLM processing (enrichments, transformations, queries)."""
2
2
 
3
3
  from .engine import Engine
4
+ from .client import EngineClient, LocalEngineClient, RemoteEngineClient, get_engine_client, get_engine_client_or_local
5
+ from .model_cache import ModelSlot, get_model_cache
4
6
  from .tasks import (
5
7
  ExecutionMeta,
6
8
  ExecutionSpec,
@@ -16,6 +18,13 @@ from .tasks import (
16
18
 
17
19
  __all__ = [
18
20
  "Engine",
21
+ "EngineClient",
22
+ "LocalEngineClient",
23
+ "RemoteEngineClient",
24
+ "get_engine_client",
25
+ "get_engine_client_or_local",
26
+ "ModelSlot",
27
+ "get_model_cache",
19
28
  "ProcessingTask",
20
29
  "ProcessingResult",
21
30
  "ModelRequest",
@@ -3,8 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- import threading
7
- from typing import Any, Dict, List, Optional
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+
8
+ from ..model_cache import ModelSlot, get_model_cache
8
9
 
9
10
  logger = logging.getLogger("topos.engine.huggingface")
10
11
 
@@ -15,24 +16,38 @@ DEFAULT_NER_MODEL = "dslim/bert-base-NER"
15
16
  DEFAULT_EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
16
17
  DEFAULT_SENTIMENT_MODEL = "cardiffnlp/twitter-roberta-base-sentiment-latest"
17
18
 
19
+ _MODEL_NAME_TO_SLOT: Dict[str, ModelSlot] = {
20
+ DEFAULT_URL_CLASSIFICATION_MODEL: ModelSlot.URL_PIPELINE,
21
+ DEFAULT_EMOTION_MODEL: ModelSlot.EMOTION,
22
+ DEFAULT_NER_MODEL: ModelSlot.NER,
23
+ DEFAULT_EMBEDDING_MODEL: ModelSlot.EMBEDDING,
24
+ DEFAULT_SENTIMENT_MODEL: ModelSlot.SENTIMENT,
25
+ }
26
+
27
+
28
+ def _match_slot(model_name: str) -> Optional[ModelSlot]:
29
+ name = (model_name or "").strip().lower()
30
+ if not name:
31
+ return None
32
+ for key, slot in _MODEL_NAME_TO_SLOT.items():
33
+ if key.lower() == name or key.split("/")[-1].lower() in name:
34
+ return slot
35
+ if "website-classifier" in name:
36
+ return ModelSlot.URL_PIPELINE
37
+ if "go_emotions" in name or "emotion" in name:
38
+ return ModelSlot.EMOTION
39
+ if "ner" in name:
40
+ return ModelSlot.NER
41
+ if "minilm" in name or "embedding" in name or "sentence-transformers" in name:
42
+ return ModelSlot.EMBEDDING
43
+ if "sentiment" in name:
44
+ return ModelSlot.SENTIMENT
45
+ return None
46
+
18
47
 
19
48
  class HuggingFaceAdapter:
20
49
  """BackendAdapter for HuggingFace: text-classification pipeline and go_emotions model."""
21
50
 
22
- def __init__(self) -> None:
23
- self._url_pipeline: Any = None
24
- self._url_lock = threading.Lock()
25
- self._emotion_model: Any = None
26
- self._emotion_tokenizer: Any = None
27
- self._emotion_loaded = False
28
- self._emotion_lock = threading.Lock()
29
- self._ner_pipeline: Any = None
30
- self._ner_lock = threading.Lock()
31
- self._embedding_model: Any = None
32
- self._embedding_lock = threading.Lock()
33
- self._sentiment_pipeline: Any = None
34
- self._sentiment_lock = threading.Lock()
35
-
36
51
  def load_model(self, model_name: str, config: Optional[Dict[str, Any]] = None) -> None:
37
52
  """Load model by name; we load on first run_inference per subtype instead."""
38
53
  pass
@@ -41,7 +56,6 @@ class HuggingFaceAdapter:
41
56
  """
42
57
  Ensure the model is downloaded (e.g. from HuggingFace Hub). Downloads if not present.
43
58
  Returns True if a download was triggered (caller may clean up cache later), False if already in cache.
44
- Logs when download starts; Hub may show progress via tqdm if enabled.
45
59
  """
46
60
  try:
47
61
  from huggingface_hub import snapshot_download
@@ -49,8 +63,7 @@ class HuggingFaceAdapter:
49
63
  return False
50
64
  logger.info("Downloading model %s (huggingface)...", model_name)
51
65
  try:
52
- # tqdm_enabled=True lets HuggingFace show a progress bar when available
53
- snapshot_download(repo_id=model_name, tqdm_enabled=True)
66
+ snapshot_download(repo_id=model_name, tqdm_enabled=False)
54
67
  except Exception:
55
68
  logger.exception("Failed to download model %s", model_name)
56
69
  return False
@@ -58,27 +71,29 @@ class HuggingFaceAdapter:
58
71
  return True
59
72
 
60
73
  def _get_url_pipeline(self, model_name: str):
61
- with self._url_lock:
62
- if self._url_pipeline is not None:
63
- return self._url_pipeline
74
+ model = model_name or DEFAULT_URL_CLASSIFICATION_MODEL
75
+
76
+ def _load():
64
77
  from transformers import pipeline
65
- self._url_pipeline = pipeline(
66
- task="text-classification",
67
- model=model_name,
68
- )
69
- return self._url_pipeline
70
78
 
71
- def _get_emotion_model(self, model_name: str):
72
- with self._emotion_lock:
73
- if self._emotion_loaded:
74
- return self._emotion_model, self._emotion_tokenizer
79
+ return pipeline(task="text-classification", model=model)
80
+
81
+ handle, _ = get_model_cache().acquire(ModelSlot.URL_PIPELINE, model, _load)
82
+ return handle
83
+
84
+ def _get_emotion_model(self, model_name: str) -> Tuple[Any, Any]:
85
+ model = model_name or DEFAULT_EMOTION_MODEL
86
+
87
+ def _load():
75
88
  from transformers import AutoModelForSequenceClassification, AutoTokenizer
76
- import torch
77
- self._emotion_tokenizer = AutoTokenizer.from_pretrained(model_name)
78
- self._emotion_model = AutoModelForSequenceClassification.from_pretrained(model_name)
79
- self._emotion_model.eval()
80
- self._emotion_loaded = True
81
- return self._emotion_model, self._emotion_tokenizer
89
+
90
+ tokenizer = AutoTokenizer.from_pretrained(model)
91
+ emo_model = AutoModelForSequenceClassification.from_pretrained(model)
92
+ emo_model.eval()
93
+ return emo_model, tokenizer
94
+
95
+ handle, _ = get_model_cache().acquire(ModelSlot.EMOTION, model, _load)
96
+ return handle
82
97
 
83
98
  def run_inference(self, payload: Dict[str, Any], config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
84
99
  """Dispatch by config.subtype to url_classification or emotion_classification."""
@@ -92,6 +107,8 @@ class HuggingFaceAdapter:
92
107
  return self._run_url_classification_batch(payload, model)
93
108
  if subtype in ("emotion_classification", "emo_27"):
94
109
  return self._run_emotion_classification(payload, model)
110
+ if subtype == "emotion_classification_batch":
111
+ return self._run_emotion_classification_batch(payload, model)
95
112
  if subtype == "entity_extraction":
96
113
  return self._run_entity_extraction(payload, model)
97
114
  if subtype == "embedding":
@@ -105,17 +122,21 @@ class HuggingFaceAdapter:
105
122
  return {"error": f"Unknown subtype: {subtype}", "status": "unsupported"}
106
123
 
107
124
  def _run_url_classification(self, payload: Dict[str, Any], model_name: str) -> Dict[str, Any]:
108
- """Same behavior as WebsiteUrlClassifier."""
109
125
  url = payload.get("url") or ""
110
126
  title = payload.get("title") or ""
111
127
  if not isinstance(url, str) or not url.strip():
112
- return {"error": "url must be a non-empty string", "category": "unknown", "confidence": 0.0, "model": model_name or DEFAULT_URL_CLASSIFICATION_MODEL}
128
+ return {
129
+ "error": "url must be a non-empty string",
130
+ "category": "unknown",
131
+ "confidence": 0.0,
132
+ "model": model_name or DEFAULT_URL_CLASSIFICATION_MODEL,
133
+ }
113
134
  model = model_name or DEFAULT_URL_CLASSIFICATION_MODEL
114
- pipeline = self._get_url_pipeline(model)
135
+ pipe = self._get_url_pipeline(model)
115
136
  clean_url = url.strip()
116
137
  clean_title = (title or "").strip()
117
138
  text = f"{clean_url} [SEP] {clean_title}" if clean_title else clean_url
118
- result = pipeline(text, truncation=True, top_k=1)
139
+ result = pipe(text, truncation=True, top_k=1)
119
140
  top_result = result[0] if isinstance(result, list) and result else {}
120
141
  return {
121
142
  "category": top_result.get("label", "unknown"),
@@ -128,7 +149,7 @@ class HuggingFaceAdapter:
128
149
  if not isinstance(items, list) or not items:
129
150
  return {"error": "items required", "items": [], "model": model_name or DEFAULT_URL_CLASSIFICATION_MODEL}
130
151
  model = model_name or DEFAULT_URL_CLASSIFICATION_MODEL
131
- pipeline = self._get_url_pipeline(model)
152
+ pipe = self._get_url_pipeline(model)
132
153
  texts: List[str] = []
133
154
  for item in items:
134
155
  url = str(item.get("url") or "").strip()
@@ -142,7 +163,7 @@ class HuggingFaceAdapter:
142
163
  classified: List[Any] = []
143
164
  if non_empty_idx:
144
165
  batch_texts = [texts[i] for i in non_empty_idx]
145
- classified = pipeline(batch_texts, truncation=True, top_k=1)
166
+ classified = pipe(batch_texts, truncation=True, top_k=1)
146
167
  if not isinstance(classified, list):
147
168
  classified = [classified]
148
169
 
@@ -165,13 +186,10 @@ class HuggingFaceAdapter:
165
186
  )
166
187
  return {"items": out_items, "model": model}
167
188
 
168
- def _run_emotion_classification(self, payload: Dict[str, Any], model_name: str) -> Dict[str, Any]:
169
- """Same behavior as Emo27Job._classify_emotion."""
170
- text = payload.get("text") or payload.get("content") or ""
171
- if not text or not isinstance(text, str):
172
- return {"error": "text or content required", "emotion_label": None, "confidence": None, "all_emotions": [], "model": model_name or DEFAULT_EMOTION_MODEL}
173
- model = model_name or DEFAULT_EMOTION_MODEL
189
+ def _classify_emotion_text(self, text: str, model_name: str) -> Dict[str, Any]:
174
190
  import torch
191
+
192
+ model = model_name or DEFAULT_EMOTION_MODEL
175
193
  emo_model, tokenizer = self._get_emotion_model(model)
176
194
  inputs = tokenizer(
177
195
  text,
@@ -201,35 +219,74 @@ class HuggingFaceAdapter:
201
219
  "model": model,
202
220
  }
203
221
 
222
+ def _run_emotion_classification(self, payload: Dict[str, Any], model_name: str) -> Dict[str, Any]:
223
+ text = payload.get("text") or payload.get("content") or ""
224
+ if not text or not isinstance(text, str):
225
+ return {
226
+ "error": "text or content required",
227
+ "emotion_label": None,
228
+ "confidence": None,
229
+ "all_emotions": [],
230
+ "model": model_name or DEFAULT_EMOTION_MODEL,
231
+ }
232
+ return self._classify_emotion_text(text, model_name)
233
+
234
+ def _run_emotion_classification_batch(self, payload: Dict[str, Any], model_name: str) -> Dict[str, Any]:
235
+ items = payload.get("items") or []
236
+ if not isinstance(items, list) or not items:
237
+ return {"error": "items required", "items": [], "model": model_name or DEFAULT_EMOTION_MODEL}
238
+ model = model_name or DEFAULT_EMOTION_MODEL
239
+ out_items: List[Dict[str, Any]] = []
240
+ for item in items:
241
+ text = str(item.get("text") or item.get("content") or "")
242
+ item_id = item.get("id") or item.get("message_id")
243
+ if not text:
244
+ out_items.append(
245
+ {
246
+ "id": item_id,
247
+ "emotion_label": None,
248
+ "confidence": None,
249
+ "all_emotions": [],
250
+ "model": model,
251
+ }
252
+ )
253
+ continue
254
+ result = self._classify_emotion_text(text, model)
255
+ out_items.append({"id": item_id, **result})
256
+ return {"items": out_items, "model": model}
257
+
204
258
  def _get_ner_pipeline(self, model_name: str):
205
- with self._ner_lock:
206
- if self._ner_pipeline is not None:
207
- return self._ner_pipeline
259
+ model = model_name or DEFAULT_NER_MODEL
260
+
261
+ def _load():
208
262
  from transformers import pipeline
209
263
 
210
- model = model_name or DEFAULT_NER_MODEL
211
- self._ner_pipeline = pipeline("ner", model=model, aggregation_strategy="simple")
212
- return self._ner_pipeline
264
+ return pipeline("ner", model=model, aggregation_strategy="simple")
265
+
266
+ handle, _ = get_model_cache().acquire(ModelSlot.NER, model, _load)
267
+ return handle
213
268
 
214
269
  def _get_embedding_model(self, model_name: str):
215
- with self._embedding_lock:
216
- if self._embedding_model is not None:
217
- return self._embedding_model
270
+ model = model_name or DEFAULT_EMBEDDING_MODEL
271
+
272
+ def _load():
218
273
  from sentence_transformers import SentenceTransformer
219
274
 
220
- model = model_name or DEFAULT_EMBEDDING_MODEL
221
- self._embedding_model = SentenceTransformer(model)
222
- return self._embedding_model
275
+ return SentenceTransformer(model)
276
+
277
+ handle, _ = get_model_cache().acquire(ModelSlot.EMBEDDING, model, _load)
278
+ return handle
223
279
 
224
280
  def _get_sentiment_pipeline(self, model_name: str):
225
- with self._sentiment_lock:
226
- if self._sentiment_pipeline is not None:
227
- return self._sentiment_pipeline
281
+ model = model_name or DEFAULT_SENTIMENT_MODEL
282
+
283
+ def _load():
228
284
  from transformers import pipeline
229
285
 
230
- model = model_name or DEFAULT_SENTIMENT_MODEL
231
- self._sentiment_pipeline = pipeline("sentiment-analysis", model=model)
232
- return self._sentiment_pipeline
286
+ return pipeline("sentiment-analysis", model=model)
287
+
288
+ handle, _ = get_model_cache().acquire(ModelSlot.SENTIMENT, model, _load)
289
+ return handle
233
290
 
234
291
  def _run_entity_extraction(self, payload: Dict[str, Any], model_name: str) -> Dict[str, Any]:
235
292
  text = payload.get("text") or payload.get("content") or ""
@@ -342,12 +399,12 @@ class HuggingFaceAdapter:
342
399
  return self._run_emotion_classification(payload, DEFAULT_EMOTION_MODEL)
343
400
 
344
401
  def unload_model(self, model_name: str) -> None:
345
- """Clear cached pipeline/model (simplified: clear if name matches)."""
346
- if model_name == DEFAULT_URL_CLASSIFICATION_MODEL or "website-classifier" in (model_name or ""):
347
- with self._url_lock:
348
- self._url_pipeline = None
349
- if model_name == DEFAULT_EMOTION_MODEL or "go_emotions" in (model_name or ""):
350
- with self._emotion_lock:
351
- self._emotion_model = None
352
- self._emotion_tokenizer = None
353
- self._emotion_loaded = False
402
+ """Evict cached pipeline/model for the given model name."""
403
+ slot = _match_slot(model_name)
404
+ if slot is not None:
405
+ get_model_cache().evict_slot(slot)
406
+
407
+ def unload_all(self) -> None:
408
+ for slot in ModelSlot:
409
+ if slot not in (ModelSlot.NSFW, ModelSlot.PRIVACY_FILTER):
410
+ get_model_cache().evict_slot(slot)
@@ -0,0 +1,85 @@
1
+ """Engine client: in-process or remote HTTP execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ from typing import Optional, Protocol
7
+
8
+ import httpx
9
+
10
+ from .engine import Engine
11
+ from .tasks import ProcessingResult, ProcessingTask
12
+
13
+
14
+ class EngineClient(Protocol):
15
+ def run(self, task: ProcessingTask) -> ProcessingResult: ...
16
+
17
+
18
+ class LocalEngineClient:
19
+ """Runs tasks in-process via Engine.run()."""
20
+
21
+ def __init__(self, engine: Optional[Engine] = None) -> None:
22
+ self._engine = engine or Engine()
23
+
24
+ @property
25
+ def engine(self) -> Engine:
26
+ return self._engine
27
+
28
+ def run(self, task: ProcessingTask) -> ProcessingResult:
29
+ return self._engine.run(task)
30
+
31
+
32
+ class RemoteEngineClient:
33
+ """POST ProcessingTask JSON to a remote Topos Engine service."""
34
+
35
+ def __init__(self, base_url: str, *, api_key: Optional[str] = None, timeout_sec: float = 120.0) -> None:
36
+ self._base_url = base_url.rstrip("/")
37
+ self._api_key = (api_key or "").strip() or None
38
+ self._timeout_sec = timeout_sec
39
+
40
+ def run(self, task: ProcessingTask) -> ProcessingResult:
41
+ headers = {"Content-Type": "application/json"}
42
+ if self._api_key:
43
+ headers["Authorization"] = f"Bearer {self._api_key}"
44
+ body = task.model_dump(mode="json")
45
+ with httpx.Client(timeout=self._timeout_sec) as client:
46
+ resp = client.post(f"{self._base_url}/v1/engine/run", json=body, headers=headers)
47
+ resp.raise_for_status()
48
+ data = resp.json()
49
+ if isinstance(data, dict) and "result" in data:
50
+ data = data["result"]
51
+ return ProcessingResult.model_validate(data)
52
+
53
+
54
+ def ml_deps_available() -> bool:
55
+ return importlib.util.find_spec("torch") is not None
56
+
57
+
58
+ def get_engine_client(engine: Optional[Engine] = None) -> EngineClient:
59
+ """Return remote client when configured or ML deps missing; else local."""
60
+ from ..config.settings import settings
61
+
62
+ remote_url = (getattr(settings, "topos_engine_service_url", None) or "").strip()
63
+ if remote_url:
64
+ return RemoteEngineClient(
65
+ remote_url,
66
+ api_key=getattr(settings, "topos_key", None),
67
+ )
68
+ if not ml_deps_available():
69
+ raise RuntimeError(
70
+ "ML dependencies unavailable and TOPOS_ENGINE_SERVICE_URL is not configured"
71
+ )
72
+ return LocalEngineClient(engine)
73
+
74
+
75
+ def get_engine_client_or_local(engine: Optional[Engine] = None) -> EngineClient:
76
+ """Like get_engine_client but always falls back to local when torch is present."""
77
+ from ..config.settings import settings
78
+
79
+ remote_url = (getattr(settings, "topos_engine_service_url", None) or "").strip()
80
+ if remote_url:
81
+ return RemoteEngineClient(
82
+ remote_url,
83
+ api_key=getattr(settings, "topos_key", None),
84
+ )
85
+ return LocalEngineClient(engine)