little_ghost 0.7.0 → 0.9.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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -6
  3. data/docs/guides/assemblies.md +21 -9
  4. data/docs/guides/code_mode.md +2 -2
  5. data/docs/guides/getting_started.md +27 -2
  6. data/docs/guides/integrations.md +20 -161
  7. data/docs/guides/mcp.md +288 -0
  8. data/docs/guides/models_and_providers.md +21 -54
  9. data/docs/guides/production.md +14 -5
  10. data/docs/guides/prompt_views.md +234 -27
  11. data/docs/guides/providers.md +275 -0
  12. data/docs/guides/sandboxing.md +42 -7
  13. data/docs/guides/skills.md +1 -3
  14. data/docs/guides/tools.md +2 -0
  15. data/exe/little_ghost +12 -0
  16. data/lib/little_ghost/agent/context_management.rb +2 -13
  17. data/lib/little_ghost/agent/delegation.rb +7 -4
  18. data/lib/little_ghost/agent/skills.rb +8 -6
  19. data/lib/little_ghost/agent/tool_loop.rb +4 -4
  20. data/lib/little_ghost/agent.rb +230 -70
  21. data/lib/little_ghost/agent_builder.rb +16 -4
  22. data/lib/little_ghost/artifacts/workspace_store.rb +16 -14
  23. data/lib/little_ghost/assembly.rb +48 -7
  24. data/lib/little_ghost/assembly_builder.rb +3 -3
  25. data/lib/little_ghost/cli.rb +429 -0
  26. data/lib/little_ghost/code_mode/broker.rb +11 -3
  27. data/lib/little_ghost/code_mode/javascript/client.rb +58 -11
  28. data/lib/little_ghost/code_mode/javascript/host.rb +37 -13
  29. data/lib/little_ghost/code_mode/javascript/session.rb +21 -9
  30. data/lib/little_ghost/code_mode/javascript_engine.rb +21 -42
  31. data/lib/little_ghost/code_mode/ruby/host.rb +8 -4
  32. data/lib/little_ghost/code_mode/ruby/session.rb +41 -14
  33. data/lib/little_ghost/code_mode/ruby_engine.rb +6 -36
  34. data/lib/little_ghost/code_mode/runtime.rb +57 -24
  35. data/lib/little_ghost/console.rb +17 -0
  36. data/lib/little_ghost/errors.rb +20 -2
  37. data/lib/little_ghost/framework_prompts.rb +376 -0
  38. data/lib/little_ghost/generators/application_generator.rb +179 -0
  39. data/lib/little_ghost/generators/templates/application/Gemfile.tt +3 -0
  40. data/lib/little_ghost/generators/templates/application/README.md.tt +29 -0
  41. data/lib/little_ghost/generators/templates/application/app/agents/application_agent.rb.tt +5 -0
  42. data/lib/little_ghost/generators/templates/application/app/prompts/application/system_prompt.erb.tt +1 -0
  43. data/lib/little_ghost/generators/templates/application/bin/application.tt +10 -0
  44. data/lib/little_ghost/generators/templates/application/config/little_ghost.rb.tt +5 -0
  45. data/lib/little_ghost/generators/templates/application/gitignore.tt +2 -0
  46. data/lib/little_ghost/generators/templates/application/keep.tt +0 -0
  47. data/lib/little_ghost/graph.rb +24 -5
  48. data/lib/little_ghost/mcp/client.rb +307 -610
  49. data/lib/little_ghost/mcp/toolset.rb +133 -95
  50. data/lib/little_ghost/mcp.rb +21 -3
  51. data/lib/little_ghost/model_operations.rb +103 -36
  52. data/lib/little_ghost/model_resolver.rb +2 -0
  53. data/lib/little_ghost/models/catalog/models_dev_source.rb +4 -1
  54. data/lib/little_ghost/prompt_resolver.rb +61 -27
  55. data/lib/little_ghost/prompts/little_ghost/agent/interjections/instructions.erb +12 -0
  56. data/lib/little_ghost/prompts/little_ghost/agent/system/default.erb +10 -0
  57. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_large.erb +10 -0
  58. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_many.erb +10 -0
  59. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/item_too_large.erb +10 -0
  60. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/preparation_failed.erb +10 -0
  61. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/resolver_invalid.erb +10 -0
  62. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/value_unstorable.erb +10 -0
  63. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/reference.erb +13 -0
  64. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/references.erb +12 -0
  65. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/workspace_references.erb +12 -0
  66. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/full_result.erb +14 -0
  67. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/storage_failed.erb +13 -0
  68. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/destination_unsafe.erb +10 -0
  69. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/secure_unavailable.erb +10 -0
  70. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_changed.erb +10 -0
  71. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unavailable.erb +10 -0
  72. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unsafe.erb +10 -0
  73. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/count_limit.erb +10 -0
  74. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/destination_exists.erb +10 -0
  75. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/too_large.erb +10 -0
  76. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/total_limit.erb +10 -0
  77. data/lib/little_ghost/prompts/little_ghost/assembly/feedback/transition_only.erb +10 -0
  78. data/lib/little_ghost/prompts/little_ghost/assembly/tools/fallback/description.erb +10 -0
  79. data/lib/little_ghost/prompts/little_ghost/code_mode/artifacts/format/references.erb +12 -0
  80. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/brokered_close.erb +10 -0
  81. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/cleanup_failed.erb +10 -0
  82. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/execution_failed.erb +11 -0
  83. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/arguments_object.erb +10 -0
  84. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/closed.erb +10 -0
  85. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/control_active.erb +10 -0
  86. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/limit_exceeded.erb +10 -0
  87. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/not_active.erb +10 -0
  88. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_active.erb +10 -0
  89. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_timed_out.erb +10 -0
  90. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unavailable_tool.erb +10 -0
  91. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unknown_program.erb +10 -0
  92. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/active_programs_limit.erb +10 -0
  93. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/cleanup_failed.erb +10 -0
  94. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/execution_limit.erb +10 -0
  95. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/invalid_request.erb +10 -0
  96. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/memory_limit.erb +10 -0
  97. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/pending_calls_limit.erb +10 -0
  98. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/source_size_limit.erb +10 -0
  99. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/tools_array.erb +10 -0
  100. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/instructions.erb +35 -0
  101. data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/feedback/parallel_callables.erb +10 -0
  102. data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/instructions.erb +40 -0
  103. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/exec/description.erb +10 -0
  104. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/description.erb +10 -0
  105. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/inputs/max_output_tokens/description.erb +10 -0
  106. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/description.erb +10 -0
  107. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/inputs/max_output_tokens/description.erb +10 -0
  108. data/lib/little_ghost/prompts/little_ghost/context_management/summary/request.erb +10 -0
  109. data/lib/little_ghost/prompts/little_ghost/context_management/summary/system_instruction.erb +16 -0
  110. data/lib/little_ghost/prompts/little_ghost/graph/context/format/failed_input.erb +11 -0
  111. data/lib/little_ghost/prompts/little_ghost/graph/context/format/previous_input.erb +13 -0
  112. data/lib/little_ghost/prompts/little_ghost/graph/context/headings/original_task.erb +10 -0
  113. data/lib/little_ghost/prompts/little_ghost/graph/context/headings/previous_inputs.erb +10 -0
  114. data/lib/little_ghost/prompts/little_ghost/mcp/errors/request_failed.erb +10 -0
  115. data/lib/little_ghost/prompts/little_ghost/mcp/errors/transformation_unserializable.erb +10 -0
  116. data/lib/little_ghost/prompts/little_ghost/mcp/tools/fallback/description.erb +10 -0
  117. data/lib/little_ghost/prompts/little_ghost/output/truncation/marker.erb +11 -0
  118. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/mount_changed.erb +10 -0
  119. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/secure_traversal_unavailable.erb +10 -0
  120. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/workspace_root_changed.erb +10 -0
  121. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/directory_listing_limit.erb +10 -0
  122. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/entry_required.erb +10 -0
  123. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/file_read_limit.erb +10 -0
  124. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/invalid_utf8.erb +10 -0
  125. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_escape.erb +10 -0
  126. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_missing.erb +10 -0
  127. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/multiply_linked.erb +10 -0
  128. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_directory.erb +10 -0
  129. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_file.erb +10 -0
  130. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/null_byte.erb +10 -0
  131. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/outside_scope.erb +10 -0
  132. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/parent_escape.erb +10 -0
  133. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_missing.erb +10 -0
  134. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_traverses_symlink.erb +10 -0
  135. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/read_only.erb +10 -0
  136. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/replace_empty.erb +10 -0
  137. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/symlink_path.erb +10 -0
  138. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_multiple.erb +10 -0
  139. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_not_found.erb +10 -0
  140. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_limit.erb +10 -0
  141. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_parent_missing.erb +10 -0
  142. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_target_symlink.erb +10 -0
  143. data/lib/little_ghost/prompts/little_ghost/sandbox/policy/feedback/feature_denied.erb +10 -0
  144. data/lib/little_ghost/prompts/little_ghost/sandbox/process/errors/program_supervisor_failed.erb +10 -0
  145. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/command_timed_out.erb +10 -0
  146. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/executable_required.erb +10 -0
  147. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_memory_exceeded.erb +10 -0
  148. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_output_exceeded.erb +10 -0
  149. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_timed_out.erb +10 -0
  150. data/lib/little_ghost/prompts/little_ghost/skills/discovery/instructions.erb +18 -0
  151. data/lib/little_ghost/prompts/little_ghost/skills/feedback/unknown.erb +11 -0
  152. data/lib/little_ghost/prompts/little_ghost/skills/resources/notices/truncated.erb +10 -0
  153. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/content.erb +28 -0
  154. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/description.erb +10 -0
  155. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/inputs/name/description.erb +10 -0
  156. data/lib/little_ghost/prompts/little_ghost/structured_output/persistence/format/redaction.erb +12 -0
  157. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/invalid_result.erb +11 -0
  158. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/schema_errors.erb +11 -0
  159. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/request.erb +14 -0
  160. data/lib/little_ghost/prompts/little_ghost/structured_output/tools/result/description.erb +11 -0
  161. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/invalid_json.erb +11 -0
  162. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/missing_tool.erb +11 -0
  163. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/multiple_tools.erb +11 -0
  164. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_complex.erb +11 -0
  165. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_deep.erb +11 -0
  166. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_large.erb +11 -0
  167. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/tool_not_exclusive.erb +11 -0
  168. data/lib/little_ghost/prompts/little_ghost/subagents/errors/create_failed.erb +10 -0
  169. data/lib/little_ghost/prompts/little_ghost/subagents/errors/previous_turn_failed.erb +10 -0
  170. data/lib/little_ghost/prompts/little_ghost/subagents/errors/restore_failed.erb +10 -0
  171. data/lib/little_ghost/prompts/little_ghost/subagents/errors/turn_failed.erb +10 -0
  172. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_ids.erb +10 -0
  173. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_path.erb +10 -0
  174. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/identity_capacity.erb +10 -0
  175. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/inactive.erb +10 -0
  176. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_chars_limit.erb +10 -0
  177. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_limit.erb +10 -0
  178. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_unsupported.erb +10 -0
  179. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_cursor.erb +10 -0
  180. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_mode.erb +10 -0
  181. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/list_limit.erb +10 -0
  182. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_limit.erb +10 -0
  183. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_type.erb +10 -0
  184. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/not_running.erb +10 -0
  185. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/queue_capacity.erb +10 -0
  186. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_invalid.erb +10 -0
  187. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_limit.erb +10 -0
  188. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/terminal_identity.erb +11 -0
  189. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/turn_capacity.erb +10 -0
  190. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_id.erb +10 -0
  191. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_kind.erb +10 -0
  192. data/lib/little_ghost/prompts/little_ghost/subagents/notices/turn_cancelled.erb +10 -0
  193. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/description.erb +10 -0
  194. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/active_id/description.erb +10 -0
  195. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/message/description.erb +10 -0
  196. data/lib/little_ghost/prompts/little_ghost/subagents/tools/list/description.erb +10 -0
  197. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/description.erb +10 -0
  198. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/id/description.erb +10 -0
  199. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/message/description.erb +10 -0
  200. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/send_mode/description.erb +10 -0
  201. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/description.erb +10 -0
  202. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/kind/description.erb +11 -0
  203. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/spawn_mode/description.erb +10 -0
  204. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task/description.erb +10 -0
  205. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task_name/description.erb +10 -0
  206. data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/description.erb +10 -0
  207. data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/inputs/ids/description.erb +10 -0
  208. data/lib/little_ghost/prompts/little_ghost/swarm/handoff/notice.erb +10 -0
  209. data/lib/little_ghost/prompts/little_ghost/swarm/handoff/request.erb +16 -0
  210. data/lib/little_ghost/prompts/little_ghost/swarm/tools/handoff/description.erb +10 -0
  211. data/lib/little_ghost/prompts/little_ghost/tools/built_in/list_files/description.erb +10 -0
  212. data/lib/little_ghost/prompts/little_ghost/tools/built_in/read_file/description.erb +10 -0
  213. data/lib/little_ghost/prompts/little_ghost/tools/built_in/replace_in_file/description.erb +10 -0
  214. data/lib/little_ghost/prompts/little_ghost/tools/built_in/shell/description.erb +10 -0
  215. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_file/description.erb +10 -0
  216. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/description.erb +10 -0
  217. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/single_progress.erb +10 -0
  218. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/unique_ids.erb +10 -0
  219. data/lib/little_ghost/prompts/little_ghost/tools/errors/unexpected_failure.erb +10 -0
  220. data/lib/little_ghost/prompts/little_ghost/tools/errors/unserializable_result.erb +10 -0
  221. data/lib/little_ghost/prompts/little_ghost/tools/feedback/invalid_input.erb +10 -0
  222. data/lib/little_ghost/prompts/little_ghost/tools/feedback/unknown.erb +10 -0
  223. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/final_warning.erb +10 -0
  224. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/termination.erb +10 -0
  225. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/warning.erb +10 -0
  226. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/additional_property.erb +10 -0
  227. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/enum.erb +11 -0
  228. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_format.erb +10 -0
  229. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_pattern.erb +10 -0
  230. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_items.erb +11 -0
  231. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_length.erb +11 -0
  232. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/maximum.erb +11 -0
  233. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_items.erb +11 -0
  234. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_length.erb +11 -0
  235. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/minimum.erb +11 -0
  236. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/required.erb +10 -0
  237. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/type.erb +11 -0
  238. data/lib/little_ghost/prompts/little_ghost/workspace/feedback/path_changed.erb +10 -0
  239. data/lib/little_ghost/provider_registry.rb +1 -0
  240. data/lib/little_ghost/providers/lm_studio/catalog_source.rb +201 -0
  241. data/lib/little_ghost/providers/lm_studio.rb +52 -0
  242. data/lib/little_ghost/providers/openai.rb +3 -75
  243. data/lib/little_ghost/providers/openai_compatible.rb +78 -1
  244. data/lib/little_ghost/runtime/hooks/artifacts.rb +58 -22
  245. data/lib/little_ghost/runtime.rb +8 -3
  246. data/lib/little_ghost/sandbox/environment_policy.rb +29 -4
  247. data/lib/little_ghost/sandbox/filesystem.rb +51 -33
  248. data/lib/little_ghost/sandbox/policy.rb +28 -6
  249. data/lib/little_ghost/sandbox/process_runner.rb +6 -2
  250. data/lib/little_ghost/sandbox/process_session.rb +8 -4
  251. data/lib/little_ghost/sandbox/scope.rb +4 -1
  252. data/lib/little_ghost/sandboxes/seatbelt.rb +3 -1
  253. data/lib/little_ghost/sandboxes/unrestricted.rb +17 -9
  254. data/lib/little_ghost/skills/catalog.rb +41 -36
  255. data/lib/little_ghost/structured_output.rb +6 -6
  256. data/lib/little_ghost/subagents/control_tool.rb +58 -0
  257. data/lib/little_ghost/subagents/manager.rb +99 -74
  258. data/lib/little_ghost/support/output_truncation.rb +28 -4
  259. data/lib/little_ghost/swarm.rb +20 -7
  260. data/lib/little_ghost/tool.rb +47 -20
  261. data/lib/little_ghost/tool_registry.rb +2 -2
  262. data/lib/little_ghost/tools/filesystem.rb +12 -4
  263. data/lib/little_ghost/tools/shell.rb +5 -1
  264. data/lib/little_ghost/tools/write_todos.rb +9 -9
  265. data/lib/little_ghost/version.rb +1 -1
  266. data/lib/little_ghost/workflow.rb +199 -27
  267. data/lib/little_ghost/workspace.rb +2 -2
  268. data/lib/little_ghost.rb +6 -2
  269. metadata +222 -7
  270. data/lib/little_ghost/mcp/types.rb +0 -216
