aia 2.0.1 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fa126ad31719e30c2d357d3573ba4b7832fe99ab082799c0e7b37c3b0d49314
4
- data.tar.gz: 20706361c420669cb035fb03c996ef28895399ba850985715e44f6b9411ca47a
3
+ metadata.gz: 1d7d1dff13dc9c4e4558c4624bf6368cb32f42a4a4896238e1750e5beaa24d7b
4
+ data.tar.gz: e430f69d905314342a538d063b5123dd5fd454dabcd6bae08a2ad3d696d74d56
5
5
  SHA512:
6
- metadata.gz: 6154106f03fa35cfbf562282eec6512dc210df4b40ae6c900eede45df5a5253f232fc6bbe7c9a0cf930a152558ffde96d45aa1894d8c338af5256f65a9ccbe82
7
- data.tar.gz: 6ce2ca929686cd6e262f5f009581a087b710129f5d89a0d020d612dffd7bc5b555f794a41cfc38ca3f8a3b1941edfeff7cb08331f2c7ea036dfce2e4372c3ae0
6
+ metadata.gz: b136ae2840a452e8aa5dae4585588036ea10420abbd071c5fd3b45e0fea86a2f6c35005680757e8c2ebde9a63d8a6abf3538a164446c03cc1300aafd41a97728
7
+ data.tar.gz: 06c4e7081bdfe6c618bb4dc868d37daf232c491d04319bf8426232b85442a94eb279b04cba69e3e456d7580d51bdaede5d761367f9331f481387c15432506df4
data/CHANGELOG.md CHANGED
@@ -1,6 +1,38 @@
1
1
  # Changelog
2
2
 
