minder-cli 0.5.7__tar.gz → 0.5.8__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 (176) hide show
  1. {minder_cli-0.5.7 → minder_cli-0.5.8}/PKG-INFO +2 -4
  2. {minder_cli-0.5.7 → minder_cli-0.5.8}/pyproject.toml +2 -5
  3. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/application/admin/jobs.py +5 -4
  4. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/application/admin/use_cases.py +10 -39
  5. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/bootstrap/providers.py +46 -55
  6. minder_cli-0.5.8/src/minder/bootstrap/workflow_seeder.py +64 -0
  7. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/cache/__init__.py +1 -2
  8. minder_cli-0.5.8/src/minder/cache/providers.py +61 -0
  9. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/config.py +12 -25
  10. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/jobs.py +5 -4
  11. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/memories.py +3 -2
  12. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/prompts.py +3 -2
  13. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/skills.py +3 -2
  14. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/resources/__init__.py +2 -1
  15. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/server.py +10 -4
  16. minder_cli-0.5.8/src/minder/store/qdrant/__init__.py +13 -0
  17. minder_cli-0.5.8/src/minder/store/qdrant/client.py +41 -0
  18. minder_cli-0.5.8/src/minder/store/qdrant/crud.py +275 -0
  19. minder_cli-0.5.8/src/minder/store/qdrant/graph_store.py +331 -0
  20. minder_cli-0.5.8/src/minder/store/qdrant/operational_store.py +859 -0
  21. minder_cli-0.5.8/src/minder/store/qdrant/vector_store.py +167 -0
  22. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/session.py +4 -3
  23. minder_cli-0.5.8/src/minder/utils.py +17 -0
  24. minder_cli-0.5.7/src/minder/cache/providers.py +0 -140
  25. minder_cli-0.5.7/src/minder/store/milvus/__init__.py +0 -11
  26. minder_cli-0.5.7/src/minder/store/milvus/client.py +0 -26
  27. minder_cli-0.5.7/src/minder/store/milvus/collections.py +0 -15
  28. minder_cli-0.5.7/src/minder/store/milvus/vector_store.py +0 -232
  29. minder_cli-0.5.7/src/minder/store/mongodb/__init__.py +0 -11
  30. minder_cli-0.5.7/src/minder/store/mongodb/client.py +0 -49
  31. minder_cli-0.5.7/src/minder/store/mongodb/graph_store.py +0 -497
  32. minder_cli-0.5.7/src/minder/store/mongodb/indexes.py +0 -96
  33. minder_cli-0.5.7/src/minder/store/mongodb/operational_store.py +0 -1147
  34. {minder_cli-0.5.7 → minder_cli-0.5.8}/.gitignore +0 -0
  35. {minder_cli-0.5.7 → minder_cli-0.5.8}/LICENSE +0 -0
  36. {minder_cli-0.5.7 → minder_cli-0.5.8}/README-pypi.md +0 -0
  37. {minder_cli-0.5.7 → minder_cli-0.5.8}/README.md +0 -0
  38. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/__init__.py +0 -0
  39. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/api/routers/prompts.py +0 -0
  40. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/application/__init__.py +0 -0
  41. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/application/admin/__init__.py +0 -0
  42. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/application/admin/dto.py +0 -0
  43. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/__init__.py +0 -0
  44. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/context.py +0 -0
  45. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/middleware.py +0 -0
  46. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/principal.py +0 -0
  47. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/rate_limiter.py +0 -0
  48. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/rbac.py +0 -0
  49. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/auth/service.py +0 -0
  50. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/bootstrap/__init__.py +0 -0
  51. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/bootstrap/agent_seeder.py +0 -0
  52. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/bootstrap/transport.py +0 -0
  53. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/chunking/__init__.py +0 -0
  54. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/chunking/code_splitter.py +0 -0
  55. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/chunking/splitter.py +0 -0
  56. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/cli.py +0 -0
  57. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/context_compactor.py +0 -0
  58. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/continuity.py +0 -0
  59. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/dev.py +0 -0
  60. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/embedding/__init__.py +0 -0
  61. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/embedding/base.py +0 -0
  62. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/embedding/local.py +0 -0
  63. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/embedding/openai.py +0 -0
  64. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/__init__.py +0 -0
  65. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/checkpoint.py +0 -0
  66. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/edges.py +0 -0
  67. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/executor.py +0 -0
  68. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/graph.py +0 -0
  69. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/memory_graph.py +0 -0
  70. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/__init__.py +0 -0
  71. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/clarification.py +0 -0
  72. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/evaluator.py +0 -0
  73. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/guard.py +0 -0
  74. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/llm.py +0 -0
  75. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/parallel_retriever.py +0 -0
  76. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/planning.py +0 -0
  77. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/reasoning.py +0 -0
  78. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/reflection.py +0 -0
  79. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/reranker.py +0 -0
  80. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/retriever.py +0 -0
  81. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/verification.py +0 -0
  82. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/nodes/workflow_planner.py +0 -0
  83. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/runtime.py +0 -0
  84. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/session_graph.py +0 -0
  85. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/state.py +0 -0
  86. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/graph/supervisor.py +0 -0
  87. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/learning/__init__.py +0 -0
  88. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/learning/error_learner.py +0 -0
  89. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/learning/pattern_extractor.py +0 -0
  90. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/learning/quality_optimizer.py +0 -0
  91. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/learning/skill_synthesizer.py +0 -0
  92. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/llm/__init__.py +0 -0
  93. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/llm/base.py +0 -0
  94. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/llm/factory.py +0 -0
  95. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/llm/llama_cpp_llm.py +0 -0
  96. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/llm/openai.py +0 -0
  97. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/model_bootstrap.py +0 -0
  98. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/__init__.py +0 -0
  99. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/agent.py +0 -0
  100. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/base.py +0 -0
  101. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/checkpoint.py +0 -0
  102. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/client.py +0 -0
  103. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/document.py +0 -0
  104. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/error.py +0 -0
  105. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/graph.py +0 -0
  106. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/history.py +0 -0
  107. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/job.py +0 -0
  108. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/prompt.py +0 -0
  109. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/repository.py +0 -0
  110. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/rule.py +0 -0
  111. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/session.py +0 -0
  112. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/skill.py +0 -0
  113. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/user.py +0 -0
  114. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/models/workflow.py +0 -0
  115. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/observability/__init__.py +0 -0
  116. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/observability/audit.py +0 -0
  117. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/observability/logging.py +0 -0
  118. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/observability/metrics.py +0 -0
  119. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/observability/tracing.py +0 -0
  120. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/__init__.py +0 -0
  121. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/__init__.py +0 -0
  122. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/commands/agent.py +0 -0
  123. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/commands/auth.py +0 -0
  124. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/commands/ide.py +0 -0
  125. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/commands/mcp.py +0 -0
  126. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/commands/sync.py +0 -0
  127. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/commands/update.py +0 -0
  128. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/main.py +0 -0
  129. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/utils/common.py +0 -0
  130. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/utils/config.py +0 -0
  131. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/utils/git.py +0 -0
  132. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/cli/utils/version.py +0 -0
  133. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/__init__.py +0 -0
  134. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/__init__.py +0 -0
  135. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/agents.py +0 -0
  136. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/api.py +0 -0
  137. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/context.py +0 -0
  138. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/dashboard.py +0 -0
  139. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/routes.py +0 -0
  140. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/runtime.py +0 -0
  141. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/presentation/http/admin/search.py +0 -0
  142. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/prompts/__init__.py +0 -0
  143. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/prompts/formatter.py +0 -0
  144. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/retrieval/__init__.py +0 -0
  145. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/retrieval/hybrid.py +0 -0
  146. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/retrieval/mmr.py +0 -0
  147. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/retrieval/multi_hop.py +0 -0
  148. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/runtime.py +0 -0
  149. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/__init__.py +0 -0
  150. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/document.py +0 -0
  151. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/error.py +0 -0
  152. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/feedback.py +0 -0
  153. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/graph.py +0 -0
  154. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/history.py +0 -0
  155. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/interfaces.py +0 -0
  156. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/relational.py +0 -0
  157. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/repo_state.py +0 -0
  158. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/rule.py +0 -0
  159. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/store/vector.py +0 -0
  160. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/__init__.py +0 -0
  161. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/agents.py +0 -0
  162. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/auth.py +0 -0
  163. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/graph.py +0 -0
  164. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/ingest.py +0 -0
  165. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/memory.py +0 -0
  166. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/query.py +0 -0
  167. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/registry.py +0 -0
  168. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/repo_scanner.py +0 -0
  169. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/seeds/__init__.py +0 -0
  170. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/seeds/default_agents.py +0 -0
  171. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/skills.py +0 -0
  172. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/tools/workflow.py +0 -0
  173. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/transport/__init__.py +0 -0
  174. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/transport/base.py +0 -0
  175. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/transport/sse.py +0 -0
  176. {minder_cli-0.5.7 → minder_cli-0.5.8}/src/minder/transport/stdio.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: minder-cli
