ollama_agent 0.3.0 → 1.0.1

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 (241) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/scheduled_tasks.lock +1 -0
  3. data/.env.example +21 -1
  4. data/CHANGELOG.md +47 -0
  5. data/CONTRIBUTING.md +23 -0
  6. data/README.md +308 -9
  7. data/Rakefile +2 -0
  8. data/config/ollama_agent/owners.yml +53 -0
  9. data/containers/ollama_agent-verification-sandbox.Dockerfile +16 -0
  10. data/db/ollama_agent/migrations/0001_initial.sql +102 -0
  11. data/db/ollama_agent/migrations/0002_cost_ledger.sql +12 -0
  12. data/db/ollama_agent/schema.sql +1 -0
  13. data/docs/CAPABILITIES.md +91 -0
  14. data/docs/CLI.md +212 -0
  15. data/docs/FEATURES.md +1298 -0
  16. data/docs/OPERATIONS.md +179 -0
  17. data/docs/TOOLS.md +104 -1
  18. data/docs/TRADING_AGENT_PLAN.md +88 -0
  19. data/docs/USAGE.md +246 -0
  20. data/docs/agile/docker_spec_activation.md +55 -0
  21. data/docs/agile/release_rollout_runbook.md +125 -0
  22. data/docs/agile/runtime_kernel_backlog.md +173 -0
  23. data/docs/agile/runtime_kernel_risk_register.md +27 -0
  24. data/docs/agile/separate_server_strategy.md +25 -0
  25. data/docs/agile/sprint_0_setup.md +52 -0
  26. data/docs/agile/sprint_execution_map.md +24 -0
  27. data/docs/new_features copy.md +14446 -0
  28. data/docs/new_features.md +11771 -0
  29. data/docs/new_features_plan.md +246 -0
  30. data/docs/new_features_plan_v2.md +223 -0
  31. data/docs/superpowers/plans/2026-05-28-interactive-completion-system.md +697 -0
  32. data/docs/superpowers/plans/2026-05-29-session-runtime-integration.md +966 -0
  33. data/docs/superpowers/specs/2026-05-29-session-runtime-integration-design.md +318 -0
  34. data/docs/trading_agent.md +3180 -0
  35. data/examples/agentic_tool_calling.rb +104 -0
  36. data/lib/ollama_agent/agent/agent_config.rb +23 -2
  37. data/lib/ollama_agent/agent/chat_coordinator.rb +88 -0
  38. data/lib/ollama_agent/agent/client_wiring.rb +3 -8
  39. data/lib/ollama_agent/agent/prompt_wiring.rb +1 -1
  40. data/lib/ollama_agent/agent/session_wiring.rb +36 -6
  41. data/lib/ollama_agent/agent/turn_loop.rb +89 -0
  42. data/lib/ollama_agent/agent.rb +153 -85
  43. data/lib/ollama_agent/agent_root_resolver.rb +23 -0
  44. data/lib/ollama_agent/cli/health_command.rb +45 -0
  45. data/lib/ollama_agent/cli/repl.rb +159 -0
  46. data/lib/ollama_agent/cli/repl_shared.rb +336 -0
  47. data/lib/ollama_agent/cli/skill_command.rb +71 -0
  48. data/lib/ollama_agent/cli/tui_repl.rb +202 -0
  49. data/lib/ollama_agent/cli.rb +149 -53
  50. data/lib/ollama_agent/console.rb +2 -2
  51. data/lib/ollama_agent/context/token_counter.rb +5 -0
  52. data/lib/ollama_agent/core/action_envelope.rb +82 -0
  53. data/lib/ollama_agent/core/budget.rb +90 -0
  54. data/lib/ollama_agent/core/loop_detector.rb +67 -0
  55. data/lib/ollama_agent/core/schema_validator.rb +135 -0
  56. data/lib/ollama_agent/core/trace_logger.rb +140 -0
  57. data/lib/ollama_agent/errors.rb +61 -0
  58. data/lib/ollama_agent/external_agents/default_agents.yml +11 -2
  59. data/lib/ollama_agent/external_agents/path_validator.rb +1 -3
  60. data/lib/ollama_agent/external_agents/probe.rb +56 -8
  61. data/lib/ollama_agent/external_agents/runner.rb +22 -43
  62. data/lib/ollama_agent/external_agents.rb +4 -0
  63. data/lib/ollama_agent/gemma_thought_content_parser.rb +211 -0
  64. data/lib/ollama_agent/indexing/context_packer.rb +140 -0
  65. data/lib/ollama_agent/indexing/diff_summarizer.rb +125 -0
  66. data/lib/ollama_agent/indexing/file_indexer.rb +129 -0
  67. data/lib/ollama_agent/indexing/repo_scanner.rb +158 -0
  68. data/lib/ollama_agent/llm/anthropic_client.rb +263 -0
  69. data/lib/ollama_agent/llm/cloud_fallback_router.rb +111 -0
  70. data/lib/ollama_agent/llm/context_builder.rb +57 -0
  71. data/lib/ollama_agent/llm/first_json_object.rb +41 -0
  72. data/lib/ollama_agent/llm/planner.rb +140 -0
  73. data/lib/ollama_agent/llm/planner_schema.rb +90 -0
  74. data/lib/ollama_agent/llm/think_block_stripper.rb +16 -0
  75. data/lib/ollama_agent/memory/long_term.rb +113 -0
  76. data/lib/ollama_agent/memory/manager.rb +120 -0
  77. data/lib/ollama_agent/memory/session_memory.rb +93 -0
  78. data/lib/ollama_agent/memory/short_term.rb +66 -0
  79. data/lib/ollama_agent/ollama_chat_thinking_stream.rb +25 -0
  80. data/lib/ollama_agent/ollama_cloud_catalog.rb +85 -0
  81. data/lib/ollama_agent/ollama_connection.rb +39 -0
  82. data/lib/ollama_agent/plugins/loader.rb +95 -0
  83. data/lib/ollama_agent/plugins/registry.rb +107 -0
  84. data/lib/ollama_agent/providers/anthropic.rb +261 -0
  85. data/lib/ollama_agent/providers/base.rb +85 -0
  86. data/lib/ollama_agent/providers/credential.rb +156 -0
  87. data/lib/ollama_agent/providers/credential_pool.rb +96 -0
  88. data/lib/ollama_agent/providers/credential_router.rb +130 -0
  89. data/lib/ollama_agent/providers/error_classifier.rb +107 -0
  90. data/lib/ollama_agent/providers/health_monitor.rb +133 -0
  91. data/lib/ollama_agent/providers/model_descriptor.rb +45 -0
  92. data/lib/ollama_agent/providers/model_registry.rb +207 -0
  93. data/lib/ollama_agent/providers/ollama.rb +184 -0
  94. data/lib/ollama_agent/providers/openai.rb +232 -0
  95. data/lib/ollama_agent/providers/quota_tracker.rb +144 -0
  96. data/lib/ollama_agent/providers/rate_window.rb +57 -0
  97. data/lib/ollama_agent/providers/registry.rb +162 -0
  98. data/lib/ollama_agent/providers/router.rb +91 -0
  99. data/lib/ollama_agent/repo_list.rb +22 -10
  100. data/lib/ollama_agent/resilience/retry_middleware.rb +7 -0
  101. data/lib/ollama_agent/ruby_index_tool_support.rb +11 -1
  102. data/lib/ollama_agent/runner.rb +97 -10
  103. data/lib/ollama_agent/runtime/approval_gate.rb +74 -0
  104. data/lib/ollama_agent/runtime/atomic_mutator.rb +366 -0
  105. data/lib/ollama_agent/runtime/blob_store.rb +98 -0
  106. data/lib/ollama_agent/runtime/cas_guard.rb +52 -0
  107. data/lib/ollama_agent/runtime/compactor.rb +250 -0
  108. data/lib/ollama_agent/runtime/compactor_runner.rb +25 -0
  109. data/lib/ollama_agent/runtime/compensation_engine.rb +85 -0
  110. data/lib/ollama_agent/runtime/compensation_manifest.rb +66 -0
  111. data/lib/ollama_agent/runtime/cost_ledger.rb +53 -0
  112. data/lib/ollama_agent/runtime/criticality_policy.rb +51 -0
  113. data/lib/ollama_agent/runtime/database_registry.rb +74 -0
  114. data/lib/ollama_agent/runtime/event_store.rb +80 -0
  115. data/lib/ollama_agent/runtime/execution_context.rb +21 -0
  116. data/lib/ollama_agent/runtime/execution_manifest.rb +41 -0
  117. data/lib/ollama_agent/runtime/execution_mode.rb +22 -0
  118. data/lib/ollama_agent/runtime/fencing_allocator.rb +30 -0
  119. data/lib/ollama_agent/runtime/file_atomic_swap.rb +76 -0
  120. data/lib/ollama_agent/runtime/integration_queue.rb +61 -0
  121. data/lib/ollama_agent/runtime/intent_reservation.rb +115 -0
  122. data/lib/ollama_agent/runtime/intent_translator.rb +193 -0
  123. data/lib/ollama_agent/runtime/isolated_validator.rb +217 -0
  124. data/lib/ollama_agent/runtime/kernel_bridge.rb +280 -0
  125. data/lib/ollama_agent/runtime/kernel_event_logger.rb +53 -0
  126. data/lib/ollama_agent/runtime/kernel_feature.rb +19 -0
  127. data/lib/ollama_agent/runtime/kernel_health.rb +96 -0
  128. data/lib/ollama_agent/runtime/kernel_pipeline.rb +691 -0
  129. data/lib/ollama_agent/runtime/kernel_pipeline_assembly.rb +119 -0
  130. data/lib/ollama_agent/runtime/kernel_tool_seed.rb +235 -0
  131. data/lib/ollama_agent/runtime/lock_manager.rb +153 -0
  132. data/lib/ollama_agent/runtime/logical_clock.rb +22 -0
  133. data/lib/ollama_agent/runtime/mutation_classifier.rb +24 -0
  134. data/lib/ollama_agent/runtime/permission_bridge.rb +89 -0
  135. data/lib/ollama_agent/runtime/permissions.rb +103 -0
  136. data/lib/ollama_agent/runtime/policies.rb +96 -0
  137. data/lib/ollama_agent/runtime/post_condition_verifier.rb +52 -0
  138. data/lib/ollama_agent/runtime/rollback_signals.rb +87 -0
  139. data/lib/ollama_agent/runtime/saga_coordinator.rb +224 -0
  140. data/lib/ollama_agent/runtime/saga_recovery_daemon.rb +91 -0
  141. data/lib/ollama_agent/runtime/saga_state.rb +44 -0
  142. data/lib/ollama_agent/runtime/sandbox.rb +130 -0
  143. data/lib/ollama_agent/runtime/schema_migrator.rb +138 -0
  144. data/lib/ollama_agent/runtime/unified_diff_apply.rb +102 -0
  145. data/lib/ollama_agent/runtime/wal.rb +54 -0
  146. data/lib/ollama_agent/runtime/workspace_wal_replay.rb +72 -0
  147. data/lib/ollama_agent/runtime_command_system/ast.rb +96 -0
  148. data/lib/ollama_agent/runtime_command_system/command_palette.rb +53 -0
  149. data/lib/ollama_agent/runtime_command_system/command_registry.rb +48 -0
  150. data/lib/ollama_agent/runtime_command_system/completers.rb +83 -0
  151. data/lib/ollama_agent/runtime_command_system/dispatch/dispatcher.rb +36 -0
  152. data/lib/ollama_agent/runtime_command_system/dispatch/handlers/model_handler.rb +20 -0
  153. data/lib/ollama_agent/runtime_command_system/dispatch/handlers/provider_handler.rb +19 -0
  154. data/lib/ollama_agent/runtime_command_system/ghost_text.rb +34 -0
  155. data/lib/ollama_agent/runtime_command_system/input_buffer.rb +43 -0
  156. data/lib/ollama_agent/runtime_command_system/interactive_menu.rb +48 -0
  157. data/lib/ollama_agent/runtime_command_system/session/events.rb +32 -0
  158. data/lib/ollama_agent/runtime_command_system/session/runtime.rb +40 -0
  159. data/lib/ollama_agent/runtime_command_system/suggestion.rb +30 -0
  160. data/lib/ollama_agent/runtime_command_system/suggestion_engine.rb +41 -0
  161. data/lib/ollama_agent/runtime_command_system.rb +11 -0
  162. data/lib/ollama_agent/sandboxed_tools/search_text.rb +27 -4
  163. data/lib/ollama_agent/sandboxed_tools.rb +20 -7
  164. data/lib/ollama_agent/security/ownership_compile_validators.rb +77 -0
  165. data/lib/ollama_agent/security/ownership_compiler.rb +89 -0
  166. data/lib/ollama_agent/security/ownership_index.rb +63 -0
  167. data/lib/ollama_agent/security/ownership_rule_tree_flattener.rb +122 -0
  168. data/lib/ollama_agent/security/resource_guard.rb +55 -0
  169. data/lib/ollama_agent/skills/architecture_refactorer.rb +52 -0
  170. data/lib/ollama_agent/skills/base.rb +57 -0
  171. data/lib/ollama_agent/skills/debug_engineer.rb +53 -0
  172. data/lib/ollama_agent/skills/feature_builder.rb +59 -0
  173. data/lib/ollama_agent/skills/json_extractor.rb +81 -0
  174. data/lib/ollama_agent/skills/llm_client.rb +42 -0
  175. data/lib/ollama_agent/skills/performance_optimizer.rb +48 -0
  176. data/lib/ollama_agent/skills/registry.rb +39 -0
  177. data/lib/ollama_agent/skills/runner.rb +35 -0
  178. data/lib/ollama_agent/state/ast_summarizer.rb +167 -0
  179. data/lib/ollama_agent/state/git_changed_paths.rb +22 -0
  180. data/lib/ollama_agent/state/reconciler.rb +29 -0
  181. data/lib/ollama_agent/state/reentry_packet.rb +53 -0
  182. data/lib/ollama_agent/state/tree_digest.rb +20 -0
  183. data/lib/ollama_agent/state/workspace_fingerprint.rb +48 -0
  184. data/lib/ollama_agent/streaming/hooks.rb +3 -1
  185. data/lib/ollama_agent/synthesis/event_schema_registry.rb +98 -0
  186. data/lib/ollama_agent/synthesis/integration_extractor/concerns.rb +174 -0
  187. data/lib/ollama_agent/synthesis/integration_extractor.rb +106 -0
  188. data/lib/ollama_agent/synthesis/integration_scan.rb +8 -0
  189. data/lib/ollama_agent/synthesis/route_synthesizer.rb +58 -0
  190. data/lib/ollama_agent/synthesis/sidekiq_synthesizer.rb +24 -0
  191. data/lib/ollama_agent/think_param.rb +23 -1
  192. data/lib/ollama_agent/tool_arguments.rb +9 -0
  193. data/lib/ollama_agent/tool_runtime/supervisor.rb +111 -0
  194. data/lib/ollama_agent/tool_runtime/tool_registry.rb +52 -0
  195. data/lib/ollama_agent/tool_runtime.rb +4 -0
  196. data/lib/ollama_agent/tools/base.rb +106 -0
  197. data/lib/ollama_agent/tools/built_in_schemas.rb +234 -0
  198. data/lib/ollama_agent/tools/filesystem_explorer.rb +77 -0
  199. data/lib/ollama_agent/tools/git_tools.rb +176 -0
  200. data/lib/ollama_agent/tools/http_tools.rb +207 -0
  201. data/lib/ollama_agent/tools/memory_tools.rb +116 -0
  202. data/lib/ollama_agent/tools/safe_calculator.rb +203 -0
  203. data/lib/ollama_agent/tools/shell_tools.rb +257 -0
  204. data/lib/ollama_agent/tools_schema.rb +15 -144
  205. data/lib/ollama_agent/topology/class_node_merger.rb +54 -0
  206. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor/concern_body.rb +45 -0
  207. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor/ir_nodes_emitter.rb +65 -0
  208. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor/mixin_dispatch.rb +101 -0
  209. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor/navigation.rb +124 -0
  210. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor/parameter_list.rb +71 -0
  211. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor/semantic_context.rb +40 -0
  212. data/lib/ollama_agent/topology/extractors/ruby_semantic_extractor.rb +67 -0
  213. data/lib/ollama_agent/topology/ir/callback_node.rb +38 -0
  214. data/lib/ollama_agent/topology/ir/class_node.rb +41 -0
  215. data/lib/ollama_agent/topology/ir/concern_node.rb +41 -0
  216. data/lib/ollama_agent/topology/ir/event_publisher_node.rb +38 -0
  217. data/lib/ollama_agent/topology/ir/module_node.rb +40 -0
  218. data/lib/ollama_agent/topology/ir/node.rb +30 -0
  219. data/lib/ollama_agent/topology/ir/route_node.rb +41 -0
  220. data/lib/ollama_agent/topology/ir/worker_node.rb +38 -0
  221. data/lib/ollama_agent/topology/linker/aggregate.rb +93 -0
  222. data/lib/ollama_agent/topology/linker/discovery.rb +33 -0
  223. data/lib/ollama_agent/topology/linker/extract.rb +35 -0
  224. data/lib/ollama_agent/topology/linker/link.rb +56 -0
  225. data/lib/ollama_agent/topology/linker/resolve.rb +42 -0
  226. data/lib/ollama_agent/topology/linker/validate.rb +97 -0
  227. data/lib/ollama_agent/topology/linker.rb +90 -0
  228. data/lib/ollama_agent/topology/signature_normalizer.rb +74 -0
  229. data/lib/ollama_agent/topology/staged_graph/symbol_ids.rb +111 -0
  230. data/lib/ollama_agent/topology/staged_graph.rb +115 -0
  231. data/lib/ollama_agent/topology/symbol_graph.rb +50 -0
  232. data/lib/ollama_agent/topology/symbol_identity.rb +42 -0
  233. data/lib/ollama_agent/topology/zeitwerk_inflector.rb +71 -0
  234. data/lib/ollama_agent/tui.rb +316 -0
  235. data/lib/ollama_agent/tui_slash_reader.rb +255 -0
  236. data/lib/ollama_agent/tui_user_prompt.rb +45 -0
  237. data/lib/ollama_agent/version.rb +1 -1
  238. data/lib/ollama_agent.rb +148 -3
  239. data/lib/tasks/docker_validator.rake +16 -0
  240. metadata +324 -6
  241. data/reproduce_429.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3518f124795a2efb13b9e88abb2b7c90f935c918da3c7d30458ff2fff245f1b