@@ -104,12 +104,20 @@ module LittleGhost
104
104
  tools = []
105
105
  agent_class.assembly_tool_declarations.each do |declaration|
106
106
  child = declared_assembly(declaration, run, agent_stream_path:)
107
+ factory = if !declaration.fetch(:preserve_context) && child.is_a?(Agent)
108
+ -> { declared_assembly(declaration, run, agent_stream_path:) }
109
+ end
107
110
  begin
108
- tools << child.as_tool(
111
+ options = {
109
112
  name: declaration.fetch(:name),
110
113
  description: declaration.fetch(:description),
111
114
  preserve_context: declaration.fetch(:preserve_context)
112
- )
115
+ }
116
+ tools << if factory
117
+ child.as_tool_with_factory(**options, factory:)
118
+ else
119
+ child.as_tool(**options)
120
+ end
113
121
  rescue
114
122
  child.close
115
123
  raise
@@ -123,8 +131,12 @@ module LittleGhost
123
131
 
124
132
  def declared_assembly(declaration, run, agent_stream_path:)
125
133
  assembly_class = declaration.fetch(:assembly)
126
- if assembly_class.is_a?(AssemblyDefinition) && assembly_class.kind == :agent
127
- declared_agent(declaration.merge(agent: assembly_class.implementation), run, agent_stream_path:)
134
+ if assembly_class.is_a?(AssemblyDefinition)
135
+ if assembly_class.kind == :agent
136
+ declared_agent(declaration.merge(agent: assembly_class.implementation), run, agent_stream_path:)
137
+ else
138
+ runtime.build_assembly(assembly_class, run:, agent_stream_path:)
139
+ end
128
140
  elsif assembly_class <= Agent
