little_ghost 0.8.0 → 0.10.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 (250) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -10
  3. data/docs/guides/assemblies.md +81 -17
  4. data/docs/guides/core_concepts.md +65 -61
  5. data/docs/guides/getting_started.md +39 -69
  6. data/docs/guides/integrations.md +20 -6
  7. data/docs/guides/production.md +17 -4
  8. data/docs/guides/prompt_views.md +234 -27
  9. data/lib/little_ghost/ag_ui/adapter.rb +64 -1
  10. data/lib/little_ghost/agent/context_management.rb +2 -13
  11. data/lib/little_ghost/agent/delegation.rb +7 -4
  12. data/lib/little_ghost/agent/skills.rb +8 -6
  13. data/lib/little_ghost/agent/tool_loop.rb +4 -4
  14. data/lib/little_ghost/agent.rb +231 -71
  15. data/lib/little_ghost/agent_builder.rb +16 -4
  16. data/lib/little_ghost/agent_stream_source.rb +41 -2
  17. data/lib/little_ghost/artifacts/workspace_store.rb +16 -14
  18. data/lib/little_ghost/assembly.rb +59 -22
  19. data/lib/little_ghost/assembly_builder.rb +1 -1
  20. data/lib/little_ghost/assembly_execution.rb +3 -1
  21. data/lib/little_ghost/cli.rb +291 -0
  22. data/lib/little_ghost/code_mode/broker.rb +16 -6
  23. data/lib/little_ghost/code_mode/javascript/client.rb +58 -11
  24. data/lib/little_ghost/code_mode/javascript/host.rb +37 -13
  25. data/lib/little_ghost/code_mode/javascript/session.rb +43 -11
  26. data/lib/little_ghost/code_mode/javascript_engine.rb +21 -42
  27. data/lib/little_ghost/code_mode/ruby/host.rb +8 -4
  28. data/lib/little_ghost/code_mode/ruby/session.rb +56 -15
  29. data/lib/little_ghost/code_mode/ruby_engine.rb +6 -36
  30. data/lib/little_ghost/code_mode/runtime.rb +57 -24
  31. data/lib/little_ghost/errors.rb +19 -1
  32. data/lib/little_ghost/execution.rb +3 -1
  33. data/lib/little_ghost/framework_prompts.rb +376 -0
  34. data/lib/little_ghost/generators/application_generator.rb +1 -1
  35. data/lib/little_ghost/graph.rb +27 -6
  36. data/lib/little_ghost/mcp/client.rb +18 -10
  37. data/lib/little_ghost/model_operations.rb +103 -36
  38. data/lib/little_ghost/prompt_resolver.rb +61 -27
  39. data/lib/little_ghost/prompts/little_ghost/agent/interjections/instructions.erb +12 -0
  40. data/lib/little_ghost/prompts/little_ghost/agent/system/default.erb +10 -0
  41. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_large.erb +10 -0
  42. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_many.erb +10 -0
  43. data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/item_too_large.erb +10 -0
  44. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/preparation_failed.erb +10 -0
  45. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/resolver_invalid.erb +10 -0
  46. data/lib/little_ghost/prompts/little_ghost/artifacts/errors/value_unstorable.erb +10 -0
  47. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/reference.erb +13 -0
  48. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/references.erb +12 -0
  49. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/workspace_references.erb +12 -0
  50. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/full_result.erb +14 -0
  51. data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/storage_failed.erb +13 -0
  52. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/destination_unsafe.erb +10 -0
  53. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/secure_unavailable.erb +10 -0
  54. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_changed.erb +10 -0
  55. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unavailable.erb +10 -0
  56. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unsafe.erb +10 -0
  57. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/count_limit.erb +10 -0
  58. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/destination_exists.erb +10 -0
  59. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/too_large.erb +10 -0
  60. data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/total_limit.erb +10 -0
  61. data/lib/little_ghost/prompts/little_ghost/assembly/feedback/transition_only.erb +10 -0
  62. data/lib/little_ghost/prompts/little_ghost/assembly/tools/fallback/description.erb +10 -0
  63. data/lib/little_ghost/prompts/little_ghost/code_mode/artifacts/format/references.erb +12 -0
  64. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/brokered_close.erb +10 -0
  65. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/cleanup_failed.erb +10 -0
  66. data/lib/little_ghost/prompts/little_ghost/code_mode/errors/execution_failed.erb +11 -0
  67. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/arguments_object.erb +10 -0
  68. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/closed.erb +10 -0
  69. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/control_active.erb +10 -0
  70. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/limit_exceeded.erb +10 -0
  71. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/not_active.erb +10 -0
  72. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_active.erb +10 -0
  73. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_timed_out.erb +10 -0
  74. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unavailable_tool.erb +10 -0
  75. data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unknown_program.erb +10 -0
  76. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/active_programs_limit.erb +10 -0
  77. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/cleanup_failed.erb +10 -0
  78. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/execution_limit.erb +10 -0
  79. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/invalid_request.erb +10 -0
  80. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/memory_limit.erb +10 -0
  81. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/pending_calls_limit.erb +10 -0
  82. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/source_size_limit.erb +10 -0
  83. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/tools_array.erb +10 -0
  84. data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/instructions.erb +35 -0
  85. data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/feedback/parallel_callables.erb +10 -0
  86. data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/instructions.erb +40 -0
  87. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/exec/description.erb +10 -0
  88. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/description.erb +10 -0
  89. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/inputs/max_output_tokens/description.erb +10 -0
  90. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/description.erb +10 -0
  91. data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/inputs/max_output_tokens/description.erb +10 -0
  92. data/lib/little_ghost/prompts/little_ghost/context_management/summary/request.erb +10 -0
  93. data/lib/little_ghost/prompts/little_ghost/context_management/summary/system_instruction.erb +16 -0
  94. data/lib/little_ghost/prompts/little_ghost/graph/context/format/failed_input.erb +11 -0
  95. data/lib/little_ghost/prompts/little_ghost/graph/context/format/previous_input.erb +13 -0
  96. data/lib/little_ghost/prompts/little_ghost/graph/context/headings/original_task.erb +10 -0
  97. data/lib/little_ghost/prompts/little_ghost/graph/context/headings/previous_inputs.erb +10 -0
  98. data/lib/little_ghost/prompts/little_ghost/mcp/errors/request_failed.erb +10 -0
  99. data/lib/little_ghost/prompts/little_ghost/mcp/errors/transformation_unserializable.erb +10 -0
  100. data/lib/little_ghost/prompts/little_ghost/mcp/tools/fallback/description.erb +10 -0
  101. data/lib/little_ghost/prompts/little_ghost/output/truncation/marker.erb +11 -0
  102. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/mount_changed.erb +10 -0
  103. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/secure_traversal_unavailable.erb +10 -0
  104. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/workspace_root_changed.erb +10 -0
  105. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/directory_listing_limit.erb +10 -0
  106. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/entry_required.erb +10 -0
  107. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/file_read_limit.erb +10 -0
  108. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/invalid_utf8.erb +10 -0
  109. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_escape.erb +10 -0
  110. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_missing.erb +10 -0
  111. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/multiply_linked.erb +10 -0
  112. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_directory.erb +10 -0
  113. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_file.erb +10 -0
  114. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/null_byte.erb +10 -0
  115. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/outside_scope.erb +10 -0
  116. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/parent_escape.erb +10 -0
  117. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_missing.erb +10 -0
  118. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_traverses_symlink.erb +10 -0
  119. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/read_only.erb +10 -0
  120. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/replace_empty.erb +10 -0
  121. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/symlink_path.erb +10 -0
  122. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_multiple.erb +10 -0
  123. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_not_found.erb +10 -0
  124. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_limit.erb +10 -0
  125. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_parent_missing.erb +10 -0
  126. data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_target_symlink.erb +10 -0
  127. data/lib/little_ghost/prompts/little_ghost/sandbox/policy/feedback/feature_denied.erb +10 -0
  128. data/lib/little_ghost/prompts/little_ghost/sandbox/process/errors/program_supervisor_failed.erb +10 -0
  129. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/command_timed_out.erb +10 -0
  130. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/executable_required.erb +10 -0
  131. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_memory_exceeded.erb +10 -0
  132. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_output_exceeded.erb +10 -0
  133. data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_timed_out.erb +10 -0
  134. data/lib/little_ghost/prompts/little_ghost/skills/discovery/instructions.erb +18 -0
  135. data/lib/little_ghost/prompts/little_ghost/skills/feedback/unknown.erb +11 -0
  136. data/lib/little_ghost/prompts/little_ghost/skills/resources/notices/truncated.erb +10 -0
  137. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/content.erb +28 -0
  138. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/description.erb +10 -0
  139. data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/inputs/name/description.erb +10 -0
  140. data/lib/little_ghost/prompts/little_ghost/structured_output/persistence/format/redaction.erb +12 -0
  141. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/invalid_result.erb +11 -0
  142. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/schema_errors.erb +11 -0
  143. data/lib/little_ghost/prompts/little_ghost/structured_output/repair/request.erb +14 -0
  144. data/lib/little_ghost/prompts/little_ghost/structured_output/tools/result/description.erb +11 -0
  145. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/invalid_json.erb +11 -0
  146. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/missing_tool.erb +11 -0
  147. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/multiple_tools.erb +11 -0
  148. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_complex.erb +11 -0
  149. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_deep.erb +11 -0
  150. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_large.erb +11 -0
  151. data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/tool_not_exclusive.erb +11 -0
  152. data/lib/little_ghost/prompts/little_ghost/subagents/errors/create_failed.erb +10 -0
  153. data/lib/little_ghost/prompts/little_ghost/subagents/errors/previous_turn_failed.erb +10 -0
  154. data/lib/little_ghost/prompts/little_ghost/subagents/errors/restore_failed.erb +10 -0
  155. data/lib/little_ghost/prompts/little_ghost/subagents/errors/turn_failed.erb +10 -0
  156. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_ids.erb +10 -0
  157. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_path.erb +10 -0
  158. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/identity_capacity.erb +10 -0
  159. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/inactive.erb +10 -0
  160. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_chars_limit.erb +10 -0
  161. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_limit.erb +10 -0
  162. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_unsupported.erb +10 -0
  163. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_cursor.erb +10 -0
  164. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_mode.erb +10 -0
  165. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/list_limit.erb +10 -0
  166. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_limit.erb +10 -0
  167. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_type.erb +10 -0
  168. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/not_running.erb +10 -0
  169. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/queue_capacity.erb +10 -0
  170. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_invalid.erb +10 -0
  171. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_limit.erb +10 -0
  172. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/terminal_identity.erb +11 -0
  173. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/turn_capacity.erb +10 -0
  174. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_id.erb +10 -0
  175. data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_kind.erb +10 -0
  176. data/lib/little_ghost/prompts/little_ghost/subagents/notices/turn_cancelled.erb +10 -0
  177. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/description.erb +10 -0
  178. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/active_id/description.erb +10 -0
  179. data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/message/description.erb +10 -0
  180. data/lib/little_ghost/prompts/little_ghost/subagents/tools/list/description.erb +10 -0
  181. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/description.erb +10 -0
  182. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/id/description.erb +10 -0
  183. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/message/description.erb +10 -0
  184. data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/send_mode/description.erb +10 -0
  185. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/description.erb +10 -0
  186. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/kind/description.erb +11 -0
  187. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/spawn_mode/description.erb +10 -0
  188. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task/description.erb +10 -0
  189. data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task_name/description.erb +10 -0
  190. data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/description.erb +10 -0
  191. data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/inputs/ids/description.erb +10 -0
  192. data/lib/little_ghost/prompts/little_ghost/swarm/handoff/notice.erb +10 -0
  193. data/lib/little_ghost/prompts/little_ghost/swarm/handoff/request.erb +16 -0
  194. data/lib/little_ghost/prompts/little_ghost/swarm/tools/handoff/description.erb +10 -0
  195. data/lib/little_ghost/prompts/little_ghost/tools/built_in/list_files/description.erb +10 -0
  196. data/lib/little_ghost/prompts/little_ghost/tools/built_in/read_file/description.erb +10 -0
  197. data/lib/little_ghost/prompts/little_ghost/tools/built_in/replace_in_file/description.erb +10 -0
  198. data/lib/little_ghost/prompts/little_ghost/tools/built_in/shell/description.erb +10 -0
  199. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_file/description.erb +10 -0
  200. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/description.erb +10 -0
  201. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/single_progress.erb +10 -0
  202. data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/unique_ids.erb +10 -0
  203. data/lib/little_ghost/prompts/little_ghost/tools/errors/unexpected_failure.erb +10 -0
  204. data/lib/little_ghost/prompts/little_ghost/tools/errors/unserializable_result.erb +10 -0
  205. data/lib/little_ghost/prompts/little_ghost/tools/feedback/invalid_input.erb +10 -0
  206. data/lib/little_ghost/prompts/little_ghost/tools/feedback/unknown.erb +10 -0
  207. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/final_warning.erb +10 -0
  208. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/termination.erb +10 -0
  209. data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/warning.erb +10 -0
  210. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/additional_property.erb +10 -0
  211. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/enum.erb +11 -0
  212. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_format.erb +10 -0
  213. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_pattern.erb +10 -0
  214. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_items.erb +11 -0
  215. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_length.erb +11 -0
  216. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/maximum.erb +11 -0
  217. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_items.erb +11 -0
  218. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_length.erb +11 -0
  219. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/minimum.erb +11 -0
  220. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/required.erb +10 -0
  221. data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/type.erb +11 -0
  222. data/lib/little_ghost/prompts/little_ghost/workspace/feedback/path_changed.erb +10 -0
  223. data/lib/little_ghost/run.rb +23 -8
  224. data/lib/little_ghost/run_context.rb +21 -0
  225. data/lib/little_ghost/runtime/hooks/artifacts.rb +58 -22
  226. data/lib/little_ghost/runtime.rb +11 -6
  227. data/lib/little_ghost/sandbox/filesystem.rb +51 -33
  228. data/lib/little_ghost/sandbox/process_runner.rb +6 -2
  229. data/lib/little_ghost/sandbox/process_session.rb +8 -4
  230. data/lib/little_ghost/sandbox/scope.rb +4 -1
  231. data/lib/little_ghost/sandboxes/seatbelt.rb +3 -1
  232. data/lib/little_ghost/sandboxes/unrestricted.rb +10 -6
  233. data/lib/little_ghost/skills/catalog.rb +32 -37
  234. data/lib/little_ghost/stream_event.rb +5 -2
  235. data/lib/little_ghost/structured_output.rb +6 -6
  236. data/lib/little_ghost/subagents/control_tool.rb +58 -0
  237. data/lib/little_ghost/subagents/manager.rb +99 -74
  238. data/lib/little_ghost/support/output_truncation.rb +28 -4
  239. data/lib/little_ghost/swarm.rb +33 -54
  240. data/lib/little_ghost/tool.rb +42 -20
  241. data/lib/little_ghost/tool_registry.rb +2 -2
  242. data/lib/little_ghost/tools/filesystem.rb +12 -4
  243. data/lib/little_ghost/tools/shell.rb +5 -1
  244. data/lib/little_ghost/tools/write_todos.rb +9 -9
  245. data/lib/little_ghost/version.rb +1 -1
  246. data/lib/little_ghost/workflow.rb +232 -82
  247. data/lib/little_ghost/workspace.rb +2 -2
  248. data/lib/little_ghost.rb +5 -2
  249. metadata +187 -2
  250. /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: 50e597abbf025721200897c852c8ea72e244c0b5bb2f1b50aa58084e55d5d0df
