mojentic 0.8.4__tar.gz → 1.0.0__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 (169) hide show
  1. {mojentic-0.8.4/src/mojentic.egg-info → mojentic-1.0.0}/PKG-INFO +76 -27
  2. {mojentic-0.8.4 → mojentic-1.0.0}/README.md +51 -4
  3. {mojentic-0.8.4 → mojentic-1.0.0}/pyproject.toml +25 -23
  4. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/async_dispatcher_example.py +12 -4
  5. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/async_llm_example.py +1 -2
  6. mojentic-1.0.0/src/_examples/broker_as_tool.py +83 -0
  7. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/broker_examples.py +5 -7
  8. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/broker_image_examples.py +1 -1
  9. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/characterize_ollama.py +3 -3
  10. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/characterize_openai.py +1 -1
  11. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/chat_session.py +2 -2
  12. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/chat_session_with_tool.py +2 -2
  13. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/coding_file_tool.py +16 -18
  14. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/current_datetime_tool_example.py +2 -2
  15. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/embeddings.py +1 -1
  16. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/ephemeral_task_manager_example.py +15 -11
  17. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/fetch_openai_models.py +10 -3
  18. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/file_deduplication.py +6 -6
  19. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/file_tool.py +5 -5
  20. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/image_analysis.py +2 -3
  21. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/image_broker.py +1 -1
  22. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/image_broker_splat.py +1 -1
  23. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/iterative_solver.py +3 -3
  24. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/model_characterization.py +2 -0
  25. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/openai_gateway_enhanced_demo.py +15 -5
  26. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/raw.py +1 -1
  27. mojentic-1.0.0/src/_examples/react/agents/decisioning_agent.py +190 -0
  28. mojentic-1.0.0/src/_examples/react/agents/summarization_agent.py +89 -0
  29. mojentic-1.0.0/src/_examples/react/agents/thinking_agent.py +107 -0
  30. mojentic-1.0.0/src/_examples/react/agents/tool_call_agent.py +83 -0
  31. mojentic-1.0.0/src/_examples/react/formatters.py +64 -0
  32. mojentic-1.0.0/src/_examples/react/models/base.py +78 -0
  33. mojentic-1.0.0/src/_examples/react/models/events.py +96 -0
  34. mojentic-1.0.0/src/_examples/react.py +82 -0
  35. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/recursive_agent.py +2 -2
  36. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/simple_llm.py +3 -3
  37. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/simple_llm_repl.py +1 -1
  38. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/simple_structured.py +1 -1
  39. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/simple_tool.py +2 -2
  40. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/solver_chat_session.py +5 -11
  41. mojentic-1.0.0/src/_examples/streaming.py +52 -0
  42. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/tell_user_example.py +4 -4
  43. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/tracer_demo.py +18 -20
  44. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/tracer_qt_viewer.py +49 -46
  45. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/working_memory.py +1 -1
  46. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/__init__.py +3 -3
  47. mojentic-1.0.0/src/mojentic/agents/__init__.py +34 -0
  48. mojentic-0.8.4/src/mojentic/agents/agent_broker.py → mojentic-1.0.0/src/mojentic/agents/agent_event_adapter.py +3 -3
  49. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/async_aggregator_agent_spec.py +32 -33
  50. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/async_llm_agent.py +9 -5
  51. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/async_llm_agent_spec.py +21 -22
  52. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/base_async_agent.py +2 -2
  53. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/base_llm_agent.py +6 -2
  54. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/iterative_problem_solver.py +11 -5
  55. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/simple_recursive_agent.py +11 -10
  56. mojentic-1.0.0/src/mojentic/agents/simple_recursive_agent_spec.py +423 -0
  57. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/async_dispatcher.py +0 -1
  58. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/async_dispatcher_spec.py +1 -1
  59. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/context/__init__.py +0 -2
  60. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/dispatcher.py +7 -8
  61. mojentic-1.0.0/src/mojentic/llm/__init__.py +16 -0
  62. mojentic-1.0.0/src/mojentic/llm/gateways/__init__.py +26 -0
  63. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/anthropic.py +1 -0
  64. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/anthropic_messages_adapter.py +0 -1
  65. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/llm_gateway.py +1 -1
  66. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/ollama.py +23 -18
  67. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai.py +243 -44
  68. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai_message_adapter_spec.py +3 -3
  69. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai_model_registry.py +7 -6
  70. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai_model_registry_spec.py +1 -2
  71. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai_temperature_handling_spec.py +2 -2
  72. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/llm_broker.py +162 -2
  73. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/llm_broker_spec.py +76 -2
  74. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/message_composers.py +6 -3
  75. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/message_composers_spec.py +5 -1
  76. mojentic-1.0.0/src/mojentic/llm/registry/__init__.py +3 -0
  77. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/registry/populate_registry_from_ollama.py +2 -2
  78. mojentic-1.0.0/src/mojentic/llm/tools/__init__.py +9 -0
  79. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ask_user_tool.py +11 -5
  80. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/current_datetime.py +9 -6
  81. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/date_resolver.py +10 -4
  82. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/date_resolver_spec.py +0 -1
  83. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/append_task_tool.py +4 -1
  84. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list.py +1 -1
  85. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool.py +4 -1
  86. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool.py +5 -2
  87. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/file_manager.py +131 -28
  88. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/file_manager_spec.py +0 -3
  89. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/llm_tool.py +1 -1
  90. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/llm_tool_spec.py +0 -2
  91. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/organic_web_search.py +4 -2
  92. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/tell_user_tool.py +6 -2
  93. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/tool_wrapper.py +2 -2
  94. mojentic-1.0.0/src/mojentic/tracer/__init__.py +12 -0
  95. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/event_store.py +7 -8
  96. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/event_store_spec.py +1 -2
  97. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/null_tracer.py +37 -43
  98. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/tracer_events.py +8 -2
  99. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/tracer_events_spec.py +6 -7
  100. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/tracer_system.py +37 -36
  101. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/tracer/tracer_system_spec.py +21 -6
  102. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/utils/__init__.py +1 -1
  103. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/utils/formatting.py +1 -0
  104. {mojentic-0.8.4 → mojentic-1.0.0/src/mojentic.egg-info}/PKG-INFO +76 -27
  105. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic.egg-info/SOURCES.txt +4 -1
  106. mojentic-1.0.0/src/mojentic.egg-info/requires.txt +27 -0
  107. mojentic-0.8.4/src/_examples/broker_as_tool.py +0 -58
  108. mojentic-0.8.4/src/_examples/react/agents/decisioning_agent.py +0 -32
  109. mojentic-0.8.4/src/_examples/react/agents/thinking_agent.py +0 -37
  110. mojentic-0.8.4/src/_examples/react/formatters.py +0 -30
  111. mojentic-0.8.4/src/_examples/react/models/base.py +0 -29
  112. mojentic-0.8.4/src/_examples/react/models/events.py +0 -28
  113. mojentic-0.8.4/src/_examples/react.py +0 -32
  114. mojentic-0.8.4/src/_examples/streaming.py +0 -34
  115. mojentic-0.8.4/src/mojentic/agents/__init__.py +0 -16
  116. mojentic-0.8.4/src/mojentic/llm/__init__.py +0 -16
  117. mojentic-0.8.4/src/mojentic/llm/gateways/__init__.py +0 -25
  118. mojentic-0.8.4/src/mojentic/llm/registry/__init__.py +0 -6
  119. mojentic-0.8.4/src/mojentic/llm/tools/__init__.py +0 -18
  120. mojentic-0.8.4/src/mojentic/tracer/__init__.py +0 -21
  121. mojentic-0.8.4/src/mojentic.egg-info/requires.txt +0 -25
  122. {mojentic-0.8.4 → mojentic-1.0.0}/LICENSE.md +0 -0
  123. {mojentic-0.8.4 → mojentic-1.0.0}/setup.cfg +0 -0
  124. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/__init__.py +0 -0
  125. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/design_analysis.py +0 -0
  126. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/ensures_files_exist.py +0 -0
  127. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/list_models.py +0 -0
  128. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/oversized_embeddings.py +0 -0
  129. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/react/__init__.py +0 -0
  130. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/react/agents/__init__.py +0 -0
  131. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/react/models/__init__.py +0 -0
  132. {mojentic-0.8.4 → mojentic-1.0.0}/src/_examples/routed_send_response.py +0 -0
  133. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/async_aggregator_agent.py +0 -0
  134. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/base_agent.py +0 -0
  135. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/base_llm_agent_spec.py +0 -0
  136. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/correlation_aggregator_agent.py +0 -0
  137. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/agents/output_agent.py +0 -0
  138. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/context/shared_working_memory.py +0 -0
  139. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/event.py +0 -0
  140. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/chat_session.py +0 -0
  141. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/chat_session_spec.py +0 -0
  142. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/embeddings_gateway.py +0 -0
  143. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/file_gateway.py +0 -0
  144. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/models.py +0 -0
  145. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/ollama_messages_adapter.py +0 -0
  146. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/ollama_messages_adapter_spec.py +0 -0
  147. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai_messages_adapter.py +0 -0
  148. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/openai_spec.py +0 -0
  149. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/gateways/tokenizer_gateway.py +0 -0
  150. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/registry/llm_registry.py +0 -0
  151. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/registry/models.py +0 -0
  152. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/__init__.py +0 -0
  153. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/append_task_tool_spec.py +0 -0
  154. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool.py +0 -0
  155. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool_spec.py +0 -0
  156. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/complete_task_tool.py +0 -0
  157. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/complete_task_tool_spec.py +0 -0
  158. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list_spec.py +0 -0
  159. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool_spec.py +0 -0
  160. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool.py +0 -0
  161. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool_spec.py +0 -0
  162. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool_spec.py +0 -0
  163. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/start_task_tool.py +0 -0
  164. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/ephemeral_task_manager/start_task_tool_spec.py +0 -0
  165. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/llm/tools/tool_wrapper_spec.py +0 -0
  166. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/router.py +0 -0
  167. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic/router_spec.py +0 -0
  168. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic.egg-info/dependency_links.txt +0 -0
  169. {mojentic-0.8.4 → mojentic-1.0.0}/src/mojentic.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mojentic