129
141
  declared_agent(declaration.merge(agent: assembly_class), run, agent_stream_path:)
130
142
  else
@@ -59,7 +59,7 @@ module LittleGhost
59
59
  max_total_bytes: DEFAULT_MAX_TOTAL_BYTES,
60
60
  max_artifacts: DEFAULT_MAX_ARTIFACTS
61
61
  )
62
- raise ToolError, "Artifact workspace path is unavailable" unless workspace
62
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_unavailable") unless workspace
63
63
 
64
64
  @workspace = workspace
65
65
  @path = path.to_sym
@@ -125,7 +125,7 @@ module LittleGhost
125
125
 
126
126
  data = String(entry.fetch(:data)).b
127
127
  if data.bytesize > @max_artifact_bytes
128
- raise ToolError, "Artifact exceeds the #{@max_artifact_bytes}-byte limit"
128
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/feedback/too_large", maximum: @max_artifact_bytes)
129
129
  end
130
130
  media_type = entry[:media_type]
131
131
  name = normalized_name(entry[:name], media_type:)
@@ -166,9 +166,11 @@ module LittleGhost
166
166
  end
167
167
 
168
168
  def reserve!(bytes)
169
- raise ToolError, "Artifact count exceeds the #{@max_artifacts}-item limit" if @artifacts.length >= @max_artifacts
169
+ if @artifacts.length >= @max_artifacts
170
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/feedback/count_limit", maximum: @max_artifacts)
171
+ end
170
172
  if @total_bytes + bytes > @max_total_bytes
