rubino-agent 0.4.0 → 0.5.0

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +11 -2
  3. data/AGENTS.md +1 -1
  4. data/CHANGELOG.md +137 -1
  5. data/CONTRIBUTING.md +10 -1
  6. data/README.md +14 -5
  7. data/Rakefile +31 -0
  8. data/docs/agents.md +42 -23
  9. data/docs/architecture.md +2 -2
  10. data/docs/commands.md +28 -1
  11. data/docs/configuration.md +20 -23
  12. data/docs/getting-started.md +5 -3
  13. data/docs/security.md +16 -5
  14. data/docs/troubleshooting.md +1 -1
  15. data/exe/rubino +16 -2
  16. data/install.sh +715 -54
  17. data/lib/rubino/active_agent.rb +73 -0
  18. data/lib/rubino/agent/action_claim_guard.rb +881 -0
  19. data/lib/rubino/agent/agent_registry.rb +5 -2
  20. data/lib/rubino/agent/definition.rb +1 -9
  21. data/lib/rubino/agent/fallback_chain.rb +0 -6
  22. data/lib/rubino/agent/iteration_budget.rb +109 -3
  23. data/lib/rubino/agent/loop.rb +476 -20
  24. data/lib/rubino/agent/model_call_runner.rb +81 -3
  25. data/lib/rubino/agent/prompts/build.txt +22 -5
  26. data/lib/rubino/agent/response_validator.rb +8 -0
  27. data/lib/rubino/agent/runner.rb +133 -8
  28. data/lib/rubino/agent/tool_executor.rb +166 -14
  29. data/lib/rubino/agent/truncation_continuation.rb +4 -1
  30. data/lib/rubino/api/server.rb +19 -0
  31. data/lib/rubino/boot/config_guard.rb +71 -0
  32. data/lib/rubino/cli/chat/completion_builder.rb +42 -6
  33. data/lib/rubino/cli/chat/idle_card_host.rb +7 -1
  34. data/lib/rubino/cli/chat/session_resolver.rb +87 -21
  35. data/lib/rubino/cli/chat_command.rb +1189 -50
  36. data/lib/rubino/cli/commands.rb +281 -1
  37. data/lib/rubino/cli/config_command.rb +68 -8
  38. data/lib/rubino/cli/doctor_command.rb +204 -12
  39. data/lib/rubino/cli/jobs_command.rb +12 -0
  40. data/lib/rubino/cli/memory_command.rb +53 -20
  41. data/lib/rubino/cli/onboarding_wizard.rb +79 -6
  42. data/lib/rubino/cli/session_command.rb +172 -18
  43. data/lib/rubino/cli/setup_command.rb +131 -8
  44. data/lib/rubino/cli/skills_command.rb +67 -20
  45. data/lib/rubino/cli/trust_gate.rb +16 -7
  46. data/lib/rubino/commands/built_ins.rb +2 -0
  47. data/lib/rubino/commands/command.rb +12 -2
  48. data/lib/rubino/commands/executor.rb +149 -12
  49. data/lib/rubino/commands/handlers/agent_switch.rb +100 -0
  50. data/lib/rubino/commands/handlers/agents.rb +133 -38
  51. data/lib/rubino/commands/handlers/config.rb +4 -1
  52. data/lib/rubino/commands/handlers/help.rb +113 -14
  53. data/lib/rubino/commands/handlers/memory.rb +15 -5
  54. data/lib/rubino/commands/handlers/sessions.rb +26 -3
  55. data/lib/rubino/commands/handlers/status.rb +9 -4
  56. data/lib/rubino/commands/loader.rb +12 -0
  57. data/lib/rubino/config/configuration.rb +86 -24
  58. data/lib/rubino/config/defaults.rb +140 -33
  59. data/lib/rubino/config/loader.rb +62 -12
  60. data/lib/rubino/config/validator.rb +341 -0
  61. data/lib/rubino/config/writer.rb +123 -31
  62. data/lib/rubino/context/compressor.rb +184 -22
  63. data/lib/rubino/context/message_boundary.rb +27 -1
  64. data/lib/rubino/context/project_languages.rb +90 -0
  65. data/lib/rubino/context/prompt_assembler.rb +104 -21
  66. data/lib/rubino/context/summary_builder.rb +45 -4
  67. data/lib/rubino/context/token_budget.rb +36 -11
  68. data/lib/rubino/context/token_estimate.rb +45 -0
  69. data/lib/rubino/context/tool_result_pruner.rb +81 -0
  70. data/lib/rubino/database/connection.rb +154 -3
  71. data/lib/rubino/database/migrations/001_create_initial_schema.rb +314 -40
  72. data/lib/rubino/database/migrator.rb +98 -5
  73. data/lib/rubino/documents/cap_exceeded.rb +13 -0
  74. data/lib/rubino/documents/converters/csv.rb +4 -3
  75. data/lib/rubino/documents/converters/docx.rb +29 -5
  76. data/lib/rubino/documents/converters/html.rb +5 -1
  77. data/lib/rubino/documents/converters/json.rb +2 -1
  78. data/lib/rubino/documents/converters/pdf.rb +11 -2
  79. data/lib/rubino/documents/converters/plain.rb +2 -1
  80. data/lib/rubino/documents/converters/pptx.rb +11 -2
  81. data/lib/rubino/documents/converters/xlsx.rb +35 -4
  82. data/lib/rubino/documents/converters/xml.rb +2 -1
  83. data/lib/rubino/documents/limits.rb +210 -0
  84. data/lib/rubino/documents.rb +10 -3
  85. data/lib/rubino/errors.rb +36 -5
  86. data/lib/rubino/interaction/cancel_token.rb +19 -3
  87. data/lib/rubino/interaction/events.rb +13 -0
  88. data/lib/rubino/interaction/lifecycle.rb +99 -13
  89. data/lib/rubino/interaction/polishing.rb +176 -0
  90. data/lib/rubino/jobs/cron_job_repository.rb +5 -8
  91. data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +11 -0
  92. data/lib/rubino/jobs/handlers/distill_skill_job.rb +65 -9
  93. data/lib/rubino/jobs/queue.rb +63 -8
  94. data/lib/rubino/jobs/runner.rb +24 -6
  95. data/lib/rubino/jobs/worker.rb +0 -4
  96. data/lib/rubino/llm/adapter_response.rb +47 -4
  97. data/lib/rubino/llm/credential_check.rb +15 -16
  98. data/lib/rubino/llm/error_classifier.rb +89 -1
  99. data/lib/rubino/llm/inline_think_filter.rb +69 -12
  100. data/lib/rubino/llm/request.rb +30 -3
  101. data/lib/rubino/llm/ruby_llm_adapter.rb +394 -46
  102. data/lib/rubino/llm/tool_bridge.rb +113 -9
  103. data/lib/rubino/mcp/manager.rb +18 -1
  104. data/lib/rubino/mcp/mcp_tool_wrapper.rb +14 -3
  105. data/lib/rubino/memory/aux_retry.rb +107 -0
  106. data/lib/rubino/memory/backends/sqlite.rb +73 -44
  107. data/lib/rubino/memory/backends.rb +23 -7
  108. data/lib/rubino/memory/salience_gate.rb +103 -0
  109. data/lib/rubino/memory/sqlite_extraction.rb +70 -0
  110. data/lib/rubino/memory/sqlite_extraction_prompt.rb +11 -0
  111. data/lib/rubino/memory/store.rb +33 -5
  112. data/lib/rubino/memory/threat_scanner.rb +52 -0
  113. data/lib/rubino/output/cost.rb +52 -0
  114. data/lib/rubino/output/headless_block_latch.rb +53 -0
  115. data/lib/rubino/output/result_serializer.rb +222 -0
  116. data/lib/rubino/output/turn_recorder.rb +77 -0
  117. data/lib/rubino/security/approval_policy.rb +227 -32
  118. data/lib/rubino/security/command_allowlist.rb +79 -4
  119. data/lib/rubino/security/doom_loop_detector.rb +21 -2
  120. data/lib/rubino/security/hardline_guard.rb +189 -16
  121. data/lib/rubino/security/pattern_matcher.rb +28 -5
  122. data/lib/rubino/security/prefix_deriver.rb +25 -6
  123. data/lib/rubino/security/readonly_commands.rb +145 -5
  124. data/lib/rubino/security/secret_path.rb +134 -0
  125. data/lib/rubino/security/url_safety.rb +255 -0
  126. data/lib/rubino/session/repository.rb +212 -11
  127. data/lib/rubino/session/store.rb +139 -14
  128. data/lib/rubino/skills/installer.rb +116 -32
  129. data/lib/rubino/skills/prompt_index.rb +2 -2
  130. data/lib/rubino/skills/registry.rb +42 -1
  131. data/lib/rubino/skills/skill.rb +63 -2
  132. data/lib/rubino/skills/skill_tool.rb +16 -5
  133. data/lib/rubino/tools/background_tasks.rb +122 -9
  134. data/lib/rubino/tools/base.rb +204 -3
  135. data/lib/rubino/tools/edit_tool.rb +73 -18
  136. data/lib/rubino/tools/glob_tool.rb +48 -9
  137. data/lib/rubino/tools/grep_tool.rb +103 -9
  138. data/lib/rubino/tools/multi_edit_tool.rb +64 -9
  139. data/lib/rubino/tools/patch_tool.rb +5 -0
  140. data/lib/rubino/tools/read_attachment_tool.rb +3 -1
  141. data/lib/rubino/tools/read_tool.rb +33 -15
  142. data/lib/rubino/tools/read_tracker.rb +153 -35
  143. data/lib/rubino/tools/registry.rb +113 -12
  144. data/lib/rubino/tools/result.rb +9 -1
  145. data/lib/rubino/tools/ruby_tool.rb +0 -0
  146. data/lib/rubino/tools/shell_registry.rb +70 -0
  147. data/lib/rubino/tools/shell_tool.rb +40 -1
  148. data/lib/rubino/tools/summarize_file_tool.rb +6 -0
  149. data/lib/rubino/tools/task_stop_tool.rb +10 -16
  150. data/lib/rubino/tools/task_tool.rb +36 -8
  151. data/lib/rubino/tools/vision_tool.rb +5 -0
  152. data/lib/rubino/tools/webfetch_tool.rb +39 -7
  153. data/lib/rubino/tools/websearch_tool.rb +92 -30
  154. data/lib/rubino/tools/write_tool.rb +23 -4
  155. data/lib/rubino/ui/api.rb +10 -1
  156. data/lib/rubino/ui/base.rb +11 -0
  157. data/lib/rubino/ui/bottom_composer.rb +382 -74
  158. data/lib/rubino/ui/cli.rb +515 -83
  159. data/lib/rubino/ui/completion_menu.rb +11 -7
  160. data/lib/rubino/ui/headless_trace.rb +63 -0
  161. data/lib/rubino/ui/live_region.rb +70 -7
  162. data/lib/rubino/ui/markdown_renderer.rb +142 -7
  163. data/lib/rubino/ui/notifier.rb +0 -2
  164. data/lib/rubino/ui/null.rb +52 -5
  165. data/lib/rubino/ui/paste_store.rb +16 -2
  166. data/lib/rubino/ui/queued_indicators.rb +6 -1
  167. data/lib/rubino/ui/status_bar.rb +61 -7
  168. data/lib/rubino/ui/streaming_markdown.rb +59 -6
  169. data/lib/rubino/ui/subagent_view.rb +15 -1
  170. data/lib/rubino/ui/tool_label.rb +52 -0
  171. data/lib/rubino/update_check.rb +39 -4
  172. data/lib/rubino/util/atomic_file.rb +117 -0
  173. data/lib/rubino/util/ignore_rules.rb +120 -0
  174. data/lib/rubino/util/output.rb +229 -12
  175. data/lib/rubino/util/secrets_mask.rb +70 -7
  176. data/lib/rubino/util/spill_store.rb +153 -0
  177. data/lib/rubino/version.rb +1 -1
  178. data/lib/rubino/workspace.rb +9 -1
  179. data/lib/rubino.rb +191 -7
  180. data/rubino-agent.gemspec +1 -0
  181. data/skills/ruby-expert/SKILL.md +1 -0
  182. metadata +41 -12
  183. data/lib/rubino/agent/router.rb +0 -65
  184. data/lib/rubino/database/migrations/002_create_runs.rb +0 -45
  185. data/lib/rubino/database/migrations/003_create_skill_states.rb +0 -15
  186. data/lib/rubino/database/migrations/004_create_cron_jobs.rb +0 -36
  187. data/lib/rubino/database/migrations/005_create_oauth_connections.rb +0 -27
  188. data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +0 -34
  189. data/lib/rubino/database/migrations/007_create_messages_fts.rb +0 -59
  190. data/lib/rubino/database/migrations/008_create_memory_facts.rb +0 -75
  191. data/lib/rubino/database/migrations/009_create_memory_graph.rb +0 -55
  192. data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a20b85c30f537ff5bdaf5abec6300eedfcda791d76b899ac3fcd972b71854ac1