3
- Version: 0.8.4
3
+ Version: 1.0.0
4
4
  Summary: Mojentic is an agentic framework that aims to provide a simple and flexible way to assemble teams of agents to solve complex problems.
5
5
  Author-email: Stacey Vetzal <stacey@vetzal.com>
6
6
  Project-URL: Homepage, https://github.com/svetzal/mojentic
@@ -11,30 +11,32 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE.md
14
- Requires-Dist: pydantic
15
- Requires-Dist: structlog
16
- Requires-Dist: numpy
17
- Requires-Dist: ollama
18
- Requires-Dist: openai
19
- Requires-Dist: anthropic
20
- Requires-Dist: tiktoken
21
- Requires-Dist: parsedatetime
22
- Requires-Dist: pytz
23
- Requires-Dist: serpapi
24
- Requires-Dist: colorama
14
+ Requires-Dist: pydantic>=2.8.0
15
+ Requires-Dist: structlog>=25.5.0
16
+ Requires-Dist: numpy>=2.3.5
17
+ Requires-Dist: ollama>=0.1.6
18
+ Requires-Dist: openai>=2.8.1
19
+ Requires-Dist: anthropic>=0.74.1
20
+ Requires-Dist: tiktoken>=0.12.0
21
+ Requires-Dist: parsedatetime>=2.6
22
+ Requires-Dist: pytz>=2025.2
23
+ Requires-Dist: serpapi>=0.1.5
24
+ Requires-Dist: colorama>=0.4.6
25
25
  Provides-Extra: dev