171
- raise ToolError, "Artifacts exceed the #{@max_total_bytes}-byte total limit"
173
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/feedback/total_limit", maximum: @max_total_bytes)
172
174
  end
173
175
 
174
176
  @total_bytes += bytes
@@ -200,9 +202,9 @@ module LittleGhost
200
202
  metadata:
201
203
  )
202
204
  rescue Errno::EEXIST
203
- raise ToolError, "Artifact destination already exists"
205
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/feedback/destination_exists")
204
206
  rescue Errno::ELOOP
205
- raise ToolError, "Artifact destination is unsafe"
207
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/destination_unsafe")
206
208
  rescue => error
207
209
  cleanup_created_file(directory, file_name, error:) if created
208
210
  raise
@@ -241,7 +243,7 @@ module LittleGhost
241
243
 
242
244
  def validate_root!
243
245
  unless OPENAT && UNLINKAT
244
- raise ToolError, "Secure artifact storage is unavailable on this platform"
246
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/secure_unavailable")
245
247
  end
246
248
 
247
249
  @workspace.validate!
@@ -251,20 +253,20 @@ module LittleGhost
251
253
  workspace_real = File.realpath(@workspace.root)
252
254
  location_safe = !beneath?(root, @workspace.root) || beneath?(real, workspace_real)