4
- data.tar.gz: d5692a31f13e338690a3be90bc4d96d27251ddad165b706afee7e0fcdee3fea7
3
+ metadata.gz: 8a3a48a6f7deb104446c624354a271ff1a1671c9b3ea846cee1144b3f55538db
4
+ data.tar.gz: cbc407cd78db827d75f150f61045b2cce759b266816e32fdba874c30eb3647c4
5
5
  SHA512:
6
- metadata.gz: 865a203c91311f039d90a98b4e88ff8f792d0049e7370f10c847e2aeb77458f2f39d80485f133a79d8c2e8d9c1eeb5dec369f53320d210ff79928965a8c0a3f2
7
- data.tar.gz: a9435d6746bc743e65db73574e13d4cd65445bf97ec5c9e2387737c176e7ab8726991062cc852a6c295910e3876d8b4a1d8c96420f5a8dd6a83dabf68a280546
6
+ metadata.gz: 3342a4c8b1856691788ac9625b81eb4058870a0eb5a75946e99e6570ce40a05a7b44475a798863ac0fc92c9fcb505cf6caf27fcedc10a8e1bd9653ba18edf440
7
+ data.tar.gz: e54077b30f385942dd3591fb7e6c4e7afc0f301e5fea9d3111732c91ce56e63a4c7d192704ebd74ea97ee4a04437784d73a8d398bf02ce6143cdd69b2227a841
data/.rubocop_todo.yml CHANGED
@@ -256,6 +256,8 @@ Metrics/BlockLength:
256
256
  Metrics/ClassLength:
257
257
  Exclude:
258
258
  - 'lib/rubino/cli/chat_command.rb'
259
+ - 'lib/rubino/commands/executor.rb'
260
+ - 'lib/rubino/llm/ruby_llm_adapter.rb'
259
261
  - 'lib/rubino/ui/bottom_composer.rb'
260
262
  - 'lib/rubino/ui/cli.rb'
261
263
 
@@ -406,7 +408,6 @@ Naming/PredicateMethod:
406
408
  Exclude:
407
409
  - 'lib/rubino/agent/model_call_runner.rb'
408
410
  - 'lib/rubino/agent/response_validator.rb'
409
- - 'lib/rubino/agent/router.rb'
410
411
  - 'lib/rubino/cli/onboarding_wizard.rb'
411
412
  - 'lib/rubino/cli/trust_gate.rb'
412
413
  - 'lib/rubino/memory/backends/sqlite.rb'
@@ -451,6 +452,7 @@ RSpec/AnyInstance:
451
452
  - 'spec/rubino/commands/commands_spec.rb'
452
453
  - 'spec/rubino/context/compressor_spec.rb'
453
454
  - 'spec/rubino/context/prompt_assembler_active_skill_spec.rb'
455
+ - 'spec/rubino/context/prompt_assembler_cache_breakpoints_spec.rb'
454
456
  - 'spec/rubino/context/prompt_assembler_ignore_rules_spec.rb'
455
457
  - 'spec/rubino/context/prompt_assembler_layering_spec.rb'
456
458
  - 'spec/rubino/context/prompt_assembler_memory_snapshot_spec.rb'
@@ -462,8 +464,10 @@ RSpec/BeforeAfterAll:
462
464
  - '**/spec/spec_helper.rb'
463
465
  - '**/spec/rails_helper.rb'
464
466
  - '**/spec/support/**/*.rb'
467
+ - 'spec/rubino/llm/tool_bridge_cache_breakpoint_spec.rb'
465
468
  - 'spec/rubino/security/approval_policy_mode_spec.rb'
466
469
  - 'spec/rubino/tools/registry_mode_spec.rb'
470
+ - 'spec/rubino/tools/registry_situational_gating_spec.rb'
467
471
  - 'spec/rubino/tools/registry_spec.rb'
468
472
 
469
473
  # Offense count: 31
@@ -550,7 +554,11 @@ RSpec/DescribeMethod:
550
554
  - 'spec/rubino/api/middleware/json_body_size_spec.rb'
551
555
  - 'spec/rubino/cli/chat_command_continue_spec.rb'
552
556
  - 'spec/rubino/cli/commands_version_spec.rb'
557
+ - 'spec/rubino/llm/ruby_llm_adapter_cache_spec.rb'
558
+ - 'spec/rubino/llm/tool_bridge_cache_breakpoint_spec.rb'
559
+ - 'spec/rubino/tools/registry_situational_gating_spec.rb'
553
560
  - 'spec/rubino/context/prompt_assembler_active_skill_spec.rb'
561
+ - 'spec/rubino/context/prompt_assembler_cache_breakpoints_spec.rb'
554
562
  - 'spec/rubino/context/prompt_assembler_ignore_rules_spec.rb'
555
563
  - 'spec/rubino/context/prompt_assembler_layering_spec.rb'
556
564
  - 'spec/rubino/context/prompt_assembler_memory_snapshot_spec.rb'
@@ -654,12 +662,13 @@ RSpec/LeakyLocalVariable:
654
662
  Exclude:
655
663
  - 'spec/rubino/no_direct_output_spec.rb'
656
664
 
657
- # Offense count: 7
665
+ # Offense count: 8
658
666
  RSpec/MultipleDescribes:
659
667
  Exclude:
660
668
  - 'spec/rubino/commands/commands_spec.rb'
661
669
  - 'spec/rubino/context/prompt_assembler_layering_spec.rb'
662
670
  - 'spec/rubino/memory/backends_spec.rb'
671
+ - 'spec/rubino/tools/encoding_robustness_spec.rb'
663
672
  - 'spec/rubino/tools/error_code_spec.rb'
664
673
  - 'spec/rubino/tools/tool_fixes_spec.rb'
665
674
  - 'spec/rubino/trust_spec.rb'
data/AGENTS.md CHANGED
@@ -77,7 +77,7 @@ lib/rubino/
77
77
  ## Surfaces this project exposes
78
78
 
79
79
  - **HTTP API** (`/v1/*`) — the canonical interface. See `docs/api/v1.md`.
80
- - **CLI** — `rubino {setup,chat,prompt,server,config,memory,sessions,jobs,tools,doctor,version}`.
80
+ - **CLI** — `rubino {setup,chat,prompt,server,config,memory,sessions,jobs,skills,tools,tls_cert,doctor,version,update}`.
81
81
  - **Library** — `require "rubino"; Rubino.run(...)`.
82
82
 
83
83
  The interactive CLI ships as part of `rubino chat`. Multi-agent routing, MCP, and plugin hooks are designed in but not fully wired yet.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,142 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.5.0] - 2026-06-15
