little_ghost 0.8.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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/guides/assemblies.md +21 -9
  4. data/docs/guides/getting_started.md +1 -1
  5. data/docs/guides/prompt_views.md +234 -27
  6. data/lib/little_ghost/agent/context_management.rb +2 -13
  7. data/lib/little_ghost/agent/delegation.rb +7 -4
  8. data/lib/little_ghost/agent/skills.rb +8 -6
  9. data/lib/little_ghost/agent/tool_loop.rb +4 -4
  10. data/lib/little_ghost/agent.rb +230 -70
  11. data/lib/little_ghost/agent_builder.rb +16 -4
  12. data/lib/little_ghost/artifacts/workspace_store.rb +16 -14
  13. data/lib/little_ghost/assembly.rb +37 -4
  14. data/lib/little_ghost/assembly_builder.rb +1 -1
  15. data/lib/little_ghost/cli.rb +291 -0
  16. data/lib/little_ghost/code_mode/broker.rb +11 -3
  17. data/lib/little_ghost/code_mode/javascript/client.rb +58 -11
  18. data/lib/little_ghost/code_mode/javascript/host.rb +37 -13
  19. data/lib/little_ghost/code_mode/javascript/session.rb +21 -9
  20. data/lib/little_ghost/code_mode/javascript_engine.rb +21 -42
  21. data/lib/little_ghost/code_mode/ruby/host.rb +8 -4
  22. data/lib/little_ghost/code_mode/ruby/session.rb +41 -14
  23. data/lib/little_ghost/code_mode/ruby_engine.rb +6 -36
  24. data/lib/little_ghost/code_mode/runtime.rb +57 -24
  25. data/lib/little_ghost/errors.rb +19 -1
  26. data/lib/little_ghost/framework_prompts.rb +376 -0
  27. data/lib/little_ghost/generators/application_generator.rb +1 -1
  28. data/lib/little_ghost/graph.rb +24 -5
  29. data/lib/little_ghost/mcp/client.rb +18 -10
  30. data/lib/little_ghost/model_operations.rb +103 -36
  31. data/lib/little_ghost/prompt_resolver.rb +61 -27
  32. data/lib/little_ghost/prompts/little_ghost/agent/interjections/instructions.erb +12 -0
  33. data/lib/little_ghost/prompts/little_ghost/agent/system/default.erb +10 -0
  34. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_large.erb +10 -0
  35. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_many.erb +10 -0
  36. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/item_too_large.erb +10 -0
  37. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/preparation_failed.erb +10 -0
  38. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/resolver_invalid.erb +10 -0
  39. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/value_unstorable.erb +10 -0
  40. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/reference.erb +13 -0
  41. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/references.erb +12 -0
  42. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/workspace_references.erb +12 -0
  43. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/full_result.erb +14 -0
  44. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/storage_failed.erb +13 -0
  45. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/destination_unsafe.erb +10 -0
  46. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/secure_unavailable.erb +10 -0
  47. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_changed.erb +10 -0
  48. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unavailable.erb +10 -0
  49. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unsafe.erb +10 -0
  50. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/count_limit.erb +10 -0
  51. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/destination_exists.erb +10 -0
  52. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/too_large.erb +10 -0
  53. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/total_limit.erb +10 -0
  54. data/lib/little_ghost/prompts/little_ghost/assembly/feedback/transition_only.erb +10 -0
  55. data/lib/little_ghost/prompts/little_ghost/assembly/tools/fallback/description.erb +10 -0
  56. data/lib/little_ghost/prompts/little_ghost/code_mode/artifacts/format/references.erb +12 -0
  57. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/brokered_close.erb +10 -0
  58. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/cleanup_failed.erb +10 -0
  59. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/execution_failed.erb +11 -0
  60. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/arguments_object.erb +10 -0
  61. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/closed.erb +10 -0
  62. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/control_active.erb +10 -0
  63. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/limit_exceeded.erb +10 -0
  64. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/not_active.erb +10 -0
  65. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_active.erb +10 -0
  66. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_timed_out.erb +10 -0
  67. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unavailable_tool.erb +10 -0
  68. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unknown_program.erb +10 -0
  69. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/active_programs_limit.erb +10 -0
  70. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/cleanup_failed.erb +10 -0
  71. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/execution_limit.erb +10 -0
  72. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/invalid_request.erb +10 -0
  73. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/memory_limit.erb +10 -0
  74. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/pending_calls_limit.erb +10 -0
  75. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/source_size_limit.erb +10 -0
  76. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/tools_array.erb +10 -0
  77. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/instructions.erb +35 -0
  78. data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/feedback/parallel_callables.erb +10 -0
  79. data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/instructions.erb +40 -0
  80. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/exec/description.erb +10 -0
  81. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/description.erb +10 -0
  82. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/inputs/max_output_tokens/description.erb +10 -0
  83. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/description.erb +10 -0
  84. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/inputs/max_output_tokens/description.erb +10 -0
  85. data/lib/little_ghost/prompts/little_ghost/context_management/summary/request.erb +10 -0
  86. data/lib/little_ghost/prompts/little_ghost/context_management/summary/system_instruction.erb +16 -0
  87. data/lib/little_ghost/prompts/little_ghost/graph/context/format/failed_input.erb +11 -0
  88. data/lib/little_ghost/prompts/little_ghost/graph/context/format/previous_input.erb +13 -0
  89. data/lib/little_ghost/prompts/little_ghost/graph/context/headings/original_task.erb +10 -0
  90. data/lib/little_ghost/prompts/little_ghost/graph/context/headings/previous_inputs.erb +10 -0
  91. data/lib/little_ghost/prompts/little_ghost/mcp/errors/request_failed.erb +10 -0
  92. data/lib/little_ghost/prompts/little_ghost/mcp/errors/transformation_unserializable.erb +10 -0
  93. data/lib/little_ghost/prompts/little_ghost/mcp/tools/fallback/description.erb +10 -0
  94. data/lib/little_ghost/prompts/little_ghost/output/truncation/marker.erb +11 -0
  95. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/mount_changed.erb +10 -0
  96. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/secure_traversal_unavailable.erb +10 -0
  97. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/workspace_root_changed.erb +10 -0
  98. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/directory_listing_limit.erb +10 -0
  99. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/entry_required.erb +10 -0
  100. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/file_read_limit.erb +10 -0
  101. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/invalid_utf8.erb +10 -0
  102. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_escape.erb +10 -0
  103. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_missing.erb +10 -0
  104. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/multiply_linked.erb +10 -0
  105. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_directory.erb +10 -0
  106. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_file.erb +10 -0
  107. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/null_byte.erb +10 -0
  108. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/outside_scope.erb +10 -0
  109. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/parent_escape.erb +10 -0
  110. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_missing.erb +10 -0
  111. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_traverses_symlink.erb +10 -0
  112. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/read_only.erb +10 -0
  113. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/replace_empty.erb +10 -0
  114. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/symlink_path.erb +10 -0
  115. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_multiple.erb +10 -0
  116. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_not_found.erb +10 -0
  117. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_limit.erb +10 -0
  118. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_parent_missing.erb +10 -0
  119. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_target_symlink.erb +10 -0
  120. data/lib/little_ghost/prompts/little_ghost/sandbox/policy/feedback/feature_denied.erb +10 -0
  121. data/lib/little_ghost/prompts/little_ghost/sandbox/process/errors/program_supervisor_failed.erb +10 -0
  122. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/command_timed_out.erb +10 -0
  123. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/executable_required.erb +10 -0
  124. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_memory_exceeded.erb +10 -0
  125. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_output_exceeded.erb +10 -0
  126. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_timed_out.erb +10 -0
  127. data/lib/little_ghost/prompts/little_ghost/skills/discovery/instructions.erb +18 -0
  128. data/lib/little_ghost/prompts/little_ghost/skills/feedback/unknown.erb +11 -0
  129. data/lib/little_ghost/prompts/little_ghost/skills/resources/notices/truncated.erb +10 -0
  130. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/content.erb +28 -0
  131. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/description.erb +10 -0
  132. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/inputs/name/description.erb +10 -0
  133. data/lib/little_ghost/prompts/little_ghost/structured_output/persistence/format/redaction.erb +12 -0
  134. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/invalid_result.erb +11 -0
  135. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/schema_errors.erb +11 -0
  136. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/request.erb +14 -0
  137. data/lib/little_ghost/prompts/little_ghost/structured_output/tools/result/description.erb +11 -0
  138. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/invalid_json.erb +11 -0
  139. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/missing_tool.erb +11 -0
  140. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/multiple_tools.erb +11 -0
  141. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_complex.erb +11 -0
  142. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_deep.erb +11 -0
  143. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_large.erb +11 -0
  144. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/tool_not_exclusive.erb +11 -0
  145. data/lib/little_ghost/prompts/little_ghost/subagents/errors/create_failed.erb +10 -0
  146. data/lib/little_ghost/prompts/little_ghost/subagents/errors/previous_turn_failed.erb +10 -0
  147. data/lib/little_ghost/prompts/little_ghost/subagents/errors/restore_failed.erb +10 -0
  148. data/lib/little_ghost/prompts/little_ghost/subagents/errors/turn_failed.erb +10 -0
  149. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_ids.erb +10 -0
  150. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_path.erb +10 -0
  151. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/identity_capacity.erb +10 -0
  152. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/inactive.erb +10 -0
  153. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_chars_limit.erb +10 -0
  154. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_limit.erb +10 -0
  155. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_unsupported.erb +10 -0
  156. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_cursor.erb +10 -0
  157. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_mode.erb +10 -0
  158. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/list_limit.erb +10 -0
  159. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_limit.erb +10 -0
  160. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_type.erb +10 -0
  161. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/not_running.erb +10 -0
  162. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/queue_capacity.erb +10 -0
  163. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_invalid.erb +10 -0
  164. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_limit.erb +10 -0
  165. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/terminal_identity.erb +11 -0
  166. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/turn_capacity.erb +10 -0
  167. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_id.erb +10 -0
  168. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_kind.erb +10 -0
  169. data/lib/little_ghost/prompts/little_ghost/subagents/notices/turn_cancelled.erb +10 -0
  170. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/description.erb +10 -0
  171. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/active_id/description.erb +10 -0
  172. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/message/description.erb +10 -0
  173. data/lib/little_ghost/prompts/little_ghost/subagents/tools/list/description.erb +10 -0
  174. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/description.erb +10 -0
  175. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/id/description.erb +10 -0
  176. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/message/description.erb +10 -0
  177. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/send_mode/description.erb +10 -0
  178. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/description.erb +10 -0
  179. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/kind/description.erb +11 -0
  180. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/spawn_mode/description.erb +10 -0
  181. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task/description.erb +10 -0
  182. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task_name/description.erb +10 -0
  183. data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/description.erb +10 -0
  184. data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/inputs/ids/description.erb +10 -0
  185. data/lib/little_ghost/prompts/little_ghost/swarm/handoff/notice.erb +10 -0
  186. data/lib/little_ghost/prompts/little_ghost/swarm/handoff/request.erb +16 -0
  187. data/lib/little_ghost/prompts/little_ghost/swarm/tools/handoff/description.erb +10 -0
  188. data/lib/little_ghost/prompts/little_ghost/tools/built_in/list_files/description.erb +10 -0
  189. data/lib/little_ghost/prompts/little_ghost/tools/built_in/read_file/description.erb +10 -0
  190. data/lib/little_ghost/prompts/little_ghost/tools/built_in/replace_in_file/description.erb +10 -0
  191. data/lib/little_ghost/prompts/little_ghost/tools/built_in/shell/description.erb +10 -0
  192. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_file/description.erb +10 -0
  193. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/description.erb +10 -0
  194. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/single_progress.erb +10 -0
  195. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/unique_ids.erb +10 -0
  196. data/lib/little_ghost/prompts/little_ghost/tools/errors/unexpected_failure.erb +10 -0
  197. data/lib/little_ghost/prompts/little_ghost/tools/errors/unserializable_result.erb +10 -0
  198. data/lib/little_ghost/prompts/little_ghost/tools/feedback/invalid_input.erb +10 -0
  199. data/lib/little_ghost/prompts/little_ghost/tools/feedback/unknown.erb +10 -0
  200. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/final_warning.erb +10 -0
  201. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/termination.erb +10 -0
  202. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/warning.erb +10 -0
  203. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/additional_property.erb +10 -0
  204. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/enum.erb +11 -0
  205. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_format.erb +10 -0
  206. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_pattern.erb +10 -0
  207. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_items.erb +11 -0
  208. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_length.erb +11 -0
  209. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/maximum.erb +11 -0
  210. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_items.erb +11 -0
  211. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_length.erb +11 -0
  212. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/minimum.erb +11 -0
  213. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/required.erb +10 -0
  214. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/type.erb +11 -0
  215. data/lib/little_ghost/prompts/little_ghost/workspace/feedback/path_changed.erb +10 -0
  216. data/lib/little_ghost/runtime/hooks/artifacts.rb +58 -22
  217. data/lib/little_ghost/runtime.rb +8 -3
  218. data/lib/little_ghost/sandbox/filesystem.rb +51 -33
  219. data/lib/little_ghost/sandbox/process_runner.rb +6 -2
  220. data/lib/little_ghost/sandbox/process_session.rb +8 -4
  221. data/lib/little_ghost/sandbox/scope.rb +4 -1
  222. data/lib/little_ghost/sandboxes/seatbelt.rb +3 -1
  223. data/lib/little_ghost/sandboxes/unrestricted.rb +10 -6
  224. data/lib/little_ghost/skills/catalog.rb +32 -37
  225. data/lib/little_ghost/structured_output.rb +6 -6
  226. data/lib/little_ghost/subagents/control_tool.rb +58 -0
  227. data/lib/little_ghost/subagents/manager.rb +99 -74
  228. data/lib/little_ghost/support/output_truncation.rb +28 -4
  229. data/lib/little_ghost/swarm.rb +20 -7
  230. data/lib/little_ghost/tool.rb +42 -20
  231. data/lib/little_ghost/tool_registry.rb +2 -2
  232. data/lib/little_ghost/tools/filesystem.rb +12 -4
  233. data/lib/little_ghost/tools/shell.rb +5 -1
  234. data/lib/little_ghost/tools/write_todos.rb +9 -9
  235. data/lib/little_ghost/version.rb +1 -1
  236. data/lib/little_ghost/workflow.rb +199 -27
  237. data/lib/little_ghost/workspace.rb +2 -2
  238. data/lib/little_ghost.rb +5 -2
  239. metadata +187 -2
  240. /data/lib/little_ghost/generators/templates/application/app/prompts/application/{system.erb.tt → system_prompt.erb.tt} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d24d2ed91efec3898453d20dfea0902edfca7c207fac0983426153208b308a1e