253
255
  unless stat.directory? && !stat.symlink? && location_safe
254
- raise ToolError, "Artifact workspace path is unsafe"
256
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_unsafe")
255
257
  end
256
258
 
257
259
  directory = File.open(root, directory_flags)
258
260
  descriptor_stat = directory.stat
259
261
  unless descriptor_stat.directory?
260
- raise ToolError, "Artifact workspace path is unsafe"
262
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_unsafe")
261
263
  end
262
264
  @workspace.validate!
263
265
  [root.freeze, [descriptor_stat.dev, descriptor_stat.ino].freeze].freeze
264
266
  rescue KeyError, Errno::ENOENT, Errno::ENOTDIR
265
- raise ToolError, "Artifact workspace path is unavailable"
267
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_unavailable")
266
268
  rescue SystemCallError
267
- raise ToolError, "Artifact workspace path is unavailable"
269
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_unavailable")
268
270
  ensure
269
271
  directory&.close
270
272
  end
@@ -274,13 +276,13 @@ module LittleGhost
274
276
  directory = File.open(@root, directory_flags)
275
277
  stat = directory.stat
276
278
  unless stat.directory? && [stat.dev, stat.ino] == @root_identity
277
- raise ToolError, "Artifact workspace path changed after initialization"
279
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_changed")
278
280
  end
