solid_loop 0.0.4

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 (137) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +156 -0
  3. data/CODE_OF_CONDUCT.md +67 -0
  4. data/CONTRIBUTING.md +82 -0
  5. data/MIT-LICENSE +21 -0
  6. data/README.md +483 -0
  7. data/Rakefile +11 -0
  8. data/app/assets/javascripts/solid_loop/chart.umd.min.js +14 -0
  9. data/app/assets/javascripts/solid_loop/chartjs-adapter-date-fns.bundle.min.js +7 -0
  10. data/app/assets/javascripts/solid_loop/chartkick.min.js +2 -0
  11. data/app/assets/stylesheets/solid_loop/application.css +15 -0
  12. data/app/controllers/solid_loop/application_controller.rb +29 -0
  13. data/app/controllers/solid_loop/dashboard_controller.rb +104 -0
  14. data/app/controllers/solid_loop/events_controller.rb +12 -0
  15. data/app/controllers/solid_loop/loops_controller.rb +77 -0
  16. data/app/controllers/solid_loop/mcp_inbound_sessions_controller.rb +13 -0
  17. data/app/controllers/solid_loop/mcp_sessions_controller.rb +68 -0
  18. data/app/controllers/solid_loop/mcp_tools_controller.rb +16 -0
  19. data/app/controllers/solid_loop/messages_controller.rb +12 -0
  20. data/app/controllers/solid_loop/tool_calls_controller.rb +12 -0
  21. data/app/helpers/solid_loop/application_helper.rb +125 -0
  22. data/app/jobs/solid_loop/application_job.rb +13 -0
  23. data/app/jobs/solid_loop/llm_completion_job.rb +139 -0
  24. data/app/jobs/solid_loop/observe_broadcast_job.rb +15 -0
  25. data/app/jobs/solid_loop/reaper_job.rb +20 -0
  26. data/app/jobs/solid_loop/tool_execution_job.rb +200 -0
  27. data/app/models/solid_loop/application_record.rb +5 -0
  28. data/app/models/solid_loop/base.rb +190 -0
  29. data/app/models/solid_loop/event.rb +7 -0
  30. data/app/models/solid_loop/loop.rb +225 -0
  31. data/app/models/solid_loop/mcp_inbound_session.rb +25 -0
  32. data/app/models/solid_loop/mcp_session.rb +12 -0
  33. data/app/models/solid_loop/mcp_tool.rb +26 -0
  34. data/app/models/solid_loop/message.rb +69 -0
  35. data/app/models/solid_loop/tool_call.rb +42 -0
  36. data/app/queries/solid_loop/admin/base_query.rb +23 -0
  37. data/app/queries/solid_loop/admin/events_query.rb +31 -0
  38. data/app/queries/solid_loop/admin/loops_query.rb +38 -0
  39. data/app/queries/solid_loop/admin/mcp_inbound_sessions_query.rb +31 -0
  40. data/app/queries/solid_loop/admin/mcp_sessions_query.rb +26 -0
  41. data/app/queries/solid_loop/admin/mcp_tools_query.rb +26 -0
  42. data/app/queries/solid_loop/admin/messages_query.rb +37 -0
  43. data/app/queries/solid_loop/admin/tool_calls_query.rb +34 -0
  44. data/app/services/solid_loop/adapters/native.rb +170 -0
  45. data/app/services/solid_loop/dialects/anthropic.rb +160 -0
  46. data/app/services/solid_loop/dialects/gemini.rb +122 -0
  47. data/app/services/solid_loop/dialects/open_ai.rb +62 -0
  48. data/app/services/solid_loop/dialects/reasoning_packer.rb +37 -0
  49. data/app/services/solid_loop/llm_usage_parser/anthropic.rb +22 -0
  50. data/app/services/solid_loop/llm_usage_parser/gemini.rb +18 -0
  51. data/app/services/solid_loop/llm_usage_parser/llama.rb +15 -0
  52. data/app/services/solid_loop/llm_usage_parser/openai.rb +18 -0
  53. data/app/services/solid_loop/llm_usage_parser.rb +19 -0
  54. data/app/services/solid_loop/mcp_session_initializer.rb +205 -0
  55. data/app/services/solid_loop/mcp_tool_execution_service.rb +153 -0
  56. data/app/services/solid_loop/middlewares/agent_initialization.rb +37 -0
  57. data/app/services/solid_loop/middlewares/error_handling.rb +107 -0
  58. data/app/services/solid_loop/middlewares/event_logging.rb +100 -0
  59. data/app/services/solid_loop/middlewares/message_building.rb +92 -0
  60. data/app/services/solid_loop/middlewares/network_calling.rb +84 -0
  61. data/app/services/solid_loop/middlewares/response_parsing.rb +348 -0
  62. data/app/services/solid_loop/middlewares/tool_call_xml_parser.rb +117 -0
  63. data/app/services/solid_loop/sse_stream_aggregator.rb +105 -0
  64. data/app/services/solid_loop/tool_failure_reconciler.rb +122 -0
  65. data/app/services/solid_loop/tool_middlewares/agent_initialization.rb +17 -0
  66. data/app/services/solid_loop/tool_middlewares/error_handling.rb +66 -0
  67. data/app/services/solid_loop/tool_middlewares/event_logging.rb +27 -0
  68. data/app/services/solid_loop/tool_middlewares/response_creation.rb +110 -0
  69. data/app/services/solid_loop/tool_middlewares/tool_execution.rb +206 -0
  70. data/app/views/layouts/solid_loop/admin.html.erb +416 -0
  71. data/app/views/layouts/solid_loop/application.html.erb +17 -0
  72. data/app/views/solid_loop/dashboard/index.html.erb +184 -0
  73. data/app/views/solid_loop/events/index.html.erb +47 -0
  74. data/app/views/solid_loop/events/show.html.erb +76 -0
  75. data/app/views/solid_loop/loops/index.html.erb +83 -0
  76. data/app/views/solid_loop/loops/show.html.erb +148 -0
  77. data/app/views/solid_loop/mcp_inbound_sessions/index.html.erb +53 -0
  78. data/app/views/solid_loop/mcp_inbound_sessions/show.html.erb +78 -0
  79. data/app/views/solid_loop/mcp_sessions/_tool_output.html.erb +11 -0
  80. data/app/views/solid_loop/mcp_sessions/index.html.erb +46 -0
  81. data/app/views/solid_loop/mcp_sessions/inspector.html.erb +94 -0
  82. data/app/views/solid_loop/mcp_sessions/show.html.erb +142 -0
  83. data/app/views/solid_loop/mcp_tools/index.html.erb +44 -0
  84. data/app/views/solid_loop/mcp_tools/show.html.erb +69 -0
  85. data/app/views/solid_loop/messages/_message.html.erb +75 -0
  86. data/app/views/solid_loop/messages/index.html.erb +80 -0
  87. data/app/views/solid_loop/messages/show.html.erb +121 -0
  88. data/app/views/solid_loop/shared/_pagination.html.erb +21 -0
  89. data/app/views/solid_loop/tool_calls/index.html.erb +53 -0
  90. data/app/views/solid_loop/tool_calls/show.html.erb +59 -0
  91. data/config/routes.rb +27 -0
  92. data/db/migrate/20260408000100_solid_loop_init.rb +136 -0
  93. data/db/migrate/20260709000100_solid_loop_create_mcp_inbound_sessions.rb +16 -0
  94. data/db/migrate/20260713000100_solid_loop_add_execution_guards.rb +6 -0
  95. data/db/migrate/20260714000100_solid_loop_add_message_steering.rb +17 -0
  96. data/db/migrate/20260714000200_solid_loop_add_message_conversation_order.rb +14 -0
  97. data/db/migrate/20260715000100_solid_loop_add_mcp_inbound_session_principal_key.rb +43 -0
  98. data/db/migrate/20260715000200_solid_loop_add_llm_lease.rb +40 -0
  99. data/db/migrate/20260715000300_solid_loop_add_tool_lease.rb +33 -0
  100. data/db/migrate/20260715000400_solid_loop_add_lease_running_check.rb +32 -0
  101. data/db/migrate/20260715000500_solid_loop_add_tool_lease_pair_check.rb +35 -0
  102. data/docs/contributing/coverage.md +64 -0
  103. data/docs/decisions/durable_attempt_lease.md +364 -0
  104. data/docs/decisions/mcp-only-tooling.md +135 -0
  105. data/docs/decisions/mcp-server.md +223 -0
  106. data/docs/decisions/reasoning_persistence.md +51 -0
  107. data/docs/decisions/ruby_llm_rejected.md +35 -0
  108. data/docs/guides/dialects.md +55 -0
  109. data/docs/guides/llm_middlewares.md +294 -0
  110. data/docs/guides/mcp_transports.md +374 -0
  111. data/docs/guides/tool_middlewares.md +148 -0
  112. data/lib/solid_loop/configuration.rb +175 -0
  113. data/lib/solid_loop/engine.rb +14 -0
  114. data/lib/solid_loop/lease_heartbeat.rb +94 -0
  115. data/lib/solid_loop/lease_renewer.rb +347 -0
  116. data/lib/solid_loop/llm_metrics.rb +16 -0
  117. data/lib/solid_loop/mcp/call_context.rb +19 -0
  118. data/lib/solid_loop/mcp/client_factory.rb +61 -0
  119. data/lib/solid_loop/mcp/http_transport.rb +52 -0
  120. data/lib/solid_loop/mcp/principal.rb +82 -0
  121. data/lib/solid_loop/mcp/result.rb +13 -0
  122. data/lib/solid_loop/mcp/server.rb +246 -0
  123. data/lib/solid_loop/mcp/stdio_transport.rb +224 -0
  124. data/lib/solid_loop/mcp/toolset.rb +347 -0
  125. data/lib/solid_loop/mcp/transport.rb +20 -0
  126. data/lib/solid_loop/mcp.rb +25 -0
  127. data/lib/solid_loop/mcp_client.rb +176 -0
  128. data/lib/solid_loop/pipeline/builder.rb +38 -0
  129. data/lib/solid_loop/pipeline/context.rb +61 -0
  130. data/lib/solid_loop/pipeline/tool_context.rb +53 -0
  131. data/lib/solid_loop/pipeline.rb +40 -0
  132. data/lib/solid_loop/reaper.rb +313 -0
  133. data/lib/solid_loop/version.rb +3 -0
  134. data/lib/solid_loop.rb +94 -0
  135. data/lib/tasks/coverage.rake +206 -0
  136. data/lib/tasks/solid_loop_tasks.rake +4 -0
  137. metadata +228 -0