4
- data.tar.gz: 74aefcfaf1d9113640f80c6a7c4462c1e4919265705f0eb37c82c9f694eabcae
3
+ metadata.gz: 271908b0fc40900504fb25cd57d2cbcc8d987ac1ab32a0db58353562793446fc
4
+ data.tar.gz: 507e5d747e8b341dd60b1edce8811e0dc5b2d41f98cb970e801284b2a326a49f
5
5
  SHA512:
6
- metadata.gz: de9e90761042a5cad938664be479e6d35d6b0423b213cde3ac05f0a033c4de7468ef96252073b1405a73510c53b650ea5d7c74be2cae9bd68922494a05cfb406
7
- data.tar.gz: 96ac0a88302659cad84e80c4491c23ab4bad211b0db8fe9a1bb370dbbcb0a2090d81cc3c922cc62d18ace4804a559efba5bbcb6ee44a6d52cec58ad5f45dfcc0
6
+ metadata.gz: 830edf81e2d4892e7b72def07ba1d1be85843c8abf5cbb25d744ad520e8586fe9de2ae1cb32084ce832bb1eb866f4fa083af95b93e59df2b3e2c47f4e5c7aea9
7
+ data.tar.gz: a8dba3d880bdc6adc4271daac984f9ab6384d34520a5be2071e66d1d746f4979952c13e7743364e2de2f2c2aa721057e12e54ca29c83a382b6506e49889ee43e
data/README.md CHANGED
@@ -148,7 +148,7 @@ upgrading, because interfaces may change between releases.
148
148
  - [Core Concepts](docs/guides/core_concepts.md) builds the mental model from Agent to Assembly.