4
+ data.tar.gz: be4d63e81003723c9a916a61359ae508a8d35a16a02b8c7e11f14dc5a3bcd12b
5
5
  SHA512:
6
- metadata.gz: de9e90761042a5cad938664be479e6d35d6b0423b213cde3ac05f0a033c4de7468ef96252073b1405a73510c53b650ea5d7c74be2cae9bd68922494a05cfb406
7
- data.tar.gz: 96ac0a88302659cad84e80c4491c23ab4bad211b0db8fe9a1bb370dbbcb0a2090d81cc3c922cc62d18ace4804a559efba5bbcb6ee44a6d52cec58ad5f45dfcc0
6
+ metadata.gz: f546cd196e43eb53f6e3ab812c362a9bfb5d81e5408b0608c14ba20ca865a77196c6a801ed3b8980f38e098b6e468555dc2520ee32d9ee72373b4307686b9768
7
+ data.tar.gz: 18294764bb5ada766b993993172cccef4e077bf7d2cb886e7f76f1a3bda7ce60a8c448547b056f55eadd2775761457f8a725d3ab895d268f9785cdc2eebac22b
data/README.md CHANGED
@@ -1,11 +1,16 @@
1
1
  # Build AI features that feel at home in Ruby
2
2
 
3
+ LittleGhost is a Ruby library for adding AI features to an existing application
4
+ or building a dedicated AI service. An **agent** combines a model with
5
+ instructions and Ruby operations it can call. Agents can work together in an
6
+ **assembly**, which your application calls like a single agent.
7
+
3
8
  > **Using a coding agent?** Start with