279
281
  @workspace.validate!
280
282
  directory
281
283
  rescue Errno::ENOENT, Errno::ELOOP, Errno::ENOTDIR
282
284
  directory&.close
283
- raise ToolError, "Artifact workspace path changed after initialization"
285
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/workspace_changed")
284
286
  rescue
285
287
  directory&.close
286
288
  raise
@@ -308,7 +310,7 @@ module LittleGhost
308
310
  def validate_regular_file!(file)
309
311
  stat = file.stat
310
312
  unless stat.file? && stat.nlink == 1 && (stat.mode & 0o777) == 0o600
311
- raise ToolError, "Artifact destination is unsafe"
313
+ raise ToolError, FrameworkPrompts.reference("artifacts/storage/errors/destination_unsafe")
312
314
  end
313
315
  end
314
316
 
@@ -50,7 +50,8 @@ module LittleGhost
50
50
  # +context+, +settings+, +metadata+, +session_id+, +actor_id+, and
51
51
  # +deadline_at+.
52
52
  def ask(message, **options)
53
- definition.implementation.new.ask(message, **options)
53
+ snapshot = definition
54
+ snapshot.implementation.new.bind_assembly_definition(snapshot).ask(message, **options)
54
55
  end
55
56
 
56
57
  # Lazily streams +message+ through a fresh standalone assembly.