149
149
  - [Models and Providers](docs/guides/models_and_providers.md) explains targets, shared roles, and per-request model selection.
150
150
  - [Provider Support](docs/guides/providers.md) has copyable setup for hosted APIs and local model servers.
151
- - [Prompts as Views](docs/guides/prompt_views.md) gives growing instructions, shared pieces, and application values a natural home.
151
+ - [Prompts as Views](docs/guides/prompt_views.md) explains Agent instructions and shared partials, including how to customize LittleGhost's bundled framework prompts.
152
152
  - [Tools](docs/guides/tools.md) explains how models call focused Ruby operations.
153
153
  - [MCP](docs/guides/mcp.md) connects agents to operations published through the Model Context Protocol.
154
154
  - [Structured Results and Content](docs/guides/structured_outputs_and_content.md) covers checked result shapes, images, and documents.
@@ -15,7 +15,7 @@ run = entrypoint.ask(question)
15
15
 
16
16
  ## Use a Workflow for explicit application logic
17
17
 
18
- A Workflow's `perform` method is ordinary Ruby. Inside it, `invoke` prepares a child call. Read `.output` when you need an intermediate answer. Return the final `invoke` call untouched so its response can stream to the caller.
18
+ A Workflow's `perform` method is ordinary Ruby. Inside it, `invoke` prepares a child call. Read `.output` when you need an intermediate answer. Return the final `invoke` call untouched so its response can stream to the caller, or return a value that Ruby computes from the intermediate answers.
19
19
 