3
- Version: 0.5.7
3
+ Version: 0.5.8
4
4
  Summary: Minder CLI is the command-line interface for the Minder self-hosted MCP platform.
5
5
  Project-URL: Homepage, https://github.com/hiimtrung/minder
6
6
  Project-URL: Repository, https://github.com/hiimtrung/minder
@@ -23,14 +23,12 @@ Requires-Dist: langgraph>=1.1.8; extra == 'server'
23
23
  Requires-Dist: litellm>=1.83.1; extra == 'server'
24
24
  Requires-Dist: llama-cpp-python>=0.3.7; extra == 'server'
25
25
  Requires-Dist: mcp>=1.26.0; extra == 'server'
26
- Requires-Dist: motor>=3.7.0; extra == 'server'
27
26
  Requires-Dist: passlib[bcrypt]>=1.7.4; extra == 'server'
28
27
  Requires-Dist: prometheus-client>=0.24.1; extra == 'server'
29
28
  Requires-Dist: pydantic-settings[toml]>=2.13.1; extra == 'server'
30
29
  Requires-Dist: pydantic>=2.12.5; extra == 'server'
31
30
  Requires-Dist: pyjwt>=2.12.1; extra == 'server'
32
- Requires-Dist: pymilvus>=2.6.12; extra == 'server'
33
- Requires-Dist: redis>=4.2.0; extra == 'server'
31
+ Requires-Dist: qdrant-client>=1.14.0; extra == 'server'
34
32
  Requires-Dist: sqlalchemy[asyncio]>=2.0.49; extra == 'server'