@@ -66,7 +67,7 @@ module LittleGhost
66
67
  snapshot = definition
67
68
  stream = nil
68
69
  Enumerator.new do |events|
69
- stream ||= snapshot.implementation.new.stream_ask(message, **options)
70
+ stream ||= snapshot.implementation.new.bind_assembly_definition(snapshot).stream_ask(message, **options)
70
71
  stream.each { |event| events << event }
71
72
  end
72
73
  end
@@ -157,6 +158,7 @@ module LittleGhost
157
158
  @runtime = runtime || run&.runtime || LittleGhost.runtime
158
159
  @workspace = workspace || run&.workspace
159
160
  @sandbox = sandbox || run&.sandbox
161
+ @assembly_definition = nil
160
162
  @standalone = standalone
161
163
  @assembly_mutex = Mutex.new
162
164
  @assembly_closed = false
@@ -169,6 +171,11 @@ module LittleGhost
169
171
  self
170
172
  end
171
173
 
174
+ def bind_assembly_definition(definition) # :nodoc:
175
+ @assembly_definition = definition
176
+ self
177
+ end
178
+
172
179
  # Builds the top-level Run used by a standalone assembly.
173
180
  def build_run(payload = nil, include_agent_events_by_default: false, **payload_options) # :nodoc:
174
181
  payload = entrypoint_payload(payload, payload_options) unless payload_options.empty?
@@ -178,7 +185,8 @@ module LittleGhost
178
185
  end
179
186
  source_class = run_entrypoint_class
180
187
  options = {entrypoint_class: source_class}
181
- options[:execution_class] = self.class unless source_class.equal?(self.class)
188
+ execution = @assembly_definition || self.class
189
+ options[:execution_class] = execution unless source_class.equal?(execution)
182
190
  options[:agent_class] = source_class if is_a?(Agent)
183
191
  options[:cancellation_token] = cancellation_token if cancellation_token
184
192
  options[:workspace] = workspace if workspace
@@ -248,9 +256,37 @@ module LittleGhost
248
256
  # the invoking Tool's current RunContext#state, which may include current
249
257
  # request values or values restored from a Session. Nested tools must still
250
258
  # authorize privileged work with current, application-established values.
259
+ # A directly bound Agent remains the target for every call, so overlapping
260
+ # calls raise AgentBusyError. Class-level Agent#agent_as_tool declarations
261
+ # build a fresh Agent per context-free call.
251
262
  def as_tool(name: self.class.assembly_id, description: self.class.description, preserve_context: false)
263
+ build_tool(name:, description:, preserve_context:, factory: nil)
264
+ end
265
+
266
+ def as_tool_with_factory(name:, description:, preserve_context:, factory:) # :nodoc:
267
+ first_target = self
268
+ first_target_mutex = Mutex.new
269
+ target_factory = lambda do
270
+ first_target_mutex.synchronize do
271
+ target = first_target
272
+ first_target = nil
273
+ target
274
+ end || factory.call
275
+ end
276
+ build_tool(name:, description:, preserve_context:, factory: target_factory)
277
+ end
278
+
279
+ private
280
+
281
+ def build_tool(name:, description:, preserve_context:, factory:)
252
282
  assembly = self