20
20
  ```ruby
21
21
  class ResponseWorkflow < LittleGhost::Workflow
@@ -41,25 +41,37 @@ Every participant passed to `invoke` can be an Agent or another Assembly. By def
41
41
 
42
42
  Each child Agent keeps its own [prompt view](prompt_views.md). The Workflow supplies request-specific input; it does not replace that Agent's reusable system instructions.
43
43
 
44
- The last child is special because its events become the Workflow's public stream. Return that `invoke` without consuming it:
44
+ The last child is special when its events should become the Workflow's public stream. Return that `invoke` without consuming it:
45
45
 
46
46
  ```ruby
47
- # Wrong: this returns a String after consuming the final invocation.
48
47
  def perform
49
- invoke(CustomerSupportAgent).output
48
+ invoke CustomerSupportAgent
50
49
  end
50
+ ```
51
51
 
52
- # Right: this returns the lazy invocation itself.
53
- def perform
54
- invoke CustomerSupportAgent
52
+ When Ruby should compute the caller-visible result, consume every child and return the computed value:
53
+
54
+ ```ruby
55
+ class EvidenceWorkflow < LittleGhost::Workflow
56
+ private
57
+
58
+ def perform
59
+ findings = parallel(
60
+ invoke(LedgerResearchAgent),
61
+ invoke(PolicyResearchAgent),
62
+ max_concurrency: 2
63
+ )
64
+
65
+ {"findings" => findings}
66
+ end
55
67
  end
56
68
  ```
57
69
 
58
- The first version produces a failed top-level Run whose error is `ProtocolError`. Use `.output` only when Ruby needs an intermediate answer before choosing the next step.
70
+ A returned String becomes the Workflow's textual response. Arrays, mappings, numbers, and booleans become a structured result available through `RunResult#output`, including when the Workflow is exposed with `assembly_as_tool`. Direct structured results must be JSON-compatible and stay within LittleGhost's structured-result size, depth, and complexity limits. Return an explicit value: `nil` remains a `ProtocolError`, which catches forgotten returns.
59
71
 
60
72
  ### Choose a branch in Ruby
61
73
 
62
- Each branch should end with its final unconsumed invocation:
74
+ Each branch should end with its final unconsumed invocation or a directly computed value:
63
75
 
64
76
  ```ruby
65
77
  class RoutedResponseWorkflow < LittleGhost::Workflow
@@ -197,7 +197,7 @@ app/
197
197
  │ └── response_workflow.rb
198
198
  ├── prompts/
199
199
  │ └── customer_support/
200
- │ └── system.erb
200
+ │ └── system_prompt.erb
201
201
  └── tools/
202
202
  └── help_center_lookup_tool.rb
203
203
  ```
@@ -1,8 +1,9 @@
1
1
  # Prompts as Views
2
2
 
3
- A short prompt fits nicely inside an Agent class. As the instructions grow, move them into a **prompt view**: an ERB file that LittleGhost finds and renders for the Agent.
4
-
5
- This keeps the Agent definition focused. It also gives shared instructions and application values a natural home.
3
+ Keep growing Agent instructions readable by moving them into a **prompt view**:
4
+ an ERB file that LittleGhost finds and renders for the Agent. The Agent prepares
5
+ any application values the prompt needs, while the view holds the instructions
6
+ sent to the model.
6
7
 