26
- Requires-Dist: pytest>=7.0.0; extra == "dev"
27
- Requires-Dist: pytest-asyncio; extra == "dev"
28
- Requires-Dist: pytest-spec; extra == "dev"
29
- Requires-Dist: pytest-cov; extra == "dev"
26
+ Requires-Dist: pytest>=9.0.1; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=1.3.0; extra == "dev"
28
+ Requires-Dist: pytest-spec>=5.2.0; extra == "dev"
29
+ Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
30
30
  Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
31
- Requires-Dist: flake8>=6.0.0; extra == "dev"
32
- Requires-Dist: mkdocs; extra == "dev"
33
- Requires-Dist: mkdocs-material; extra == "dev"
34
- Requires-Dist: mkdocs-llmstxt; extra == "dev"
35
- Requires-Dist: mkdocstrings[python]; extra == "dev"
36
- Requires-Dist: griffe-fieldz; extra == "dev"
37
- Requires-Dist: pymdown-extensions; extra == "dev"
31
+ Requires-Dist: flake8>=7.3.0; extra == "dev"
32
+ Requires-Dist: bandit>=1.7.0; extra == "dev"
33
+ Requires-Dist: pip-audit>=2.0.0; extra == "dev"
34
+ Requires-Dist: mkdocs>=1.5.2; extra == "dev"
35
+ Requires-Dist: mkdocs-material>=9.6.10; extra == "dev"
36
+ Requires-Dist: mkdocs-llmstxt>=0.4.0; extra == "dev"
37
+ Requires-Dist: mkdocstrings[python]>=0.21.0; extra == "dev"
38
+ Requires-Dist: griffe-fieldz>=0.3.0; extra == "dev"
39
+ Requires-Dist: pymdown-extensions>=10.3; extra == "dev"
38
40
  Dynamic: license-file
39
41
 
40
42
  # Mojentic
@@ -64,16 +66,26 @@ Mojentic is a framework that provides a simple and flexible way to interact with
64
66
 
65
67
  ## 🔧 Installation
66
68
 