4
- data.tar.gz: 101d5e4bdbfbd5e6196e9744c94f1803c1ca32fea746b275cda8ad30783788d1
3
+ metadata.gz: 4d56b99449761370899bf612059caed5b1d7f58e0f4495b71cee23409b5cfb14
4
+ data.tar.gz: 3944154608557316fcb1b6126a39a9e94d63e8cf5fb86b41e96875c229c9a74c
5
5
  SHA512:
6
- metadata.gz: 1aad321bf07bb4ddea9daffeca46724e842408ac3f5d96997d77c3f81b30f10a3b1d28a4f8260bc14cde249cb63069e3fcc5aa1aecc982c0852a979733a2b1b3
7
- data.tar.gz: 5d67531648baf16bf30b63e9d3726f14243d19d939f56875fb245254ad9538eded9ea4c1fe3e38cf1ded9d93b77f392f1ac4145ae0e351b654a531802696f230
6
+ metadata.gz: da1673330de3f111382ec58b124b5b37fa4b973b4bdba0a1931449a06fc54963b0f5af1887ba5515d6ebd9fc4b56469c70164ef3378e68dbcc9cb5ca6f0518dd
7
+ data.tar.gz: c880764ea78000b140118e542cd77e1b8d3a8d06c37ba79cdfe2966c5bde21fb5bd43af5485799ef5044f37cdb55e5eba33a27b12a3bd2701bd7dced1e9f0ca2
@@ -0,0 +1 @@
1
+ {"sessionId":"721fd992-6855-4945-b1c5-b7cef534bb84","pid":1585817,"procStart":"20606262","acquiredAt":1778253145677}
data/.env.example CHANGED
@@ -1,4 +1,21 @@
1
1
  # Example environment variables for OllamaAgent gem