7
8
  ## Start with the inline prompt
8
9
 
@@ -28,7 +29,7 @@ class CustomerSupportAgent < LittleGhost::Agent
28
29
  end
29
30
  ```
30
31
 
31
- Then create `app/prompts/customer_support/system.erb`:
32
+ Then create `app/prompts/customer_support/system_prompt.erb`:
32
33
 
33
34
  ```erb
34
35
  You help customers understand their orders and account.
@@ -38,57 +39,115 @@ Never invent company guidance. Check the help center when policy matters.
38
39
  Use the order status tool before making a claim about a private order.
39
40
  ```
40
41
 
41
- That is enough. `CustomerSupportAgent` becomes `customer_support`, so LittleGhost looks for `customer_support/system.erb` under `app/prompts`.
42
+ That is enough. LittleGhost turns `CustomerSupportAgent` into the logical path
43
+ `customer_support` and looks for
44
+ `app/prompts/customer_support/system_prompt.erb`.
42
45
 
43
- The prompt is still a system instruction sent to the selected model provider. Keeping it in a view improves organization; it does not keep the content inside your process.
46
+ The prompt is still a system instruction sent to the selected model provider.
47
+ Keeping it in a view improves organization; it does not keep the content inside
48
+ your process.
44
49
 
45
- ## Give the view application values
50
+ ## Prepare application values with Ruby
46
51
 
47
- Use `prompt_local` for a value the application owns:
52
+ Define `system_prompt` as an instance method when the view needs values from
53
+ your application. Assign those values to instance variables:
48
54
 
49
55
  ```ruby
50
56
  class CustomerSupportAgent < LittleGhost::Agent
51
- prompt_local :company_name, "Northstar"
57
+ def system_prompt
58
+ @company_name = "Northstar"
59
+ @policy_version = SupportPolicy.current_version
60
+ end
52
61
  end
53
62
  ```
54
63
 
55
- The local is available by name in the view:
64
+ LittleGhost calls the method before it renders the view. The instance variables
65
+ are available there by the same names:
56
66
 
57
67
  ```erb
58
- You are a customer support agent for <%= company_name %>.
68
+ You are a customer support agent for <%= @company_name %>.
69
+ Follow support policy <%= @policy_version %>.
59
70
  Answer clearly and concisely.
60
71
  ```
61
72
 
62
- A block can resolve a trusted value for each Agent instance. Add it to the Agent class too:
73
+ If you know Rails controllers, the pairing is familiar: the method prepares
74
+ values, and the ERB file presents them. The method's return value is ignored. If
75
+ it raises an exception, the Run fails in the usual way.
76
+
77
+ An inline `system_prompt` declaration still takes precedence over a view. Remove
78
+ the inline declaration when you want LittleGhost to call the instance method and
79
+ render `system_prompt.erb`.
80
+
81
+ Subclass actions can call `super` before adding their own values:
63
82
 
64
83
  ```ruby
65
- class CustomerSupportAgent < LittleGhost::Agent
66
- prompt_local(:policy_version) { SupportPolicy.current_version }
84
+ class BillingSupportAgent < CustomerSupportAgent
85
+ def system_prompt
86
+ super
87
+ @billing_region = "US"
88
+ end
67
89
  end
68
90
  ```
69
91
 
70
- Prompt views also receive `invocation`, `run`, and `agent`. Reach for those when the instruction truly depends on the current request. Keep user wording in the caller message unless you deliberately want it inside the system instruction.
92
+ Prompt views also receive three built-in local variables: `invocation` for the
93
+ current request, `run` for its lifecycle, and `agent` for the run-scoped Agent.
94
+ Use them when an instruction genuinely depends on the current request. Keep the
95
+ customer's wording in the caller message unless it belongs in the system
96
+ instruction.
71
97
 
72
- Every rendered value may be sent to the model provider. Pass only data that belongs in the prompt.
98
+ Every rendered value may be sent to the model provider. Put only data that
99
+ belongs in the prompt into the view.
73
100
 
74
101
  ## Share a small partial
75
102
 
76
103
  Partials keep repeated instructions in one place. Create `app/prompts/shared/_voice.erb`:
77
104
 
78
105
  ```erb
79
- Use a warm, direct voice for <%= company_name %>.
106
+ Use a warm, direct voice for <%= @company_name %>.
80
107
  Prefer one clear next step over a long list of possibilities.
81
108
  ```
82
109
 
83
110
  Render it from the Agent's system view:
84
111
 
85
112
  ```erb