35
33
  Requires-Dist: yarl>=1.16.0; extra == 'server'
36
34
  Requires-Dist: zipp>=3.21.0; extra == 'server'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "minder-cli"
7
- version = "0.5.7"
7
+ version = "0.5.8"
8
8
  description = "Minder CLI is the command-line interface for the Minder self-hosted MCP platform."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -30,14 +30,12 @@ server = [
30
30
  "litellm>=1.83.1",
31
31
  "llama-cpp-python>=0.3.7",
32
32
  "mcp>=1.26.0",
33
- "motor>=3.7.0",
34
33
  "passlib[bcrypt]>=1.7.4",
35
34
  "prometheus-client>=0.24.1",
36
35
  "pydantic>=2.12.5",
37
36
  "pydantic-settings[toml]>=2.13.1",
38
37
  "pyjwt>=2.12.1",
39
- "pymilvus>=2.6.12",
40
- "redis>=4.2.0",
38
+ "qdrant-client>=1.14.0",
41
39
  "sqlalchemy[asyncio]>=2.0.49",
42
40
  "yarl>=1.16.0",
43
41
  "zipp>=3.21.0",
@@ -54,7 +52,6 @@ minder = "minder.cli:main"
54
52
 
55
53
  [dependency-groups]
56
54
  dev = [
57
- "fakeredis[lua]>=2.27.0",
58
55
  "mypy>=1.20.1",
59
56
  "pytest>=9.0.2",
60
57
  "pytest-asyncio>=1.3.0",
@@ -10,6 +10,7 @@ from typing import Any
10
10
  from minder.config import MinderConfig
11
11
  from minder.store.interfaces import IOperationalStore
12
12
  from minder.tools.skills import SkillTools
13
+ from minder.utils import _iso
13
14
 
14
15
 
15
16
  def _utcnow() -> datetime:
@@ -304,10 +305,10 @@ class AdminJobService:
304
305
  "progress_total": int(getattr(job, "progress_total", 0) or 0),
305
306
  "message": getattr(job, "message", None),
306
307
  "events": list(getattr(job, "events", []) or []),
307
- "created_at": created_at.isoformat() if created_at else None,
308
- "updated_at": updated_at.isoformat() if updated_at else None,
309
- "started_at": started_at.isoformat() if started_at else None,
310
- "finished_at": finished_at.isoformat() if finished_at else None,
308
+ "created_at": _iso(created_at),
309
+ "updated_at": _iso(updated_at),
310
+ "started_at": _iso(started_at),
311
+ "finished_at": _iso(finished_at),
311
312
  }
312
313
 
313
314
 
@@ -63,6 +63,7 @@ from minder.observability.audit import AuditEmitter
63
63
  from minder.store.interfaces import IGraphRepository, IOperationalStore
64
64
  from minder.tools.graph import GraphTools
65
65
  from minder.tools.registry import SCOPEABLE_TOOLS
66
+ from minder.utils import _iso
66
67
 
67
68
  _UNSET: Any = object() # sentinel for optional update fields
68
69
 
@@ -335,9 +336,7 @@ class AdminConsoleUseCases:
335
336
  {
336
337
  "event_type": str(getattr(event, "event_type", "")),
337
338
  "created_at": (
338
- getattr(event, "created_at").isoformat()
339
- if getattr(event, "created_at", None)
340
- else "unknown time"
339
+ _iso(getattr(event, "created_at", None)) or "unknown time"
341
340
  ),
342
341
  }
343
342
  for event in filtered[:limit]
@@ -424,7 +423,7 @@ class AdminConsoleUseCases:
424
423
  "resource_id": event.resource_id,
425
424
  "resource_name": None,
426
425
  "outcome": event.outcome,
427
- "created_at": event.created_at.isoformat() if event.created_at else None,
426
+ "created_at": _iso(event.created_at),
428
427
  "audit_metadata": getattr(event, "audit_metadata", None),
429
428
  }