2
+ #
3
+ # Dotenv precedence (highest first):
4
+ # 1. XDG global config: ~/.config/ollama_agent/.env (or $OLLAMA_AGENT_DOTENV_PATH)
5
+ # 2. Local project .env (loaded by ollama-client gem via Dotenv.overload)
6
+ # 3. Shell ENV
7
+ # To bypass the XDG global and use only the local project .env + shell ENV:
8
+ # OLLAMA_AGENT_USE_LOCAL_DOTENV=1
9
+ #
10
+ # Kernel runtime feature flag (see docs/USAGE.md):
11
+ # OLLAMA_AGENT_KERNEL=false # default — legacy tool path
12
+ # OLLAMA_AGENT_KERNEL=shadow # full saga FSM, no disk writes (observation)
13
+ # OLLAMA_AGENT_KERNEL=true # mutations route through saga + atomic mutator
14
+ #
15
+ # Docker validator opt-in (see docs/agile/docker_spec_activation.md):
16
+ # DOCKER_AVAILABLE=true
17
+ # OLLAMA_AGENT_VALIDATOR_IMAGE=ollama-agent-validator:dev
18
+
2
19
  # Disable colored output (set to any value to respect https://no-color.org/)
3
20
  NO_COLOR=
4
21
  # Enable colored output (default: enabled). Set to "0" to disable.
@@ -11,8 +28,11 @@ OLLAMA_AGENT_THINKING_MARKDOWN=0
11
28
  OLLAMA_BASE_URL=https://ollama.com
12
29
  # API key for Ollama (if required)
13
30
  OLLAMA_API_KEY=your_api_key_here
14
- # Think parameter: true/false/high/medium/low (default: true)
31
+ # Think parameter: true/false/high/medium/low (default: true). gpt-oss ignores booleans in the API;
32
+ # when think resolves to true, we send low|medium|high (default medium, override below).
15
33
  OLLAMA_AGENT_THINK=1
34
+ # gpt-oss only: low, medium, or high (used when OLLAMA_AGENT_THINK is true)
35
+ # OLLAMA_AGENT_GPT_OSS_THINK=medium
16
36
  # Root directory for the agent (default: current working directory)
17
37
  OLLAMA_AGENT_ROOT=.
18
38
  # Debug mode (set to "1" for verbose output)