86
- You are a customer support agent for <%= company_name %>.
113
+ You are a customer support agent for <%= @company_name %>.
114
+
115
+ <%= partial "shared/voice" %>
116
+ ```
117
+
118
+ The underscore marks a partial. Partials share the Agent's instance variables,
119
+ so `@company_name` remains available without extra wiring.
120
+
121
+ Use an explicit local for a value that belongs only to one partial. For example,
122
+ create `app/prompts/shared/_closing.erb`:
123
+
124
+ ```erb
125
+ Offer one clear next step for <%= audience %>.
126
+ ```
127
+
128
+ Then pass `audience` when you render it:
87
129
 
88
- <%= partial "shared/voice", locals: {company_name: company_name} %>
130
+ ```erb
131
+ <%= partial "shared/closing", locals: {audience: "account owners"} %>
89
132
  ```
90
133
 
91
- The underscore marks a partial. Its locals are explicit, so it does not quietly inherit everything available to the parent view.
134
+ Locals do not flow into nested partials unless you pass them again.
135
+
136
+ ## Keep repeated renders predictable
137
+
138
+ LittleGhost calls `system_prompt` once for each file-backed prompt render. Each
139
+ call starts with the instance variables captured after LittleGhost's
140
+ `after_initialize` callbacks finish. Values left behind by a later callback,
141
+ Tool, or earlier render do not become prompt assigns by accident.
142
+
143
+ After preparing the view, LittleGhost restores the Agent's live instance
144
+ variables. The objects assigned to the view are still ordinary Ruby objects,
145
+ though, so the view sees the same mutable objects. Treat them as read-only, or
146
+ duplicate a value before assigning it when the template needs an isolated copy.
147
+
148
+ For state that should change across invocations, read from `run`, a session, or
149
+ an application object in `system_prompt` instead of relying on an earlier
150
+ prompt render.
92
151
 
93
152
  ## Choose a different template path
94
153
 
@@ -104,10 +163,154 @@ LittleGhost chooses one prompt source in this order:
104
163
 
105
164
  1. An inline `system_prompt`
106
165
  2. An explicit `system_template`
107
- 3. The Agent's conventional `system.erb` view
166
+ 3. The Agent's conventional `system_prompt.erb` view
108
167
 
109
168
  Applications can add prompt lookup roots through `Configuration#prompt_paths`.
110
- Earlier roots win, which lets application code override a shared prompt package.
169
+ Earlier roots win, so an application can override a view from a shared prompt
170
+ package.
171
+
172
+ ## Customize LittleGhost's framework prompts
173
+
174
+ You can change LittleGhost's own model-facing language without replacing its
175
+ Ruby behavior. Tool descriptions, structured-result repair requests, context
176
+ summaries, code-mode instructions, and other framework messages all come from
177
+ bundled prompt views under the `little_ghost/` namespace.
178
+
179
+ ### Decide when to customize
180
+
181
+ Models can respond differently to the same instruction, even when the Tool,
182
+ schema, and task stay the same. Wording that works well with one model may lead
183
+ another model to miss a Tool constraint, mishandle a repair request, or recover
184
+ less reliably from a failed call. Adjusting the relevant framework prompt gives
185
+ you a direct way to improve that interaction without forking LittleGhost.
186
+
187
+ An override can also align framework language with your application's
188
+ terminology and voice. Start with the narrowest template that covers the
189
+ behavior you want to change, and evaluate it with the models your application
190
+ uses. Keep protocol checks, authorization, and execution limits in Ruby;
191
+ prompt wording guides the model but does not enforce those boundaries.
192
+
193
+ ### Find the prompt you want to change
194
+
195
+ Browse the catalog from your application:
196
+
197
+ ```sh
198
+ $ little_ghost prompts list
199
+ $ little_ghost prompts list structured_output
200
+ $ little_ghost prompts list structured_output/repair
201
+ $ little_ghost prompts show structured_output/repair/request
202
+ ```
203
+
204
+ The bare `list` command shows top-level features with template counts. Pass a
205
+ feature or component path to browse its immediate children, or use `list --all`
206
+ to print every template key. Keys follow `feature/component/purpose`. For
207
+ example, built-in Tool descriptions live under `tools/built_in`, while JSON
208
+ Schema feedback lives under `tools/validation/schema`. Directories named
209
+ `feedback` contain conditions the model can correct. Directories named `errors`
210
+ contain operational failures.
211
+
212
+ `show` prints the bundled ERB source. Every bundled template starts with a
213
+ structured ERB comment that explains where LittleGhost renders it, its locals,
214
+ and any formatting constraints. These headers use `<%# ... -%>`, so neither the
215
+ comment nor its trailing newline enters the rendered prompt. Each interior line
216
+ also starts with `#` so Ruby-aware editors recognize the whole header as a
217
+ comment. A `#` line outside `<%# ... -%>` and an HTML comment are output text and
218
+ would be sent to the model.
219
+
220
+ Bundled prompts use plain text and Markdown. When a prompt needs headings or
221
+ structured sections, the default template uses Markdown headings and lists
222
+ rather than XML-like wrapper tags.
223
+
224
+ ### Copy an override into your application
225
+
226
+ Copy one template into the default application prompt root before editing it:
227
+
228
+ ```sh
229
+ $ little_ghost prompts copy structured_output/repair/request
230
+ ```
231
+
232
+ That creates
233
+ `app/prompts/little_ghost/structured_output/repair/request.erb`. Use `--root` when the
234
+ application configures a different prompt root:
235
+
236
+ ```sh
237
+ $ little_ghost prompts copy structured_output/repair/request --root config/prompts
238
+ ```
239
+
240
+ Use `--agent` when one Agent needs different wording from the rest of the
241
+ application. Pass the Agent's logical path, which is its underscored class name
242
+ without the `Agent` suffix:
243
+
244
+ ```sh
245
+ $ little_ghost prompts copy structured_output/repair/request --agent customer_support
246
+ ```
247
+
248
+ That creates
249
+ `app/prompts/customer_support/little_ghost/structured_output/repair/request.erb`.
250
+ Namespaced classes use a namespaced logical path, such as
251
+ `Admin::CustomerSupportAgent` becoming `admin/customer_support`.
252
+
253
+ Copy the complete catalog when an application deliberately wants to own every
254
+ piece of framework wording:
255
+
256
+ ```sh
257
+ $ little_ghost prompts copy --all
258
+ ```
259
+
260
+ Copy commands refuse to overwrite any existing destination. With `--all`, the
261
+ command checks every destination before it starts writing. Copied templates
262
+ retain their invisible documentation headers so the override remains
263
+ self-explanatory in the application.
264
+
265
+ ### Choose the override scope
266
+
267
+ For every framework prompt that applies to an Agent invocation, LittleGhost
268
+ uses the first matching template in this order:
269
+
270
+ 1. An invocation-specific `TrustedPath` root
271
+ 2. The Agent-scoped directory inside a configured prompt root
272
+ 3. The runtime-wide directory inside a configured prompt root
273
+ 4. The template bundled with LittleGhost
274
+
275
+ Each root keeps the same `little_ghost/<key>.erb` namespace. Agent scope inserts
276
+ the logical path before that namespace.
277
+
278
+ Runtime-wide overrides normally live in the default `app/prompts` root. Add or
279
+ replace configured roots when the application uses another location:
280
+
281
+ ```ruby
282
+ LittleGhost.configure do |config|
283
+ config.prompt_paths = ["config/prompts", "app/prompts"]
284
+ end
285
+ ```
286
+
287
+ The uncommon invocation-specific layer is explicit because it can change model
288
+ instructions for only one call. From `config/little_ghost.rb`:
289
+
290
+ ```ruby
291
+ campaign_prompts = LittleGhost::TrustedPath.new(
292
+ path: File.expand_path("campaign_prompts", __dir__)
293
+ )
294
+
295
+ CustomerSupportAgent.ask(
296
+ "Where is my order?",
297
+ template_paths: [campaign_prompts]
298
+ )
299
+ ```
300
+
301
+ The invocation root contains
302
+ `little_ghost/structured_output/repair/request.erb`, as a runtime root does.
303
+ Direct `LittleGhost.generate` calls accept the same `template_paths:` option.
304
+
305
+ ### Know what an override can change
306
+
307
+ Framework templates control model-facing language, not protocol roles, Tool
308
+ names, schemas, validation behavior, security checks, or execution limits. A
309
+ required template cannot render as blank.
310
+
311
+ LittleGhost is pre-1.0. Template keys and their documented locals are maintained
312
+ on a best-effort basis until 1.0, so review release notes and copied overrides
313
+ when upgrading.
111
314
 