@@ -0,0 +1,206 @@
1
+ require "json"
2
+
3
+ namespace :coverage do
4
+ GROUPS = %w[
5
+ app/controllers app/models app/services app/helpers
6
+ app/jobs app/mailers app/queries app/channels
7
+ app/concerns app/views
8
+ ].freeze
9
+
10
+ desc "Parse coverage/coverage.json and print summary. Args: sort (worst|partial|uncovered|full), filter, git (staged|head|branch)"
11
+ task :report, [:sort, :filter, :git] do |_t, args|
12
+ CoverageReporter.report(args)
13
+ end
14
+
15
+ class CoverageReporter
16
+ COVERAGE_PATH = File.expand_path("../../coverage/coverage.json", __dir__)
17
+
18
+ def self.report(args)
19
+ new(args).report
20
+ end
21
+
22
+ def initialize(args)
23
+ @args = args
24
+ end
25
+
26
+ def report
27
+ unless File.exist?(COVERAGE_PATH)
28
+ abort "coverage/coverage.json not found. Run tests first:\n bundle exec rspec"
29
+ end
30
+
31
+ data = JSON.parse(File.read(COVERAGE_PATH))
32
+ raw_coverage = parse_coverage(data)
33
+
34
+ git_mode = @args[:git]
35
+ git_changed_lines = git_mode ? load_git_changes(git_mode) : {}
36
+
37
+ if git_mode && git_changed_lines.empty?
38
+ puts "No changed files found for git mode: #{git_mode}"
39
+ return
40
+ end
41
+
42
+ filter_pattern = @args[:filter] ? Regexp.new(@args[:filter], Regexp::IGNORECASE) : nil
43
+ file_stats = []
44
+ total_lines = 0
45
+ covered_lines = 0
46
+
47
+ raw_coverage.each do |file_path, line_data|
48
+ next unless file_path.include?("/app/")
49
+
50
+ lines = extract_lines(line_data)
51
+ next if lines.nil?
52
+
53
+ short = file_path.sub(%r{.*/(?=app/)}, "")
54
+ next if filter_pattern && short !~ filter_pattern
55
+ next if git_mode && !git_changed_lines.key?(short)
56
+
57
+ stat = git_mode ? stat_for_changed(short, lines, git_changed_lines[short]) : stat_for_all(short, lines)
58
+ next if stat.nil?
59
+
60
+ total_lines += stat[:total]
61
+ covered_lines += stat[:covered]
62
+ file_stats << stat
63
+ end
64
+
65
+ if file_stats.empty?
66
+ puts "No matching files found."
67
+ return
68
+ end
69
+
70
+ overall = total_lines > 0 ? (covered_lines.to_f / total_lines * 100).round(2) : 0.0
71
+ header = ["Coverage Report", filter_pattern && "[filter: #{@args[:filter]}]", git_mode && "[git: #{git_mode}]"].compact.join(" ")
72
+
73
+ puts "=" * 60
74
+ puts " #{header}"
75
+ puts " #{covered_lines}/#{total_lines} lines (#{overall}%)"
76
+ puts "=" * 60
77
+
78
+ sort_mode = (@args[:sort] || "worst").to_sym
79
+
80
+ file_stats.group_by { |f| f[:group] }.sort.each do |group_name, files|
81
+ g_total = files.sum { |f| f[:total] }
82
+ g_covered = files.sum { |f| f[:covered] }
83
+ g_pct = g_total > 0 ? (g_covered.to_f / g_total * 100).round(1) : 0.0
84
+
85
+ filtered = apply_sort(files, sort_mode)
86
+ next if filtered.empty?
87
+
88
+ label = git_mode ? "#{group_name} (changed lines)" : group_name
89
+ puts "\n--- #{label} (#{g_pct}% | #{g_covered}/#{g_total}) ---"
90
+ filtered.each do |f|
91
+ tag = f[:changed_only] ? " [changed]" : ""
92
+ puts " #{f[:pct].to_s.rjust(5)}% #{f[:path]}#{tag} (#{f[:missed]} missed of #{f[:total]})"
93
+ next if f[:missed_lines].empty?
94
+ puts " uncovered: #{format_line_ranges(f[:missed_lines])}"
95
+ end
96
+ end
97
+
98
+ puts
99
+ end
100
+
101
+ private
102
+
103
+ def parse_coverage(data)
104
+ if data["files"].is_a?(Array)
105
+ data["files"].each_with_object({}) { |f, h| h[f["filename"]] = f["coverage"] }
106
+ else
107
+ data["coverage"] || {}
108
+ end
109
+ end
110
+
111
+ def extract_lines(line_data)
112
+ case line_data
113
+ when Hash then line_data["lines"]
114
+ when Array then line_data
115
+ end
116
+ end
117
+
118
+ def stat_for_changed(short, lines, changed_line_nums)
119
+ relevant = lines.each_with_index.select { |l, i| !l.nil? && changed_line_nums.include?(i + 1) }
120
+ cov = relevant.count { |l, _| l > 0 }
121
+ miss = relevant.count { |l, _| l == 0 }
122
+ return nil if (cov + miss) == 0
123
+ build_stat(short, cov, miss, relevant.select { |l, _| l == 0 }.map { |_, i| i + 1 }.sort, changed_only: true)
124
+ end
125
+
126
+ def stat_for_all(short, lines)
127
+ relevant = lines.compact
128
+ cov = relevant.count { |l| l > 0 }
129
+ miss = relevant.count { |l| l == 0 }
130
+ miss_lines = lines.each_with_index.select { |l, _| l == 0 }.map { |_, i| i + 1 }.sort
131
+ return nil if (cov + miss) == 0
132
+ build_stat(short, cov, miss, miss_lines, changed_only: false)
133
+ end
134
+
135
+ def build_stat(short, cov, miss, miss_lines, changed_only:)
136
+ tot = cov + miss
137
+ {
138
+ path: short,
139
+ pct: (cov.to_f / tot * 100).round(1),
140
+ covered: cov,
141
+ total: tot,
142
+ missed: miss,
143
+ missed_lines: miss_lines,
144
+ group: detect_group(short),
145
+ changed_only: changed_only
146
+ }
147
+ end
148
+
149
+ def detect_group(short)
150
+ GROUPS.find { |prefix| short.start_with?(prefix) } || "app/other"
151
+ end
152
+
153
+ def apply_sort(files, mode)
154
+ case mode
155
+ when :uncovered then files.select { |f| f[:pct] == 0.0 }.sort_by { |f| -f[:total] }
156
+ when :partial then files.select { |f| f[:pct] > 0 && f[:pct] < 100 }.sort_by { |f| f[:pct] }
157
+ when :full then files.select { |f| f[:pct] == 100.0 }.sort_by { |f| -f[:total] }
158
+ else files.sort_by { |f| f[:pct] }
159
+ end
160
+ end
161
+
162
+ def format_line_ranges(line_nums)
163
+ return "" if line_nums.empty?
164
+ ranges, start = [], line_nums[0]
165
+ finish = start
166
+ line_nums[1..].each do |n|
167
+ n == finish + 1 ? (finish = n) : (ranges << range_str(start, finish); start = finish = n)
168
+ end
169
+ (ranges << range_str(start, finish)).join(", ")
170
+ end
171
+
172
+ def range_str(s, f) = s == f ? s.to_s : "#{s}-#{f}"
173
+
174
+ def load_git_changes(mode)
175
+ diff_output = case mode
176
+ when "staged" then `git diff --cached --diff-filter=ACMR --unified=0 -- app/`
177
+ when "unstaged" then `git diff --diff-filter=ACMR --unified=0 -- app/`
178
+ when "head"
179
+ `git diff HEAD~1...HEAD --diff-filter=ACMR --unified=0 -- app/`
180
+ when "branch"
181
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
182
+ base = `git merge-base main #{branch} 2>/dev/null`.strip
183
+ base = `git merge-base master #{branch} 2>/dev/null`.strip if base.empty?
184
+ base.empty? ? `git diff HEAD --diff-filter=ACMR --unified=0 -- app/` \
185
+ : `git diff #{base}...HEAD --diff-filter=ACMR --unified=0 -- app/`
186
+ else
187
+ `git diff HEAD --diff-filter=ACMR --unified=0 -- app/`
188
+ end
189
+
190
+ result = {}
191
+ current_file = nil
192
+ diff_output.each_line do |line|
193
+ if line =~ %r{^\+\+\+ b/(app/.+)$}
194
+ current_file = $1.strip
195
+ result[current_file] ||= []
196
+ elsif line =~ /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/
197
+ next unless current_file
198
+ start_line = $1.to_i
199
+ count = [($2 || "1").to_i, 1].max
200
+ result[current_file].concat((start_line...(start_line + count)).to_a)
201
+ end
202
+ end
203
+ result
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :solid_loop do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,228 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solid_loop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Ruslan
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.1'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: faraday
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: event_stream_parser
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.0'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.0'
60
+ description: Infrastructure layer for running autonomous AI agents as long-lived Rails
61
+ background processes (ActiveJob + PostgreSQL) using Middleware architecture and
62
+ MCP tools.
63
+ email:
64
+ - ru.ruslan@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - CHANGELOG.md
70
+ - CODE_OF_CONDUCT.md
71
+ - CONTRIBUTING.md
72
+ - MIT-LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - app/assets/javascripts/solid_loop/chart.umd.min.js
76
+ - app/assets/javascripts/solid_loop/chartjs-adapter-date-fns.bundle.min.js
77
+ - app/assets/javascripts/solid_loop/chartkick.min.js
78
+ - app/assets/stylesheets/solid_loop/application.css
79
+ - app/controllers/solid_loop/application_controller.rb
80
+ - app/controllers/solid_loop/dashboard_controller.rb
81
+ - app/controllers/solid_loop/events_controller.rb
82
+ - app/controllers/solid_loop/loops_controller.rb
83
+ - app/controllers/solid_loop/mcp_inbound_sessions_controller.rb
84
+ - app/controllers/solid_loop/mcp_sessions_controller.rb
85
+ - app/controllers/solid_loop/mcp_tools_controller.rb
86
+ - app/controllers/solid_loop/messages_controller.rb
87
+ - app/controllers/solid_loop/tool_calls_controller.rb
88
+ - app/helpers/solid_loop/application_helper.rb
89
+ - app/jobs/solid_loop/application_job.rb
90
+ - app/jobs/solid_loop/llm_completion_job.rb
91
+ - app/jobs/solid_loop/observe_broadcast_job.rb
92
+ - app/jobs/solid_loop/reaper_job.rb
93
+ - app/jobs/solid_loop/tool_execution_job.rb
94
+ - app/models/solid_loop/application_record.rb
95
+ - app/models/solid_loop/base.rb
96
+ - app/models/solid_loop/event.rb
97
+ - app/models/solid_loop/loop.rb
98
+ - app/models/solid_loop/mcp_inbound_session.rb
99
+ - app/models/solid_loop/mcp_session.rb
100
+ - app/models/solid_loop/mcp_tool.rb
101
+ - app/models/solid_loop/message.rb
102
+ - app/models/solid_loop/tool_call.rb
103
+ - app/queries/solid_loop/admin/base_query.rb
104
+ - app/queries/solid_loop/admin/events_query.rb
105
+ - app/queries/solid_loop/admin/loops_query.rb
106
+ - app/queries/solid_loop/admin/mcp_inbound_sessions_query.rb
107
+ - app/queries/solid_loop/admin/mcp_sessions_query.rb
108
+ - app/queries/solid_loop/admin/mcp_tools_query.rb
109
+ - app/queries/solid_loop/admin/messages_query.rb
110
+ - app/queries/solid_loop/admin/tool_calls_query.rb
111
+ - app/services/solid_loop/adapters/native.rb
112
+ - app/services/solid_loop/dialects/anthropic.rb
113
+ - app/services/solid_loop/dialects/gemini.rb
114
+ - app/services/solid_loop/dialects/open_ai.rb
115
+ - app/services/solid_loop/dialects/reasoning_packer.rb
116
+ - app/services/solid_loop/llm_usage_parser.rb
117
+ - app/services/solid_loop/llm_usage_parser/anthropic.rb
118
+ - app/services/solid_loop/llm_usage_parser/gemini.rb
119
+ - app/services/solid_loop/llm_usage_parser/llama.rb
120
+ - app/services/solid_loop/llm_usage_parser/openai.rb
121
+ - app/services/solid_loop/mcp_session_initializer.rb
122
+ - app/services/solid_loop/mcp_tool_execution_service.rb
123
+ - app/services/solid_loop/middlewares/agent_initialization.rb
124
+ - app/services/solid_loop/middlewares/error_handling.rb
125
+ - app/services/solid_loop/middlewares/event_logging.rb
126
+ - app/services/solid_loop/middlewares/message_building.rb
127
+ - app/services/solid_loop/middlewares/network_calling.rb
128
+ - app/services/solid_loop/middlewares/response_parsing.rb
129
+ - app/services/solid_loop/middlewares/tool_call_xml_parser.rb
130
+ - app/services/solid_loop/sse_stream_aggregator.rb
131
+ - app/services/solid_loop/tool_failure_reconciler.rb
132
+ - app/services/solid_loop/tool_middlewares/agent_initialization.rb
133
+ - app/services/solid_loop/tool_middlewares/error_handling.rb
134
+ - app/services/solid_loop/tool_middlewares/event_logging.rb
135
+ - app/services/solid_loop/tool_middlewares/response_creation.rb
136
+ - app/services/solid_loop/tool_middlewares/tool_execution.rb
137
+ - app/views/layouts/solid_loop/admin.html.erb
138
+ - app/views/layouts/solid_loop/application.html.erb
139
+ - app/views/solid_loop/dashboard/index.html.erb
140
+ - app/views/solid_loop/events/index.html.erb
141
+ - app/views/solid_loop/events/show.html.erb
142
+ - app/views/solid_loop/loops/index.html.erb
143
+ - app/views/solid_loop/loops/show.html.erb
144
+ - app/views/solid_loop/mcp_inbound_sessions/index.html.erb
145
+ - app/views/solid_loop/mcp_inbound_sessions/show.html.erb
146
+ - app/views/solid_loop/mcp_sessions/_tool_output.html.erb
147
+ - app/views/solid_loop/mcp_sessions/index.html.erb
148
+ - app/views/solid_loop/mcp_sessions/inspector.html.erb
149
+ - app/views/solid_loop/mcp_sessions/show.html.erb
150
+ - app/views/solid_loop/mcp_tools/index.html.erb
151
+ - app/views/solid_loop/mcp_tools/show.html.erb
152
+ - app/views/solid_loop/messages/_message.html.erb
153
+ - app/views/solid_loop/messages/index.html.erb
154
+ - app/views/solid_loop/messages/show.html.erb
155
+ - app/views/solid_loop/shared/_pagination.html.erb
156
+ - app/views/solid_loop/tool_calls/index.html.erb
157
+ - app/views/solid_loop/tool_calls/show.html.erb
158
+ - config/routes.rb
159
+ - db/migrate/20260408000100_solid_loop_init.rb
160
+ - db/migrate/20260709000100_solid_loop_create_mcp_inbound_sessions.rb
161
+ - db/migrate/20260713000100_solid_loop_add_execution_guards.rb
162
+ - db/migrate/20260714000100_solid_loop_add_message_steering.rb
163
+ - db/migrate/20260714000200_solid_loop_add_message_conversation_order.rb
164
+ - db/migrate/20260715000100_solid_loop_add_mcp_inbound_session_principal_key.rb
165
+ - db/migrate/20260715000200_solid_loop_add_llm_lease.rb
166
+ - db/migrate/20260715000300_solid_loop_add_tool_lease.rb
167
+ - db/migrate/20260715000400_solid_loop_add_lease_running_check.rb
168
+ - db/migrate/20260715000500_solid_loop_add_tool_lease_pair_check.rb
169
+ - docs/contributing/coverage.md
170
+ - docs/decisions/durable_attempt_lease.md
171
+ - docs/decisions/mcp-only-tooling.md
172
+ - docs/decisions/mcp-server.md
173
+ - docs/decisions/reasoning_persistence.md
174
+ - docs/decisions/ruby_llm_rejected.md
175
+ - docs/guides/dialects.md
176
+ - docs/guides/llm_middlewares.md
177
+ - docs/guides/mcp_transports.md
178
+ - docs/guides/tool_middlewares.md
179
+ - lib/solid_loop.rb
180
+ - lib/solid_loop/configuration.rb
181
+ - lib/solid_loop/engine.rb
182
+ - lib/solid_loop/lease_heartbeat.rb
183
+ - lib/solid_loop/lease_renewer.rb
184
+ - lib/solid_loop/llm_metrics.rb
185
+ - lib/solid_loop/mcp.rb
186
+ - lib/solid_loop/mcp/call_context.rb
187
+ - lib/solid_loop/mcp/client_factory.rb
188
+ - lib/solid_loop/mcp/http_transport.rb
189
+ - lib/solid_loop/mcp/principal.rb
190
+ - lib/solid_loop/mcp/result.rb
191
+ - lib/solid_loop/mcp/server.rb
192
+ - lib/solid_loop/mcp/stdio_transport.rb
193
+ - lib/solid_loop/mcp/toolset.rb
194
+ - lib/solid_loop/mcp/transport.rb
195
+ - lib/solid_loop/mcp_client.rb
196
+ - lib/solid_loop/pipeline.rb
197
+ - lib/solid_loop/pipeline/builder.rb
198
+ - lib/solid_loop/pipeline/context.rb
199
+ - lib/solid_loop/pipeline/tool_context.rb
200
+ - lib/solid_loop/reaper.rb
201
+ - lib/solid_loop/version.rb
202
+ - lib/tasks/coverage.rake
203
+ - lib/tasks/solid_loop_tasks.rake
204
+ homepage: https://github.com/ruslan/solid_loop
205
+ licenses:
206
+ - MIT
207
+ metadata:
208
+ source_code_uri: https://github.com/ruslan/solid_loop
209
+ changelog_uri: https://github.com/ruslan/solid_loop/blob/main/CHANGELOG.md
210
+ rubygems_mfa_required: 'true'
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: '3.1'
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubygems_version: 3.6.7
226
+ specification_version: 4
227
+ summary: ActiveJob-backed infrastructure for autonomous AI agents in Rails.
228
+ test_files: []