4
+
5
+ ### Added
6
+
7
+ - **One-shot tool-activity trace.** The non-interactive text path (`rubino
8
+ prompt` / `-q` / piped `chat`) now prints a concise per-tool activity trace
9
+ by default — one line per tool completion (`· edit foo.rb`, `· bash npm
10
+ test`) — routed to STDERR so the final answer on STDOUT stays clean
11
+ (`x=$(rubino prompt …)` captures only the answer). `--quiet`/`-Q` silences
12
+ the trace (machine-silent path); `--verbose`/`-v` widens each line's args.
13
+ `--output-format json`/`stream-json` (structured events on stdout) and the
14
+ interactive TUI tool-cards are unchanged. Mirrors the Codex/gemini-cli/Hermes
15
+ stderr-trace norm (Hermes `-q` default / `-Q` quiet).
16
+
17
+ - **Prompt-cache breakpoints (`cache_control`).** The conversation now inserts
18
+ cache breakpoints so the stable prefix (system + tool schemas + prior turns)
19
+ is reused across round-trips, cutting input-token cost/latency.
20
+ - **Situational tool-schema gating.** Tool definitions sent to the model are
21
+ scoped to the situation instead of always shipping the full set, reducing
22
+ prompt size and accidental tool selection.
23
+ - **Primary-agent switching.** Switch the active primary agent inline with
24
+ `/<name>`, the `/agent` command, or `Tab`; `@` remains reserved for file
25
+ references.
26
+ - **Detached post-turn polishing.** A post-turn polishing pass runs detached and
27
+ is cancellable with `Esc`, so it never blocks the next prompt.
28
+ - **Stdin pipe for one-shot.** Piped stdin is consumed as the prompt for
29
+ one-shot runs (`echo … | rubino prompt`), enabling unix-style composition.
30
+ - **Per-round-trip loop accounting.** Round-trips are counted, usage is summed
31
+ across them, and `tool_calls` are persisted on the streaming path.
32
+ - **Machine-readable headless output (`--output-format json | stream-json`, #312).**
33
+ `rubino prompt` / `chat -q` can now emit Claude-Code-aligned JSON for
34
+ CI/automation instead of prose. `--output-format json` (or the `--json` alias)
35
+ prints a single `{type:"result", subtype, is_error, result, session_id,
36
+ exit_reason, num_turns, duration_ms, usage:{input/output/cache_* tokens},
37
+ total_cost_usd, model}` object on stdout at completion; `--output-format
38
+ stream-json` emits JSONL (a `system`/`init` line, then Messages-API-shaped
39
+ `assistant`/`user` step objects, then the same final `result`). In both modes
40
+ ALL JSON goes to stdout and ALL logs/diagnostics/errors to stderr, and markdown
41
+ rendering is suppressed. The fail-closed / exit-code contract is preserved: a
42
+ blocked tool still emits the result with `is_error:true` and a non-zero exit.
43
+ The schema lives in a single shared serializer (`Rubino::Output::ResultSerializer`)
44
+ so it never drifts. `text` (default) is unchanged.
45
+ - **Higher tool-loop budget with an interactive extension prompt (#399).** The
46
+ `max_tool_iterations` default is raised from 8 to 25 so longer agent runs no
47
+ longer hit the cap mid-task. When the cap is reached interactively, the run
48
+ pauses with a budget-extension prompt — **Continue +N** (grant another batch),
49
+ **Summarize** (wrap up with what's done), or **Abort** — instead of failing
50
+ silently; headless runs keep the force-summarize behavior.
51
+ - **TUI: Ctrl-L clear-screen and a resize-while-typing fix (#395 / #401).**
52
+ `Ctrl-L` now clears the screen from the composer. Fixed a bug where resizing
53
+ the terminal while typing reflowed and duplicated the in-progress input into
54
+ the scrollback.
55
+
56
+ ### Security
57
+
58
+ - **Hardened/narrowed the command-allowlist convenience layer (SEC-R2-1/2/3).**
59
+ Closes three default-config / bare-`git` paths that could run arbitrary code
60
+ or write arbitrary files past the headless gate **without `--yolo`**:
61
+ - removed code-loading test/build runners (`bundle exec rspec`, …) from the
62
+ **shipped default** `command_allowlist` — they load and execute arbitrary
63
+ project code by design (`rspec -r FILE`), so they are not safely
64
+ auto-approvable (SEC-R2-3);
65
+ - an allowlisted **git** head is now vetted for GLOBAL flags before the
66
+ subcommand (`git -c alias.x='!cmd' x`, `-c core.sshCommand=…`, `-C dir`,
67
+ `--exec-path`) and for code-loading/mutating subcommands (`apply`, `am`,
68
+ `push`, hooks, …); the "approve git always" path now persists only a
69
+ narrowed `git <read-only verb>`, never bare `git` (SEC-R2-1);
70
+ - any allowlisted head whose argument is itself a program
71
+ (`awk`/`sed`/`perl`/`python`/`ruby`/`node`/`tar`/`tee`/`xargs`/shells) is
72
+ default-denied auto-approval, and write flags on read heads (`sort -o`, …)
73
+ are rejected (SEC-R2-2).
74
+
75
+ An allowlist is a convenience layer, **not** a security boundary (per industry
76
+ practice the OS sandbox is the real floor, tracked separately); this narrows
77
+ it to close the above default-config and bare-`git` RCEs.
78
+
79
+ ### Hardening
80
+
81
+ Four adversarial QA rounds fixed ~45 issues across the agent. Highlights:
82
+
83
+ - **Security.** Hardline-floor canonicalization; OOXML zip-bomb total-archive
84
+ cap; CWE-150 argument sanitization; threat-scanner; tightened
85
+ command-allowlist (see above).
86
+ - **Correctness.** UTF-8-safe edits; atomic compaction with auto-switch-to-child;
87
+ resume keeps the full tool history; cwd-scoped sessions; corrupt-DB recovery
88
+ (incl. `NotADatabaseException`); job-queue compare-and-swap; headless job drain
89
+ so memory works in automation.
90
+ - **Interrupt.** True cancel — stream cancellation with the partial persisted;
91
+ clean one-shot `SIGINT`/`SIGTERM` labels.
92
+ - **Performance.** Bounded huge-output memory; spill/paste eviction; streaming
93
+ grep with consistent ignore rules.
94
+ - **UX.** Config validation; `doctor` checks; resilient timeouts and error
95
+ classification.
96
+
97
+ Every fix was container-verified (non-root QA image, real MiniMax for live
98
+ behavior, true 0 failures); a full pre-release functionality sweep confirmed all
99
+ subsystems release-ready.
100
+
101
+ ## [0.4.1] - 2026-06-13
102
+
103
+ ### Security
104
+
105
+ - **Headless approvals now fail closed (#260).** A one-shot / scripted run
106
+ (`rubino prompt`, `chat -q`, no TTY) no longer auto-runs a tool that would
107
+ otherwise prompt: a write/edit, or a shell command not covered by your
108
+ `permissions` / command allowlist / read-only auto-allow, is **blocked, not
109
+ run**. A `blocked: <tool> needs approval …` line goes to stderr and the run
110
+ exits **2**, so CI/automation fails loudly instead of silently skipping (or
111
+ auto-executing). Full auto-exec now requires an explicit **`--yolo`** —
112
+ honored ONLY as a CLI flag, never grantable by a project-local/persisted
113
+ config — and **`--no-yolo`** forces fail-closed even over a yolo boot default.
114
+
115
+ ### Fixed — installer
116
+
117
+ - **`mise` method (#256)** alongside Homebrew and `rv`, with `global`/`local`
118
+ scope (`RUBINO_INSTALL_SCOPE`); `RUBINO_INSTALL_METHOD` now accepts `mise`.
119
+ - **Activation/PATH is persisted to your shell rc (#268)** (`.zshrc` /
120
+ `.bashrc` / `.profile`) and a **post-install fresh-shell gate** fails loudly
121
+ if `rubino` isn't on PATH in a new shell. `RUBINO_NO_MODIFY_RC=1` opts out.
122
+ - **`mise` installs pin the latest published gem version (#258/#268)** instead
123
+ of drifting to a pre-release / age-gated build.
124
+ - **Method-aware prereq preflight (#272)** (xz/git/toolchain) with real gem
125
+ error surfacing, and a **Debian-12 / glibc-too-old steer from rv → mise
126
+ (#241/#242/#272)** so users don't land on a broken musl Ruby.
127
+
128
+ ### Fixed
129
+
130
+ - **Config corruption + `doctor` crash on a scalar written over a section (#259).**
131
+ - **Streaming persistence (#266):** pre-tool narration is persisted and the
132
+ `tool_calls` audit is populated.
133
+ - **TUI render (#269):** table columns sized to content, nested/markdown fences
134
+ consumed, interrupt "ghost" line cleared.
135
+ - **Memory extraction bounded by a per-session cursor (#249)** — no more
136
+ re-scanning the whole transcript every turn.
137
+ - **Boots under a bare C/POSIX locale (#273)** without
138
+ `Encoding::CompatibilityError`.
139
+ - **Session summary folded into the single system message (#253/#254).**
4
140
 
5
141
  ## [0.4.0] - 2026-06-13
6
142
 
data/CONTRIBUTING.md CHANGED
@@ -21,11 +21,20 @@ Run the CLI from the checkout with `bundle exec rubino <command>`.
21
21
  ## Tests
22
22
 
23
23
  ```bash
24
- bundle exec rspec # full suite
24
+ bundle exec rspec # full suite (sequential; generates the coverage report)
25
25
  bundle exec rspec path/to/file_spec.rb
26
26
  bundle exec rake # default task == spec
27
+ bundle exec rake parallel:spec # full suite across all CPU cores (no coverage report)
28
+ bundle exec rake parallel:spec[4] # ...forced to 4 workers
27
29
  ```
28
30
 
31
+ `parallel:spec` shards the suite across one process per core via the
32
+ `parallel_tests` gem; each worker is isolated by `TEST_ENV_NUMBER`
33
+ (per-worker `RUBINO_HOME`, document fixtures, and example-status file).
34
+ SimpleCov is skipped under parallel runs (the workers would race the
35
+ coverage resultset) — use the sequential `bundle exec rspec` when you need
36
+ the coverage report.
37
+
29
38
  The HTTP boundary is locked by an end-to-end contract suite under `spec/rubino/api/contract/`. When the docs and the contract suite disagree, **the contract suite is canonical** — update the docs to match.
30
39
 
31
40
  ## Lint
data/README.md CHANGED
@@ -19,7 +19,15 @@ One line, Linux and macOS (x86_64 / arm64). Installs a compatible Ruby, then the
19
19
  curl -fsSL https://raw.githubusercontent.com/Jhonnyr97/rubino-agent/main/install.sh | bash
20
20
  ```
21
21
 
22
- On **Linux** the installer fetches a precompiled Ruby via [`rv`](https://github.com/spinel-coop/rv). On **macOS**, if [Homebrew](https://brew.sh) is present it asks whether to use Homebrew (`brew install ruby`) or `rv`; without Homebrew it uses `rv` directly. Skip the prompt with `RUBINO_INSTALL_METHOD=brew` or `=rv`.
22
+ The installer supports **three** methods for getting a compatible Ruby + the gem:
23
+
24
+ - **`rv`** ([`rv`](https://github.com/spinel-coop/rv)) — fetches a precompiled Ruby into user space.
25
+ - **Homebrew** (`brew install ruby`) — offered on **macOS** when [Homebrew](https://brew.sh) is present.
26
+ - **`mise`** ([mise](https://mise.jdx.dev)) — a polyglot tool manager; installs `rubino` via its `gem:` backend and pins the latest published gem version.
27
+
28
+ On **macOS** (interactive) you're asked to pick Homebrew / `rv` / `mise`; on **Linux** (interactive) you pick `rv` / `mise` (Homebrew is offered only if `brew` is already on PATH). Skip the prompt with `RUBINO_INSTALL_METHOD=brew`, `=rv`, or `=mise`. For the **mise** method, `RUBINO_INSTALL_SCOPE=global` (default, user-wide `~/.config/mise/config.toml`) or `=local` (this directory only, `./mise.toml`) chooses the scope.
29
+
30
+ On **Debian 12 / old-glibc** systems `rv` would install a musl Ruby this glibc box can't execute; the installer detects that and **steers you from `rv` to `mise`** (precompiled, glibc-correct) so you don't land on a broken `rubino`.
23
31
 
24
32
  > **Review before you pipe.** Piping a script into your shell runs whatever it contains. Read it first:
25
33
  > ```bash
@@ -27,7 +35,7 @@ On **Linux** the installer fetches a precompiled Ruby via [`rv`](https://github.
27
35
  > less install.sh && bash install.sh
28
36
  > ```
29
37
 
30
- The installer is idempotent — safe to re-run and prints the exact `PATH` line for the `rubino` executable plus the next step.
38
+ The installer is idempotent — safe to re-run. It **persists the activation / `PATH` line to your shell rc** (`.zshrc` / `.bashrc` / `.profile`) and then runs a **fresh-shell verification gate** — it opens a clean login shell and fails loudly if `rubino` isn't on `PATH` there, instead of merely printing a hint you might miss. Opt out of any rc modification with `RUBINO_NO_MODIFY_RC=1` (the installer then prints the line for you to add yourself).
31
39
 
32
40
  **Manual install** (if you'd rather not pipe, or already manage Ruby yourself):
33
41
 
@@ -99,7 +107,7 @@ model:
99
107
 
100
108
  agent:
101
109
  max_turns: 90
102
- max_tool_iterations: 8
110
+ max_tool_iterations: 25
103
111
 
104
112
  memory:
105
113
  enabled: true
@@ -142,7 +150,7 @@ Full reference (every key, env vars, precedence): **[docs/configuration.md](docs
142
150
 
143
151
  ## Built-in tools
144
152
 
145
- The agent ships **33 built-in tools**: `read`, `summarize_file`, `write`, `edit`, `multi_edit`, `grep`, `glob`, `git`, `github`, `shell`, `shell_output`, `shell_tail`, `shell_input`, `shell_kill`, `ruby`, `run_tests`, `apply_patch`, `webfetch`, `websearch`, `question`, `todowrite`, `memory`, `session_search`, `attach_file`, `vision`, `skill`, `task`, `task_result`, `task_stop`, `ask_parent`, `steer`, `probe`, `answer_child`. Each is gated by a `tools.<key>` config flag (opt-out) and the approval model. See **[docs/tools.md](docs/tools.md)**.
153
+ The agent ships **27 built-in tools** (the set `rubino tools` lists): `read`, `read_attachment`, `summarize_file`, `write`, `edit`, `multi_edit`, `apply_patch`, `grep`, `glob`, `git`, `github`, `shell`, `shell_output`, `shell_tail`, `shell_input`, `shell_kill`, `ruby`, `run_tests`, `web`, `question`, `todowrite`, `memory`, `session_search`, `attach_file`, `vision`, `skill`, `task`. A single `web` tool gates both fetching a URL and searching (config key `tools.web`, off by default). Each tool is gated by a `tools.<key>` config flag (opt-out) and the approval model. See **[docs/tools.md](docs/tools.md)**.
146
154
 
147
155
  ## Skills
148
156
 
@@ -189,7 +197,8 @@ These are designed-in but not fully wired yet — don't depend on them in produc
189
197
 
190
198
  ```bash
191
199
  bundle install
192
- bundle exec rspec # run tests
200
+ bundle exec rspec # run tests (sequential, with coverage)
201
+ bundle exec rake parallel:spec # run tests across all CPU cores
193
202
  bundle exec rubino doctor # verify setup
194
203
  ```
195
204
 
data/Rakefile CHANGED
@@ -6,3 +6,34 @@ require "rspec/core/rake_task"
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
+
10
+ # Parallel test execution across CPU cores via the `parallel_tests` gem.
11
+ #
12
+ # rake parallel:spec # auto: one worker per core
13
+ # rake parallel:spec[4] # force 4 workers
14
+ #
15
+ # Each worker is its own process with a distinct TEST_ENV_NUMBER, so the
16
+ # per-process isolation already baked into spec/spec_helper.rb (RUBINO_HOME,
17
+ # document fixtures, example-status file) keeps workers from colliding.
18
+ # SimpleCov is skipped in parallel (workers would race the resultset); run the
19
+ # plain sequential `rake spec` / `bundle exec rspec` for a coverage report.
20
+ #
21
+ # Balancing: we use parallel_tests' default **filesize** grouping rather than
22
+ # runtime grouping. Runtime grouping (`--group-by runtime`) is strict — it
23
+ # aborts with RuntimeLogTooSmallError whenever the recorded log is missing an
24
+ # entry for any current spec file (i.e. the first run after ANY new spec is
25
+ # added), which makes the entrypoint brittle. The wall-clock floor here is a
26
+ # single ~70s example (agent_e2e error-retry) that cannot be split across
27
+ # workers regardless of grouping, so filesize grouping already lands the
28
+ # longest worker on essentially that floor while staying deterministic and
29
+ # never breaking on a freshly-added spec.
30
+ namespace :parallel do
31
+ desc "Run the RSpec suite in parallel across CPU cores (rake parallel:spec[N])"
32
+ task :spec, [:count] do |_t, args|
33
+ count = args[:count]
34
+ cmd = %w[bundle exec parallel_rspec]
35
+ cmd += ["-n", count.to_s] if count && !count.empty?
36
+ cmd += ["--", "spec"]
37
+ sh(*cmd)
38
+ end
39
+ end
data/docs/agents.md CHANGED
@@ -1,15 +1,22 @@
1
1
  # Agents & Subagents
2
2
 
3
- rubino has two distinct multi-agent surfaces. Only the first one ships today:
3
+ rubino has two distinct multi-agent surfaces, and **both ship today**:
4
4
 
5
5
  1. **Background subagents** (✅ shipping) — the agent delegates bounded sub-tasks
6
6
  to isolated subagent runs via its `task` tool, and you supervise them with
7
- `/agents` and `/reply`. This is the surface you will actually use.
8
- 2. **Primary-agent switching** ( not yet wired) — Tab-cycling between primary
9
- agents and `@mention` routing. The machinery exists (`Agent::Router`,
10
- `Agent::Definition`, `AgentRegistry`) but no call site passes an agent
11
- definition yet, so the default (build) agent handles every turn. See
12
- [the last section](#planned-primary-agent-switching--mentions-not-yet-wired).
7
+ `/agents` and `/reply`.
8
+ 2. **Primary-agent switching** ( shipping) — pick the primary agent that
9
+ handles your turns: `/agent <name>` (or a bare `/<name>` for a primary)
10
+ pins it for the session, **Tab** cycles through the primaries, and a one-shot
11
+ `/<name> <message>` routes a single message to any agent. The selected agent's
12
+ Definition (its system prompt and tool scope) is threaded into the runner each
13
+ turn, so the choice actually changes the model's persona/tools. See
14
+ [Primary-agent switching](#primary-agent-switching) below.
15
+
16
+ > **Channels are cleanly separated:** `@` is the **workspace file** picker
17
+ > (`@path/to/file`), and `/` is the **agent/command** channel. There are no
18
+ > `@mention` agent routes — a filename like `@explore.rb` is always a file, never
19
+ > an agent. Use `/explore`, `/plan`, etc. to reach an agent.
13
20
 
14
21
  ---
15
22
 
@@ -132,14 +139,15 @@ apply (hardline floor still enforced — see [security.md](security.md)).
132
139
  ## Built-in agent definitions
133
140
 
134
141
  These definitions exist in `Agent::AgentRegistry` today. The two *subagents*
135
- are live as `task` targets; the two *primary* agents are only reachable as the
136
- default (`build`) or via the plan **mode** (`/mode plan`), not via agent
137
- switching; the *utility* agents are internal.
142
+ are live as `task` targets; the two *primary* agents are switchable per session
143
+ (`/agent <name>`, a bare `/<name>`, or Tab see
144
+ [Primary-agent switching](#primary-agent-switching)); the *utility* agents are
145
+ internal.
138
146
 
139
147
  | Agent | Type | Access | Description |
140
148
  |-------|------|--------|-------------|
141
- | **build** | primary | Full tools | Default development agent. Handles every turn today. |
142
- | **plan** | primary | Read-only | Analysis/planning definition (the shipping read-only surface is `/mode plan`). |
149
+ | **build** | primary | Full tools | Default development agent (the registry default). |
150
+ | **plan** | primary | Read-only | Analysis/planning agent. Switch to it with `/agent plan`; `/mode plan` is the orthogonal read-only run **mode**. |
143
151
  | **explore** | subagent | Read-only | Fast codebase search and navigation (`task` target). |
144
152
  | **general** | subagent | Full tools | Complex multi-step tasks (`task` target). |
145
153
  | **compaction** | utility | None | Internal: compresses context. Hidden. |
@@ -173,18 +181,29 @@ pattern-based permission overrides (merged over the global rules by
173
181
 
174
182
  ---
175
183
 
176
- ## Planned: primary-agent switching & @mentions (not yet wired)
184
+ ## Primary-agent switching
177
185
 
178
- > **Status:** the machinery exists `Agent::Router` (@mention detection,
179
- > Tab-cycling, default routing) and the `agent_definition:` plumbing through the
180
- > runner but **no call site passes an agent definition**, so Tab and
181
- > `@explore`/`@plan`/`@general` mentions currently do nothing. Use the
182
- > background-subagent surface above for real work.
183
-
184
- The intended design: press **Tab** to cycle through primary agents, or route a
185
- single message with an `@mention`:
186
+ You choose which primary agent handles your turns. The pinned agent is a
187
+ process-level slot (`Rubino::ActiveAgent`, sibling to `Rubino::Modes`): a fresh
188
+ `rubino chat` boots on the registry default (`build`), and an explicit switch
189
+ takes effect for the rest of that process (no premature persistence). Switching
190
+ is entirely on the **slash** channel and **Tab** — there is no `@mention` agent
191
+ routing (`@` is the workspace file picker).
186
192
 
187
193
  ```
188
- you > @explore Where is the database connection configured?
189
- you > @plan How should we restructure the auth module?
194
+ you > /agent plan # pin a primary agent for the session
195
+ you > /plan # bare /<name> same, for a primary agent
196
+ you > <Tab> # cycle through the primary agents, wrapping around
197
+ you > /explore Where is the database connection configured? # one-shot route a single message
190
198
  ```
199
+
200
+ - **`/agent <name>`** (or a bare **`/<name>`** when `<name>` is a primary) pins
201
+ the agent for the session. Only **primary** agents are switchable; subagents
202
+ (`explore`/`general`) are never pinned.
203
+ - **Tab** cycles through the primary agents.
204
+ - **`/<name> <message>`** routes a single message to any agent (primary or
205
+ subagent) without changing the sticky selection.
206
+
207
+ The selected agent's Definition — its system prompt and tool scope — is threaded
208
+ into the runner on every turn, so switching actually changes the model's
209
+ persona and the tools it can call, not just a cosmetic label.
data/docs/architecture.md CHANGED
@@ -148,8 +148,8 @@ User Input
148
148
  ├─→ Commands::Executor (if /command)
149
149
  │ └─→ Render template → feed to agent
150
150
 
151
- ├─→ Agent::Router (if @mention)
152
- │ └─→ Select agent definition
151
+ ├─→ ActiveAgent (if /agent, /<name>, or Tab)
152
+ │ └─→ Select primary agent definition
153
153
 
154
154
  └─→ Interaction::Lifecycle
155
155
 
data/docs/commands.md CHANGED
@@ -60,7 +60,9 @@ is set.
60
60
  | `--new` | | Start a fresh session (bare `chat` resumes the last one by default) |
61
61
  | `--model` | `-m` | Override the model (e.g. `claude-sonnet-4-5`) |
62
62
  | `--provider` | | Override the provider (e.g. `anthropic`, `bedrock`) |
63
- | `--yolo` | | Skip approval prompts (equivalent to `/mode yolo`) |
63
+ | `--yolo` | | Skip approval prompts (equivalent to `/mode yolo`). Honored **only** as a CLI flag — cannot be set from config |
64
+ | `--no-yolo` | | Force fail-closed approvals even over a yolo default (the security half of [#260](#exit-codes-scripting-around-prompt--one-shot)) |
65
+ | `--add-dir` | | Add an extra allowed workspace directory write/edit can reach (repeatable) |
64
66
  | `--max-turns` | | Max tool iterations per turn |
65
67
  | `--ignore-rules` | | Skip `AGENTS.md` and context files |
66
68
 
@@ -93,6 +95,18 @@ Pasting **text** into the chat input goes through the file-backed paste pipeline
93
95
  policy along the way (a write outside the workspace boundary, a denied
94
96
  approval, a hardline-blocked command). A refusal the agent handled and
95
97
  explained is expected behavior, not an error.
98
+ - **Headless approvals fail closed (security).** A one-shot / scripted run has
99
+ no interactive session, so a tool that would otherwise prompt for approval —
100
+ a write/edit, or a shell command **not** covered by your `permissions` /
101
+ command allowlist / read-only auto-allow — is **blocked, not run**. A
102
+ single-line `blocked: <tool> needs approval but no interactive session (use
103
+ --yolo to allow, or allowlist it)` goes to stderr and the run exits
104
+ **non-zero (2)**, so automation/CI fails loudly instead of silently skipping
105
+ (or, worse, auto-executing) the action. Anything you already allowlisted, and
106
+ every read-only command, still runs unprompted. Pass `--yolo` to opt back
107
+ into full auto-execute; `--no-yolo` forces fail-closed even if a yolo default
108
+ was set. `--yolo` is honored **only** as a CLI flag — a project-local config
109
+ can never grant it.
96
110
  - It exits **non-zero** when the run itself fails: no usable credentials, the
97
111
  `--resume`/`--session` target doesn't exist or is ambiguous, or the provider
98
112
  call errors out. The reason is printed to stderr; the answer (when any) stays
@@ -160,9 +174,11 @@ Type these inside `rubino chat`. Generated from `BuiltIns::DESCRIPTIONS` (drift-
160
174
  | `/compact` | Compact the context now: older turns become a summary |
161
175
  | `/export` | Write the session transcript as markdown (/export [path]) |
162
176
  | `/memory` | Inspect/search/forget what the agent remembers (show ID, backend, --all) |
177
+ | `/agent` | Switch the primary agent (/agent <name>; a bare /<name> or Tab cycles) |
163
178
  | `/agents` | List background subagents; steer/probe a running one, or view output |
164
179
  | `/tasks` | Alias for /agents |
165
180
  | `/reply` | Answer a subagent that is blocked waiting on you (ask_parent) |
181
+ | `/stop` | Stop a running subagent (/stop <id>; alias for /agents <id> --stop) |
166
182
  | `/jobs` | List the background job queue (status counts); /jobs <id> for detail |
167
183
  | `/skills` | List skills; activate one ('none' clears), or enable/disable NAME |
168
184
  | `/mcp` | List MCP servers and their tools; restart or disable one |
@@ -371,6 +387,17 @@ Custom commands live as Markdown templates in `.rubino/commands/` (project) or `
371
387
 
372
388
  `/commands` lists the available custom commands and explains how to author them. See the [README](../README.md) for the template format (`$ARGUMENTS`, YAML frontmatter).
373
389
 
390
+ ### Primary agents: `/agent`, `/<name>`, and Tab
391
+
392
+ Each turn runs under an **agent** — a persona with its own system prompt and tool scope. The built-ins are `build` (full access, the default) and `plan` (read-only analysis); `explore` and `general` are subagents you invoke one-shot. Switching the primary agent changes who answers the *next* turn:
393
+
394
+ - `/agent` lists the switchable primaries (the current one marked `▸`) and the one-shot subagents.
395
+ - `/agent <name>` — or a bare `/<name>` for a primary — **pins** that agent for the rest of the session (sticky). The active agent shows as an `agent <name>` chip in the status bar (omitted when it's the default `build`).
396
+ - **Tab** on an empty prompt cycles the primary agents (the agent counterpart of Shift+Tab's mode cycle), updating the chip live.
397
+ - `/<name> <message>` routes a **single** turn to that agent — any visible agent, primary or subagent (e.g. `/explore where is the parser`) — without disturbing your sticky pick.
398
+
399
+ Distinct from `/agents` (plural), which drills into the background `task` subagents. `@` is the file picker, so a filename like `@explore.rb` is never shadowed by an agent named `explore`; agent switching lives entirely on the slash channel and Tab.
400
+
374
401
  ### Modes
375
402
 
376
403
  `/mode` (or the `--yolo` flag) switches between the modes below. **Shift+Tab** cycles them from the prompt (default → plan → yolo), updates the mode token that LEADS the status bar under the input (dim `default`, yellow `plan`, red `yolo`), and shows a transient `mode <old> → <new>` footer. Entering `yolo` from the cycle takes a second deliberate Shift+Tab to confirm (the toast says so, and warns when running background subagents would lose their approval gates); an explicit `/mode yolo` switches directly.
@@ -102,8 +102,10 @@ auxiliary:
102
102
  ```yaml
103
103
  agent:
104
104
  max_turns: 90 # Max turns per session
105
- max_tool_iterations: 8 # Max consecutive tool calls
106
- max_turn_seconds: 120 # Timeout per turn
105
+ max_tool_iterations: 25 # Max per-turn model<->tool round-trips (cap)
106
+ budget_extension_prompt: true # At the cap, prompt continue/summarize/abort (interactive only)
107
+ budget_extension_step: null # "+N" per extension (null = max_tool_iterations)
108
+ max_turn_seconds: 120 # Timeout per turn (outer rail; extensions never raise it)
107
109
  api_max_retries: 5 # LLM API retry count (exp backoff)
108
110
  api_retry_backoff_cap_seconds: 16 # Max per-retry backoff draw
109
111
  api_retry_backoff_overload_cap_seconds: 60 # Higher cap used only for overload (529/503)
@@ -287,7 +289,7 @@ tools:
287
289
  workspace_strict: true # Sandbox write/edit/delete to workspace_root; false = any reachable path
288
290
  git: true
289
291
  shell: true # ON by default (the agent ships to run inside an isolated VM);
290
- # every command is still gated by security.require_confirmation_for_shell
292
+ # dangerous commands are still gated by security.confirm_policy
291
293
  ruby: true
292
294
  web: false # Gates BOTH the webfetch and websearch tools
293
295
  memory: true
@@ -372,12 +374,13 @@ attachments:
372
374
 
373
375
  ```yaml
374
376
  security:
375
- # confirm_policy: "confirm_all" # confirm_all (default) | dangerous_only; derived from the alias below when absent
376
- require_confirmation_for_shell: true # legacy alias for confirm_policy; true => confirm_all
377
- command_allowlist: # prefix-matched commands pre-approved (empty = approve nothing)
377
+ confirm_policy: "dangerous_only" # dangerous_only (default) | confirm_all
378
+ # (the old require_confirmation_for_shell key was removed)
379
+ command_allowlist: # pre-approved commands (read-only intent only; empty = approve nothing)
378
380
  - "git status"
379
381
  - "git diff"
380
- - "bundle exec rspec"
382
+ # Test/build runners (bundle exec rspec, rake, npm test) are NOT shipped here:
383
+ # they load and run arbitrary project code, so add one only if you accept that.
381
384
  website_blocklist:
382
385
  enabled: false
383
386
  domains: []
@@ -461,20 +464,6 @@ formatters:
461
464
  "*.py": "black"
462
465
  ```
463
466
 
464
- ### agents
465
-
466
- Custom agent definitions:
467
-
468
- ```yaml
469
- agents:
470
- security:
471
- type: subagent
472
- model: "anthropic/claude-sonnet-4-20250514"
473
- description: "Security-focused code review"
474
- tools: [read, grep, glob]
475
- mcp_servers: []
476
- ```
477
-
478
467
  ### prompts
479
468
 
480
469
  System-prompt layering. The defaults ship the built-in role prompts.
@@ -513,9 +502,17 @@ formatters:
513
502
  "*.py": "black"
514
503
  ```
515
504
 
516
- ### agents (planned)
505
+ ### agents (planned — not yet read)
506
+
507
+ > **Status: planned, has no effect today.** The `agents:` key is reserved but is
508
+ > **not read** by the registry, so declaring custom agents in `config.yml` does
509
+ > nothing yet. Primary-agent *switching* among the built-in agents already ships
510
+ > (`/agent`, `/<name>`, Tab — see [agents.md](agents.md#primary-agent-switching));
511
+ > what is not wired is authoring NEW agents from config. To register a custom
512
+ > agent today, use `AgentRegistry#register` programmatically (see
513
+ > [agents.md](agents.md#custom-agents-via-code)).
517
514
 
518
- Custom agent definitions (multi-agent routing is not fully wired yet — see [agents.md](agents.md)):
515
+ The intended shape, once config-authored agents land:
519
516
 
520
517
  ```yaml
521
518
  agents:
@@ -4,7 +4,7 @@ From nothing to a working first answer in about five minutes. This is the happy
4
4
 
5
5
  ## 1. Install
6
6
 
7
- The fastest path on Linux (x86_64 / arm64) is the one-line installer. It installs a compatible Ruby via [`rv`](https://github.com/spinel-coop/rv), then the gem — all in user space, no sudo:
7
+ The fastest path on Linux and macOS (x86_64 / arm64) is the one-line installer. It installs a compatible Ruby and then the gem — all in user space, no sudo:
8
8
 
9
9
  ```bash
10
10
  curl -fsSL https://raw.githubusercontent.com/Jhonnyr97/rubino-agent/main/install.sh | bash
@@ -17,7 +17,9 @@ curl -fsSL https://raw.githubusercontent.com/Jhonnyr97/rubino-agent/main/install
17
17
  less install.sh && bash install.sh
18
18
  ```
19
19
 
20
- The installer is idempotent (safe to re-run) and prints the exact `PATH` line for the `rubino` executable when it finishes.
20
+ On Linux it offers a choice of Ruby provider — [`rv`](https://github.com/spinel-coop/rv) or [`mise`](https://mise.jdx.dev) (and Homebrew if `brew` is already present on macOS); pick non-interactively with `RUBINO_INSTALL_METHOD=rv|mise|brew`. On a **Debian 12 / old-glibc** box, prefer **mise**: `rv`'s musl build there yields a Ruby this glibc system can't run, so the installer steers `rv → mise` automatically. (For the mise method, `RUBINO_INSTALL_SCOPE=global|local` chooses user-wide vs this-directory-only. See the [README install matrix](../README.md#install).)
21
+
22
+ The installer is idempotent (safe to re-run). When it finishes it **persists the activation / `PATH` line to your shell rc** (`.zshrc` / `.bashrc` / `.profile`) and then **verifies in a fresh shell** that `rubino` is on `PATH`, failing loudly if it isn't — so a new terminal just works. Opt out of rc edits with `RUBINO_NO_MODIFY_RC=1` (it then prints the line for you to add).
21
23
 
22
24
  **Already manage Ruby yourself?** Requirements are Ruby >= 3.1 and SQLite3; then:
23
25
 
@@ -85,7 +87,7 @@ The first thing you see is a banner with the workspace, git branch, and model. T
85
87
 
86
88
  ```
87
89
  ▍❯ what does this project do?
88
- default · MiniMax-M3 · ctx ~0/128k
90
+ default · openai/gpt-4.1 · ctx ~0/128k
89
91
  ```
90
92
 
91
93
  > If you skipped the wizard during `setup`, a bare `rubino chat` re-runs it before the first turn (when on a TTY). If you're piping input or using `-q`, there's no prompt to run — instead you get a clear, actionable error telling you how to set a key (see below).