112
315
  ## Treat views as application code
113
316
 
@@ -115,13 +318,16 @@ Prompt views run as ERB inside the Ruby process and can call Ruby. Keep prompt
115
318
  directories with the rest of your application code rather than letting a
116
319
  request choose one.
117
320
 
118
- `TrustedPath` is available for the uncommon case where application code selects
119
- a request-specific root. It marks that choice explicitly; it does not inspect
120
- or restrict the directory.
321
+ Use `TrustedPath` only when application code needs to select a request-specific
322
+ root. Do not derive its path from a request parameter, model output, or Tool
323
+ argument. The wrapper confirms that the directory exists and resolves symbolic
324
+ links. It does not inspect who can edit the directory or restrict what its ERB
325
+ can execute.
121
326
 
122
327
  ## Build request-specific input in a Workflow
123
328
 
124
- A prompt view defines reusable instructions for one Agent. A Workflow may still build request-specific input for that Agent:
329
+ A prompt view defines reusable instructions for one Agent. A Workflow may still
330
+ build request-specific input for that Agent:
125
331
 
126
332
  ```ruby
127
333
  invoke CustomerSupportAgent, input: <<~MESSAGE
@@ -132,7 +338,8 @@ invoke CustomerSupportAgent, input: <<~MESSAGE
132
338
  MESSAGE
133
339
  ```
134
340
 
135
- The Workflow is composing this request. `CustomerSupportAgent` still receives its own system prompt view when it runs.
341
+ The Workflow is composing this request. `CustomerSupportAgent` still receives
342
+ its own system prompt view when it runs.
136
343
 
137
344
  Continue with [Tools](tools.md) to give those Agents application capabilities
138
345
  while keeping model input, trusted context, and delegated sandbox operations
@@ -35,17 +35,6 @@ module LittleGhost
35
35
  DEFAULT_PRESERVE_RECENT_MESSAGES = 10 # :nodoc:
36
36
  ESTIMATED_CHARS_PER_TOKEN = 4 # :nodoc:
37
37
  OUTPUT_LIMIT_STOP_REASONS = %i[max_tokens limit_output_tokens limit_total_tokens limit_turns].freeze # :nodoc:
38
- SUMMARIZATION_PROMPT = <<~PROMPT # :nodoc:
39
- You are a conversation summarizer. Provide a concise summary of the conversation history.
40
-
41
- Format requirements:
42
- - Create a structured, concise summary in bullet-point format.
43
- - Do not respond conversationally, address the user directly, or comment on tool availability.
44
- - Preserve key topics, questions, significant tool executions and results, code or technical information, and key insights.
45
- - Do not assume tool executions failed unless otherwise stated.
46
- - Write the summary in the third person.
47
- PROMPT
48
-
49
38
  def self.included(base) # :nodoc:
50
39
  base.extend(ClassMethods)
51
40
  base.class_attribute :context_management_configuration_value