3
- ## [2.0.1] 2026-11-02
3
+ ## [2.1.0] - 2026-11-07
4
+
5
+ Coding-agent CLIs (Claude Code, Codex, GitHub Copilot) can be used as models, and a prompt file can ask for a special mode such as `debate` in its front matter. Batch runs now exit 1 when an LLM call fails. The unit test suite runs without API keys, a UTF-8 locale or a particular Ruby patch level, and every test file runs on its own.
6
+
7
+ ### Added
8
+
9
+ - **Coding agents as models.** `--model claude_code/opus`, `codex/default` or `copilot/auto` sends prompts to the Claude Code, Codex or GitHub Copilot CLI in batch mode (stdin in, JSON out) through the `ruby_llm-providers-claude_code`, `-codex` and `-copilot` gems, which resume the agent's own session between turns. A bare provider name (`--model claude_code`) now means that provider's default model. Providers that answer `accepts_tools?` with `false` get no aia tools or MCP servers attached, with a one-line notice. An installed provider gem is found and loaded even when aia itself runs under Bundler (a `bin/aia` checkout, or an app that lists aia in its Gemfile) and the gem is not in that Gemfile, provided the bundle already satisfies the gem's dependencies.
10
+
11
+ - **`mode:` front matter key.** A prompt file can ask for one of the special modes that the chat directives `/debate`, `/verify`, `/decompose`, `/delegate`, `/spawn`, `/orchestrate` and `/concurrent` request, by naming it in its front matter: `mode: debate`. Prompts run from the command line, in a pipeline or as executable prompts then get that mode, which until now ran only in chat; a `/debate` line inside a prompt file was sent to the model as text. As in chat, a debate needs several robots (`--model a,b,c`).
12
+
13
+ ### Fixed
14
+
15
+ - **`--provider` outranks `AIA_MODEL` and the config file.** `--provider claude_code` replaced the provider only of models given without a `<provider>/` prefix, so with `AIA_MODEL=lms/qwen3.8` exported the flag did nothing and the prompt still went to LM Studio. A command-line `--provider` now applies to every model that came from the environment or a config file, prefix or not, keeping their model names; it sets the provider, never the model, so `--model claude_code` (a bare provider name) remains the way to ask for a provider's default model. Models named on the command line with `--model` keep an explicit prefix, since both values come from the same place. When a provider gem cannot be loaded because aia is running under Bundler, the warning now names the Gemfile to add the `ruby_llm-providers-<slug>` gem to.
16
+
17
+ - **Batch runs exit 1 on LLM errors.** robot_lab shields a robot's exception into an `Error: ...` reply so one failing robot cannot crash a network, which left `aia prompt` printing a bad API key, rate limit or provider outage as the answer and exiting 0. A hook now records the real exception for each pipeline step: outside chat the step fails, the error goes to stderr and aia exits 1; in a chat session the error is shown and the session continues. Unshielded RubyLLM errors (image generation, provider setup) exit 1 with a one-line message instead of a backtrace.
18
+
19
+ - **`--paint` works again, and `--image_quality` / `--image_style` reach the API.** Since 2.0.0 the image step handed `RubyLLM.paint` a `params:` keyword that ruby_llm 2.1 does not have, so every `--paint` run stopped with `ArgumentError: unknown keyword: :params` before any request was made. The options now go through `provider_options:`, which ruby_llm merges into the request, so `--image_quality hd --image_style vivid` take effect (#135).
20
+
21
+ - **`DelegateHandler` and `DelegateToForemanTool` load what they use.** `delegate_handler.rb` now requires `TaskDecomposer` and `TaskExecutor`, and `delegate_to_foreman_tool.rb` requires `TaskBoardTool`. Each worked only because its one caller also required those files, so loading either on its own and calling it raised `uninitialized constant`.
22
+
23
+ **Test suite**
24
+
25
+ - **Failing test runs exit non-zero.** `test_helper.rb`'s `exit` override, which stops aia's exit calls from ending a test run early, also swallowed Minitest's exit status, so `rake test` and single test files exited 0 when tests failed. The override now stands down after the suite runs.
26
+
27
+ - **Test suite loads on Ruby 4.1.** `ostruct` is a bundled gem since Ruby 4.0 and only reached the test bundle as a transitive dependency of `oj` 3.17.7; `oj` 3.17.8 dropped it, so `bundle update` left the suite unable to load `OpenStruct`. It is now a development dependency. The speech player test no longer stubs `ENV`, which Ruby 4.1 rejects as a shareable object.
28
+
29
+ - **Unit suite passes without API keys or a UTF-8 locale.** The network-building tests create real RubyLLM chats, which ruby_llm 2.1 refuses without a provider API key, so 37 tests errored unless `OPENAI_API_KEY` was set. They now skip that credential check; they never call an API. The gemspec version test errored under a non-UTF-8 locale; it now loads the gemspec and compares its version with `AIA::VERSION`. `test_helper.rb` requires `ostruct`, so test files that use `OpenStruct` also run on their own.
30
+
31
+ - **Every unit test file runs on its own.** Six files raised `uninitialized constant` when run alone, because aia loads a class each one uses lazily: `TaskDecomposer`, `TaskExecutor`, `TaskBoardTool` (one file each) and `ToolFilter::TFIDF` (three files). Each file now requires that class.
32
+
33
+ - **`ConfigTest` ignores the shell's `AIA_PLUGINS_DIR`.** The test that checks `paths.plugins_dir` defaults to `nil` read whatever the developer's shell had exported and failed, and the two tests that set the variable deleted it afterwards. All three now set the variable for their own run and restore it.
34
+
35
+ ## [2.0.1] - 2026-11-02
4
36
 
5
37
  ### Fixed
6
38
 
@@ -11,7 +43,7 @@
11
43
  - `logger.mcp.file` defaults to `~/.config/aia/logs/mcp.log` (was `STDOUT`), so MCP connection warnings no longer print over the spinner. Failed servers are still listed in the startup banner. A config file that sets `file: STDOUT` keeps the old behavior.
12
44
  - Log directories are created when they don't exist.
13
45
 
14
- ## [2.0.0] 2026-11-02
46
+ ## [2.0.0] - 2026-11-02
15
47
 
16
48
  AIA 2.0 rebuilds the CLI on [robot_lab](https://github.com/MadBomber/robot_lab): every session is a crew of robots, MCP servers come from ruby_llm 2.1's built-in client, and any provider RubyLLM registers can be used. This entry summarizes everything since 1.1.1, the last 1.x release, including the `2.0.0.pre.alpha` and `2.0.0.pre.beta2` pre-releases.
17
49
 
@@ -436,7 +468,7 @@ If you used a 2.0 pre-release: the KBS rule engine, `/rules`, user-defined rule
436
468
  - Added `handle_tool_crash` method that logs errors with 5-line traceback
437
469
  - Tool crashes no longer crash AIA - conversation continues gracefully
438
470
 
439
- ## [0.9.24] 2025-12-17
471
+ ## [0.9.24] - 2025-12-17
440
472
  ### Fixes
441
473
  - Ran into a problem with the `shared_tools` gem and the --require parameter of AIA which required changes to both gems.
442
474
 
@@ -461,7 +493,7 @@ If you used a 2.0 pre-release: the KBS rule engine, `/rules`, user-defined rule
461
493
  - Added `handle_tool_crash` method for graceful error handling
462
494
  - Removed debug statements
463
495
 
464
- ## [0.9.23] 2025-12-06
496
+ ## [0.9.23] - 2025-12-06
465
497
 
466
498
  ### New Features
467
499
  - **MCP Server Configuration**: Added native support for defining MCP (Model Context Protocol) servers in the config file
@@ -494,7 +526,7 @@ If you used a 2.0 pre-release: the KBS rule engine, `/rules`, user-defined rule
494
526
  MY_VAR: "value"
495
527
  ```
496
528
 
497
- ## [0.9.22] 2025-11-12
529
+ ## [0.9.22] - 2025-11-12
498
530
 
499
531
  ### Bug Fixes
500
532
  - **TEST SUITE**: Fixed all Mocha test isolation issues causing stub contamination between tests
@@ -510,14 +542,14 @@ If you used a 2.0 pre-release: the KBS rule engine, `/rules`, user-defined rule
510
542
  - Standardized stub usage pattern across test suite for consistency
511
543
  - Eliminated stub leakage that caused "unexpected invocation" and "AIA was instantiated in one test but receiving invocations in another" errors
512
544
 
513
- ## [0.9.21] 2025-10-08
545
+ ## [0.9.21] - 2025-10-08
514
546
  ### Bug Fixes
515
547
  - **Checkpoint Directive Output**: Fixed `//checkpoint` directive to return empty string instead of status message (lib/aia/directives/configuration.rb:155)
516
548
  - Prevents checkpoint creation messages from entering AI context
517
549
  - Outputs confirmation to STDOUT instead for user feedback
518
550
  - Prevents potential AI manipulation of checkpoint system
519
551
 
520
- ## [0.9.20] 2025-10-06
552
+ ## [0.9.20] - 2025-10-06
521
553
  ### Added
522
554
  - **Enhanced Multi-Model Role System (ADR-005)**: Implemented per-model role assignment with inline syntax
523
555
  - New inline syntax: `--model MODEL[=ROLE][,MODEL[=ROLE]]...`
@@ -581,7 +613,7 @@ If you used a 2.0 pre-release: the KBS rule engine, `/rules`, user-defined rule
581
613
  - Enhanced `apply_file_config_to_struct` to detect and process model arrays with role hashes
582
614
  - Enhanced `envar_options` to parse inline syntax for `:model` key (lib/aia/config/base.rb:212-217)
583
615
 
584
- ## [0.9.19] 2025-10-06
616
+ ## [0.9.19] - 2025-10-06
585
617
 
586
618
  ### Bug Fixes
587
619
  - **CRITICAL BUG FIX**: Fixed multi-model cross-talk issue (#118) where models could see each other's conversation history
@@ -644,7 +676,7 @@ bin/aia --chat --model lms/openai/gpt-oss-20b,ollama/gpt-oss:20b --tokens
644
676
  # (Previously: LMS would say "5 times", Ollama "6 times" due to cross-talk)
645
677
  ```
646
678
 
647
- ## [0.9.18] 2025-10-05
679
+ ## [0.9.18] - 2025-10-05
648
680
 
649
681
  ### Bug Fixes
650
682
  - **BUG FIX**: Fixed RubyLLM provider error parsing to handle both OpenAI and LM Studio error formats
@@ -663,7 +695,7 @@ bin/aia --chat --model lms/openai/gpt-oss-20b,ollama/gpt-oss:20b --tokens
663
695
  - Cleaned up debug puts statements from RubyLLMAdapter and provider_fix
664
696
  - Added robust JSON parsing with fallback error handling
665
697
 
666
- ## [0.9.17] 2025-10-04
698
+ ## [0.9.17] - 2025-10-04
667
699
 
668
700
  ### New Features
669
701
  - **NEW FEATURE**: Enhanced local model support with comprehensive validation and error handling
@@ -713,7 +745,7 @@ aia --model ollama/llama3.2 --chat
713
745
  > //models
714
746
  ```
715
747
 
716
- ## [0.9.16] 2025-09-26
748
+ ## [0.9.16] - 2025-09-26
717
749
 
718
750
  ### New Features
719
751
  - **NEW FEATURE**: Added support for Ollama AI provider
@@ -724,7 +756,7 @@ aia --model ollama/llama3.2 --chat
724
756
  - **ENHANCEMENT**: Expanded AI provider ecosystem with three new local/self-hosted model options
725
757
  - **ENHANCEMENT**: Improved flexibility for users preferring local LLM deployments
726
758
 
727
- ## [0.9.15] 2025-09-21
759
+ ## [0.9.15] - 2025-09-21
728
760
 
729
761
  ### New Features
730
762
  - **NEW FEATURE**: Added `//paste` directive to insert clipboard contents into prompts
@@ -734,7 +766,7 @@ aia --model ollama/llama3.2 --chat
734
766
  - Enhanced DirectiveProcessor with clipboard integration using the clipboard gem
735
767
  - Added comprehensive test coverage for paste directive functionality
736
768
 
737
- ## [0.9.14] 2025-09-19
769
+ ## [0.9.14] - 2025-09-19
738
770
 
739
771
  ### New Features
740
772
  - **NEW FEATURE**: Added `//checkpoint` directive to create named snapshots of conversation context
@@ -760,7 +792,7 @@ aia --model ollama/llama3.2 --chat
760
792
  - Refactored help directive to collect directives from all registered modules
761
793
  - Added comprehensive test coverage for checkpoint and restore functionality
762
794
 
763
- ## [0.9.13] 2025-09-02
795
+ ## [0.9.13] - 2025-09-02
764
796
  ### New Features
765
797
  - **NEW FEATURE**: Added `--tokens` flag to show token counts for each model
766
798
  - **NEW FEATURE**: Added `--cost` flag to enable cost estimation for each model
@@ -778,7 +810,7 @@ aia --model ollama/llama3.2 --chat
778
810
  - Enhanced test suite with improved assertions and coverage
779
811
  - Updated various gem dependencies to latest stable versions
780
812
 
781
- ## [0.9.12] 2025-08-28
813
+ ## [0.9.12] - 2025-08-28
782
814
 
783
815
  ### New Features
784
816
  - **MAJOR NEW FEATURE**: Multi-model support - specify multiple AI models simultaneously with comma-separated syntax
@@ -845,15 +877,15 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
845
877
  - TODO: focus on log file consistency using Logger
846
878
 
847
879
 
848
- ## [0.9.11] 2025-07-31
880
+ ## [0.9.11] - 2025-07-31
849
881
  - added a cost per 1 million input tokens to available_models query output
850
882
  - updated ruby_llm to version 1.4.0
851
883
  - updated all other gem dependencies to their latest versions
852
884
 
853
- ## [0.9.10] 2025-07-18
885
+ ## [0.9.10] - 2025-07-18
854
886
  - updated ruby_llm-mcp to version 0.6.1 which solves problems with MCP tools not being installed
855
887
 
856
- ## [0.9.9] 2025-07-10
888
+ ## [0.9.9] - 2025-07-10
857
889
  - refactored the Session and Config classes into more testable method_missing
858
890
  - updated the test suire for both the Session and Config classes
859
891
  - added support for MCP servers coming into AIA via the shared_tools gem
@@ -864,13 +896,13 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
864
896
  - //available_models now has context window size and capabilities for each model returned
865
897
 
866
898
 
867
- ## [0.9.8] 2025-06-25
899
+ ## [0.9.8] - 2025-06-25
868
900
  - fixing an issue with pipelined prompts
869
901
  - now showing the complete modality of the model on the processing line.
870
902
  - changed -p option from prompts_dir to pipeline
871
903
  - found problem with simple cov and deep cov w/r/t their reported test coverage; they have problems with heredoc and complex conditionals.
872
904
 
873
- ## [0.9.7] 2025-06-20
905
+ ## [0.9.7] - 2025-06-20
874
906
 
875
907
  - **NEW FEATURE**: Added `--available_models` CLI option to list all available AI models
876
908
  - **NEW FEATURE**: Added `//tools` to show a list of available tools and their description
@@ -881,7 +913,7 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
881
913
  - **DOCUMENTATION**: Updated README for better clarity and structure
882
914
  - **DEPENDENCY**: Updated Gemfile.lock with latest dependency versions
883
915
 
884
- ## [0.9.6] 2025-06-13
916
+ ## [0.9.6] - 2025-06-13
885
917
  - fixed issue 84 with the //llms directive
886
918
  - changed the monkey patch to the RubyLLM::Model::Modalities class at the suggestions of the RubyLLM author in prep for a PR against that gem.
887
919
  - added the shared_tools gem - need to add examples on how to use it with the --tools option
@@ -889,11 +921,11 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
889
921
  - added images/aia.png to README.md
890
922
  - let claude code rewrite the README.md file. Some details were dropped but overall in reads better. Need to add the details to a wiki or other documentation site.
891
923
 
892
- ## [0.9.5] 2025-06-04
924
+ ## [0.9.5] - 2025-06-04
893
925
  - changed the RubyLLM::Modalities class to use method_missing for mode query
894
926
  - hunting for why the //llms query directive is not finding image_to_image LLMs.
895
927
 
896
- ## [0.9.4] 2025-06-03
928
+ ## [0.9.4] - 2025-06-03
897
929
  - using RubyLLM v1.3.0
898
930
  - setting up a docs infrastructure to behave like the ruby_llm gem's guides side
899
931
  - fixed bug in the text-to-image workflow
@@ -901,7 +933,7 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
901
933
  - need to pay attention to the test suite
902
934
  - also need to ensure the non text2text modes are working
903
935
 
904
- ## [0.9.3rc1] 2025-05-24
936
+ ## [0.9.3rc1] - 2025-05-24
905
937
  - using ruby_llm v1.3.0rc1
906
938
  - added a models database refresh based on integer days interval with the --refresh option
907
939
  - config file now has a "last_refresh" String in format YYYY-MM-DD
@@ -910,41 +942,41 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
910
942
  - fixed a bug in the prompt_manager gem which is now at v0.5.5
911
943
 
912
944
 
913
- ## [0.9.2] 2025-05-18
945
+ ## [0.9.2] - 2025-05-18
914
946
  - removing the MCP experiment
915
947
  - adding support for RubyLLM::Tool usage in place of the MCP stuff
916
948
  - updated prompt_manager to v0.5.4 which fixed shell integration problem
917
949
 
918
- ## [0.9.1] 2025-05-16
950
+ ## [0.9.1] - 2025-05-16
919
951
  - rethink MCP approach in favor of just RubyLLM::Tool
920
952
  - fixed problem with //clear
921
953
  - fixed a problem with a priming prompt in a chat loop
922
954
 
923
- ## [0.9.0] 2025-05-13
955
+ ## [0.9.0] - 2025-05-13
924
956
  - Adding experimental MCP Client support
925
957
  - removed the CLI options --erb and --shell but kept them in the config file with a default of true for both
926
958
 
927
- ## [0.8.6] 2025-04-23
959
+ ## [0.8.6] - 2025-04-23
928
960
  - Added a client adapter for the ruby_llm gem
929
961
  - Added the adapter config item and the --adapter option to select at runtime which client to use ai_client or ruby_llm
930
962
 
931
- ## [0.8.5] 2025-04-19
963
+ ## [0.8.5] - 2025-04-19
932
964
  - documentation updates
933
965
  - integrated the https://pure.md web service for inserting web pages into the context window
934
966
  - //include http?://example.com/stuff
935
967
  - //webpage http?://example.com/stuff
936
968
 
937
- ## [0.8.2] 2025-04-18
969
+ ## [0.8.2] - 2025-04-18
938
970
  - fixed problems with pre-loaded context and chat repl
939
971
  - piped content into `aia --chat` is now a part of the context/instructions
940
972
  - content via "aia --chat < some_file" is added to the context/instructions
941
973
  - `aia --chat context_file.txt context_file2.txt` now works
942
974
  - `aia --chat prompt_id context)file.txt` also works
943
975
 
944
- ## [0.8.1] 2025-04-17
976
+ ## [0.8.1] - 2025-04-17
945
977
  - bumped version to 0.8.1 after correcting merge conflicts
946
978
 
947
- ## [0.8.0] WIP - 2025-04-15
979
+ ## [0.8.0] - 2025-04-15
948
980
  - Updated PromptManager to v0.5.1 which has some of the functionality that was originally developed in the AIA.
949
981
  - Enhanced README.md to include a comprehensive table of configuration options with defaults and associated environment variables.
950
982
  - Added a note in README.md about the expandability of configuration options from a config file for dynamic prompt generation.
@@ -952,7 +984,7 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
952
984
  - Ensured version consistency across `.version`, `aia.gemspec`, and `lib/aia/version.rb`.
953
985
  - Verified and updated documentation to ensure readiness for release on RubyGems.org.
954
986
 
955
- ## [0.7.1] WIP - 2025-03-22
987
+ ## [0.7.1] - 2025-03-22
956
988
  - Added `UIPresenter` class for handling user interface presentation.
957
989
  - Added `DirectiveProcessor` class for processing chat-based directives.
958
990
  - Added `HistoryManager` class for managing conversation and variable history.
@@ -966,7 +998,7 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
966
998
  - Improved error handling and user feedback for directive processing.
967
999
  - Enhanced logging and output options for chat sessions.
968
1000
 
969
- ## [0.7.0] WIP - 2025-03-17
1001
+ ## [0.7.0] - 2025-03-17
970
1002
  - Major code refactoring for better organization and maintainability:
971
1003
  - Extracted `DirectiveProcessor` class to handle chat-based directives
972
1004
  - Extracted `HistoryManager` class for conversation and variable history management
@@ -977,13 +1009,13 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
977
1009
  - Improved output handling to suppress STDOUT when chat mode is off and output file is specified
978
1010
  - Updated spinner format in the process_prompt method for better user experience
979
1011
 
980
- ## [0.6.?] WIP
1012
+ ## [0.6.?] - WIP
981
1013
  - Implemented Tony Stark's Clean Slate Protocol on the develop branch
982
1014
 
983
- ## [0.5.17] 2024-05-17
1015
+ ## [0.5.17] - 2024-05-17
984
1016
  - removed replaced `semver` with `versionaire`
985
1017
 
986
- ## [0.5.16] 2024-04-02
1018
+ ## [0.5.16] - 2024-04-02
987
1019
  - fixed prompt pipelines
988
1020
  - added //next and //pipeline directives as shortcuts to //config [next,pipeline]
989
1021
  - Added new backend "client" as an internal OpenAI client
@@ -992,82 +1024,82 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
992
1024
  - Added --voice default: alloy (if "siri" and Mac? then uses cli tool "say")
993
1025
  - Added --image_size and --image_quality (--is --iq)
994
1026
 
995
- ## [0.5.15] 2024-03-30
1027
+ ## [0.5.15] - 2024-03-30
996
1028
  - Added the ability to accept piped in text to be appended to the end of the prompt text: curl $URL | aia ad_hoc
997
1029
  - Fixed bugs with entering directives as follow-up prompts during a chat session
998
1030
 
999
- ## [0.5.14] 2024-03-09
1031
+ ## [0.5.14] - 2024-03-09
1000
1032
  - Directly access OpenAI to do text to speech when using the `--speak` option
1001
1033
  - Added --voice to specify which voice to use
1002
1034
  - Added --speech_model to specify which TTS model to use
1003
1035
 
1004
- ## [0.5.13] 2024-03-03
1036
+ ## [0.5.13] - 2024-03-03
1005
1037
  - Added CLI-utility `llm` as a backend processor
1006
1038
 
1007
- ## [0.5.12] 2024-02-24
1039
+ ## [0.5.12] - 2024-02-24
1008
1040
  - Happy Birthday Ruby!
1009
1041
  - Added --next CLI option
1010
1042
  - Added --pipeline CLI option
1011
1043
 
1012
- ## [0.5.11] 2024-02-18
1044
+ ## [0.5.11] - 2024-02-18
1013
1045
  - allow directives to return information that is inserted into the prompt text
1014
1046
  - added //shell command directive
1015
1047
  - added //ruby ruby_code directive
1016
1048
  - added //include path_to_file directive
1017
1049
 
1018
- ## [0.5.10] 2024-02-03
1050
+ ## [0.5.10] - 2024-02-03
1019
1051
  - Added --roles_dir to isolate roles from other prompts if desired
1020
1052
  - Changed --prompts to --prompts_dir to be consistent
1021
1053
  - Refactored common fzf usage into its own tool class
1022
1054
 
1023
- ## [0.5.9] 2024-02-01
1055
+ ## [0.5.9] - 2024-02-01
1024
1056
  - Added a "I'm working" spinner thing when "--verbose" is used as an indication that the backend is in the process of composing its response to the prompt.
1025
1057
 
1026
- ## [0.5.8] 2024-01-17
1058
+ ## [0.5.8] - 2024-01-17
1027
1059
  - Changed the behavior of the --dump option. It must now be followed by path/to/file.ext where ext is a supported config file format: yml, yaml, toml
1028
1060
 
1029
- ## [0.5.7] 2024-01-15
1061
+ ## [0.5.7] - 2024-01-15
1030
1062
  - Added ERB processing to the config_file
1031
1063
 
1032
- ## [0.5.6] 2024-01-15
1064
+ ## [0.5.6] - 2024-01-15
1033
1065
  - Adding processing for directives, shell integration and erb to the follow up prompt in a chat session
1034
1066
  - some code refactoring.
1035
1067
 
1036
- ## [0.5.3] 2024-01-14
1068
+ ## [0.5.3] - 2024-01-14
1037
1069
  - adding ability to render markdown to the terminal using the "glow" CLI utility
1038
1070
 
1039
- ## [0.5.2] 2024-01-13
1071
+ ## [0.5.2] - 2024-01-13
1040
1072
  - wrap response when its going to the terminal
1041
1073
 
1042
- ## [0.5.1] 2024-01-12
1074
+ ## [0.5.1] - 2024-01-12
1043
1075
  - removed a wicked puts "loaded" statement
1044
1076
  - fixed missed code when the options were changed to --out_file and --log_file
1045
1077
  - fixed completion functions by updating $PROMPT_DIR to $AIA_PROMPTS_DIR to match the documentation.
1046
1078
 
1047
- ## [0.5.0] 2024-01-05
1079
+ ## [0.5.0] - 2024-01-05
1048
1080
  - breaking changes:
1049
1081
  - changed `--config` to `--config_file`
1050
1082
  - changed `--env` to `--shell`
1051
1083
  - changed `--output` to `--out_file`
1052
1084
  - changed default `out_file` to `STDOUT`
1053
1085
 
1054
- ## [0.4.3] 2023-12-31
1086
+ ## [0.4.3] - 2023-12-31
1055
1087
  - added --env to process embedded system environment variables and shell commands within a prompt.
1056
1088
  - added --erb to process Embedded RuBy within a prompt because have embedded shell commands will only get you in a trouble. Having ERB will really get you into trouble. Remember the simple prompt is usually the best prompt.
1057
1089
 
1058
- ## [0.4.2] 2023-12-31
1090
+ ## [0.4.2] - 2023-12-31
1059
1091
  - added the --role CLI option to pre-pend a "role" prompt to the front of a primary prompt.
1060
1092
 
1061
- ## [0.4.1] 2023-12-31
1093
+ ## [0.4.1] - 2023-12-31
1062
1094
  - added a chat mode
1063
1095
  - prompt directives now supported
1064
1096
  - version bumped to match the `prompt_manager` gem
1065
1097
 
1066
- ## [0.3.20] 2023-12-28
1098
+ ## [0.3.20] - 2023-12-28
1067
1099
  - added work around to issue with multiple context files going to the `mods` backend
1068
1100
  - added shellwords gem to sanitize prompt text on the command line
1069
1101
 
1070
- ## [0.3.19] 2023-12-26
1102
+ ## [0.3.19] - 2023-12-26
1071
1103
  - major code refactoring.
1072
1104
  - supports config files \*.yml, \*.yaml and \*.toml
1073
1105
  - usage implemented as a man page. --help will display the man page/
@@ -1080,7 +1112,7 @@ aia --chat -m gpt-4o-mini,gpt-3.5-turbo
1080
1112
  3. envar values over-rides ...
1081
1113
  4. default values
1082
1114
 
1083
- ## [0.3.0] = 2023-11-23
1115
+ ## [0.3.0] - 2023-11-23
1084
1116
 
1085
1117
  - Matching version to [prompt_manager](https://github.com/prompt_manager) This version allows for the user of history in the entry of values to prompt keywords. KW_HISTORY_MAX is set at 5. Changed CLI interaction to use historical selection and editing of prior keyword values.
1086
1118
 
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <img src="docs/assets/images/aia.png" alt="Robots waiter ready to take your order."><br />
8
8
  **The Prompt is the Code**<br />
9
- <p>Check out the new <a href="https://madbomber.github.io/aia/extending/multi-model/#giving-each-model-a-role">Inline Role Syntax</a> when working with multiple concurrent models.</p>
9
+ <p>New in 2.1: <a href="#coding-agents-as-models-claude-code-codex-copilot">coding agents as models</a>. Point <code>--model</code> at <code>claude_code/</code>, <code>codex/</code> or <code>copilot/</code> and the Claude Code, Codex or GitHub Copilot CLI answers your prompt, alone or as a member of a crew.</p>
10
10
  </div>
11
11
 
12
12
  ---
@@ -33,6 +33,7 @@ For more information on AIA visit these locations:
33
33
  | Prompt management | File-based system with history, variables, and fuzzy search | Limited or none |
34
34
  | Workflow pipelines | Native `--next` / `--pipeline` chaining | Manual chaining required |
35
35
  | Local model support | Ollama and LM Studio out of the box | Varies |
36
+ | Coding agents as models | Claude Code, Codex and Copilot CLIs used like any other model, alone or in a crew | Not available |
36
37
  | Tool integration | RubyLLM tools + MCP server ecosystem | Limited |
37
38
  | Shell & ERB integration | Native — prompts are live Ruby/shell programs | External only |
38
39
  | Agent orchestration | Debate, spawn, verify, decompose, delegate directives | Not available |
@@ -74,15 +75,15 @@ For more information on AIA visit these locations:
74
75
 
75
76
  ```plain
76
77
 
77
- , , AIA v2.0.0 is Online
78
+ , , AIA v2.1.0 is Online
78
79
  (\____/)
79
- (_oo_) Models: gpt-4o-mini
80
- (O) DB: refreshed 2026-03-19 at 10:56
81
- __|||__ \) Libs: ruby_llm v2.1.1, robot_lab v0.5.0, simple_flow v0.4.0,
82
- [/ Tobor \] / trak_flow v0.2.0, typed_bus v0.0.1
83
- / \_______/ \/ Tools: none loaded
84
- / /___\ MCP: (none configured)
85
- (\ /_____\ Crew: @tobor
80
+ (_oo_) Models: claude-sonnet-4-6
81
+ (O) DB: refreshed 2026-11-01 at 22:28
82
+ __|||__ \) Libs: ruby_llm v2.1.1, robot_lab v0.6.0, simple_flow v0.4.0, trak_flow v0.2.0, typed_bus v0.0.1
83
+ [/ Tobor \] / Tools: none loaded
84
+ / \_______/ \/ MCP: (none configured)
85
+ / /___\ Crew: @tobor
86
+ (\ /_____\
86
87
  ::: :::
87
88
  ::: :::
88
89
 
@@ -175,6 +176,7 @@ Implement a schema registry with event-driven synchronization...
175
176
  - [Ollama Integration](#ollama-integration)
176
177
  - [LM Studio Integration](#lm-studio-integration)
177
178
  - [Listing Local Models](#listing-local-models)
179
+ - [Coding Agents as Models (Claude Code, Codex, Copilot)](#coding-agents-as-models-claude-code-codex-copilot)
178
180
  - [Shell Integration](#shell-integration)
179
181
  - [Embedded Ruby (ERB)](#embedded-ruby-erb)
180
182
  - [Prompt Sequences](#prompt-sequences)
@@ -296,7 +298,8 @@ aia --fuzzy
296
298
  | Option | Description | Example |
297
299
  |--------|-------------|---------|
298
300
  | `--chat` | Start interactive chat session | `aia --chat` |
299
- | `--model MODEL` | Specify AI model(s) to use. Supports `MODEL[=ROLE]` syntax | `aia --model gpt-4o-mini,gpt-3.5-turbo` or `aia --model gpt-4o=architect,claude=security` |
301
+ | `--model MODEL` | Specify AI model(s) to use. Supports `MODEL[=ROLE]` syntax. A bare provider name (`claude_code`) means that provider's default model | `aia --model gpt-4o-mini,gpt-3.5-turbo` or `aia --model gpt-4o=architect,claude=security` |
302
+ | `--provider SLUG` | Provider for models named without a `provider/` prefix. Also re-points models from `AIA_MODEL` or the config file at this provider, keeping their names. Scopes `--list-models` | `aia --provider ollama -m llama3.2` |
300
303
  | `--consensus` | Enable consensus mode for multi-model | `aia --consensus` |
301
304
  | `--no-consensus` | Force individual responses | `aia --no-consensus` |
302
305
  | `--role ROLE` | Use a role/system prompt (default for all models) | `aia --role expert` |
@@ -348,6 +351,8 @@ AIA determines configuration settings using this order (highest to lowest priori
348
351
 
349
352
  **Note:** When `-c` / `--config-file` is used, it replaces the user config and environment variables. Configuration resets to bundled defaults, then the specified file is applied, then CLI arguments take precedence.
350
353
 
354
+ **`--provider` sets the provider, not the model.** Models named with `--model` and no `provider/` prefix get it. Models that came from `AIA_MODEL` or a config file get it too, replacing their own prefix but keeping their names, because the command line outranks both. So `--provider claude_code` with `AIA_MODEL=lms/qwen3.8` exported asks Claude Code for a model named `qwen3.8`. To use a provider's default model, name the provider as the model: `--model claude_code` means `claude_code/default`.
355
+
351
356
  ### Configuration Methods
352
357
 
353
358
  **Environment Variables:**
@@ -964,6 +969,46 @@ The `/available_models` directive (alias: `/models`) lists all models known to t
964
969
  - 📡 **Offline**: Works without internet connection
965
970
  - 🔧 **Control**: Full control over model and parameters
966
971
 
972
+ ### Coding Agents as Models (Claude Code, Codex, Copilot)
973
+
974
+ An agent CLI can stand in for a model. With the matching
975
+ `ruby_llm-providers-*` gem installed, the provider prefix sends the prompt to
976
+ the agent's batch mode on stdin and reads its answer back from stdout. The
977
+ agent keeps its own tools, settings and MCP servers and works in the current
978
+ directory, so any aia prompt, pipeline or multi-model run can be driven by an
979
+ agent that reads and edits files.
980
+
981
+ | Prefix | Gem | CLI |
982
+ |---|---|---|
983
+ | `claude_code/` | `ruby_llm-providers-claude_code` | `claude -p` |
984
+ | `codex/` | `ruby_llm-providers-codex` | `codex exec` |
985
+ | `copilot/` | `ruby_llm-providers-copilot` | `copilot` (non-interactive) |
986
+
987
+ ```bash
988
+ gem install ruby_llm-providers-claude_code
989
+
990
+ # The part after the slash is the agent's --model; a bare prefix means its default
991
+ aia --model claude_code/opus refactor_prompt
992
+ aia --model claude_code my_prompt
993
+
994
+ # --provider alone keeps the model name from AIA_MODEL or the config file and
995
+ # hands it to the agent; for the agent's default model use --model as above
996
+ aia --provider claude_code --model sonnet my_prompt
997
+
998
+ # Chat keeps the agent's session alive between turns (--resume)
999
+ aia --chat --model claude_code/sonnet
1000
+
1001
+ # Cross-vendor review: one agent implements, another reviews
1002
+ aia --model claude_code/sonnet=implementer,codex/default=reviewer --consensus review_prompt
1003
+
1004
+ aia --list-models --provider claude_code
1005
+ ```
1006
+
1007
+ Because the agent brings its own tools, aia's `--tools` and MCP servers are
1008
+ not attached to these robots (a one-line notice says so). Permissions, the
1009
+ working directory, timeouts and extra CLI flags are set through the gem's
1010
+ `RubyLLM.config.<provider>_*` options; see each gem's README.
1011
+
967
1012
  ### Shell Integration
968
1013
 
969
1014
  AIA automatically processes shell patterns in prompts:
@@ -1393,18 +1438,18 @@ mcp_servers:
1393
1438
 
1394
1439
  #### Verifying MCP Server Configuration
1395
1440
 
1396
- When MCP servers are configured, AIA displays them in the startup robot:
1441
+ When MCP servers are configured, AIA lists them in the startup robot with each server's tool count in parentheses. Servers that failed to connect are named after `FAILED:`:
1397
1442
 
1398
1443
  ```
1399
- , , AIA v2.0.0 is Online
1444
+ , , AIA v2.1.0 is Online
1400
1445
  (\____/)
1401
- (_oo_) Models: gpt-4o-mini
1402
- (O) DB: refreshed 2026-03-19 at 10:56
1403
- __|||__ \) Libs: ruby_llm v2.1.1, robot_lab v0.5.0, simple_flow v0.4.0,
1404
- [/ Tobor \] / trak_flow v0.2.0, typed_bus v0.0.1
1405
- / \_______/ \/ Tools: 24 tools loaded
1406
- / /___\ MCP: github, htm
1407
- (\ /_____\ Crew: @tobor
1446
+ (_oo_) Models: claude-sonnet-4-6
1447
+ (O) DB: refreshed 2026-11-01 at 22:28
1448
+ __|||__ \) Libs: ruby_llm v2.1.1, robot_lab v0.6.0, simple_flow v0.4.0, trak_flow v0.2.0, typed_bus v0.0.1
1449
+ [/ Tobor \] / Tools: 109 tools loaded
1450
+ / \_______/ \/ MCP: github(42), brew(14), redis(53) | FAILED: htm
1451
+ / /___\ Crew: @tobor
1452
+ (\ /_____\
1408
1453
  ::: :::
1409
1454
  ::: :::
1410
1455
 
@@ -99,8 +99,9 @@ module AIA
99
99
  end
100
100
 
101
101
  opts.on('--provider SLUG',
102
- 'Default provider for models given without a provider/ prefix; ' \
103
- 'also scopes --list-models (any RubyLLM-registered provider)') do |slug|
102
+ 'Provider for models given without a provider/ prefix; also re-points ' \
103
+ 'models from AIA_MODEL or the config file at it, keeping their names. ' \
104
+ 'Scopes --list-models (any RubyLLM-registered provider)') do |slug|
104
105
  options[:provider] = slug
105
106
  end
106
107
 
@@ -13,6 +13,10 @@
13
13
 
14
14
  module AIA
15
15
  class ModelSpec
16
+ # The model id used when a bare provider name is given (e.g. `claude_code`):
17
+ # the provider's own default model.
18
+ DEFAULT_MODEL = 'default'
19
+
16
20
  attr_accessor :name, :role, :instance, :internal_id, :provider
17
21
 
18
22
  def initialize(hash = {})
@@ -76,18 +80,23 @@ module AIA
76
80
 
77
81
  private
78
82
 
79
- # Extract a provider prefix from the model name. The prefix is a provider
80
- # exactly when RubyLLM knows it (registered already, or loadable as a
83
+ # Extract a provider from the model name. "<provider>/<model-id>" splits
84
+ # on the slash; a bare "<provider>" with no slash names that provider's
85
+ # DEFAULT_MODEL, which is how agent-CLI providers such as claude_code,
86
+ # codex and copilot are usually given. The prefix is a provider exactly
87
+ # when RubyLLM knows it (registered already, or loadable as a
81
88
  # conventional ruby_llm-providers-<prefix> gem) — no provider names are
82
89
  # hardcoded here. Names whose first segment is not a provider (registry
83
- # ids like "meta-llama/llama-4-maverick") pass through untouched.
90
+ # ids like "meta-llama/llama-4-maverick", plain ids like "gpt-4o") pass
91
+ # through untouched.
84
92
  def extract_provider_from_name!
85
- return unless @name&.include?('/')
93
+ return if @name.nil?
86
94
 
87
95
  prefix, rest = @name.split('/', 2)
88
96
  return unless ProviderRegistry.available?(prefix)
97
+
89
98
  @provider = prefix
90
- @name = rest
99
+ @name = rest || DEFAULT_MODEL
91
100
  end
92
101
  end
93
102
  end
data/lib/aia/config.rb CHANGED
@@ -227,7 +227,7 @@ module AIA
227
227
  apply_models_env_var unless overrides[:models]
228
228
  apply_plugins_dir_env_var unless overrides[:plugins_dir]
229
229
  apply_overrides(overrides) if overrides && !overrides.empty?
230
- apply_default_provider
230
+ apply_cli_provider(overrides.key?(:models) ? :fill : :replace)
231
231
  expand_paths
232
232
  process_mcp_files(overrides[:mcp_files]) if overrides[:mcp_files]
233
233
  end
@@ -382,13 +382,22 @@ module AIA
382
382
  end
383
383
  end
384
384
 
385
- # `--provider SLUG` gives prefixless models a default provider. A model
386
- # named with an explicit "<provider>/" prefix keeps its own.
387
- def apply_default_provider
385
+ # `--provider SLUG` on the command line. Given together with --model
386
+ # (+mode+ :fill), it is the provider for the models named there without
387
+ # a "<provider>/" prefix; an explicit prefix keeps its own provider,
388
+ # since both values come from the command line. Given without --model
389
+ # (+mode+ :replace), the models from AIA_MODEL or the config file keep
390
+ # their names but take this provider, prefix or not, because a
391
+ # command-line flag outranks both.
392
+ #
393
+ # @param mode [Symbol] :fill or :replace
394
+ def apply_cli_provider(mode)
388
395
  return if provider.nil? || provider.to_s.empty?
389
396
 
390
397
  Array(models).each do |spec|
391
- spec.provider ||= provider if spec.respond_to?(:provider)
398
+ next unless spec.respond_to?(:provider)
399
+
400
+ spec.provider = provider if mode == :replace || spec.provider.nil?
392
401
  end
393
402
  end
394
403
 
@@ -9,6 +9,9 @@
9
9
  # Decomposition is delegated to TaskDecomposer.
10
10
  # Step execution is delegated to TaskExecutor.
11
11
 
12
+ require_relative 'task_decomposer'
13
+ require_relative 'task_executor'
14
+
12
15
  module AIA
13
16
  class DelegateHandler
14
17
  include ContentExtractor
data/lib/aia/errors.rb CHANGED
@@ -36,4 +36,8 @@ module AIA
36
36
 
37
37
  # Raised when all sub-tasks fail in SpecialModeHandler decompose mode
38
38
  class DecomposeError < Error; end
39
+
40
+ # Raised when a robot's LLM call fails during a batch (non-chat) pipeline
41
+ # step; robot_lab shields the exception into reply text, this surfaces it
42
+ class LLMError < Error; end
39
43
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/aia/llm_error_collector.rb
4
+ #
5
+ # A robot_lab hook that remembers the exceptions robots raise while a
6
+ # pipeline step runs. robot_lab's Network shields a robot's exception into
7
+ # an "Error: ..." reply so one failing robot cannot crash the network, which
8
+ # also hides the failure from aia: a batch run would print the error as the
9
+ # answer and exit 0. The on_error hook fires before the shield, so the
10
+ # PipelineOrchestrator can ask afterwards whether the step really failed.
11
+
12
+ module AIA
13
+ class LLMErrorCollector < RobotLab::Hook
14
+ Entry = Data.define(:robot_name, :error) do
15
+ def to_s
16
+ prefix = robot_name.empty? ? '' : "#{robot_name}: "
17
+ "#{prefix}#{error.class}: #{error.message}"
18
+ end
19
+ end
20
+
21
+ @errors = []
22
+ @mutex = Mutex.new
23
+
24
+ class << self
25
+ # Register the collector on +registry+ once. robot_lab's global
26
+ # registry (the default) is consulted by every Robot#run, including
27
+ # the robots a Network runs in parallel threads.
28
+ #
29
+ # @param registry [RobotLab::HookRegistry]
30
+ def install!(registry = RobotLab.hooks)
31
+ return if registry.registrations.any? { |reg| reg.handler_class == self }
32
+
33
+ registry.on(self)
34
+ end
35
+
36
+ # robot_lab hook: a robot run raised +ctx.error+.
37
+ #
38
+ # @param ctx [RobotLab::RunHookContext]
39
+ def on_error(ctx)
40
+ robot_name = ctx.respond_to?(:robot) ? ctx.robot&.name.to_s : ''
41
+ @mutex.synchronize { @errors << Entry.new(robot_name: robot_name, error: ctx.error) }
42
+ end
43
+
44
+ # Forget the errors of the previous step.
45
+ def reset!
46
+ @mutex.synchronize { @errors.clear }
47
+ end
48
+
49
+ # @return [Array<Entry>] the errors recorded since the last reset!
50
+ def errors
51
+ @mutex.synchronize { @errors.dup }
52
+ end
53
+
54
+ def errors?
55
+ !errors.empty?
56
+ end
57
+
58
+ # One line naming the prompt and every distinct error it hit.
59
+ #
60
+ # @param prompt_id [String]
61
+ # @return [String]
62
+ def message(prompt_id)
63
+ "LLM error in prompt '#{prompt_id}': #{errors.map(&:to_s).uniq.join('; ')}"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -38,6 +38,8 @@ module AIA
38
38
  bridge = config.flags.track_pipeline ? TrakFlowBridge.new : nil
39
39
  bridge&.create_plan_from_pipeline(config.pipeline)
40
40
 
41
+ LLMErrorCollector.install!
42
+
41
43
  until config.pipeline.empty?
42
44
  prompt_id = config.pipeline.shift
43
45
  next if prompt_id.nil? || prompt_id.empty?
@@ -69,7 +71,15 @@ module AIA
69
71
  # Front matter may have changed the model or its settings
70
72
  @robot = @robot_sync.call(@robot) if @robot_sync
71
73
 
74
+ LLMErrorCollector.reset!
75
+ if special_mode_handled?(prompt_text)
76
+ bridge&.update_step_status(prompt_id, :completed) unless llm_errors_handled?(prompt_id, bridge)
77
+ return
78
+ end
79
+
72
80
  result, content = compute_result(prompt_text, config)
81
+ return if llm_errors_handled?(prompt_id, bridge)
82
+
73
83
  bridge&.update_step_status(prompt_id, :completed)
74
84
 
75
85
  @tracker.record_turn(model: config.models.first.name, input: prompt_text, result: result)
@@ -79,6 +89,46 @@ module AIA
79
89
  raise
80
90
  end
81
91
 
92
+ # robot_lab shields a robot's exception into an "Error: ..." reply, so a
93
+ # failed LLM call comes back looking like an answer. The collector hook
94
+ # saw the real exception. Outside chat the step fails and aia exits 1
95
+ # (see AIA.run); in a chat session the error is shown and the session
96
+ # carries on. True when the step failed.
97
+ def llm_errors_handled?(prompt_id, bridge)
98
+ return false unless LLMErrorCollector.errors?
99
+
100
+ message = LLMErrorCollector.message(prompt_id)
101
+ raise AIA::LLMError, message unless AIA.chat?
102
+
103
+ @ui.display_info(message)
104
+ @ui.display_separator
105
+ bridge&.update_step_status(prompt_id, :failed, reason: message)
106
+ true
107
+ end
108
+
109
+ # A directive in the prompt (/debate, /verify, /decompose, /delegate,
110
+ # /spawn, /orchestrate) asked for a special mode: hand the turn to the
111
+ # SpecialModeHandler, as ChatLoop does. True when it ran the turn, which
112
+ # includes displaying, saving and recording it. Concurrent MCP is left
113
+ # to execute_prompt, which honors it alongside config.concurrency.auto.
114
+ # A --paint step has no robot turn to hand over.
115
+ def special_mode_handled?(prompt_text)
116
+ return false if AIA.paint? || !special_mode_requested?
117
+
118
+ special_mode_handler.robot = @robot
119
+ special_mode_handler.handle(prompt_text)
120
+ end
121
+
122
+ # True when the turn state holds a mode for SpecialModeHandler to run.
123
+ def special_mode_requested?
124
+ mode = AIA.turn_state.active_mode
125
+ !mode.nil? && mode != :concurrent_mcp
126
+ end
127
+
128
+ def special_mode_handler
129
+ @special_mode_handler ||= SpecialModeHandler.new(robot: @robot, ui_presenter: @ui, tracker: @tracker)
130
+ end
131
+
82
132
  # Show a step's response, then save and speak it. A --paint step saved
83
133
  # an image next to the output file, so there is no text to keep or speak.
84
134
  def present_result(content, result, config)
@@ -108,16 +158,16 @@ module AIA
108
158
  img_cfg = config.image
109
159
  quality = img_cfg.quality
110
160
  style = img_cfg.style
111
- params = {}
112
- params[:quality] = quality if quality && !quality.to_s.strip.empty?
113
- params[:style] = style if style && !style.to_s.strip.empty?
161
+ provider_options = {}
162
+ provider_options[:quality] = quality if quality && !quality.to_s.strip.empty?
163
+ provider_options[:style] = style if style && !style.to_s.strip.empty?
114
164
 
115
165
  image = @ui.with_spinner("Generating image") do
116
166
  RubyLLM.paint(
117
167
  prompt_text,
118
- model: img_cfg.model || 'gpt-image-1',
119
- size: img_cfg.size || '1024x1024',
120
- params: params
168
+ model: img_cfg.model || 'gpt-image-1',
169
+ size: img_cfg.size || '1024x1024',
170
+ provider_options:
121
171
  )
122
172
  end
123
173
 
@@ -22,6 +22,9 @@ module AIA
22
22
  'erb' => [%w[config erb], %w[config flags erb]]
23
23
  }.freeze
24
24
 
25
+ # Front matter `mode:` names that differ from their TurnState mode.
26
+ MODE_SHORTHANDS = { 'concurrent' => :concurrent_mcp }.freeze
27
+
25
28
  def initialize
26
29
  @prompts_dir = AIA.config.prompts.dir
27
30
 
@@ -36,6 +39,22 @@ module AIA
36
39
  register_pm_directives
37
40
  end
38
41
 
42
+ # The TurnState mode a front matter `mode:` +value+ names: a chat
43
+ # directive's name without the slash (debate, verify, decompose,
44
+ # delegate, spawn, orchestrate, concurrent).
45
+ #
46
+ # @param value [String, Symbol]
47
+ # @return [Symbol] one of TurnState::EXCLUSIVE_MODES
48
+ # @raise [ConfigurationError] for a name that is not a mode
49
+ def self.mode_for(value)
50
+ name = value.to_s.strip.downcase
51
+ mode = MODE_SHORTHANDS.fetch(name, name.to_sym)
52
+ return mode if TurnState::EXCLUSIVE_MODES.include?(mode)
53
+
54
+ names = (TurnState::EXCLUSIVE_MODES - MODE_SHORTHANDS.values).map(&:to_s) + MODE_SHORTHANDS.keys
55
+ raise ConfigurationError, "Unknown mode: #{value.inspect} (one of #{names.sort.join(', ')})"
56
+ end
57
+
39
58
  # A shell/erb flag is on unless it is explicitly false.
40
59
  #
41
60
  # @param flags [#shell, #erb, nil]
@@ -212,6 +231,11 @@ module AIA
212
231
 
213
232
  erb_val = shorthand(meta_hash, :erb)
214
233
  cfg.flags.erb = erb_val unless erb_val.nil?
234
+
235
+ # mode → the special mode for this prompt's turn, as the chat
236
+ # directive of that name (/debate, /verify, ...) would request it
237
+ mode_val = shorthand(meta_hash, :mode)
238
+ AIA.turn_state.request(self.class.mode_for(mode_val)) if mode_val
215
239
  end
216
240
 
217
241
  # Fetch a front-matter shorthand value by string or symbol key.
@@ -37,7 +37,9 @@ module AIA
37
37
  # Attempt to require the conventional gem path for a provider slug.
38
38
  # Returns true when the provider ends up registered. Failed lookups are
39
39
  # cached so repeated non-provider prefixes (model ids also use "/")
40
- # don't pay the require cost every time.
40
+ # don't pay the require cost every time. When the plain require fails
41
+ # because aia runs under Bundler and the gem is installed but not in the
42
+ # Gemfile, the gem is sideloaded from the RubyGems search path.
41
43
  def load(slug)
42
44
  slug = slug.to_s
43
45
  return false unless slug.match?(/\A[a-z0-9_]+\z/)
@@ -50,22 +52,71 @@ module AIA
50
52
  require "ruby_llm/providers/#{slug}"
51
53
  registered?(slug)
52
54
  rescue LoadError
53
- false
55
+ sideload(slug)
54
56
  end
55
57
  end
56
58
 
59
+ # Load an installed `ruby_llm-providers-<slug>` gem that the active
60
+ # bundle hides. Under Bundler only the Gemfile's gems can be required,
61
+ # so a provider gem added with `gem install` is invisible to a plain
62
+ # require even though it sits on disk. This finds its gemspec on the
63
+ # RubyGems search path and, when every runtime dependency it declares is
64
+ # already satisfied by the bundle, adds its lib directory to the load
65
+ # path and requires it. The bundle itself is not changed. Returns true
66
+ # when the provider ends up registered.
67
+ def sideload(slug)
68
+ spec = installed_spec(slug)
69
+ return false unless spec
70
+
71
+ unmet = spec.runtime_dependencies.reject { |dep| dependency_met?(dep) }
72
+ unless unmet.empty?
73
+ AIA.debug_warn("#{spec.full_name} is installed but needs #{unmet.join(', ')}, which this bundle does not provide")
74
+ return false
75
+ end
76
+
77
+ spec.full_require_paths.each { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
78
+ require "ruby_llm/providers/#{slug}"
79
+ registered?(slug)
80
+ rescue LoadError
81
+ false
82
+ end
83
+
84
+ # The newest installed gemspec for `ruby_llm-providers-<slug>`, read from
85
+ # the specifications directories on the RubyGems search path, or nil.
86
+ # Reads the files directly, so it sees gems the bundle does not.
87
+ def installed_spec(slug)
88
+ name = "ruby_llm-providers-#{slug}"
89
+ Gem.path
90
+ .flat_map { |dir| Dir[File.join(dir, 'specifications', "#{name}-*.gemspec")] }
91
+ .filter_map { |file| Gem::Specification.load(file) }
92
+ .select { |spec| spec.name == name }
93
+ .max_by(&:version)
94
+ end
95
+
96
+ # Whether a gem dependency is satisfied by a gem already active in this
97
+ # process (under Bundler, that is the bundle).
98
+ def dependency_met?(dep)
99
+ active = Gem.loaded_specs[dep.name]
100
+ !active.nil? && dep.requirement.satisfied_by?(active.version)
101
+ end
102
+
57
103
  # A provider instance ready for API calls (model listing etc.).
58
104
  # Raises RubyLLM's own errors for unknown or unconfigured providers.
59
105
  def instantiate(slug)
60
106
  RubyLLM::Provider.resolve!(slug.to_sym).new(RubyLLM.config)
61
107
  end
62
108
 
63
- # Slugs of conventionally named provider gems visible to the bundle
64
- # (ruby_llm-providers-<slug>), whether or not they are loaded yet.
109
+ # Slugs of conventionally named provider gems (ruby_llm-providers-<slug>)
110
+ # that are installed, whether in the bundle or only on the RubyGems
111
+ # search path, and whether or not they are loaded yet.
65
112
  def installed_gem_slugs
66
- Gem::Specification.filter_map do |spec|
67
- spec.name.delete_prefix('ruby_llm-providers-') if spec.name.start_with?('ruby_llm-providers-')
68
- end.uniq
113
+ prefix = 'ruby_llm-providers-'
114
+ bundled = Gem::Specification.map(&:name)
115
+ on_disk = Gem.path.flat_map { |dir| Dir[File.join(dir, 'specifications', "#{prefix}*.gemspec")] }
116
+ .map { |file| File.basename(file, '.gemspec').sub(/-[^-]+\z/, '') }
117
+ (bundled + on_disk).select { |name| name.start_with?(prefix) }
118
+ .map { |name| name.delete_prefix(prefix) }
119
+ .uniq
69
120
  end
70
121
 
71
122
  # Register every installed conventional provider gem, so a full listing
@@ -243,9 +243,49 @@ module AIA
243
243
  # @return [RobotLab::Robot]
244
244
  def build_robot(spec, **opts)
245
245
  opts[:provider] = resolve_provider(spec) if spec.provider
246
+ opts = without_tools(spec, opts) unless provider_accepts_tools?(spec.provider)
246
247
  RobotLab.build(model: spec.name, **opts)
247
248
  end
248
249
 
250
+ # Whether a provider can take RubyLLM tools. Agent-CLI providers
251
+ # (claude_code, codex, copilot) run their own tools and say so with a
252
+ # class-level `accepts_tools?` returning false; every other provider
253
+ # accepts tools. Nothing is hardcoded here — the provider class decides.
254
+ #
255
+ # @param provider [String, nil] a RubyLLM provider slug
256
+ # @return [Boolean]
257
+ def provider_accepts_tools?(provider)
258
+ klass = provider && ProviderRegistry.providers[provider.to_s]
259
+ return true unless klass.respond_to?(:accepts_tools?)
260
+
261
+ klass.accepts_tools?
262
+ end
263
+
264
+ # Strip the tools and MCP servers from a robot's build options for a
265
+ # provider that brings its own, telling the user once per provider what
266
+ # was left out so a `--tools` or `--mcp-use` that silently did nothing
267
+ # is not a mystery.
268
+ #
269
+ # @param spec [AIA::ModelSpec]
270
+ # @param opts [Hash] RobotLab.build options
271
+ # @return [Hash] opts with empty local_tools: and mcp:
272
+ def without_tools(spec, opts)
273
+ dropped = []
274
+ dropped << 'tools' if opts[:local_tools]&.any?
275
+ dropped << 'MCP servers' if opts[:mcp]&.any?
276
+ notice_tools_dropped(spec.provider, dropped) unless dropped.empty?
277
+ opts.merge(local_tools: [], mcp: [])
278
+ end
279
+
280
+ # One notice per provider per process.
281
+ def notice_tools_dropped(provider, dropped)
282
+ @tools_dropped_notices ||= {}
283
+ return if @tools_dropped_notices[provider]
284
+
285
+ @tools_dropped_notices[provider] = true
286
+ AIA.debug_warn("#{provider} runs its own tools; aia #{dropped.join(' and ')} are not attached to it")
287
+ end
288
+
249
289
  # Resolve a spec's provider to a RubyLLM provider slug. Slugs pass
250
290
  # through unchanged — RubyLLM's registry is the single source of truth —
251
291
  # but the method stays as the seam where optional provider gems get
@@ -265,10 +305,23 @@ module AIA
265
305
 
266
306
  AIA.debug_warn(
267
307
  "provider '#{provider}' is not registered with RubyLLM and no " \
268
- "ruby_llm/providers/#{provider} library could be loaded"
308
+ "ruby_llm/providers/#{provider} library could be loaded#{bundler_hint(provider)}"
269
309
  )
270
310
  end
271
311
 
312
+ # Under Bundler only the gems in the Gemfile can be required, so a
313
+ # provider gem installed with `gem install` stays invisible. Name the
314
+ # Gemfile to add it to; outside Bundler there is nothing to add.
315
+ #
316
+ # @param provider [String] a provider slug
317
+ # @param gemfile [String, nil] the active Gemfile; nil when not under Bundler
318
+ # @return [String] text to append to the warning, "" outside Bundler
319
+ def bundler_hint(provider, gemfile = ENV.fetch('BUNDLE_GEMFILE', nil))
320
+ return '' if gemfile.nil? || gemfile.empty?
321
+
322
+ "; aia is running under Bundler, so add gem 'ruby_llm-providers-#{provider}' to #{gemfile}"
323
+ end
324
+
272
325
  private
273
326
 
274
327
  # Configure RobotLab providers from AIA config
@@ -253,8 +253,6 @@ module AIA
253
253
  def delegate_handler
254
254
  @delegate_handler ||= begin
255
255
  require_relative 'delegate_handler'
256
- require_relative 'task_decomposer'
257
- require_relative 'task_executor'
258
256
  DelegateHandler.new(
259
257
  robot: @robot, ui_presenter: @ui_presenter,
260
258
  tracker: @tracker, task_coordinator: AIA.task_coordinator
@@ -7,6 +7,8 @@
7
7
  # with full TaskBoardTool access. Foreman is built on first use
8
8
  # and cached for the session.
9
9
 
10
+ require_relative 'task_board_tool'
11
+
10
12
  module AIA
11
13
  class DelegateToForemanTool < RubyLLM::Tool
12
14
  description "Delegate a task management request to Foreman, the task board manager. " \
data/lib/aia/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # lib/aia/version.rb
2
2
 
3
3
  module AIA
4
- VERSION = '2.0.1'.freeze
4
+ VERSION = '2.1.0'.freeze
5
5
  end
data/lib/aia.rb CHANGED
@@ -73,6 +73,7 @@ require_relative 'aia/mention_router'
73
73
  require_relative 'aia/special_mode_handler'
74
74
  require_relative 'aia/mcp_connection_manager'
75
75
  require_relative 'aia/startup_coordinator'
76
+ require_relative 'aia/llm_error_collector'
76
77
  require_relative 'aia/pipeline_orchestrator'
77
78
  require_relative 'aia/chat_loop'
78
79
  require_relative 'aia/session'
@@ -185,16 +186,23 @@ module AIA
185
186
  RobotFactory.setup(@config)
186
187
  load_fzf_integration
187
188
  start_session
188
- rescue AIA::ConfigurationError => e
189
- $stderr.puts e.message
190
- exit 1
191
189
  rescue AIA::Error => e
192
- $stderr.puts e.message
193
- exit 1
190
+ fail_run(e.message)
191
+ rescue RubyLLM::Error, RubyLLM::ConfigurationError => e
192
+ # An LLM failure that nothing shielded (image generation, provider setup)
193
+ fail_run("LLM error: #{e.class}: #{e.message}")
194
194
  end
195
195
 
196
196
  private
197
197
 
198
+ # Print a one-line fatal error to stderr and exit 1.
199
+ #
200
+ # @param message [String]
201
+ def fail_run(message)
202
+ $stderr.puts message
203
+ exit 1
204
+ end
205
+
198
206
  # Record startup in all three log files. Only aia.log gets the full
199
207
  # configuration dump; llm.log and mcp.log just note that AIA started,
200
208
  # since their configuration is already covered by the aia.log entry.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aia
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -331,6 +331,20 @@ dependencies:
331
331
  - - ">="
332
332
  - !ruby/object:Gem::Version
333
333
  version: '0'
334
+ - !ruby/object:Gem::Dependency
335
+ name: ostruct
336
+ requirement: !ruby/object:Gem::Requirement
337
+ requirements:
338
+ - - ">="
339
+ - !ruby/object:Gem::Version
340
+ version: '0'
341
+ type: :development
342
+ prerelease: false
343
+ version_requirements: !ruby/object:Gem::Requirement
344
+ requirements:
345
+ - - ">="
346
+ - !ruby/object:Gem::Version
347
+ version: '0'
334
348
  - !ruby/object:Gem::Dependency
335
349
  name: rake
336
350
  requirement: !ruby/object:Gem::Requirement
@@ -401,16 +415,17 @@ dependencies:
401
415
  - - ">="
402
416
  - !ruby/object:Gem::Version
403
417
  version: '0'
404
- description: 'AIA is a powerful CLI console application that brings multi-model AI
405
- capabilities to your command line, supporting 20+ providers including OpenAI, Anthropic,
406
- and Google. Built on robot_lab for robust robot orchestration, AIA v2 provides a
407
- thin CLI shell over a rich execution engine. Run multiple AI models simultaneously
408
- for comparison, get consensus responses from collaborative AI teams, or compare
409
- individual outputs side-by-side. With dynamic prompt management, embedded directives,
410
- shell and Ruby integration, interactive chats, and comprehensive history tracking,
411
- AIA transforms how you interact with AI.
418
+ description: |
419
+ AIA runs large language models from the terminal. Prompts are Markdown files with YAML front matter: parameters
420
+ that AIA asks for, directives such as /include and /shell, ERB and shell expansion, and keys that pick the model,
421
+ temperature or a special mode. Run a prompt once, chain prompts into a pipeline, make a prompt file executable,
422
+ or open an interactive chat with history, checkpoints and skills.
412
423
 
413
- '
424
+ Every session is a crew of robots built on robot_lab. Name several models to compare them side by side, ask for a
425
+ consensus or a debate, have two independent answers reconciled with /verify, or decompose and delegate work to
426
+ specialists. Robots use RubyLLM tools and MCP servers through ruby_llm 2.1's built-in client, with per-tool
427
+ approval and automatic tool filtering. Any provider RubyLLM registers can be used, local or remote, plus optional
428
+ ruby_llm-providers-* gems that run Claude Code, Codex or GitHub Copilot as models. Requires Ruby 4.0.9 or newer.
414
429
  email:
415
430
  - dvanhoozer@gmail.com
416
431
  executables:
@@ -456,6 +471,7 @@ files:
456
471
  - lib/aia/history_transfer.rb
457
472
  - lib/aia/input_collector.rb
458
473
  - lib/aia/layered_orchestrator.rb
474
+ - lib/aia/llm_error_collector.rb
459
475
  - lib/aia/logger.rb
460
476
  - lib/aia/mcp_config_normalizer.rb
461
477
  - lib/aia/mcp_connection_manager.rb
@@ -533,12 +549,14 @@ metadata:
533
549
  post_install_message: |2+
534
550
 
535
551
  ╔══════════════════════════════════════════════════════════════╗
536
- ║ AIA — AI Assistant v2.0 ║
552
+ ║ AIA — AI Assistant v2.1.0 ║
537
553
  ║ ║
538
- ║ v2 is powered by robot_lab for robust orchestration ║
554
+ ║ Prompts as Markdown, crews of robots, MCP servers, and ║
555
+ ║ any RubyLLM provider, including coding-agent CLIs. ║
539
556
  ╚══════════════════════════════════════════════════════════════╝
540
557
 
541
558
  Get started: aia --help
559
+ What is new: https://github.com/MadBomber/aia/blob/main/CHANGELOG.md
542
560
  Full docs: https://madbomber.github.io/aia
543
561
 
544
562
  rdoc_options: []
@@ -557,7 +575,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
557
575
  requirements: []
558
576
  rubygems_version: 4.0.23
559
577
  specification_version: 4
560
- summary: Multi-model AI CLI with dynamic prompts, consensus responses, shell & Ruby
561
- integration, and seamless chat workflows.
578
+ summary: 'Command-line AI assistant: Markdown prompts with directives, ERB and shell,
579
+ chat with crews of robots, MCP servers, and any RubyLLM provider including coding-agent
580
+ CLIs.'
562
581
  test_files: []
563
582
  ...