data/CHANGELOG.md CHANGED
@@ -1,8 +1,50 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.1] - 2026-06-02
4
+
5
+ ### Added
6
+
7
+ - **Tool `list_directory_contents`** (`OllamaAgent::Tools::FilesystemExplorer`) — inspect files and subdirectories inside the current workspace. All paths are resolved relative to the project root (`context[:root]` / `OLLAMA_AGENT_ROOT`); traversal outside that boundary (including `../../etc` and absolute paths) is rejected before the filesystem is touched. Returns `[DIR]` / `[FILE]` entries with byte sizes.
8
+ - **Tool `calculate`** (`OllamaAgent::Tools::SafeCalculator`) — evaluate an arithmetic expression using a hand-written **Shunting-yard** tokenizer and RPN evaluator. Supports `+`, `-`, `*`, `/`, `**` (right-associative), parentheses, and unary `+`/`-`. **No `eval` is used at any point.** Right-associativity of `**` is correct: `2 ** 3 ** 2` → `512`. Division by zero returns a non-finite message rather than raising.
9
+ - Both tools are available in **all permission profiles** (`read_only`, `standard`, `developer`, `full`), registered in `CORE_TOOLS` so they appear in the Ollama tool schema automatically, and dispatched through the existing `SandboxedTools#execute_tool` loop.
10
+ - `examples/agentic_tool_calling.rb` — runnable demo covering filesystem inspection, arithmetic evaluation, and a combined prompt that chains both tools. Mirrors the KDnuggets *Easy Agentic Tool Calling with Gemma 4* article pattern, adapted to the `OllamaAgent` Ruby runtime.
11
+ - `docs/TOOLS.md` — new **Built-in agentic tools** section documenting `list_directory_contents` and `calculate` with Ruby API examples.
12
+
13
+ ### Removed
14
+
15
+ - **`trading_agent` extraction**: Removed the nested `trading_agent/` project/gem from the `ollama_agent` repository to a dedicated standalone sibling repository (`trading_agent`).
16
+
17
+ ### Documentation
18
+
19
+ - **README:** New **Reasoning / thinking output** section—how to set `think` (including **GPT-OSS** `low`/`medium`/`high`), streaming vs one-shot, display env vars, troubleshooting when **Thinking** is missing, Gemma-style tags, and **`Runner.build`** example. **`OLLAMA_AGENT_STREAM`** and GPT-OSS notes added to the environment table; Ollama Cloud example sets **`OLLAMA_AGENT_THINK`**.
20
+
21
+ ### Fixed
22
+
23
+ - **RuboCop and Style Cleanups**: Fixed 424 RuboCop offenses and refined `.rubocop.yml` parameters.
24
+ - **CI/CD Correction**: Removed obsolete nested `trading_agent` job from CI/CD main workflow.
25
+ - **Thinking display:** Coerce non-string `message.thinking` from the API (e.g. array-shaped chunks) before deciding whether reasoning is present (`GemmaThoughtContentParser.merge_into_message_data!` + `ChatStreamThinkingFormat`). After a **streaming** chat completion, run the same merge so content-embedded reasoning tags still split into **Thinking** / **Assistant**.
26
+
27
+ ## [1.0.0] - 2026-04-11
28
+
29
+ First **stable** release under [Semantic Versioning](https://semver.org/): within the **`1.x`** series, the documented public Ruby API and CLI behavior are intended to remain **backwards compatible** except where called out in the changelog.
30
+
31
+ ### Changed
32
+
33
+ - **CLI default:** `ollama_agent` with no subcommand runs **`ask`** with the **interactive TUI** when no query is given (same as `ollama_agent ask` with no query). Use **`ask -i` without `--tui`** for the plain line REPL, **`ask "…"`** for a one-shot task, or another subcommand (`self_review`, `sessions`, …) for other workflows.
34
+
35
+ ### Breaking changes (from 0.3.x)
36
+
37
+ - **`tty-spinner` removed** from the gem; **`OllamaAgent::TUI`** no longer exposes `with_spinner`, `dismiss_progress_spinner`, `pause_progress_for_user_prompt`, or `resume_progress_after_user_prompt`. Code that called those methods or relied on the transitive `tty-spinner` dependency must be updated.
38
+ - **`Agent#chat_assistant_message`** uses the streaming HTTP path **only** when something subscribes to **`on_token`** (not merely **`on_thinking`**). Integrations that registered only `on_thinking` expecting streamed chat must also subscribe to `on_token` or use another supported hook.
39
+
40
+ ### Fixed
41
+
42
+ - TUI slash-command tab completion no longer raises `FrozenError` when editing a completed frozen candidate (e.g. `/help`).
43
+
3
44
  ## [0.3.0] - 2026-04-06
4
45
 
5
46
  ### Added
47
+
6
48
  - `ToolRuntime` — JSON plan loop for custom tools (`OllamaJsonPlanner`, registry, executor); see `docs/TOOL_RUNTIME.md`
7
49
  - Optional **ruby_mastery** context for `self_review` / `improve` (`OLLAMA_AGENT_RUBY_MASTERY`, `--no-ruby-mastery`)
8
50
  - `OllamaAgent::ModelEnv` — shared model name resolution from environment
@@ -11,14 +53,17 @@
11
53
  - External agents / argv expansion and related orchestration refinements
12
54
 
13
55
  ### Changed
56
+
14
57
  - `SearchBackend` finds `rg` / `grep` by scanning `PATH` (avoids relying on a `command` executable on trimmed `PATH`)
15
58
 
16
59
  ### Fixed
60
+
17
61
  - `SelfImprovement::Improver#run` accepts `max_tokens` and `context_summarize` from the CLI (Ruby 3 keyword compatibility)
18
62
 
19
63
  ## [0.2.0] - 2026-03-26
20
64
 
21
65
  ### Added
66
+
22
67
  - `write_file` tool — create or overwrite files (complements `edit_file` for surgical diffs)
23
68
  - `OllamaAgent::Tools.register` — extensible tool registry for library consumers
24
69
  - `Streaming::Hooks` — event bus (`on_token`, `on_tool_call`, `on_tool_result`, `on_complete`, `on_error`, `on_retry`)
@@ -32,10 +77,12 @@
32
77
  - `docs/ARCHITECTURE.md`, `docs/TOOLS.md`, `docs/SESSIONS.md`
33
78
 
34
79
  ### Changed
80
+
35
81
  - `READ_ONLY_TOOLS` now excludes both `edit_file` and `write_file`
36
82
  - `Agent` now exposes `#hooks` (`Streaming::Hooks`) and `#session_id`
37
83
 
38
84
  ### New environment variables
85
+
39
86
  - `OLLAMA_AGENT_STREAM`, `OLLAMA_AGENT_MAX_TOKENS`
40
87
  - `OLLAMA_AGENT_MAX_RETRIES`, `OLLAMA_AGENT_RETRY_BASE_DELAY`
41
88
  - `OLLAMA_AGENT_AUDIT`, `OLLAMA_AGENT_AUDIT_LOG_PATH`
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,23 @@
1
+ # Contributing
2
+
3
+ ## Tests
4
+
5
+ - Run **`bundle exec rspec`** before opening a PR.
6
+ - Prefer **behavior-focused** examples; stub external binaries (`patch`, `rg`) when asserting failure paths.
7
+ - Add or extend specs when you change the agent loop, tool schemas, sandbox, or budget/loop detection.
8
+
9
+ ## Style
10
+
11
+ - Run **`bundle exec rubocop`**. The repository is moving toward a clean full-tree baseline; CI currently runs RuboCop with **`continue-on-error: true`** until legacy offenses are cleared.
12
+ - Add **`# frozen_string_literal: true`** to new Ruby files.
13
+
14
+ ## Error handling
15
+
16
+ - Do **not** use **`rescue Exception`** (it catches `Interrupt` / `SignalException`). Rescue **`StandardError`** (or narrower types) and either re-raise, wrap in **`OllamaAgent::Error`**, or log and return a safe value—never swallow signals.
17
+
18
+ ## Architecture notes
19
+
20
+ - **Tools:** Prefer **`OllamaAgent::Tools::BuiltInSchemas.register`** for new first-party tool schemas instead of hard-coding branches in unrelated modules.
21
+ - **Logging:** Use the agent’s **`Logger`** (or `OllamaAgent::Agent`’s `logger` reader) instead of bare **`warn`** for user-relevant messages so embedders can stub or redirect output.
22
+
23
+ Thank you for helping improve `ollama_agent`.
data/README.md CHANGED
@@ -1,15 +1,32 @@
1
1
  # ollama_agent
2
2
 
3
- Version: 0.1.0
3
+ Version: 1.0.0
4
4
 
5
5
  Ruby gem that runs a **CLI coding agent** against a local [Ollama](https://ollama.com) model. It exposes tools to **list files**, **read files**, **search the tree** (ripgrep or grep), and **apply unified diffs** so the model can make small, reviewable edits.
6
6
 
7
+ ## Contents
8
+
9
+ - [Features](#features)
10
+ - [Kernel runtime (deterministic execution)](#kernel-runtime-deterministic-execution) — see also [CAPABILITIES](docs/CAPABILITIES.md), [CLI](docs/CLI.md), [OPERATIONS](docs/OPERATIONS.md), [USAGE](docs/USAGE.md)
11
+ - [Requirements](#requirements)
12
+ - [Security and sandbox](#security-and-sandbox)
13
+ - [Installation](#installation)
14
+ - [Usage](#usage)
15
+ - [Agentic tool calling](#agentic-tool-calling-local-environment-tools)
16
+ - [Skills](#skills-deterministic-json-contract-pipelines)
17
+ - [Troubleshooting](#troubleshooting)
18
+ - [How it works](#how-it-works)
19
+ - [Development](#development)
20
+ - [License](#license)
21
+
7
22
  ## Features
8
23
 
9
24
  - Tool `list_files` – list project files.
10
25
  - Tool `read_file` – read file contents.
11
26
  - Tool `search_code` – search code with ripgrep or grep.
12
27
  - Tool `edit_file` – apply unified diffs safely.
28
+ - Tool `list_directory_contents` – sandboxed filesystem inspection; see [Agentic tool calling](#agentic-tool-calling-local-environment-tools).
29
+ - Tool `calculate` – safe arithmetic evaluator (Shunting-yard, no `eval`); see [Agentic tool calling](#agentic-tool-calling-local-environment-tools).
13
30
  - CLI built with Thor, entry point `exe/ollama_agent`.
14
31
  - **`self_review`** – self-review / improvement with a **`--mode`**:
15
32
  - **`analysis`** (default, alias `1`) — read-only tools; report only; no writes.
@@ -19,9 +36,34 @@ Ruby gem that runs a **CLI coding agent** against a local [Ollama](https://ollam
19
36
  - **`orchestrate`** / **`OLLAMA_AGENT_ORCHESTRATOR=1`** — optional **orchestrator** tools to probe and delegate to other local CLI agents (see [Orchestrator](#orchestrator-external-cli-agents)); **`agents`** lists availability.
20
37
  - **Ruby API** — embed **`Runner`**, **`Agent`**, custom tools, hooks, sessions, and (optionally) **`ToolRuntime`**; see [Library usage (Ruby)](#library-usage-ruby).
21
38
 
39
+ ## Kernel runtime (deterministic execution)
40
+
41
+ **Documentation (post-kernel):** [Capability matrix](docs/CAPABILITIES.md) · [CLI reference](docs/CLI.md) · [Operations / incidents](docs/OPERATIONS.md) · [Usage guide](docs/USAGE.md).
42
+
43
+ The **runtime kernel** is an optional execution layer behind `OLLAMA_AGENT_KERNEL`. It wraps file mutations in a **saga-style finite state machine**: intent reservation, **atomic writes** (CAS + pre-image hashes), ownership checks against compiled rules, **SQLite-backed WAL** and sagas, isolated post-mutation validation, and compensation on failure. The workspace root remains the **trust boundary**; the kernel adds structured **ownership** and **fencing** so replays and automation stay auditable. When cloud or validator paths fail, **circuit-breaker style** escalation limits (see the rollout runbook) keep bad states from compounding.
44
+
45
+ | `OLLAMA_AGENT_KERNEL` | Behavior |
46
+ | --- | --- |
47
+ | unset / `false` (default) | Legacy tool paths; kernel pipeline is not used for tool routing. |
48
+ | `shadow` | Same routing as `true`, but the pipeline runs in **shadow** mode: saga + WAL + observability run, while workspace bytes for certain mutations stay off the “real” path (see runbook). |
49
+ | `true` / `1` | Tool intents for configured mutation tools go through **`OllamaAgent::Runtime::KernelPipeline`**. |
50
+
51
+ **Quick start (kernel on):**
52
+
53
+ ```bash
54
+ OLLAMA_AGENT_KERNEL=true bundle exec ollama_agent ask "Your task"
55
+ ```
56
+
57
+ Design notes and roadmap items live in **`docs/new_features_plan_v2.md`**. Operational rollout, shadow mode, and rollback expectations are in **`docs/agile/release_rollout_runbook.md`** (incident SQL, health JSON, and compaction details are expanded in **`docs/OPERATIONS.md`**). For **E7 validator activation** (Docker-backed isolated checks), see **`docs/agile/docker_spec_activation.md`**.
58
+
59
+ **Compaction and disk bounds:** long-lived workspaces accumulate kernel SQLite rows and content-addressed blobs. Use **`OllamaAgent::Runtime::Compactor`** (logical `current_epoch` only — no wall clock) to prune sealed sagas, cold-archive old WAL rows into `event_store_archive.db`, purge expired recovery leases and stale intent reservations, and unlink blob files not referenced by compensations or in-flight mutation WAL payloads. **`OllamaAgent::Runtime::CompactorRunner`** wraps the compactor with an epoch interval for daemon loops (opt-in; nothing starts automatically).
60
+
61
+ **Permission unification:** when `OLLAMA_AGENT_KERNEL` is on and `config/ollama_agent/owners.yml` exists, **`OllamaAgent::Runtime::PermissionBridge`** reconciles legacy **`Runtime::Permissions` / `Runtime::Policies`** with **`Security::OwnershipIndex`** + **`CriticalityPolicy`** before pipeline execution. On divergence the bridge logs and **prefers the kernel decision** (stricter path wins). `OllamaAgent::PermissionConflictError` is raised only by the strict `#allow_mutation?` API for tests and diagnostics. With the kernel **off**, only the legacy permission path runs (no bridge).
62
+
22
63
  ## Requirements
23
64
 
24
65
  - Ruby ≥ 3.2 (enforced in the gemspec as `required_ruby_version`)
66
+ - **Runtime kernel (SQLite):** the **sqlite3** gem is a runtime dependency; kernel storage uses `event_store.db` and `runtime.db` under `.ollama_agent/kernel/` in the configured project root.
25
67
  - **Local:** Ollama running and a capable tool-calling model, **or**
26
68
  - **Ollama Cloud:** API key and a cloud-capable model name (see below)
27
69
 
@@ -30,6 +72,15 @@ Ruby gem that runs a **CLI coding agent** against a local [Ollama](https://ollam
30
72
  - **`patch`** — required for `edit_file` (GNU `patch` on `PATH`). On Windows, use Git Bash, WSL, GnuWin32, or another environment that provides `patch`.
31
73
  - **`rg` (ripgrep) or `grep`** — text mode for `search_code` needs at least one of these on `PATH` (ripgrep is preferred when present).
32
74
 
75
+ ## Security and sandbox
76
+
77
+ - **Project root** — File tools and search are constrained to the configured workspace (`--root` / `OLLAMA_AGENT_ROOT`). Treat that directory as the trust boundary: only aim the agent at trees you are willing to modify.
78
+ - **`list_directory_contents`** — Paths are resolved with `File.expand_path` relative to the project root and rejected before the filesystem is touched if they escape that boundary. `../../etc`, `/etc`, and any other traversal are caught by a prefix check, not a regex.
79
+ - **`calculate`** — Uses a hand-written tokenizer and Shunting-yard evaluator. `eval` is never called. Only numeric literals and the operators `+`, `-`, `*`, `/`, `**` are accepted; any other character is an error.
80
+ - **`run_shell` (optional tool)** — Commands are parsed into an argument vector (no shell) and must match an allowlist; a denylist blocks obviously dangerous patterns. You can still shoot yourself in the foot with an allowed prefix (for example `git` with destructive subcommands), so keep profiles and permissions tight in automated setups.
81
+ - **Timeouts** — Text search honors `OLLAMA_AGENT_SEARCH_TIMEOUT_SEC` (default 120). Shell execution has its own per-invocation timeout.
82
+ - **Logging** — Budget, loop-detection, and `list_local_model_names` failures go through Ruby’s `Logger` (stderr by default). Set `OLLAMA_AGENT_LOG_LEVEL=debug` or `OLLAMA_AGENT_DEBUG=1` for more detail.
83
+
33
84
  ## Installation
34
85
 
35
86
  From RubyGems (when published) or from this repository:
@@ -40,6 +91,16 @@ bundle install
40
91
 
41
92
  ## Usage
42
93
 
94
+ **Default:** run the gem with **no subcommand** to open the **interactive TUI** (same as `ask` with no query):
95
+
96
+ ```bash
97
+ ollama_agent
98
+ # or from this repo:
99
+ bundle exec ruby exe/ollama_agent
100
+ ```
101
+
102
+ Other entry points are **opt-in**: pass a **subcommand** (`self_review`, `sessions`, …) or **`ask` / `orchestrate`** with a **query** for a one-shot task, or flags for a plain line REPL (see below).
103
+
43
104
  From the project you want the agent to modify (set the working directory accordingly):
44
105
 
45
106
  ```bash
@@ -63,10 +124,26 @@ Long-running models (slow local inference):
63
124
  bundle exec ruby exe/ollama_agent ask --timeout 300 "Your task"
64
125
  ```
65
126
 
66
- Interactive REPL:
127
+ ### Agent budget (steps, tokens, cost)
128
+
129
+ Each **model round-trip** that runs during a session counts as one **step** toward `OLLAMA_AGENT_MAX_TURNS` (default **64**), enforced together with token and optional cost limits in `OllamaAgent::Core::Budget`. Exploratory tasks that **list, read, and search** across a **large repository** can burn through steps quickly; if you see `budget exceeded — step limit (64)`, raise the limit—for example:
130
+
131
+ ```bash
132
+ export OLLAMA_AGENT_MAX_TURNS=128
133
+ bundle exec ruby exe/ollama_agent ask "Your wide-ranging task"
134
+ ```
135
+
136
+ Narrower prompts, **`--read-only`**, or a smaller `--root` also reduce step usage. With **`OLLAMA_AGENT_DEBUG=1`**, the agent prints an extra hint when the **maximum tool rounds** for a run are reached.
137
+
138
+ ### `search_code` and regex patterns
139
+
140
+ In **text** mode, the tool passes your pattern to **ripgrep** (or **grep**). Patterns are **regular expressions**: literal parentheses, brackets, and unbalanced groups can trigger errors (for example `unclosed group`). Escape metacharacters or use **fixed-string** mode when your tool schema exposes it.
141
+
142
+ **Plain line REPL** (no TUI boxes / markdown shell): use **`ask` (or `orchestrate`) with `-i` and without `--tui`**—for example when you omit the query you must opt out of the default TUI this way:
67
143
 
68
144
  ```bash
69
145
  bundle exec ruby exe/ollama_agent ask --interactive
146
+ # same idea: explicit -i, no --tui
70
147
  ```
71
148
 
72
149
  Self-review modes (default project root is the **current working directory** unless you set `--root` or `OLLAMA_AGENT_ROOT`):
@@ -90,19 +167,62 @@ bundle exec ruby exe/ollama_agent improve --apply
90
167
 
91
168
  For mode 3, `-y` skips all patch prompts; `--no-semi` prompts for every patch when not using `-y`.
92
169
 
93
- With a **thinking-capable** model, enable reasoning output:
170
+ ### Reasoning / thinking output
171
+
172
+ On [thinking-capable models](https://docs.ollama.com/capabilities/thinking), Ollama can return **reasoning** separately from the final answer (`message.thinking` vs `message.content`). The CLI labels them **Thinking** (dim) and **Assistant** (green / Markdown).
173
+
174
+ #### Enable `think` on the request
175
+
176
+ The agent sends Ollama’s `think` field only when you set it (CLI or env). If you omit it, the server uses its own defaults—and some models then omit or change reasoning in the response.
177
+
178
+ | You want | CLI | Environment |
179
+ |----------|-----|-------------|
180
+ | Reasoning on (typical Qwen / DeepSeek-style) | `--think true` | `OLLAMA_AGENT_THINK=true` or `1` |
181
+ | Reasoning off | `--think false` | `OLLAMA_AGENT_THINK=false` or `0` |
182
+ | **GPT-OSS** style levels | `--think low`, `medium`, or `high` | `OLLAMA_AGENT_THINK=medium` (example) |
183
+
184
+ Examples:
94
185
 
95
186
  ```bash
96
187
  OLLAMA_AGENT_THINK=true bundle exec ruby exe/ollama_agent ask -i
97
- # or
98
188
  bundle exec ruby exe/ollama_agent ask -i --think true
189
+ # GPT-OSS: prefer a level, not only true/false
190
+ bundle exec ruby exe/ollama_agent ask --think medium "Your task"
191
+ ```
192
+
193
+ #### Streaming vs one-shot (default)
194
+
195
+ | Mode | Flags | What you see |
196
+ |------|--------|----------------|
197
+ | **One-shot** (default) | neither `--stream` nor `OLLAMA_AGENT_STREAM=1` | Each model round completes over HTTP; **Thinking** / **Assistant** are printed from the assembled **`message`** (including Gemma-style reasoning tags stripped from `content` when the API omits `thinking`). |
198
+ | **Streaming** | `--stream` or `OLLAMA_AGENT_STREAM=1` | Reasoning streams in **dim** text under one **Thinking** line, then **Assistant** and the reply stream—similar to Cursor. Uses `hooks[:on_thinking]` on the ollama-client chat stream (see `OllamaAgent::OllamaChatThinkingStreamPatch`). |
199
+
200
+ ```bash
201
+ OLLAMA_AGENT_THINK=medium OLLAMA_AGENT_STREAM=1 bundle exec ruby exe/ollama_agent ask "Your task"
99
202
  ```
100
203
 
101
- The CLI uses **ANSI colors** on a TTY (banner, prompt, patch prompts). **Assistant replies** are rendered as **Markdown** (headings, lists, bold, code fences) via `tty-markdown` when stdout is a TTY and **`NO_COLOR`** is unset. Disable Markdown rendering with **`OLLAMA_AGENT_MARKDOWN=0`**. Disable all colors with **`NO_COLOR`** or **`OLLAMA_AGENT_COLOR=0`**.
204
+ **Note:** Subscribing only to `on_thinking` does **not** enable the streaming chat path; the agent uses streaming when something listens for **`on_token`** (the console streamer registers both). See CHANGELOG **1.0.0** if you embed the library.
102
205
 
103
- When **thinking** is enabled, internal reasoning is shown under a **Thinking** label; the user-facing reply is labeled **Assistant** in green when the model returns both fields. By default (**`OLLAMA_AGENT_THINKING_STYLE=compact`**, Cursor-like), one **Thinking** header is printed per `ask` run and every later reasoning chunk in that run is appended with **blank lines only** (no repeated banner, no rule lines)—including after turns where the model printed tool JSON or other non-empty `content`. Set **`OLLAMA_AGENT_THINKING_STYLE=framed`** for the legacy boxed style (banner + long rulers on every assistant message). Thinking body text is **plain dim** by default. Set **`OLLAMA_AGENT_THINKING_MARKDOWN=1`** to render thinking through Markdown too (muted colors).
206
+ #### Display style (TTY)
104
207
 
105
- With **`--stream`** / **`OLLAMA_AGENT_STREAM=1`**, reasoning streams in **dim** text under a single **Thinking** line, then **`Assistant`** and the reply stream in normal styling—closer to Cursor than printing everything as one token stream. (This uses a small hook on ollama-client’s chat stream; `hooks[:on_thinking]` is also emitted for custom subscribers.)
208
+ By default **`OLLAMA_AGENT_THINKING_STYLE=compact`**: one **Thinking** header per `ask` run; later reasoning chunks in the same run are separated by **blank lines** only (including after tool rounds). **`OLLAMA_AGENT_THINKING_STYLE=framed`** repeats the full boxed banner per message. Thinking body is **plain dim** unless **`OLLAMA_AGENT_THINKING_MARKDOWN=1`**.
209
+
210
+ The CLI uses **ANSI colors** on a TTY (banner, prompt, patch prompts). **Assistant** replies use **Markdown** via `tty-markdown` when stdout is a TTY and **`NO_COLOR`** is unset. Disable Markdown with **`OLLAMA_AGENT_MARKDOWN=0`**; disable colors with **`NO_COLOR`** or **`OLLAMA_AGENT_COLOR=0`**.
211
+
212
+ #### If you see no **Thinking** block
213
+
214
+ 1. **Set `think` explicitly**—especially for **GPT-OSS** (`low` / `medium` / `high`).
215
+ 2. **Confirm the model returns `message.thinking`** (e.g. `curl` / `ollama` CLI against `/api/chat` with the same `think` value). If the API never sends `thinking`, the agent has nothing to show.
216
+ 3. **Try streaming** (`--stream` or `OLLAMA_AGENT_STREAM=1`) if you want live reasoning tokens.
217
+ 4. **Embedded reasoning in `content`:** Some templates (e.g. Gemma) put tags such as `<|channel>thought` … `<channel|>` or `<redacted_thinking>` … `</redacted_thinking>` inside `content`. The agent strips those into **Thinking** when present (`OllamaAgent::GemmaThoughtContentParser`). If your model uses different delimiters, reasoning may stay inside the main reply until parsers are extended.
218
+
219
+ #### Ruby API
220
+
221
+ ```ruby
222
+ OllamaAgent::Runner.build(stream: true, think: "medium").run("Your task")
223
+ ```
224
+
225
+ Custom subscribers can attach to **`hooks[:on_thinking]`** and **`hooks[:on_token]`** on the same **`Runner`** instance (see `OllamaAgent::Streaming::Hooks`).
106
226
 
107
227
  ### Ollama Cloud
108
228
 
@@ -115,9 +235,63 @@ With **`--stream`** / **`OLLAMA_AGENT_STREAM=1`**, reasoning streams in **dim**
115
235
  export OLLAMA_BASE_URL="https://ollama.com"
116
236
  export OLLAMA_API_KEY="your_key"
117
237
  export OLLAMA_AGENT_MODEL="gpt-oss:120b-cloud" # example; pick a cloud model from `ollama list` / the catalog
238
+ # Reasoning for GPT-OSS: set a level (see "Reasoning / thinking output" above)
239
+ export OLLAMA_AGENT_THINK=medium
240
+ bundle exec ruby exe/ollama_agent ask "Your task"
241
+ ```
242
+
243
+ #### Multi-Key Provider Credential Orchestration & Failover
244
+
245
+ To handle rate limits (RPM/TPM window exhaustion), daily quotas, or network timeouts when using Ollama Cloud (or other providers like OpenAI and Anthropic), you can configure a thread-safe, quota-aware **Credential Pool** with automatic reactive failover.
246
+
247
+ The pool can be configured dynamically via the Ruby API or auto-detected from environment variables.
248
+
249
+ ##### Environment Auto-Detection
250
+
251
+ If no explicit credentials are passed in, `ollama_agent` automatically scans the environment for keys indexed `1` to `5` and initializes a `CredentialPool`:
252
+
253
+ * **Ollama Cloud**: Configure keys `OLLAMA_API_KEY_1` through `OLLAMA_API_KEY_5`. When any of these are present, requests are routed to `"https://api.ollama.com"` (aliased as `"ollama_cloud"`).
254
+ * **OpenAI**: Configure keys `OPENAI_API_KEY_1` through `OPENAI_API_KEY_5`.
255
+ * **Anthropic**: Configure keys `ANTHROPIC_API_KEY_1` through `ANTHROPIC_API_KEY_5`.
256
+
257
+ Example for Ollama Cloud:
258
+ ```bash
259
+ export OLLAMA_API_KEY_1="ollama_key_abc123"
260
+ export OLLAMA_API_KEY_2="ollama_key_def456"
118
261
  bundle exec ruby exe/ollama_agent ask "Your task"
119
262
  ```
120
263
 
264
+ ##### Ruby API Configuration
265
+
266
+ You can also pass a structured array of credential hashes to `OllamaAgent::Runner.build`:
267
+
268
+ ```ruby
269
+ runner = OllamaAgent::Runner.build(
270
+ credentials: [
271
+ {
272
+ id: "ollama-cloud-primary",
273
+ provider: "ollama_cloud",
274
+ api_key: "ollama_...",
275
+ weight: 2, # weighted round-robin priority (default: 1)
276
+ limits: { rpm: 10, tpm: 10000, daily_tokens: 1000000 } # automatic quota tracking
277
+ },
278
+ {
279
+ id: "openai-backup",
280
+ provider: "openai",
281
+ api_key: "sk-...",
282
+ weight: 1
283
+ }
284
+ ]
285
+ )
286
+ runner.run("Your task")
287
+ ```
288
+
289
+ ##### Failover Behavior
290
+ 1. **Weighted Round-Robin**: Requests are balanced across healthy and available credentials.
291
+ 2. **Quota Tracking**: Daily token/request and RPM/TPM sliding windows are tracked locally.
292
+ 3. **Reactive Failover**: If a key encounters a rate limit (`HTTP 429`), temporary provider error (`HTTP 5xx`), or quota exhaustion, it is temporarily cooled down and the request is retried with the next available key.
293
+ 4. **Permanent Disabling**: If a key encounters an authentication failure (`HTTP 401` or `HTTP 403`), it is permanently disabled to prevent dead-key hammering.
294
+
121
295
  ### Environment
122
296
 
123
297
  | Variable | Purpose |
@@ -136,7 +310,8 @@ bundle exec ruby exe/ollama_agent ask "Your task"
136
310
  | `OLLAMA_AGENT_MARKDOWN` | Set to `0` to disable Markdown formatting of assistant replies (plain text only) |
137
311
  | `OLLAMA_AGENT_THINKING_STYLE` | `compact` (default) = one **Thinking** label per run, blank lines between later reasoning chunks; `framed` = repeat full banner/rulers each message |
138
312
  | `OLLAMA_AGENT_THINKING_MARKDOWN` | Set to `1` to render **thinking** text with Markdown (muted); default is plain dim text |
139
- | `OLLAMA_AGENT_THINK` | Model **thinking** mode for compatible models: `true` / `false`, or `high` / `medium` / `low` (see ollama-client `think:`). Empty = omit (server default). |
313
+ | `OLLAMA_AGENT_STREAM` | Set to `1` to stream tokens and reasoning to stdout (same as CLI **`--stream`** on `ask` / `self_review` / `improve`). |
314
+ | `OLLAMA_AGENT_THINK` | Model **thinking** mode for compatible models: `true` / `false`, or `high` / `medium` / `low` (see ollama-client `think:`). Empty = omit (server default). **GPT-OSS:** use `low` / `medium` / `high`. |
140
315
  | `OLLAMA_AGENT_PATCH_RISK_MAX_DIFF_LINES` | Max changed-line count before a diff is treated as "large" for semi-auto patch risk (default **80**) |
141
316
  | `OLLAMA_AGENT_INDEX_REBUILD` | Set to `1` to drop the cached Prism Ruby index before the next symbol search in this process |
142
317
  | `OLLAMA_AGENT_RUBY_INDEX_MAX_FILES` | Max `.rb` files to parse per index build (default **5000**) |
@@ -178,6 +353,59 @@ Use the **`orchestrate`** command (or **`OLLAMA_AGENT_ORCHESTRATOR=1`** with **`
178
353
  - Adjust **`argv` / `version_argv`** in YAML to match your real CLI (vendor flags differ). If a tool has no stable non-interactive mode, do not expose it in the registry.
179
354
  - Tool contract version: **`OllamaAgent::ORCHESTRATOR_TOOLS_SCHEMA_VERSION`**.
180
355
 
356
+ ### Agentic tool calling (local environment tools)
357
+
358
+ Two built-in tools let the model observe its environment and compute precisely — the pattern described in [Easy Agentic Tool Calling with Gemma 4](https://www.kdnuggets.com/easy-agentic-tool-calling-with-gemma-4) — adapted here for the Ruby runtime.
359
+
360
+ #### `list_directory_contents`
361
+
362
+ Lists files and subdirectories inside the current workspace. The model decides when to inspect the environment rather than guessing at what exists.
363
+
364
+ ```bash
365
+ bundle exec ruby exe/ollama_agent ask \
366
+ "What scripts are in the current folder, and which one looks like it handles CSV processing?"
367
+ ```
368
+
369
+ All paths are sandboxed to `OLLAMA_AGENT_ROOT`. Traversal attempts (`../../etc`, absolute paths) are rejected before the filesystem is touched.
370
+
371
+ #### `calculate`
372
+
373
+ Evaluates an arithmetic expression using a Shunting-yard parser. The model offloads precision arithmetic instead of computing in its weights.
374
+
375
+ ```bash
376
+ bundle exec ruby exe/ollama_agent ask \
377
+ "What is the standard deviation of 12, 18, 23, 29, 31, 35, 44, 47 — compute it step by step using the formula."
378
+ ```
379
+
380
+ Supports `+`, `-`, `*`, `/`, `**` (right-associative), parentheses, and unary `+`/`-`. No `eval`.
381
+
382
+ #### Combining both tools
383
+
384
+ The model can chain the two tools in a single request — inspect the workspace first, then compute something about what it found:
385
+
386
+ ```bash
387
+ bundle exec ruby exe/ollama_agent ask \
388
+ "Look at the files in the current folder and tell me the total size in kilobytes, rounded to two decimal places."
389
+ ```
390
+
391
+ Internally: the model calls `list_directory_contents` to get byte sizes, then calls `calculate` with the sum and division by 1024.
392
+
393
+ #### Ruby API
394
+
395
+ ```ruby
396
+ require "ollama_agent"
397
+
398
+ runner = OllamaAgent::Runner.build(root: Dir.pwd)
399
+
400
+ # Filesystem inspection
401
+ puts runner.run("What files are in lib/ollama_agent/tools/?")
402
+
403
+ # Arithmetic
404
+ puts runner.run("What is (412 + 1834 + 10786 + 88 + 2210) / 1024, rounded to 2 decimal places?")
405
+ ```
406
+
407
+ See `examples/agentic_tool_calling.rb` for a runnable end-to-end demo.
408
+
181
409
  ### Library usage (Ruby)
182
410
 
183
411
  Most of this README is **CLI-first** (commands and environment variables above). The same capabilities exist as **Ruby APIs**—the [Features](#features) list (file tools, `self_review` / `improve`, orchestrator, skills, etc.) is implemented under `lib/ollama_agent/`. For a **layer diagram** (agent → tools → hooks → session), see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
@@ -236,6 +464,75 @@ last = OllamaAgent::ToolRuntime::Loop.new(
236
464
  # last => e.g. { "status" => "done", "echo" => "bye" }
237
465
  ```
238
466
 
467
+ ## Skills (deterministic JSON-contract pipelines)
468
+
469
+ Skills are single-purpose generators that bypass the tool-calling agent loop and
470
+ return **strict JSON** validated against a schema. They are meant for pipelines
471
+ that need predictable, parseable output — code review, refactoring suggestions,
472
+ performance audits, debugging triage — without the unpredictability of free-form
473
+ LLM prose.
474
+
475
+ Built-in skills:
476
+
477
+ - `architecture_refactor` — restructure code without changing behavior
478
+ - `performance_optimizer` — identify bottlenecks and emit optimized code
479
+ - `debug_engineer` — root-cause a bug and propose a fix
480
+ - `feature_builder` — design and implement a production-ready feature
481
+
482
+ Each skill:
483
+
484
+ 1. Renders a deterministic prompt (LLM `temperature: 0` by default).
485
+ 2. Extracts the first balanced JSON object from the response (tolerates prose
486
+ and ```` ```json ```` fences).
487
+ 3. Validates against the skill's `SCHEMA` and raises `ContractError` on mismatch.
488
+
489
+ ### CLI
490
+
491
+ ```bash
492
+ # list registered skills
493
+ ollama_agent skill list
494
+
495
+ # run a single skill
496
+ ollama_agent skill run architecture_refactor --code-file lib/orders/manager.rb
497
+
498
+ # compose a pipeline; later skills receive earlier outputs merged in
499
+ ollama_agent skill pipeline architecture_refactor performance_optimizer \
500
+ --code-file lib/exit_management.rb
501
+ ```
502
+
503
+ Override the model with `--model`, `OLLAMA_AGENT_SKILL_MODEL`, or
504
+ `OLLAMA_AGENT_MODEL`.
505
+
506
+ ### Ruby
507
+
508
+ ```ruby
509
+ result = OllamaAgent::Skills::ArchitectureRefactorer.new.call(
510
+ code: File.read("lib/orders/manager.rb")
511
+ )
512
+ # => { folder_structure: [...], architecture_notes: "...", refactored_code: "..." }
513
+
514
+ OllamaAgent::Skills::Runner.new(
515
+ [:architecture_refactor, :performance_optimizer]
516
+ ).call(code: File.read("lib/exit_management.rb"))
517
+ ```
518
+
519
+ Inject your own LLM client (anything responding to `#generate(prompt) → String`)
520
+ in tests:
521
+
522
+ ```ruby
523
+ class FakeLlm
524
+ def generate(_prompt)
525
+ '{"bottlenecks": [], "optimizations": [], "optimized_code": "x"}'
526
+ end
527
+ end
528
+
529
+ OllamaAgent::Skills::PerformanceOptimizer.new(llm: FakeLlm.new).call(code: "...")
530
+ ```
531
+
532
+ By default skills run against the local Ollama provider (local-first, auditable).
533
+ They go through `OllamaAgent::Providers::Registry`, so any registered provider
534
+ (OpenAI, Anthropic, custom) is usable by passing your own `LlmClient`.
535
+
239
536
  ## Troubleshooting
240
537
 
241
538
  - **Use a tool-capable model** — Set `OLLAMA_AGENT_MODEL` to a model that supports function/tool calling (e.g. a recent coder-tuned variant). If the model only prints `{"name": "read_file", ...}` in plain text, tools never run unless you enable `OLLAMA_AGENT_PARSE_TOOL_JSON=1`.
@@ -256,6 +553,8 @@ bundle exec rspec
256
553
  bundle exec rubocop
257
554
  ```
258
555
 
556
+ Ongoing refactors (contributors): the **`Agent`** class is a thin façade over **`TurnLoop`**, **`ChatCoordinator`**, session/client wiring, and **`Tools::BuiltInSchemas`** so new behavior should land in those collaborators instead of growing monolithic methods. See [CONTRIBUTING.md](CONTRIBUTING.md).
557
+
259
558
  ### CI and RubyGems release
260
559
 
261
560
  - **CI** — [`.github/workflows/main.yml`](.github/workflows/main.yml) runs **RSpec** and **RuboCop** on pushes to `main` / `master` and on pull requests (Ruby **3.3.4** and **3.2.0**).
@@ -271,7 +570,7 @@ Repository **secrets** (Settings → Secrets and variables → Actions):
271
570
  Release steps:
272
571
 
273
572
  1. Bump `OllamaAgent::VERSION` in `lib/ollama_agent/version.rb` and commit to `main`.
274
- 2. Tag: `git tag v0.1.0` (must match the version string) and `git push origin v0.1.0`.
573
+ 2. Tag: `git tag v1.0.0` (must match the version string) and `git push origin v1.0.0`.
275
574
 
276
575
  ## License
277
576
 
data/Rakefile CHANGED
@@ -3,6 +3,8 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
 
6
+ Dir.glob(File.expand_path("lib/tasks/*.rake", __dir__)).each { |path| load path }
7
+
6
8
  RSpec::Core::RakeTask.new(:spec)
7
9
 
8
10
  require "rubocop/rake_task"
@@ -0,0 +1,53 @@
1
+ # Example ownership graph for a typical Rails app (workspace-relative prefixes, no trailing slashes).
2
+ # Copy or extend for your repo; compile with OllamaAgent::Security::OwnershipCompiler.
3
+
4
+ version: 1
5
+ rules:
6
+ - prefix: app
7
+ owner: application
8
+ mutable_in_modes: [normal, replay, validation, dry_run]
9
+ criticality: routine
10
+ forbidden: false
11
+ children:
12
+ - prefix: app/models
13
+ owner: domain
14
+ mutable_in_modes: [normal, replay, validation]
15
+ criticality: sensitive
16
+ children: []
17
+
18
+ - prefix: config
19
+ owner: platform
20
+ mutable_in_modes: [normal, replay, validation, dry_run]
21
+ criticality: sensitive
22
+ children: []
23
+
24
+ - prefix: db/migrate
25
+ owner: data
26
+ mutable_in_modes: [normal, validation]
27
+ criticality: critical
28
+ children: []
29
+
30
+ - prefix: lib
31
+ owner: shared_libraries
32
+ mutable_in_modes: [normal, replay, validation, dry_run]
33
+ criticality: routine
34
+ children: []
35
+
36
+ - prefix: log
37
+ owner: observability
38
+ mutable_in_modes: [normal, dry_run]
39
+ criticality: routine
40
+ children: []
41
+
42
+ - prefix: tmp
43
+ owner: scratch
44
+ mutable_in_modes: [normal, dry_run]
45
+ criticality: routine
46
+ children: []
47
+
48
+ - prefix: .env
49
+ owner: secrets
50
+ mutable_in_modes: [normal, replay, validation, dry_run]
51
+ criticality: critical
52
+ forbidden: true
53
+ children: []