430
429
 
@@ -563,11 +562,7 @@ class AdminConsoleUseCases:
563
562
  "display_name": getattr(user, "display_name", user.username),
564
563
  "role": user.role,
565
564
  "is_active": bool(getattr(user, "is_active", True)),
566
- "created_at": (
567
- user.created_at.isoformat()
568
- if getattr(user, "created_at", None)
569
- else None
570
- ),
565
+ "created_at": _iso(getattr(user, "created_at", None)),
571
566
  }
572
567
 
573
568
  # ------------------------------------------------------------------
@@ -650,11 +645,7 @@ class AdminConsoleUseCases:
650
645
  "description": getattr(workflow, "description", ""),
651
646
  "enforcement": getattr(workflow, "enforcement", "strict"),
652
647
  "steps": steps,
653
- "created_at": (
654
- workflow.created_at.isoformat()
655
- if getattr(workflow, "created_at", None)
656
- else None
657
- ),
648
+ "created_at": _iso(getattr(workflow, "created_at", None)),
658
649
  }
659
650
 
660
651
  # ------------------------------------------------------------------
@@ -755,16 +746,8 @@ class AdminConsoleUseCases:
755
746
  "artifact_types": list(getattr(agent, "artifact_types", []) or []),
756
747
  "tags": list(getattr(agent, "tags", []) or []),
757
748
  "is_default": bool(getattr(agent, "is_default", False)),
758
- "created_at": (
759
- agent.created_at.isoformat()
760
- if getattr(agent, "created_at", None)
761
- else None
762
- ),
763
- "updated_at": (
764
- agent.updated_at.isoformat()
765
- if getattr(agent, "updated_at", None)
766
- else None
767
- ),
749
+ "created_at": _iso(getattr(agent, "created_at", None)),
750
+ "updated_at": _iso(getattr(agent, "updated_at", None)),
768
751
  }
769
752
 
770
753
  # ------------------------------------------------------------------
@@ -827,16 +810,8 @@ class AdminConsoleUseCases:
827
810
  "active_skills": dict(getattr(session, "active_skills", {}) or {}),
828
811
  "state": dict(getattr(session, "state", {}) or {}),
829
812
  "ttl": int(getattr(session, "ttl", 86400) or 86400),
830
- "created_at": (
831
- session.created_at.isoformat()
832
- if getattr(session, "created_at", None)
833
- else ""
834
- ),
835
- "last_active": (
836
- session.last_active.isoformat()
837
- if getattr(session, "last_active", None)
838
- else ""
839
- ),
813
+ "created_at": _iso(getattr(session, "created_at", None)) or "",
814
+ "last_active": _iso(getattr(session, "last_active", None)) or "",
840
815
  }
841
816
 
842
817
  # ------------------------------------------------------------------
@@ -1034,11 +1009,7 @@ class AdminConsoleUseCases:
1034
1009
  "workflow_name": getattr(state, "workflow_name", None) if state else None,
1035
1010
  "workflow_state": getattr(state, "state", None) if state else None,
1036
1011
  "current_step": getattr(state, "current_step", None) if state else None,
1037
- "created_at": (
1038
- repo.created_at.isoformat()
1039
- if getattr(repo, "created_at", None)
1040
- else None
1041
- ),
1012
+ "created_at": _iso(getattr(repo, "created_at", None)),
1042
1013
  }
1043
1014
 