253
- description = "Delegate a task to #{name}." if description.to_s.empty?
283
+ if description.to_s.empty?
284
+ description = if is_a?(Agent)
285
+ render_framework_prompt("assembly/tools/fallback/description", name: name.to_s)
286
+ else
287
+ FrameworkPrompts.for_runtime(runtime).render("assembly/tools/fallback/description", locals: {name: name.to_s})
288
+ end
289
+ end
254
290
  mutex = Mutex.new
255
291
  retained_history = []
256
292
  tool_class = Tool.define(
@@ -264,7 +300,10 @@ module LittleGhost
264
300
  }
265
301
  ) do |input, context: nil|
266
302
  invocation = lambda do
267
- target = if assembly.is_a?(Agent)
303
+ factory_target = !factory.nil?
304
+ target = if factory_target
305
+ factory.call
306
+ elsif assembly.is_a?(Agent)
268
307
  assembly
269
308
  elsif assembly.run
270
309
  assembly.send(:build_tool_assembly)
@@ -293,7 +332,7 @@ module LittleGhost
293
332
  retained_history.replace(result.messages.reject { |message| message.role == :system }) if preserve_context
294
333
  result.structured? ? result.structured_result.value : result.text
295
334
  ensure
296
- target&.close unless target.equal?(assembly)
335
+ target&.close if factory_target || !target.equal?(assembly)
297
336
  end
298
337
  preserve_context ? mutex.synchronize(&invocation) : invocation.call
299
338
  end
@@ -308,6 +347,8 @@ module LittleGhost
308
347
  ))
309
348
  end
310
349
 
350
+ public
351
+
311
352
  # Adds an interjection to the single active leaf Agent.
312
353
  def interject(message, **options)
313
354
  child = @assembly_mutex.synchronize do
@@ -377,7 +418,7 @@ module LittleGhost
377
418
  end
378
419
 
379
420
  def build_tool_assembly
380
- child = runtime.build_assembly(self.class, run:, agent_stream_path:)
421
+ child = runtime.build_assembly(@assembly_definition || self.class, run:, agent_stream_path:)
381
422
  child.bind_agent_stream_path(agent_stream_path)
382
423
  child
383
424
  end
@@ -123,7 +123,7 @@ module LittleGhost
123
123
  snapshot = definition
124
124
  return (runtime || run.runtime).build_assembly(snapshot, run:) if run
125
125
 
126
- snapshot.implementation.new(runtime:)
126
+ snapshot.implementation.new(runtime:).bind_assembly_definition(snapshot)
127
127
  end
128
128
 
129
129
  # Executes the current declarations and returns their Run.
@@ -133,7 +133,7 @@ module LittleGhost
133
133
  def stream_ask(message, **options)
134
134
  snapshot = definition
135
135
  Enumerator.new do |events|
136
- snapshot.implementation.new(runtime:).stream_ask(message, **options).each { |event| events << event }
136
+ snapshot.implementation.new(runtime:).bind_assembly_definition(snapshot).stream_ask(message, **options).each { |event| events << event }
137
137
  end
138
138
  end
139
139
 
@@ -263,7 +263,7 @@ module LittleGhost
263
263
  class AgentBuilder < AssemblyBuilder
264
264
  DECLARATIONS = %i[
265
265
  model limits result_schema capture_diagnostics system_template system_prompt
266
- tools prompt_local after_initialize before_invocation after_invocation
266
+ tools after_initialize before_invocation after_invocation
267
267
  before_model after_model after_model_error before_tool after_tool
268
268
  manage_context detect_tool_loops skills subagent subagents
269
269
  subagent_long_poll_duration agent_as_tool agents_as_tools