69
+ We recommend using [uv](https://docs.astral.sh/uv/) for fast, reliable Python project management.
70
+
67
71
  ```bash
68
- # Install from PyPI
72
+ # Install from PyPI using uv
73
+ uv pip install mojentic
74
+
75
+ # Or with pip
69
76
  pip install mojentic
70
77
  ```
71
78
 
72
- Or install from source
79
+ Or install from source:
73
80
 
74
81
  ```bash
75
82
  git clone https://github.com/svetzal/mojentic.git
76
83
  cd mojentic
84
+
85
+ # Using uv (recommended)
86
+ uv sync
87
+
88
+ # Or with pip
77
89
  pip install -e .
78
90
  ```
79
91
 
@@ -91,7 +103,7 @@ openai_llm = LLMBroker(model="gpt-5", gateway=OpenAIGateway(api_key="your_api_ke
91
103
  # Or use other models: "gpt-4o", "gpt-4.1", "o1-mini", "o3-mini", etc.
92
104
 
93
105
  # Or use Ollama for local LLMs
94
- ollama_llm = LLMBroker(model="llama3")
106
+ ollama_llm = LLMBroker(model="qwen3:32b")
95
107
 
96
108
  # Simple text generation
97
109
  result = openai_llm.generate(messages=[LLMMessage(content='Hello, how are you?')])
@@ -121,6 +133,35 @@ result = openai_llm.generate(messages=[
121
133
  print(result)
122
134
  ```
123
135
 
136
+ ## 🔑 OpenAI configuration
137
+
138
+ OpenAIGateway now supports environment-variable defaults so you can get started without hardcoding secrets:
139
+
140
+ - If you omit `api_key`, it will use the `OPENAI_API_KEY` environment variable.
141
+ - If you omit `base_url`, it will use the `OPENAI_API_ENDPOINT` environment variable (useful for custom endpoints like Azure/OpenAI-compatible proxies).
142
+ - Precedence: values you pass explicitly to `OpenAIGateway(api_key=..., base_url=...)` always override environment variables.
143
+
144
+ Examples:
145
+
146
+ ```python
147
+ from mojentic.llm import LLMBroker
148
+ from mojentic.llm.gateways import OpenAIGateway
149
+
150
+ # 1) Easiest: rely on environment variables
151
+ # export OPENAI_API_KEY=sk-...
152
+ # export OPENAI_API_ENDPOINT=https://api.openai.com/v1 # optional
153
+ llm = LLMBroker(
154
+ model="gpt-4o-mini",
155
+ gateway=OpenAIGateway() # picks up OPENAI_API_KEY/OPENAI_API_ENDPOINT automatically
156
+ )
157
+
158
+ # 2) Explicitly override one or both values
159
+ llm = LLMBroker(
160
+ model="gpt-4o-mini",
161
+ gateway=OpenAIGateway(api_key="your_key", base_url="https://api.openai.com/v1")
162
+ )
163
+ ```
164
+
124
165
  ## 🤖 OpenAI Model Support
125
166
 
126
167
  The framework automatically handles parameter differences between model types, so you can switch between any models without code changes.
@@ -163,11 +204,19 @@ Visit [the documentation](https://svetzal.github.io/mojentic/) for comprehensive
163
204
  git clone https://github.com/svetzal/mojentic.git
164
205
  cd mojentic
165
206
 
166
- # Install dependencies
207
+ # Using uv (recommended)
208
+ uv sync --extra dev
209
+
210
+ # Or with pip
167
211
  pip install -e ".[dev]"
168
212
 
169
213
  # Run tests
170
214
  pytest
215
+
216
+ # Quality checks
217
+ flake8 src # Linting
218
+ bandit -r src # Security scan
219
+ pip-audit # Dependency vulnerabilities
171
220
  ```
172
221
 
173
222
  ## ✅ Project Status
@@ -25,16 +25,26 @@ Mojentic is a framework that provides a simple and flexible way to interact with
25
25
 
26
26
  ## 🔧 Installation
27
27
 
28
+ We recommend using [uv](https://docs.astral.sh/uv/) for fast, reliable Python project management.
29
+
28
30
  ```bash
29
- # Install from PyPI
31
+ # Install from PyPI using uv
32
+ uv pip install mojentic
33
+
34
+ # Or with pip
30
35
  pip install mojentic
31
36
  ```
32
37
 
33
- Or install from source
38
+ Or install from source:
34
39
 
35
40
  ```bash
36
41
  git clone https://github.com/svetzal/mojentic.git
37
42
  cd mojentic
43
+
44
+ # Using uv (recommended)
45
+ uv sync
46
+
47
+ # Or with pip
38
48
  pip install -e .
39
49
  ```
40
50
 
@@ -52,7 +62,7 @@ openai_llm = LLMBroker(model="gpt-5", gateway=OpenAIGateway(api_key="your_api_ke
52
62
  # Or use other models: "gpt-4o", "gpt-4.1", "o1-mini", "o3-mini", etc.
53
63
 
54
64
  # Or use Ollama for local LLMs
55
- ollama_llm = LLMBroker(model="llama3")
65
+ ollama_llm = LLMBroker(model="qwen3:32b")
56
66
 
57
67
  # Simple text generation
58
68
  result = openai_llm.generate(messages=[LLMMessage(content='Hello, how are you?')])
@@ -82,6 +92,35 @@ result = openai_llm.generate(messages=[
82
92
  print(result)
83
93
  ```
84
94
 
95
+ ## 🔑 OpenAI configuration
96
+
97
+ OpenAIGateway now supports environment-variable defaults so you can get started without hardcoding secrets:
98
+
99
+ - If you omit `api_key`, it will use the `OPENAI_API_KEY` environment variable.
100
+ - If you omit `base_url`, it will use the `OPENAI_API_ENDPOINT` environment variable (useful for custom endpoints like Azure/OpenAI-compatible proxies).
101
+ - Precedence: values you pass explicitly to `OpenAIGateway(api_key=..., base_url=...)` always override environment variables.
102
+
103
+ Examples:
104
+
105
+ ```python
106
+ from mojentic.llm import LLMBroker
107
+ from mojentic.llm.gateways import OpenAIGateway
108
+
109
+ # 1) Easiest: rely on environment variables
110
+ # export OPENAI_API_KEY=sk-...
111
+ # export OPENAI_API_ENDPOINT=https://api.openai.com/v1 # optional
112
+ llm = LLMBroker(
113
+ model="gpt-4o-mini",
114
+ gateway=OpenAIGateway() # picks up OPENAI_API_KEY/OPENAI_API_ENDPOINT automatically
115
+ )
116
+
117
+ # 2) Explicitly override one or both values
118
+ llm = LLMBroker(
119
+ model="gpt-4o-mini",
120
+ gateway=OpenAIGateway(api_key="your_key", base_url="https://api.openai.com/v1")
121
+ )
122
+ ```
123
+
85
124
  ## 🤖 OpenAI Model Support
86
125
 
87
126
  The framework automatically handles parameter differences between model types, so you can switch between any models without code changes.
@@ -124,11 +163,19 @@ Visit [the documentation](https://svetzal.github.io/mojentic/) for comprehensive
124
163
  git clone https://github.com/svetzal/mojentic.git
125
164
  cd mojentic
126
165
 
127
- # Install dependencies
166
+ # Using uv (recommended)
167
+ uv sync --extra dev
168
+
169
+ # Or with pip
128
170
  pip install -e ".[dev]"
129
171
 
130
172
  # Run tests
131
173
  pytest
174
+
175
+ # Quality checks
176
+ flake8 src # Linting
177
+ bandit -r src # Security scan
178
+ pip-audit # Dependency vulnerabilities
132
179
  ```
133
180
 
134
181
  ## ✅ Project Status
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mojentic"
3
- version = "0.8.4"
3
+ version = "1.0.0"
4
4
  authors = [
5
5
  { name = "Stacey Vetzal", email = "stacey@vetzal.com" },
6
6
  ]
@@ -14,33 +14,35 @@ classifiers = [
14
14
  ]
15
15
 
16
16
  dependencies = [
17
- "pydantic",
18
- "structlog",
19
- "numpy",
20
- "ollama",
21
- "openai",
22
- "anthropic",
23
- "tiktoken",
24
- "parsedatetime",
25
- "pytz",
26
- "serpapi",
27
- "colorama",
17
+ "pydantic>=2.8.0",
18
+ "structlog>=25.5.0",
19
+ "numpy>=2.3.5",
20
+ "ollama>=0.1.6",
21
+ "openai>=2.8.1",
22
+ "anthropic>=0.74.1",
23
+ "tiktoken>=0.12.0",
24
+ "parsedatetime>=2.6",
25
+ "pytz>=2025.2",
26
+ "serpapi>=0.1.5",
27
+ "colorama>=0.4.6",
28
28
  ]
29
29
 
30
30
  [project.optional-dependencies]
31
31
  dev = [
32
- "pytest>=7.0.0",
33
- "pytest-asyncio",
34
- "pytest-spec",
35
- "pytest-cov",
32
+ "pytest>=9.0.1",
33
+ "pytest-asyncio>=1.3.0",
34
+ "pytest-spec>=5.2.0",
35
+ "pytest-cov>=7.0.0",
36
36
  "pytest-mock>=3.10.0",
37
- "flake8>=6.0.0",
38
- "mkdocs",
39
- "mkdocs-material",
40
- "mkdocs-llmstxt",
41
- "mkdocstrings[python]",
42
- "griffe-fieldz",
43
- "pymdown-extensions",
37
+ "flake8>=7.3.0",
38
+ "bandit>=1.7.0",
39
+ "pip-audit>=2.0.0",
40
+ "mkdocs>=1.5.2",
41
+ "mkdocs-material>=9.6.10",
42
+ "mkdocs-llmstxt>=0.4.0",
43
+ "mkdocstrings[python]>=0.21.0",
44
+ "griffe-fieldz>=0.3.0",
45
+ "pymdown-extensions>=10.3",
44
46
  ]
45
47
 
46
48
  [build-system]
@@ -62,7 +62,11 @@ class TextAnalyzerAgent(BaseAsyncLLMAgent):
62
62
  def __init__(self, llm: LLMBroker):
63
63
  super().__init__(
64
64
  llm=llm,
65
- behaviour="You are a text analysis assistant. Your job is to provide a detailed analysis of the given text, including key themes, structure, and notable elements.",
65
+ behaviour=(
66
+ "You are a text analysis assistant. "
67
+ "Your job is to provide a detailed analysis of the given text, "
68
+ "including key themes, structure, and notable elements."
69
+ ),
66
70
  response_model=AnalysisResponse
67
71
  )
68
72
 
@@ -93,7 +97,11 @@ class TextSummarizerAgent(BaseAsyncLLMAgent):
93
97
  def __init__(self, llm: LLMBroker):
94
98
  super().__init__(
95
99
  llm=llm,
96
- behaviour="You are a text summarization assistant. Your job is to provide concise, accurate summaries of texts while preserving the key information and main points.",
100
+ behaviour=(
101
+ "You are a text summarization assistant. "
102
+ "Your job is to provide concise, accurate summaries of texts "
103
+ "while preserving the key information and main points."
104
+ ),
97
105
  response_model=SummaryResponse
98
106
  )
99
107
 
@@ -143,8 +151,8 @@ Analysis: {analysis_event.analysis}
143
151
 
144
152
  Summary: {summary_event.summary}
145
153
 
146
- Please create a well-structured, insightful report that integrates the analysis and summary,
147
- highlighting the most important aspects of the text. The report should provide a comprehensive
154
+ Please create a well-structured, insightful report that integrates the analysis and summary,
155
+ highlighting the most important aspects of the text. The report should provide a comprehensive
148
156
  understanding of the text's content, structure, and significance.
149
157
  """
150
158
  # Create a temporary LLM agent to generate the response
@@ -5,14 +5,13 @@ This script shows how to create and use asynchronous LLM agents with the AsyncDi
5
5
  """
6
6
 
7
7
  import asyncio
8
- from typing import List, Optional
8
+ from typing import List
9
9
 
10
10
  from pydantic import BaseModel, Field
11
11
 
12
12
  from mojentic.agents.async_aggregator_agent import AsyncAggregatorAgent
13
13
  from mojentic.agents.async_llm_agent import BaseAsyncLLMAgent
14
14
  from mojentic.async_dispatcher import AsyncDispatcher
15
- from mojentic.context.shared_working_memory import SharedWorkingMemory
16
15
  from mojentic.event import Event
17
16
  from mojentic.llm import LLMBroker
18
17
  from mojentic.router import Router
@@ -0,0 +1,83 @@
1
+ import os
2
+ from mojentic.agents.base_llm_agent import BaseLLMAgent
3
+ from mojentic.llm.llm_broker import LLMBroker
4
+ from mojentic.llm.tools.date_resolver import ResolveDateTool
5
+ from mojentic.llm.tools.file_manager import ListFilesTool, ReadFileTool, WriteFileTool, FilesystemGateway
6
+ from mojentic.llm.tools.tool_wrapper import ToolWrapper
7
+
8
+ #
9
+ # This is largely a fail, but it was interesting.
10
+ #
11
+
12
+ temporal_specialist = BaseLLMAgent(
13
+ llm=LLMBroker(model="qwen3:7b"),
14
+ tools=[ResolveDateTool()],
15
+ behaviour=(
16
+ "You are a historian and sociologist who focuses on sorting out temporal events, "
17
+ "determining what happened or will happen when."
18
+ )
19
+ )
20
+
21
+ if not os.path.exists("local"):
22
+ os.mkdir("local")
23
+
24
+ # Create a filesystem gateway for the local directory
25
+ fs = FilesystemGateway(base_path="local")
26
+
27
+ knowledge_specialist = BaseLLMAgent(
28
+ llm=LLMBroker(model="qwen3:32b"),
29
+ tools=[
30
+ ListFilesTool(fs),
31
+ ReadFileTool(fs),
32
+ WriteFileTool(fs),
33
+ ],
34
+ behaviour=(
35
+ "You are a knowledge management agent who focuses on sorting out facts and information, "
36
+ "able to organize elemental ideas and make connections between them. You can list files "
37
+ "to find out where you stored information, read files to review that information, and "
38
+ "write files to store that information for later retrieval."
39
+ )
40
+ )
41
+
42
+
43
+ coordinator = BaseLLMAgent(
44
+ llm=LLMBroker(model="qwen3:32b"),
45
+ behaviour="You are a coordinator who can manage multiple agents and delegate tasks to them to solve problems.",
46
+ tools=[
47
+ ToolWrapper(
48
+ temporal_specialist,
49
+ "temporal_specialist",
50
+ "A historian and sociologist who focuses on sorting out temporal events, figuring out "
51
+ "dates, determining what happened or will happen when."
52
+ ),
53
+ ToolWrapper(
54
+ knowledge_specialist,
55
+ "knowledge_specialist",
56
+ "A knowledge management specialist who focuses on sorting out facts and information, "
57
+ "able to organize elemental ideas and make connections between them. Can list files to "
58
+ "find out where you stored information, read files to review that information, and "
59
+ "write files to store that information for later retrieval."
60
+ ),
61
+ ]
62
+ )
63
+
64
+ result = coordinator.generate_response(
65
+ """
66
+ I have several things I need to do this week:
67
+
68
+ - On Monday, I need to ensure that I have called Scotiabank and ordered replacement cards for "
69
+ "my current, credit, and line of credit accounts.
70
+ - On Wednesday, I need to drive into Toronto for work. While in Toronto I need to pick up "
71
+ "razors. I need to make sure I see Gregg, Britney and Vikram.
72
+ - On Thursday, I need to ensure I'm up by 7am so that I can be showered and ready for work by 9.
73
+ - On Friday, I need to ensure that I have my laundry done and my bags packed for my trip to "
74
+ "Ottawa.
75
+
76
+ Create me a markdown file for each day of the week, named "YYYY-MM-DD-ToDo.md" where the date "
77
+ "is the date of that day.
78
+ Make a list of to-do items in the markdown file, and add a section for the day's daily notes that "
79
+ "I can fill out each day.
80
+ """
81
+ )
82
+
83
+ print(result)
@@ -1,18 +1,16 @@
1
1
  import logging
2
2
  import os
3
-
4
- from mojentic.llm import LLMBroker
5
- from mojentic.llm.gateways import OpenAIGateway
6
-
7
- logging.basicConfig(level=logging.WARN)
8
-
9
3
  from pathlib import Path
10
4
 
11
5
  from pydantic import BaseModel, Field
12
6
 
7
+ from mojentic.llm import LLMBroker
8
+ from mojentic.llm.gateways import OpenAIGateway
13
9
  from mojentic.llm.gateways.models import LLMMessage
14
10
  from mojentic.llm.tools.date_resolver import ResolveDateTool
15
11
 
12
+ logging.basicConfig(level=logging.WARN)
13
+
16
14
 
17
15
  def openai_llm(model="gpt-5"):
18
16
  api_key = os.getenv("OPENAI_API_KEY")
@@ -21,7 +19,7 @@ def openai_llm(model="gpt-5"):
21
19
  return llm
22
20
 
23
21
 
24
- def ollama_llm(model="llama3.3-70b-32k"):
22
+ def ollama_llm(model="qwen3:32b"):
25
23
  llm = LLMBroker(model=model)
26
24
  return llm
27
25
 
@@ -13,7 +13,7 @@ def openai_llm(model="gpt-4o"):
13
13
  return llm
14
14
 
15
15
 
16
- def ollama_llm(model="llama3.3-70b-32k"):
16
+ def ollama_llm(model="qwen3:32b"):
17
17
  llm = LLMBroker(model=model)
18
18
  return llm
19
19
 
@@ -1,7 +1,7 @@
1
1
  from ollama import chat
2
2
  from pydantic import BaseModel, Field
3
3
 
4
- from mojentic.llm.gateways.models import LLMMessage, MessageRole
4
+ from mojentic.llm.gateways.models import LLMMessage
5
5
  from mojentic.llm.gateways.ollama import OllamaGateway
6
6
  from mojentic.llm.tools.date_resolver import ResolveDateTool
7
7
 
@@ -13,7 +13,7 @@ def check_ollama_gateway():
13
13
  label: str = Field(..., description="The label describing the feeling.")
14
14
 
15
15
  response = gateway.complete(
16
- model="llama3.2:1b",
16
+ model="qwen3:7b",
17
17
  messages=[LLMMessage(content="Hello, how are you?")],
18
18
  object_model=Feeling,
19
19
  temperature=1.0,
@@ -25,7 +25,7 @@ def check_ollama_gateway():
25
25
 
26
26
  def check_tools_call():
27
27
  response = chat(
28
- model="llama3.3-70b-32k",
28
+ model="qwen3:32b",
29
29
  messages=[
30
30
  # {
31
31
  # 'role': 'user',
@@ -2,7 +2,7 @@ import os
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
- from mojentic.llm.gateways.models import LLMMessage, MessageRole
5
+ from mojentic.llm.gateways.models import LLMMessage
6
6
  from mojentic.llm.gateways.openai import OpenAIGateway
7
7
 
8
8
  api_key = os.getenv("OPENAI_API_KEY")
@@ -1,6 +1,6 @@
1
1
  from mojentic.llm import ChatSession, LLMBroker
2
2
 
3
- llm_broker = LLMBroker(model="llama3.3-70b-32k")
3
+ llm_broker = LLMBroker(model="qwen3:32b")
4
4
  chat_session = ChatSession(llm_broker)
5
5
 
6
6
  while True:
@@ -9,4 +9,4 @@ while True:
9
9
  break
10
10
  else:
11
11
  response = chat_session.send(query)
12
- print(response)
12
+ print(response)
@@ -1,7 +1,7 @@
1
1
  from mojentic.llm import ChatSession, LLMBroker
2
2
  from mojentic.llm.tools.date_resolver import ResolveDateTool
3
3
 
4
- llm_broker = LLMBroker(model="llama3.3-70b-32k")
4
+ llm_broker = LLMBroker(model="qwen3:32b")
5
5
  chat_session = ChatSession(llm_broker, tools=[ResolveDateTool()])
6
6
 
7
7
  while True:
@@ -10,4 +10,4 @@ while True:
10
10
  break
11
11
  else:
12
12
  response = chat_session.send(query)
13
- print(response)
13
+ print(response)
@@ -30,7 +30,7 @@ from mojentic.llm.tools.file_manager import (
30
30
  EditFileWithDiffTool, CreateDirectoryTool, FilesystemGateway
31
31
  )
32
32
 
33
- base_dir = Path(__file__).parent.parent.parent.parent / "code-playground2"
33
+ base_dir = Path(__file__).parent.parent.parent.parent / "code-playground3"
34
34
 
35
35
  # Initialize the LLM broker
36
36
 
@@ -38,9 +38,7 @@ api_key = os.getenv("OPENAI_API_KEY")
38
38
  gateway = OpenAIGateway(api_key)
39
39
  llm = LLMBroker(model="o4-mini", gateway=gateway)
40
40
 
41
- # llm = LLMBroker("qwen2.5-coder:32b")
42
- # llm = LLMBroker("llama3.3")
43
- # llm = LLMBroker(model="qwen3-128k:32b")
41
+ llm = LLMBroker("qwen3-coder:30b")
44
42
 
45
43
  # Create a filesystem gateway for the sandbox
46
44
  fs = FilesystemGateway(base_path=str(base_dir))
@@ -75,28 +73,28 @@ solver = IterativeProblemSolver(
75
73
  system_prompt="""
76
74
  # 0 - Project Identity & Context
77
75
 
78
- You are an expert and principled software engineer, well versed in writing Python games. You work
79
- carefully and purposefully and always check your work with an eye to testability and correctness.
80
- You know that every line of code you write is a liability, and you take care that every line
76
+ You are an expert and principled software engineer, well versed in writing Python games. You work
77
+ carefully and purposefully and always check your work with an eye to testability and correctness.
78
+ You know that every line of code you write is a liability, and you take care that every line
81
79
  matters.
82
80
 
83
81
  # 1 - Universal Engineering Principles
84
82
 
85
83
  * **Code is communication** — optimise for the next human reader.
86
- * **Simple Design Heuristics** — guiding principles, not iron laws; consult the user when you
84
+ * **Simple Design Heuristics** — guiding principles, not iron laws; consult the user when you
87
85
  need to break them.
88
86
  1. **All tests pass** — correctness is non‑negotiable.
89
87
  2. **Reveals intent** — code should read like an explanation.
90
- 3. **No *****knowledge***** duplication** — avoid multiple spots that must change together;
88
+ 3. **No *****knowledge***** duplication** — avoid multiple spots that must change together;
91
89
  identical code is only a smell when it hides duplicate *decisions*.
92
90
  4. **Minimal entities** — remove unnecessary indirection, classes, or parameters.
93
91
  * **Small, safe increments** — single‑reason commits; avoid speculative work (**YAGNI**).
94
92
  * **Tests are the executable spec** — red first, green always; test behaviour not implementation.
95
93
  * **Compose over inherit**; favour pure functions where practical, avoid side-effects.
96
- * **Functional core, imperative shell** — isolate pure business logic from I/O and side effects;
94
+ * **Functional core, imperative shell** — isolate pure business logic from I/O and side effects;
97
95
  push mutations to the system boundaries, build mockable gateways at those boundaries.
98
96
  * **Psychological safety** — review code, not colleagues; critique ideas, not authors.
99
- * **Version‑control etiquette** — descriptive commit messages, branch from `main`, PRs require
97
+ * **Version‑control etiquette** — descriptive commit messages, branch from `main`, PRs require
100
98
  green CI.
101
99
 
102
100
  # 2 - Python‑Specific Conventions
@@ -115,21 +113,21 @@ green CI.
115
113
 
116
114
  ## 2.2 Core Libraries
117
115
 
118
- Mandatory: pydantic, structlog, pytest, pytest-spec, pytest-cov, pytest-mock, flake8, black,
119
- pre‑commit, mkdocs‑material. Add new libs only when they eliminate **significant** boilerplate or
116
+ Mandatory: pydantic, structlog, pytest, pytest-spec, pytest-cov, pytest-mock, flake8, black,
117
+ pre‑commit, mkdocs‑material. Add new libs only when they eliminate **significant** boilerplate or
120
118
  risk.
121
119
 
122
120
  ## 2.3 Project Structure & Imports
123
121
 
124
122
  * **src‑layout**: code in `src/<package_name>/`; tests live beside code as `*_spec.py`.
125
- * Import order: 1) stdlib, 2) third‑party, 3) first‑party — each group alphabetised with a blank
123
+ * Import order: 1) stdlib, 2) third‑party, 3) first‑party — each group alphabetised with a blank
126
124
  line.
127
125
 
128
126
  ## 2.4 Naming & Style
129
127
 
130
128
  * `snake_case` for functions & vars, `PascalCase` for classes, `UPPER_SNAKE` for constants.
131
129
  * Prefix intentionally unused vars/args with `_`.
132
- * **flake8** (with plugins) handles linting, and **black** auto‑formats code. Max line length
130
+ * **flake8** (with plugins) handles linting, and **black** auto‑formats code. Max line length
133
131
  **100**.
134
132
  * Cyclomatic complexity cap: **10** (flake8 `C901`).
135
133
  * Use **f‑strings**; avoid magic numbers.
@@ -144,14 +142,14 @@ line.
144
142
 
145
143
  * Configure **structlog** for JSON output by default.
146
144
  * Never use `print` for diagnostics; reserve for user‑facing CLI UX.
147
- * Log levels: `DEBUG` (dev detail) → `INFO` (lifecycle) → `WARNING` (recoverable) → `ERROR` (user
145
+ * Log levels: `DEBUG` (dev detail) → `INFO` (lifecycle) → `WARNING` (recoverable) → `ERROR` (user
148
146
  visible).
149
147
 
150
148
  ## 2.7 Testing Strategy
151
149
 
152
150
  * **pytest** with **pytest-spec** for specification-style output.
153
151
  * Test files end with `_spec.py` and live in the same folder as the code under test.
154
- * Use **Arrange / Act / Assert** blocks separated by a blank line (no comments) **or** BDD
152
+ * Use **Arrange / Act / Assert** blocks separated by a blank line (no comments) **or** BDD
155
153
  `describe/should` classes.
156
154
  * Function names: use `should_*` and BDD-style specifications.
157
155
  * Class names: use `Describe*` and BDD-style test suites.
@@ -162,7 +160,7 @@ visible).
162
160
  # 3 - Planning and Goal Tracking
163
161
 
164
162
  - Use the provided task manager tools to create your plans and work through them step by step.
165
- - Before declaring yourself finished list all tasks, ensure they are all complete, and that you
163
+ - Before declaring yourself finished list all tasks, ensure they are all complete, and that you
166
164
  have not missed any steps
167
165
  - If you've missed or forgotten some steps, add them to the task list and continue
168
166
  - When all tasks are complete, and you can think of no more to add, declare yourself finished.