1044
1015
  async def sync_repository_graph(
@@ -1,12 +1,32 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
4
5
 
5
- from minder.cache.providers import RedisCacheProvider
6
+ from minder.cache.providers import LRUCacheProvider
6
7
  from minder.config import MinderConfig
7
8
  from minder.store.interfaces import ICacheProvider, IGraphRepository, IOperationalStore, IVectorStore
8
9
  from minder.store.vector import VectorStore
9
10
 
11
+ if TYPE_CHECKING:
12
+ from minder.store.qdrant.client import QdrantClientWrapper
13
+
14
+ # Single shared Qdrant client — all three stores reuse the same connection pool.
15
+ _qdrant_client: QdrantClientWrapper | None = None
16
+
17
+
18
+ def _get_qdrant_client(config: MinderConfig) -> QdrantClientWrapper:
19
+ global _qdrant_client
20
+ if _qdrant_client is None:
21
+ from minder.store.qdrant.client import QdrantClientWrapper
22
+ _qdrant_client = QdrantClientWrapper(
23
+ url=config.qdrant.url,
24
+ api_key=config.qdrant.api_key or None,
25
+ prefer_grpc=config.qdrant.prefer_grpc,
26
+ prefix=config.qdrant.collection_prefix,
27
+ )
28
+ return _qdrant_client
29
+
10
30
 
11
31
  def _sqlite_db_url(raw_path: str) -> str:
12
32
  db_path = Path(raw_path).expanduser()
@@ -17,64 +37,45 @@ def _sqlite_db_url(raw_path: str) -> str:
17
37
  def build_store(config: MinderConfig) -> IOperationalStore:
18
38
  provider = config.relational_store.provider
19
39
 
20
- if provider == "mongodb":
21
- from minder.store.mongodb.client import MongoClient
22
- from minder.store.mongodb.operational_store import MongoOperationalStore
23
-
24
- client = MongoClient(
25
- uri=config.mongodb.uri,
26
- database=config.mongodb.database,
27
- min_pool_size=config.mongodb.min_pool_size,
28
- max_pool_size=config.mongodb.max_pool_size,
29
- )
30
- return MongoOperationalStore(client) # type: ignore[return-value]
40
+ if provider == "qdrant":
41
+ from minder.store.qdrant.operational_store import QdrantOperationalStore
42
+ return QdrantOperationalStore(_get_qdrant_client(config)) # type: ignore[return-value]
31
43
 
32
44
  if provider in ("sqlite", "postgresql"):
33
45
  from minder.store.relational import RelationalStore
34
-
35
- if provider == "sqlite":
36
- db_url = _sqlite_db_url(config.relational_store.db_path)
37
- else:
38
- db_url = config.relational_store.uri
39
-
46
+ db_url = (
47
+ _sqlite_db_url(config.relational_store.db_path)
48
+ if provider == "sqlite"
49
+ else config.relational_store.uri
50
+ )
40
51
  return RelationalStore(db_url) # type: ignore[return-value]
41
52
 
42
53
  raise ValueError(
43
54
  f"Unsupported relational_store.provider '{provider}'. "
44
- "Supported: 'mongodb', 'sqlite', 'postgresql'."
55
+ "Supported: 'qdrant', 'sqlite', 'postgresql'."
45
56
  )
46
57
 
47
58
 
48
59
  def build_cache(config: MinderConfig) -> ICacheProvider:
49
- provider = config.cache.provider
50
-
51
- if provider == "redis":
52
- return RedisCacheProvider(
53
- uri=config.redis.uri,
54
- prefix=config.redis.prefix,
55
- default_ttl=config.redis.cache_ttl,
56
- )
57
-
58
- raise ValueError(
59
- f"Unsupported cache.provider '{provider}'. "
60
- "Only 'redis' is supported. Set [cache] provider = \"redis\" in minder.toml."
60
+ return LRUCacheProvider(
61
+ max_size=config.cache.max_size,
62
+ default_ttl=config.cache.ttl_seconds,
61
63
  )
62
64
 
63
65
 
64
66
  def build_vector_store(config: MinderConfig, store: IOperationalStore) -> IVectorStore:
65
- if config.vector_store.provider == "milvus":
66
- from minder.store.milvus.client import MilvusClient
67
- from minder.store.milvus.vector_store import MilvusVectorStore
68
-
69
- client = MilvusClient(uri=config.vector_store.uri)
70
- return MilvusVectorStore(
71
- client,
72
- store,
73
- prefix=config.vector_store.collection_prefix,
67
+ provider = config.vector_store.provider
68
+
69
+ if provider == "qdrant":
70
+ from minder.store.qdrant.vector_store import QdrantVectorStore
71
+ return QdrantVectorStore(
72
+ _get_qdrant_client(config),
73
+ store, # type: ignore[arg-type]
74
+ prefix=config.qdrant.collection_prefix,
74
75
  dimensions=config.embedding.dimensions,
75
76
  )
76
77
 
77
- return VectorStore(store, store)
78
+ return VectorStore(store, store) # type: ignore[arg-type]
78
79
 
79
80
 
80
81
  def build_graph_store(config: MinderConfig) -> IGraphRepository | None:
@@ -85,21 +86,12 @@ def build_graph_store(config: MinderConfig) -> IGraphRepository | None:
85
86
  if provider == "auto":
86
87
  provider = config.relational_store.provider
87
88
 
88
- if provider == "mongodb":
89
- from minder.store.mongodb.client import MongoClient
90
- from minder.store.mongodb.graph_store import MongoGraphStore
91
-
92
- client = MongoClient(
93
- uri=config.mongodb.uri,
94
- database=config.mongodb.database,
95
- min_pool_size=config.mongodb.min_pool_size,
96
- max_pool_size=config.mongodb.max_pool_size,
97
- )
98
- return MongoGraphStore(client) # type: ignore[return-value]
89
+ if provider == "qdrant":
90
+ from minder.store.qdrant.graph_store import QdrantGraphStore
91
+ return QdrantGraphStore(_get_qdrant_client(config)) # type: ignore[return-value]
99
92
 
100
93
  if provider in ("sqlite", "postgresql"):
101
94
  from minder.store.graph import KnowledgeGraphStore
102
-
103
95
  if provider == "sqlite":
104
96
  if config.graph_store.provider == "auto" and config.relational_store.provider == "sqlite":
105
97
  db_url = _sqlite_db_url(config.relational_store.db_path)
@@ -110,10 +102,9 @@ def build_graph_store(config: MinderConfig) -> IGraphRepository | None:
110
102
  db_url = config.relational_store.uri
111
103
  else:
112
104
  db_url = config.graph_store.uri
113
-
114
105
  return KnowledgeGraphStore(db_url)
115
106
 
116
107
  raise ValueError(
117
108
  f"Unsupported graph_store.provider '{provider}'. "
118
- "Supported: 'auto', 'mongodb', 'sqlite', 'postgresql'."
109
+ "Supported: 'auto', 'qdrant', 'sqlite', 'postgresql'."
119
110
  )
@@ -0,0 +1,64 @@
1
+ """Seed default workflows on first startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import uuid
7
+ from typing import Any
8
+
9
+ from minder.store.interfaces import IOperationalStore
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ DEFAULT_WORKFLOWS: list[dict[str, Any]] = [
14
+ {
15
+ "name": "tdd",
16
+ "version": 1,
17
+ "description": "Test-Driven Development workflow ensuring tests are written before implementation.",
18
+ "enforcement": "strict",
19
+ "steps": [
20
+ {
21
+ "name": "Problem Analysis",
22
+ "description": "Analyze the problem, understand requirements, and plan the implementation.",
23
+ "gate": "Provide a clear problem analysis and implementation plan.",
24
+ },
25
+ {
26
+ "name": "Test Writing",
27
+ "description": "Write failing tests that define the expected behavior and cover edge cases.",
28
+ "gate": "Verify that tests are written and fail as expected (red state).",
29
+ },
30
+ {
31
+ "name": "Implementation",
32
+ "description": "Implement the minimum code necessary to make the tests pass.",
33
+ "gate": "Verify that all tests pass (green state) and no regressions are introduced.",
34
+ },
35
+ {
36
+ "name": "Review",
37
+ "description": "Review the code for quality, adherence to patterns, and clean up technical debt.",
38
+ "gate": "Code review approval and verification of quality standards.",
39
+ },
40
+ ],
41
+ "policies": {"block_step_skips": True},
42
+ "default_for_repo": True,
43
+ }
44
+ ]
45
+
46
+
47
+ async def seed_default_workflows(store: IOperationalStore) -> None:
48
+ """Insert default workflows only if they do not already exist.
49
+
50
+ Never overwrites user-modified defaults — guards by name existence check.
51
+ """
52
+ for defn in DEFAULT_WORKFLOWS:
53
+ name = defn["name"]
54
+ existing = await store.get_workflow_by_name(name)
55
+ if existing is not None:
56
+ logger.debug("Workflow %r already exists, skipping seed", name)
57
+ continue
58
+
59
+ # Make a copy and inject an id if not present
60
+ payload = dict(defn)
61
+ payload["id"] = uuid.uuid4()
62
+
63
+ await store.create_workflow(**payload)
64
+ logger.info("Seeded default workflow: %r", name)
@@ -2,9 +2,8 @@
2
2
  Cache package — providers for runtime caching layer.
3
3
  """
4
4
 
5
- from minder.cache.providers import LRUCacheProvider, RedisCacheProvider
5
+ from minder.cache.providers import LRUCacheProvider
6
6
 
7
7
  __all__ = [
8
8
  "LRUCacheProvider",
9
- "RedisCacheProvider",
10
9
  ]
@@ -0,0 +1,61 @@
1
+ """
2
+ Cache Providers — implements ICacheProvider for runtime cache/session layer.
3
+
4
+ Provides in-memory LRU cache provider as a zero-dependency fallback.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ class LRUCacheProvider:
11
+ """
12
+ In-memory LRU cache provider implementing ICacheProvider.
13
+
14
+ Used as zero-dependency fallback.
15
+ """
16
+
17
+ def __init__(self, *, max_size: int = 1000, default_ttl: int = 3600) -> None:
18
+ self._max_size = max_size
19
+ self._default_ttl = default_ttl
20
+ self._store: dict[str, str] = {}
21
+
22
+ async def get(self, key: str) -> str | None:
23
+ return self._store.get(key)
24
+
25
+ async def set(self, key: str, value: str, *, ttl: int | None = None) -> None:
26
+ if len(self._store) >= self._max_size:
27
+ # Evict oldest entry (FIFO as simple approximation)
28
+ oldest_key = next(iter(self._store))
29
+ del self._store[oldest_key]
30
+ self._store[key] = value
31
+
32
+ async def delete(self, key: str) -> None:
33
+ self._store.pop(key, None)
34
+
35
+ async def exists(self, key: str) -> bool:
36
+ return key in self._store
37
+
38
+ async def expire(self, key: str, ttl: int) -> None:
39
+ pass # No-op for in-memory store
40
+
41
+ async def incr(self, key: str) -> int:
42
+ current = int(self._store.get(key, "0"))
43
+ current += 1
44
+ self._store[key] = str(current)
45
+ return current
46
+
47
+ async def keys(self, pattern: str) -> list[str]:
48
+ import fnmatch
49
+ return [k for k in self._store if fnmatch.fnmatch(k, pattern)]
50
+
51
+ async def flush_namespace(self, namespace: str) -> None:
52
+ prefix = f"{namespace}:"
53
+ to_delete = [k for k in self._store if k.startswith(prefix)]
54
+ for k in to_delete:
55
+ del self._store[k]
56
+
57
+ async def health_check(self) -> bool:
58
+ return True
59
+
60
+ async def close(self) -> None:
61
+ self._store.clear()
@@ -51,38 +51,29 @@ class LLMConfig(BaseModel):
51
51
 
52
52
 
53
53
  class VectorStoreConfig(BaseModel):
54
- provider: str = "milvus_lite" # "milvus" (standalone) | "milvus_lite" | "memory"
55
- db_path: str = "~/.minder/data/milvus.db" # used by milvus_lite only
56
- uri: str = "http://localhost:19530" # used by milvus standalone
54
+ provider: str = "qdrant" # "qdrant" | "memory"
57
55
  collection_prefix: str = "minder_"
58
56
 
59
57
 
60
58
  class RelationalStoreConfig(BaseModel):
61
- provider: str = "mongodb" # "mongodb" | "sqlite" | "postgresql"
62
- db_path: str = "minder.db" # used by sqlite
63
- uri: str = "postgresql+asyncpg://localhost/minder" # used by postgresql
59
+ provider: str = "qdrant" # "qdrant" | "sqlite" | "postgresql"
60
+ db_path: str = "minder.db" # sqlite fallback
61
+ uri: str = "postgresql+asyncpg://localhost/minder" # postgresql only
64
62
 
65
63
 
66
64
  class GraphStoreConfig(BaseModel):
67
65
  enabled: bool = True
68
- provider: str = "auto" # "auto" | "mongodb" | "sqlite" | "postgresql"
69
- # auto: mirrors relational_store.provider (mongodb → mongodb, sqlite → sqlite, postgresql → postgresql)
66
+ provider: str = "auto" # "auto" mirrors relational_store.provider
70
67
  db_path: str = "~/.minder/data/graph.db" # sqlite only
71
68
  uri: str = "postgresql+asyncpg://localhost/minder_graph" # postgresql only
72
69
 
73
70
 
74
- class MongoDBConfig(BaseModel):
75
- uri: str = "mongodb://localhost:27017"
76
- database: str = "minder"
77
- min_pool_size: int = 2
78
- max_pool_size: int = 10
79
-
71
+ class QdrantConfig(BaseModel):
72
+ url: str = "http://localhost:6333"
73
+ api_key: Optional[str] = None
74
+ prefer_grpc: bool = False
75
+ collection_prefix: str = "minder_"
80
76
 
81
- class RedisConfig(BaseModel):
82
- uri: str = "redis://localhost:6379/0"
83
- prefix: str = "minder:"
84
- session_ttl: int = 86400
85
- cache_ttl: int = 3600
86
77
 
87
78
 
88
79
  class RetrievalConfig(BaseModel):
@@ -112,10 +103,7 @@ class GraphConfig(BaseModel):
112
103
 
113
104
  class CacheConfig(BaseModel):
114
105
  enabled: bool = True
115
- provider: str = "redis" # "redis" is the only supported runtime backend
116
- max_size: int = (
117
- 1000 # unused; kept for backwards-compat with any existing .env files
118
- )
106
+ max_size: int = 1000
119
107
  ttl_seconds: int = 3600
120
108
 
121
109
 
@@ -160,8 +148,7 @@ class Settings(BaseSettings):
160
148
  default_factory=RelationalStoreConfig
161
149
  )
162
150
  graph_store: GraphStoreConfig = Field(default_factory=GraphStoreConfig)
163
- mongodb: MongoDBConfig = Field(default_factory=MongoDBConfig)
164
- redis: RedisConfig = Field(default_factory=RedisConfig)
151
+ qdrant: QdrantConfig = Field(default_factory=QdrantConfig)
165
152
  retrieval: RetrievalConfig = Field(default_factory=RetrievalConfig)
166
153
  memory: MemoryConfig = Field(default_factory=MemoryConfig)
167
154
  session: SessionConfig = Field(default_factory=SessionConfig)
@@ -11,6 +11,7 @@ from starlette.responses import JSONResponse, StreamingResponse
11
11
  from starlette.routing import BaseRoute, Route
12
12
 
13
13
  from minder.application.admin.jobs import AdminJobService, iter_job_stream
14
+ from minder.utils import _iso
14
15
 
15
16
  from .context import AdminRouteContext
16
17
 
@@ -62,10 +63,10 @@ def _serialize_job(job: Any) -> dict[str, Any]:
62
63
  "progress_percent": progress_percent,
63
64
  "message": getattr(job, "message", None),
64
65
  "events": list(getattr(job, "events", []) or []),
65
- "created_at": created_at.isoformat() if created_at else None,
66
- "updated_at": updated_at.isoformat() if updated_at else None,
67
- "started_at": started_at.isoformat() if started_at else None,
68
- "finished_at": finished_at.isoformat() if finished_at else None,
66
+ "created_at": _iso(created_at),
67
+ "updated_at": _iso(updated_at),
68
+ "started_at": _iso(started_at),
69
+ "finished_at": _iso(finished_at),
69
70
  }
70
71
 
71
72
 
@@ -13,6 +13,7 @@ from minder.config import MinderConfig
13
13
  from minder.embedding.local import LocalEmbeddingProvider
14
14
  from minder.observability.metrics import record_admin_operation
15
15
  from minder.tools.memory import MemoryTools
16
+ from minder.utils import _iso
16
17
 
17
18
  from .context import AdminRouteContext
18
19
 
@@ -47,8 +48,8 @@ def _serialize_memory(skill: Any) -> dict[str, Any]:
47
48
  "content": str(skill.content),
48
49
  "language": str(getattr(skill, "language", "markdown") or "markdown"),
49
50
  "tags": list(getattr(skill, "tags", []) or []),
50
- "created_at": skill.created_at.isoformat() if skill.created_at else None,
51
- "updated_at": skill.updated_at.isoformat() if skill.updated_at else None,
51
+ "created_at": _iso(skill.created_at),
52
+ "updated_at": _iso(skill.updated_at),
52
53
  }
53
54
 
54
55
 
@@ -11,6 +11,7 @@ from minder.config import MinderConfig
11
11
  from minder.observability.metrics import record_admin_operation
12
12
  from minder.prompts.formatter import PromptDraft, polish_prompt_draft
13
13
  from minder.prompts import PromptRegistry
14
+ from minder.utils import _iso
14
15
  from .context import AdminRouteContext
15
16
 
16
17
  logger = logging.getLogger(__name__)
@@ -48,8 +49,8 @@ def _serialize_prompt(prompt: Any) -> dict[str, Any]:
48
49
  "description": prompt.description,
49
50
  "content_template": prompt.content_template,
50
51
  "arguments": list(getattr(prompt, "arguments", []) or []),
51
- "created_at": prompt.created_at.isoformat() if prompt.created_at else None,
52
- "updated_at": prompt.updated_at.isoformat() if prompt.updated_at else None,
52
+ "created_at": _iso(prompt.created_at),
53
+ "updated_at": _iso(prompt.updated_at),
53
54
  "is_builtin": bool(getattr(prompt, "is_builtin", False)),
54
55
  }
55
56
 
@@ -12,6 +12,7 @@ from starlette.routing import BaseRoute, Route
12
12
  from minder.config import MinderConfig
13
13
  from minder.observability.metrics import record_admin_operation
14
14
  from minder.tools.skills import SkillTools
15
+ from minder.utils import _iso
15
16
 
16
17
  from .context import AdminRouteContext
17
18
 
@@ -86,8 +87,8 @@ def _serialize_skill(skill: Any) -> dict[str, Any]:
86
87
  ),
87
88
  "source": dict(source_metadata) if isinstance(source_metadata, dict) else None,
88
89
  "excerpt_kind": str(getattr(skill, "excerpt_kind", "none") or "none"),
89
- "created_at": skill.created_at.isoformat() if skill.created_at else None,
90
- "updated_at": skill.updated_at.isoformat() if skill.updated_at else None,
90
+ "created_at": _iso(skill.created_at),
91
+ "updated_at": _iso(skill.updated_at),
91
92
  }
92
93
 
93
94
 
@@ -10,6 +10,7 @@ from typing import Any
10
10
  from mcp.server.fastmcp import FastMCP
11
11
 
12
12
  from minder.store.interfaces import IGraphRepository, IOperationalStore
13
+ from minder.utils import _iso
13
14
 
14
15
 
15
16
  class ResourceRegistry:
@@ -296,7 +297,7 @@ def _serialize_graph_node(node: Any) -> dict[str, Any]:
296
297
  "node_type": node.node_type,
297
298
  "name": node.name,
298
299
  "metadata": node.extra_metadata or {},
299
- "created_at": node.created_at.isoformat() if node.created_at else None,
300
+ "created_at": _iso(node.created_at),
300
301
  }
301
302
 
302
303