@@ -193,9 +182,9 @@ module LittleGhost
193
182
  started_at = monotonic_time
194
183
  summary_request = ModelRequest.new(
195
184
  messages: [
196
- Message.new(role: :system, content: SUMMARIZATION_PROMPT),
185
+ Message.new(role: :system, content: render_framework_prompt("context_management/summary/system_instruction")),
197
186
  *messages,
198
- Message.new(role: :user, content: "Please summarize this conversation.")
187
+ Message.new(role: :user, content: render_framework_prompt("context_management/summary/request"))
199
188
  ],
200
189
  tools: [],
201
190
  settings: request.settings,
@@ -17,8 +17,10 @@ module LittleGhost
17
17
  # Static declarations may be combined with a resolver that returns dynamic
18
18
  # Subagents::Definition objects. Managed conversations persist when a session
19
19
  # store is configured unless <tt>persist: false</tt> keeps them local to one
20
- # invocation. An agent exposed as a tool starts with empty history unless
21
- # <tt>preserve_context: true</tt> serializes calls and retains its history.
20
+ # invocation. A class-declared agent exposed as a tool starts with an empty
21
+ # history and a fresh run-scoped Agent for every call. Set
22
+ # <tt>preserve_context: true</tt> to serialize calls through one Agent and
23
+ # retain its history.
22
24
  #
23
25
  # Tool overrides must be classes. A delegated agent otherwise receives only
24
26
  # its own declared tools; it does not inherit the parent's registry. The
@@ -91,8 +93,9 @@ module LittleGhost
91
93
 
92
94
  # Exposes +agent_class+ as one ordinary tool.
93
95
  #
94
- # Pass <tt>preserve_context: true</tt> to retain the delegated agent's
95
- # conversational history between calls to that tool instance.
96
+ # By default, every tool call builds and closes a fresh delegated Agent.
97
+ # Pass <tt>preserve_context: true</tt> to serialize calls through one
98
+ # Agent and retain its conversational history between calls.
96
99
  def agent_as_tool(agent_class, name: nil, description: nil, model: nil, tools: nil,
97
100
  preserve_context: false)
98
101
  assembly_as_tool(
@@ -40,7 +40,8 @@ module LittleGhost
40
40
  paths:,
41
41
  resource_root: binding.run&.runtime&.skill_resource_root,
42
42
  workspace: binding.workspace,
43
- sandbox: binding.sandbox
43
+ sandbox: binding.sandbox,
44
+ prompt_renderer: binding.agent.method(:render_framework_prompt)
44
45
  )
45
46
  )
46
47
  return [] if catalog.names.empty?
@@ -70,10 +71,6 @@ module LittleGhost
70
71
  end
71
72
  self.skills_configuration_value = options.merge(paths: configured_paths)
72
73
  tools CatalogTools
73
- prompt_local(:skills_prompt) do
74
- tool = tools.fetch("skills") if tools.names.include?("skills")
75
- tool&.catalog&.discovery_prompt.to_s
76
- end
77
74
  before_invocation :include_skills_prompt
78
75
  end
79
76
 
@@ -82,8 +79,13 @@ module LittleGhost
82
79
 
83
80
  private
84
81
 
82
+ def skills_prompt
83
+ tool = tools.fetch("skills") if tools.names.include?("skills")
84
+ tool&.catalog&.discovery_prompt.to_s
85
+ end
86
+
85
87
  def include_skills_prompt(payload)
86
- prompt = prompt_locals[:skills_prompt].to_s
88
+ prompt = skills_prompt
87
89
  return Support::Callbacks.continue if prompt.empty?
88
90
 
89
91
  messages = payload.fetch(:messages).dup
@@ -27,8 +27,6 @@ module LittleGhost
27
27
  # changed result resets that sequence, while a terminating repeat prevents
28
28
  # the duplicate tool body from running again.
29
29
  module ToolLoop
30
- WARNING = "Repeated tool call detected. Change the approach or arguments before calling this tool again." # :nodoc:
31
- FINAL_WARNING = "Final repeated tool call warning. Calling this tool again with identical arguments and result will stop the run." # :nodoc:
32
30
  TRACKED_INVOCATION_LIMIT = 1_000 # :nodoc:
33
31
 
34
32
  def self.included(base) # :nodoc:
@@ -120,7 +118,7 @@ module LittleGhost
120
118
  newly_terminated = false
121
119
  if repeat && repeat[:count] >= @tool_loop_terminate_at - 1
122
120
  unless state[:termination]
123
- state[:termination] = "Stopped after detecting a repeated tool-call loop in #{tool_use.name.inspect}."
121
+ state[:termination] = render_framework_prompt("tools/loop/notices/termination", tool_name: tool_use.name)
124
122
  newly_terminated = true
125
123
  end
126
124
  end
@@ -165,7 +163,9 @@ module LittleGhost
165
163
  return Support::Callbacks.continue unless count == @tool_loop_warning_at || count == @tool_loop_terminate_at - 1
166
164
 
167
165
  action = (count == @tool_loop_warning_at) ? :warn : :final_warning
168
- warning = (action == :warn) ? WARNING : FINAL_WARNING
166
+ warning = render_framework_prompt(
167
+ (action == :warn) ? "tools/loop/notices/warning" : "tools/loop/notices/final_warning"
168
+ )
169
169
  Instrumentation.publish(
170
170
  :tool_loop,
171
171
  operation_id: payload[:operation_id],