4
9
  > [`llms.txt`](https://littleghostai.org/llms.txt) for a concise map
5
10
  > of the guides and API. [`llms-full.txt`](https://littleghostai.org/llms-full.txt)
6
11
  > contains the complete documentation in one file.
7
12
 
8
- LittleGhost is a Ruby library for building AI features with agents and composable assemblies. With `OPENROUTER_API_KEY` set, start with one class, give it a prompt, and call it like the rest of your application code:
13
+ With the gem installed and `OPENROUTER_API_KEY` set, start with one class:
9
14
 
10
15
  ```ruby
11
16
  require "little_ghost"
@@ -100,14 +105,13 @@ end
100
105
  The schema checks the shape of the input. Your Ruby code still decides whether
101
106
  the operation is allowed. The result goes back to the model as context.
102
107
 
103
- An ordinary Tool runs in your Ruby process. When a Tool needs files or child
104
- processes, it can delegate that work through a Sandbox. Code mode goes one step
105
- further: a sandboxed interpreter can compose several Tools, while every Tool
106
- call still returns to your Ruby Tool for validation and permission checks.
108
+ An ordinary Tool runs in your Ruby process. For operations that need files or
109
+ child processes, see [Workspaces and Sandboxes](docs/guides/sandboxing.md).
107
110
 
108
111
  ## Grow without changing the caller
109
112
 
110
- An **agent** owns one model loop. An **assembly** is one or more agents working as a unit. You call either one the same way:
113
+ When a task needs several agents, choose how they work together. These example
114
+ classes use different coordination styles, but their callers all use `.ask`:
111
115
 
112
116
  ```ruby
113
117
  CustomerSupportAgent.ask(question)
@@ -118,12 +122,12 @@ SupportFlowGraph.ask(question)
118
122
 
119
123
  Choose the coordination style that matches who should control the next step:
120
124
 
121
- - A **subagent** lets a model delegate an addressable task.
125
+ - A **subagent** is a specialist an agent can ask for help.
122
126
  - A **workflow** uses ordinary Ruby for ordering and branching.
123
127
  - A **swarm** lets configured agents choose permitted handoffs.
124
128
  - A **graph** makes allowed routes explicit as nodes and edges.
125
129
 
126
- A Workflow or Graph can contain agents, other assemblies, or both. Named classes are the clearest place to begin. Builders are there when your application discovers the participants or routes at runtime.
130
+ A Workflow or Graph can contain agents, other assemblies, or both.
127
131
 
128
132
  ```text
129
133
  request ──> CustomerSupportAgent
@@ -137,7 +141,9 @@ request ──> SupportFlowGraph ──> TriageAgent ──edge──> ResponseA
137
141
 
138
142
  The result stays familiar too. Every call returns a `Run` with the response,
139
143
  outcome, usage, and any final error. A coordinated assembly also records which
140
- participants ran. Use `.stream_ask` to watch the work as it happens.
144
+ participants ran. Use `.stream_ask` to watch the work as it happens and choose
145
+ which participants to display. [Getting Started](docs/guides/getting_started.md)
146
+ shows how to stream an Agent's answer and read the completed result.
141
147
 
142
148
  LittleGhost is pre-1.0. Pin the gem version and review release notes before
143
149
  upgrading, because interfaces may change between releases.
@@ -148,7 +154,7 @@ upgrading, because interfaces may change between releases.
148
154
  - [Core Concepts](docs/guides/core_concepts.md) builds the mental model from Agent to Assembly.
149
155
  - [Models and Providers](docs/guides/models_and_providers.md) explains targets, shared roles, and per-request model selection.
150
156
  - [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.
157
+ - [Prompts as Views](docs/guides/prompt_views.md) explains Agent instructions and shared partials, including how to customize LittleGhost's bundled framework prompts.
152
158
  - [Tools](docs/guides/tools.md) explains how models call focused Ruby operations.
153
159
  - [MCP](docs/guides/mcp.md) connects agents to operations published through the Model Context Protocol.
154
160
  - [Structured Results and Content](docs/guides/structured_outputs_and_content.md) covers checked result shapes, images, and documents.
@@ -15,7 +15,12 @@ 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
19
+ child call. Read `.output` for its text or structured answer, or `.result` for
20
+ the complete `RunResult`. Return an invocation to select its answer as the
21
+ Workflow result. You can inspect that answer first, or return a value that Ruby
22
+ computes from intermediate answers. Every participating Agent publishes live
23
+ progress regardless of how you access or select results.
19
24
 
20
25
  ```ruby
21
26
  class ResponseWorkflow < LittleGhost::Workflow
@@ -41,25 +46,69 @@ Every participant passed to `invoke` can be an Agent or another Assembly. By def
41
46
 
42
47
  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
48
 
44
- The last child is special because its events become the Workflow's public stream. Return that `invoke` without consuming it:
49
+ When one child should supply the final answer, return its invocation:
45
50
 
46
51
  ```ruby
47
- # Wrong: this returns a String after consuming the final invocation.
48
52
  def perform
49
- invoke(CustomerSupportAgent).output
53
+ invoke CustomerSupportAgent
50
54
  end
55
+ ```
51
56
 
52
- # Right: this returns the lazy invocation itself.
53
- def perform
54
- invoke CustomerSupportAgent
57
+ To inspect an answer before selecting it, keep the invocation, read its output,
58
+ and return the same invocation after your checks. For example, given a
59
+ `ReviewAgent` whose structured result includes an `approved` boolean:
60
+
61
+ ```ruby
62
+ class ReviewedResponseWorkflow < LittleGhost::Workflow
63
+ private
64
+
65
+ def perform
66
+ candidate = invoke(CustomerSupportAgent)
67
+ review = invoke(ReviewAgent, input: candidate.output, history: [], context: {}).output
68
+
69
+ review.fetch("approved") ? candidate : invoke(EscalationAgent)
70
+ end
71
+ end
72
+ ```
73
+
74
+ Returning the candidate does not repeat the work or replay its text. The final
75
+ result preserves its conversation, state, and structured value. Usage and steps
76
+ include both the candidate and the review. Repeated `.result` or `.output` reads
77
+ reuse the completed result. An invocation must belong to the workflow returning
78
+ it and must complete successfully.
79
+
80
+ You can watch each Agent while the Workflow runs, including while an answer is
81
+ being reviewed. Reviewing an answer does not hide its progress. Choose which
82
+ participants your audience may see, as described in
83
+ [Watch every agent](#watch-every-agent-in-an-assembly).
84
+
85
+ Cancellation and deadlines apply before each child runs and before the Workflow
86
+ returns its answer. They also apply if a checkpoint callback cancels the run or
87
+ finishes after the deadline.
88
+
89
+ When Ruby should compute the caller-visible result, consume every child and return the computed value:
90
+
91
+ ```ruby
92
+ class EvidenceWorkflow < LittleGhost::Workflow
93
+ private
94
+
95
+ def perform
96
+ findings = parallel(
97
+ invoke(LedgerResearchAgent),
98
+ invoke(PolicyResearchAgent),
99
+ max_concurrency: 2
100
+ )
101
+
102
+ {"findings" => findings}
103
+ end
55
104
  end
56
105
  ```
57
106
 
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.
107
+ 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`. Computed values appear in the final result, not as synthetic text-delta events. 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
108
 
60
109
  ### Choose a branch in Ruby
61
110
 
62
- Each branch should end with its final unconsumed invocation:
111
+ Each branch should end with its final invocation or a directly computed value:
63
112
 
64
113
  ```ruby
65
114
  class RoutedResponseWorkflow < LittleGhost::Workflow
@@ -293,7 +342,9 @@ Retries start at zero. When `retries` is greater than zero, `retry_on` must list
293
342
 
294
343
  ## Watch every agent in an assembly
295
344
 
296
- Follow each participant while a composite assembly runs by handling its contextual `:agent_stream` events. These events arrive alongside the coherent public answer and assembly lifecycle events:
345
+ Follow each participant by handling source-tagged `:agent_stream` events. Every
346
+ Run uses this same progress channel, from one root Agent to nested assemblies
347
+ and subagents. Lifecycle events and the final result remain separate:
297
348
 
298
349
  ```ruby
299
350
  stream = SupportFlowGraph.stream_ask("Why was I charged twice?")
@@ -321,19 +372,31 @@ run.completed? # => true
321
372
 
322
373
  `source.agent_id` identifies the Agent class, `source.agent_path` distinguishes managed subagents, and `source.operation_id` groups one invocation. `source.assembly_path` lists the enclosing Workflow, Swarm, or Graph steps from the outside inward.
323
374
 
375
+ An Agent or Assembly invoked as a Tool within the Run adds a `:tool` step to that path. The
376
+ step identifies the invoking Agent and Tool name, and its descendants retain
377
+ the boundary. Tool-invoked Agents therefore remain distinct from the root
378
+ Agent even when their managed-subagent path is `/root`.
379
+
324
380
  The routed input and inner event are copied and frozen before they reach the
325
381
  observer, so changing an event can't affect the running assembly. Parallel
326
382
  participants can interleave. Events from each Agent retain their order, and
327
383
  LittleGhost never calls the stream block concurrently.
328
384
 
329
- The contextual wrapper arrives before the corresponding ordinary event. An assembly's final Agent therefore appears through both projections. Filter for `:agent_stream` when building an all-agent view, or handle ordinary events when rendering only the final answer. Pass `include_agent_events: false` when a composite assembly caller only wants the ordinary public stream. Standalone Agent streams keep their ordinary events by default and accept `include_agent_events: true` when source metadata is useful.
385
+ Agent progress appears only through `:agent_stream`, without duplicate raw
386
+ events. Returning an invocation selects its result; it does not replay that
387
+ Agent's progress. Handle the terminal Run event or read `run.result` for the
388
+ complete answer and aggregate usage. Pass `include_agent_events: false` to omit
389
+ all Agent progress without changing execution, lifecycle events, or results.
330
390
 
331
- The AG-UI adapter ignores contextual wrappers. Translate them explicitly if an AG-UI client should receive participant activity.
391
+ The [AG-UI adapter](integrations.md) displays root Agent progress by default.
392
+ Supply its `source_filter:` callable when a client should see selected assembly
393
+ participants.
332
394
 
333
- > **Safety note:** A composite stream can include inputs, reasoning, Tool
334
- > arguments and results, errors, and output from every participant. Check that
335
- > the destination may see the complete Run, or filter the events before sending
336
- > or storing them.
395
+ > **Safety note:** Every Run stream can include inputs, reasoning, Tool arguments
396
+ > and results, errors, and private output from nested participants. The example
397
+ > above observes the complete Run. Before sending or storing events for a
398
+ > narrower audience, select allowed sources and filter their fields. Result
399
+ > selection does not authorize disclosure of a participant's progress.
337
400
 
338
401
  ## Inspect what the assembly did
339
402
 
@@ -350,7 +413,8 @@ policy = trajectory.find { |step| step.participant == "policy" }
350
413
  trajectory.concurrent?(ledger.id, policy.id)
351
414
  ```
352
415
 
353
- Step outputs and buffered events have size limits. Use your application's
416
+ The trajectory retains step outputs, not a transcript of streaming events.
417
+ Step outputs and final results have size limits. Use your application's
354
418
  instrumentation when you need deeper diagnostics.
355
419
 
356
420
  ## Compose assemblies inside assemblies
@@ -1,6 +1,9 @@
1
1
  # Core Concepts
2
2
 
3
- Define an Agent in a Ruby class, then call it with `.ask`.
3
+ Start with an Agent: a Ruby class that gives a model instructions and operations
4
+ it can call. This guide builds on the help center example in
5
+ [Getting Started](getting_started.md), including its `HelpCenterLookupTool` and
6
+ provider setup.
4
7
 
5
8
  ```ruby
6
9
  class CustomerSupportAgent < LittleGhost::Agent
@@ -9,16 +12,20 @@ class CustomerSupportAgent < LittleGhost::Agent
9
12
  tools HelpCenterLookupTool
10
13
  end
11
14
 
12
- run = CustomerSupportAgent.ask("Where is my order?")
15
+ run = CustomerSupportAgent.ask("What is the refund policy?")
13
16
  run.response
14
17
  ```
15
18
 
16
19
  From there, add only what the work needs. Give the agent a tool. Let it ask a specialist for help. Or coordinate several agents while the rest of your application keeps making the same call.
17
20
 
18
- ## An Agent owns one model loop
21
+ ## An Agent carries a request through to an answer
19
22
 
20
23
  An **Agent** defines one model-driven behavior. It chooses the model, supplies the instructions and tools, and carries one request through to an answer.
21
24
 
25
+ The model can answer immediately or ask to call a Tool. LittleGhost runs the
26
+ Tool and sends its result back to the model, which can continue working. That
27
+ back-and-forth is the **model loop**.
28
+
22
29
  The class holds the behavior you want to reuse. Each call brings its own input, history, context, settings, and attachments. Request data never needs to live on the class.
23
30
 
24
31
  ```text
@@ -60,71 +67,32 @@ inside the Tool using identity and account information from your application.
60
67
  [Tools](tools.md) follows that path from model input to application code,
61
68
  including run-scoped bindings, concurrency, retries, and sandbox delegation.
62
69
 
63
- ## A Run owns one top-level execution
70
+ ## A Run records one request
64
71
 
65
72
  Every `.ask` or `.stream_ask` creates a **Run**. Think of it as the record of one trip through LittleGhost. It opens what the request needs, records how the work ended, and closes the resources it owns.
66
73
 
67
74
  ```ruby
68
- run = CustomerSupportAgent.ask("Where is order 481?")
75
+ run = CustomerSupportAgent.ask("What is the refund policy?")
69
76
 
70
77
  run.completed? # => true
71
78
  run.response
72
- # One possible response: Order 481 is out for delivery.
73
- run.usage # => normalized token usage
79
+ # One possible response: Refunds are available within 30 days.
80
+ run.usage # Token counts reported by the model provider.
74
81
  run.result # => the complete LittleGhost::RunResult
75
82
  ```
76
83
 
77
84
  The Agent defines reusable behavior; the Run records what happened this time.
78
85
 
79
- ### Follow one request
80
-
81
- One Run owns the trip from request to result:
82
-
83
- ```text
84
- Run
85
- ├── Invocation: caller input, history, and application context
86
- ├── RunContext: mutable working state for this execution
87
- └── Agent and Tools ──> RunResult
88
- ```
89
-
90
- An **Invocation** is the request in LittleGhost's standard shape. Its `context`
91
- contains current request values supplied by your application. A Tool can read
92
- those values through `run.invocation.context` when it checks permission.
93
-
94
- A **Session** stores conversation state between Runs when persistence is
95
- configured. The **RunContext** carries mutable working state in `context.state`
96
- during one Run. LittleGhost loads saved Session state before adding the current
97
- Invocation context. Recheck saved values before using them for permission
98
- decisions.
99
-
100
- A Tool's **Binding** gives the Tool access to objects created for this run,
101
- including the Agent, Run, Workspace, and Sandbox. These objects are separate
102
- from arguments chosen by the model. [Tools](tools.md) explains the binding;
103
- [Workspaces and Sandboxes](sandboxing.md) explains delegated files and child
104
- processes.
105
-
106
- The final **RunResult** keeps the complete assembly result. Its `text` is the final text answer. Its `output` returns structured data when the Agent declared a result schema, and text otherwise. The top-level `Run#response` is always the caller-facing text.
107
-
108
- ### See how a call ended
109
-
110
- Top-level calls normally return a Run, even when execution fails. The terminal event carries the same outcome when you stream:
86
+ The final **RunResult**, available through `run.result`, holds the answer and
87
+ details such as token usage. Its `output` returns text unless you configured
88
+ the Agent to return checked data, such as a hash of named fields. See
89
+ [Structured Results and Content](structured_outputs_and_content.md) for that
90
+ alternative. Use `run.response` when you want the text answer.
111
91
 
112
- | What happened | Run outcome | Terminal event | What Ruby does |
113
- | --- | --- | --- | --- |
114
- | The assembly completed | `completed` | `:run_stop` | Returns the Run |
115
- | Model, provider, or assembly execution failed | `failed` | `:run_error` | Returns the Run; inspect `run.error` |
116
- | The deadline stopped work | `partial` | `:run_partial` | Returns the Run with any response produced so far |
117
- | Cancellation stopped work | `cancelled` | `:run_cancel` | Returns the Run without a response |
118
- | Tool input or a `ToolError` failed | The model may recover | No terminal event by itself | Gives a safe error result back to the model |
119
- | Input, configuration, or resources failed before a Run could start | No Run exists | None | Raises the exception |
120
-
121
- Unexpected Tool exception messages are hidden from the model. The original
122
- exception remains available to application callbacks and diagnostics.
123
-
124
- Failures while closing resources, delivering events, or reporting
125
- instrumentation sit outside the normal result path. They raise a Ruby exception
126
- because LittleGhost can no longer promise that it delivered a clean ending.
127
- [Running in Production](production.md) covers supervision and shutdown.
92
+ When a Tool needs to know who is asking, pass information from your application
93
+ with the request. [Tools](tools.md) explains how it reaches the Tool. When a
94
+ conversation should continue across requests, a **Session** saves its history
95
+ and working state; [Running in Production](production.md) covers that setup.
128
96
 
129
97
  ## An Assembly can look like one Agent
130
98
 
@@ -157,6 +125,10 @@ The coordination types differ mainly in who decides what happens next:
157
125
 
158
126
  A **subagent** is a specialist that a parent Agent can call for help. The parent model chooses when to delegate, reads the result, and then continues its own answer.
159
127
 
128
+ The examples below omit the specialist Agent definitions. Each is an Agent
129
+ class like `CustomerSupportAgent`, with instructions and tools suited to its
130
+ task. [Compose Agents](assemblies.md) expands on these coordination patterns.
131
+
160
132
  ```ruby
161
133
  class CustomerSupportAgent < LittleGhost::Agent
162
134
  model "openrouter:openai/gpt-5.6-luna"
@@ -166,15 +138,13 @@ end
166
138
 
167
139
  Use a subagent when delegation is part of one model's decision-making. Use a Workflow when application code must guarantee that a step happens.
168
140
 
169
- When an Agent also uses code mode, subagent controls stay in the Agent's
170
- conversation. Code-mode programs can compose ordinary Tools, while spawning,
171
- messaging, and checking on subagents remain decisions for the parent model.
172
-
173
141
  ### Workflows make Ruby the coordinator
174
142
 
175
143
  A **Workflow** coordinates work with ordinary Ruby. Its `perform` method can call an Agent or another Assembly, read a result, choose a branch, or run independent steps together.
176
144
 
177
- `invoke` prepares a lazy child call. Reading `.output` runs an intermediate child. Return the final `invoke` itself, without reading its output, so that answer can stream to the caller.
145
+ `invoke` prepares a participant's call without running it yet. Read `.output`
146
+ to run it and use its answer in Ruby. Return the final `invoke` call to use
147
+ that participant's answer as the Workflow's result.
178
148
 
179
149
  ```ruby
180
150
  class ResponseWorkflow < LittleGhost::Workflow
@@ -192,6 +162,13 @@ class ResponseWorkflow < LittleGhost::Workflow
192
162
  end
193
163
  ```
194
164
 
165
+ In this example, research finishes before the support Agent begins. `input.text`
166
+ is the original question; the Workflow adds the research to it.
167
+
168
+ You can also inspect an answer before choosing it as the final result, without
169
+ running the participant again. [Compose Agents](assemblies.md) shows how, along
170
+ with ways to display each participant's progress.
171
+
195
172
  Workflow children receive the caller's history and application context by default. Pass `history: []`, `context: {}`, or redacted values when a participant should receive less.
196
173
 
197
174
  ### Swarms let agents hand work to one another
@@ -216,7 +193,8 @@ Agent should use it as context rather than proof that an action is permitted.
216
193
 
217
194
  ### Graphs make routes visible
218
195
 
219
- A **Graph** connects named Assembly nodes with declared edges. Nodes can contain Agents, Workflows, Swarms, or other Graphs.
196
+ A **Graph** lays out the steps and routes through a task. Each named **node**
197
+ runs an Agent or another Assembly. An **edge** says which node can run next.
220
198
 
221
199
  ```ruby
222
200
  class SupportFlowGraph < LittleGhost::Graph
@@ -236,6 +214,10 @@ class SupportFlowGraph < LittleGhost::Graph
236
214
  end
237
215
  ```
238
216
 
217
+ Here, `TriageAgent` is expected to answer `billing` for billing questions.
218
+ That answer selects the conditional billing route. Otherwise, the unconditional
219
+ general route is the fallback; it does not run alongside the billing route.
220
+
239
221
  Graph nodes receive the original task and results from the nodes immediately
240
222
  before them. They do not receive caller history or application context unless
241
223
  their declarations opt in. [Compose Agents](assemblies.md) explains parallel
@@ -256,6 +238,28 @@ run.result.trajectory.transitions
256
238
  This record shows which participants ran. [Compose Agents](assemblies.md)
257
239
  explains builders, detailed routing records, and live events from nested Agents.
258
240
 
241
+ ## Handle the outcome
242
+
243
+ An Agent or coordinated Assembly normally returns a Run even when the work
244
+ fails. Check its outcome before using the answer:
245
+
246
+ | What happened | Run outcome | What to inspect |
247
+ | --- | --- | --- |
248
+ | Work completed | `completed` | `run.response` or `run.result` |
249
+ | Model, provider, or assembly execution failed | `failed` | `run.error` |
250
+ | The deadline stopped work | `partial` | Any response produced so far |
251
+ | Cancellation stopped work | `cancelled` | No response is returned |
252
+
253
+ A Tool error need not end the Run: LittleGhost can give the model a safe error
254
+ result so it can try again. Unexpected exception messages stay in application
255
+ diagnostics rather than going to the model.
256
+
257
+ Some failures raise Ruby exceptions instead of returning a Run, including
258
+ invalid setup before work starts and failures while closing resources or
259
+ delivering events. [Running in Production](production.md) covers error handling
260
+ and shutdown; [Run](rdoc-ref:LittleGhost::Run) lists the streaming events for
261
+ each outcome.
262
+
259
263
  The pieces now fit together: Agents define behavior. Tools connect them to Ruby. Runs record one execution. Assemblies let the system grow without changing the caller.
260
264
 
261
265
  Continue with [Models and Providers](models_and_providers.md) to choose model
@@ -17,7 +17,8 @@ $ export OPENROUTER_API_KEY="..."
17
17
 
18
18
  Use your application's secret manager outside a local shell, and never commit provider credentials.
19
19
 
20
- This guide uses OpenRouter to keep setup to one key. Prefer another hosted
20
+ This guide uses OpenRouter, a service that sends requests to your chosen AI
21
+ model. Set `OPENROUTER_API_KEY` to a key from your OpenRouter account. Prefer another hosted
21
22
  provider or a local model server? Start with [Provider Support](providers.md).
22
23
 
23
24
  ## See your first answer
@@ -41,10 +42,11 @@ else
41
42
  end
42
43
  ```
43
44
 
44
- Run the file and you have a working AI feature:
45
+ `model` selects the service and model to call. `system_prompt` supplies the
46
+ instructions the model follows on each request. Run the file with your bundle:
45
47
 
46
48
  ```sh
47
- $ ruby customer_support_agent.rb
49
+ $ bundle exec ruby customer_support_agent.rb
48
50
  ```
49
51
 
50
52
  `CustomerSupportAgent.ask` creates a `LittleGhost::Run` for this request. When the work finishes, the Run holds the outcome and response.
@@ -55,7 +57,9 @@ The selected external provider may receive system instructions, caller input, co
55
57
 
56
58
  ## Connect the agent to your application
57
59
 
58
- The first agent can answer general questions. A **tool** gives it a focused operation backed by your Ruby code:
60
+ The first agent can answer general questions. A **tool** gives it a focused
61
+ operation backed by your Ruby code. Add this class after the `require` line,
62
+ before `CustomerSupportAgent`:
59
63
 
60
64
  ```ruby
61
65
  class HelpCenterLookupTool < LittleGhost::Tool
@@ -80,7 +84,9 @@ class HelpCenterLookupTool < LittleGhost::Tool
80
84
  end
81
85
  ```
82
86
 
83
- Make the tool available to the agent and tell the model when to use it:
87
+ `input_schema` describes the arguments the model may supply. Here, `topic` must
88
+ be one of the help center's keys. Replace the Agent definition and the call at
89
+ the end of the file with these:
84
90
 
85
91
  ```ruby
86
92
  class CustomerSupportAgent < LittleGhost::Agent
@@ -106,84 +112,48 @@ LittleGhost checks the model's arguments before it calls
106
112
  `HelpCenterLookupTool#call`. The Tool's result then becomes context for the
107
113
  model.
108
114
 
109
- ### Use application context for private data
110
-
111
- The schema checks shape, not permission. When a Tool reads private data or
112
- changes something, use identity and account information established by your
113
- application rather than asking the model to supply it.
114
-
115
- While an Agent is working, LittleGhost binds each Tool instance to the current
116
- Run. The Tool can read request values through its `run` accessor:
117
-
118
- ```ruby
119
- class OrderStatusTool < LittleGhost::Tool
120
- ORDER_STATUSES = {
121
- ["user-7", "account-2", "481"] => "out for delivery"
122
- }.freeze
123
-
124
- description "Look up an order that belongs to the current customer."
125
- input_schema(
126
- type: "object",
127
- properties: {order_number: {type: "string"}},
128
- required: ["order_number"],
129
- additionalProperties: false
130
- )
131
-
132
- def call(input)
133
- lookup = [
134
- run.invocation.actor_id,
135
- run.invocation.context.fetch("account_id"),
136
- input.fetch("order_number")
137
- ]
138
-
139
- ORDER_STATUSES.fetch(lookup) do
140
- raise LittleGhost::ToolError, "Order not found"
141
- end
142
- end
143
- end
144
-
145
- class CustomerSupportAgent < LittleGhost::Agent
146
- tools HelpCenterLookupTool, OrderStatusTool
147
- end
148
-
149
- run = CustomerSupportAgent.ask(
150
- "Where is order 481?",
151
- actor_id: "user-7",
152
- context: {account_id: "account-2"}
153
- )
154
- ```
155
-
156
- Here, `order_number` came from the model. The application supplied `actor_id`
157
- and `account_id` after authenticating the caller. LittleGhost places those
158
- request values on `run.invocation`; context keys become strings.
159
-
160
- > **Safety note:** Treat model-selected Tool arguments like any other external
161
- > input. Check permission using the current user and account before returning
162
- > private data or performing a write.
163
-
164
- That is enough to authorize the first Tool safely. [Core Concepts](core_concepts.md) names the request and working-state objects behind `run`, and [Running in Production](production.md) explains what changes when you add saved conversations.
115
+ > **Safety note:** The schema checks arguments, not permission. This example
116
+ > reads a public help center. Before a Tool returns private data or changes
117
+ > anything, check permission using the user and account identified by your
118
+ > application—not values supplied by the model. [Tools](tools.md) shows how to
119
+ > pass that information to a Tool.
165
120
 
166
121
  ## Stream the same agent
167
122
 
168
- Use `.stream_ask` when a console, HTTP response, or user interface should receive progress as it happens:
123
+ Use `.stream_ask` when a console, HTTP response, or user interface should receive
124
+ the answer as it is written. Replace the `.ask` call with the following code.
125
+
126
+ Each `:agent_stream` event contains the Agent's progress and a `source` that
127
+ identifies which Agent produced it. The source check below selects the Agent
128
+ you called directly: `/root` with no enclosing assembly steps. `:text_delta`
129
+ contains the next piece of its answer.
169
130
 
170
131
  ```ruby
171
132
  stream = CustomerSupportAgent.stream_ask("Can I get a refund?")
172
133
 
173
134
  run = stream.each do |event|
174
135
  case event.type
175
- when :text_delta
176
- print event.data.fetch(:text)
136
+ when :agent_stream
137
+ source = event.data.fetch(:source)
138
+ next unless source.agent_path == "/root" && source.assembly_path.empty?
139
+
140
+ progress = event.data.fetch(:event)
141
+ print progress.data.fetch(:text) if progress.type == :text_delta
177
142
  when :run_error
178
143
  warn event.data.fetch(:message)
179
144
  end
180
145
  end
181
146
 
182
- puts "\n#{run.response}" if run.completed?
147
+ run.response # The completed answer, separate from live progress.
183
148
  warn run.error.class.name if run.failed?
184
149
  ```
185
150
 
186
- The stream yields `LittleGhost::StreamEvent` values. Text, tool activity, usage, and completion all look the same across providers. When enumeration finishes, `.each` returns the same `LittleGhost::Run` that now holds the final outcome and response.
151
+ When enumeration finishes, `.each` returns the Run with the final outcome and
152
+ complete response. You can display progress and still read the finished answer.
153
+
154
+ If you later add other Agents, their progress arrives in the same stream. Keep
155
+ the source check when your audience should see only this Agent's text. See
156
+ [Compose Agents](assemblies.md) to display several participants.
187
157
 
188
158
  ## Give the code a home
189
159
 
@@ -197,7 +167,7 @@ app/
197
167
  │ └── response_workflow.rb
198
168
  ├── prompts/
199
169
  │ └── customer_support/
200
- │ └── system.erb
170
+ │ └── system_prompt.erb
201
171
  └── tools/
202
172
  └── help_center_lookup_tool.rb
203
173
  ```
@@ -223,8 +193,8 @@ The source repository also contains a complete
223
193
  [single-file Agent](https://github.com/littleghostai/little_ghost/tree/main/examples/basic_agent)
224
194
  and a
225
195
  [coding harness](https://github.com/littleghostai/little_ghost/tree/main/examples/coding_harness)
226
- that demonstrates Agents, prompt views, Tools, a Graph, a Workspace, and a
227
- native Sandbox.
196
+ that shows a larger application with several agents and tools for working
197
+ with files.
228
198
 
229
199
  You now have the smallest useful LittleGhost application: one Agent, one Tool, and one familiar Ruby call.
230
200