little_ghost 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +34 -6
- data/docs/guides/assemblies.md +21 -9
- data/docs/guides/code_mode.md +2 -2
- data/docs/guides/getting_started.md +27 -2
- data/docs/guides/integrations.md +20 -161
- data/docs/guides/mcp.md +288 -0
- data/docs/guides/models_and_providers.md +21 -54
- data/docs/guides/production.md +14 -5
- data/docs/guides/prompt_views.md +234 -27
- data/docs/guides/providers.md +275 -0
- data/docs/guides/sandboxing.md +42 -7
- data/docs/guides/skills.md +1 -3
- data/docs/guides/tools.md +2 -0
- data/exe/little_ghost +12 -0
- data/lib/little_ghost/agent/context_management.rb +2 -13
- data/lib/little_ghost/agent/delegation.rb +7 -4
- data/lib/little_ghost/agent/skills.rb +8 -6
- data/lib/little_ghost/agent/tool_loop.rb +4 -4
- data/lib/little_ghost/agent.rb +230 -70
- data/lib/little_ghost/agent_builder.rb +16 -4
- data/lib/little_ghost/artifacts/workspace_store.rb +16 -14
- data/lib/little_ghost/assembly.rb +48 -7
- data/lib/little_ghost/assembly_builder.rb +3 -3
- data/lib/little_ghost/cli.rb +429 -0
- data/lib/little_ghost/code_mode/broker.rb +11 -3
- data/lib/little_ghost/code_mode/javascript/client.rb +58 -11
- data/lib/little_ghost/code_mode/javascript/host.rb +37 -13
- data/lib/little_ghost/code_mode/javascript/session.rb +21 -9
- data/lib/little_ghost/code_mode/javascript_engine.rb +21 -42
- data/lib/little_ghost/code_mode/ruby/host.rb +8 -4
- data/lib/little_ghost/code_mode/ruby/session.rb +41 -14
- data/lib/little_ghost/code_mode/ruby_engine.rb +6 -36
- data/lib/little_ghost/code_mode/runtime.rb +57 -24
- data/lib/little_ghost/console.rb +17 -0
- data/lib/little_ghost/errors.rb +20 -2
- data/lib/little_ghost/framework_prompts.rb +376 -0
- data/lib/little_ghost/generators/application_generator.rb +179 -0
- data/lib/little_ghost/generators/templates/application/Gemfile.tt +3 -0
- data/lib/little_ghost/generators/templates/application/README.md.tt +29 -0
- data/lib/little_ghost/generators/templates/application/app/agents/application_agent.rb.tt +5 -0
- data/lib/little_ghost/generators/templates/application/app/prompts/application/system_prompt.erb.tt +1 -0
- data/lib/little_ghost/generators/templates/application/bin/application.tt +10 -0
- data/lib/little_ghost/generators/templates/application/config/little_ghost.rb.tt +5 -0
- data/lib/little_ghost/generators/templates/application/gitignore.tt +2 -0
- data/lib/little_ghost/generators/templates/application/keep.tt +0 -0
- data/lib/little_ghost/graph.rb +24 -5
- data/lib/little_ghost/mcp/client.rb +307 -610
- data/lib/little_ghost/mcp/toolset.rb +133 -95
- data/lib/little_ghost/mcp.rb +21 -3
- data/lib/little_ghost/model_operations.rb +103 -36
- data/lib/little_ghost/model_resolver.rb +2 -0
- data/lib/little_ghost/models/catalog/models_dev_source.rb +4 -1
- data/lib/little_ghost/prompt_resolver.rb +61 -27
- data/lib/little_ghost/prompts/little_ghost/agent/interjections/instructions.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/agent/system/default.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_large.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/batch_too_many.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/batch/feedback/item_too_large.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/errors/preparation_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/errors/resolver_invalid.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/errors/value_unstorable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/reference.erb +13 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/references.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/format/workspace_references.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/full_result.erb +14 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/presentation/notices/storage_failed.erb +13 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/destination_unsafe.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/secure_unavailable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_changed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unavailable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/errors/workspace_unsafe.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/count_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/destination_exists.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/too_large.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/artifacts/storage/feedback/total_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/assembly/feedback/transition_only.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/assembly/tools/fallback/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/artifacts/format/references.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/errors/brokered_close.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/errors/cleanup_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/errors/execution_failed.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/arguments_object.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/closed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/control_active.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/limit_exceeded.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/not_active.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_active.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/program_timed_out.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unavailable_tool.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/feedback/unknown_program.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/active_programs_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/cleanup_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/execution_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/invalid_request.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/memory_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/pending_calls_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/source_size_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/errors/tools_array.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/javascript/instructions.erb +35 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/feedback/parallel_callables.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/ruby/instructions.erb +40 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/exec/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/stop/inputs/max_output_tokens/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/code_mode/tools/wait/inputs/max_output_tokens/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/context_management/summary/request.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/context_management/summary/system_instruction.erb +16 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/format/failed_input.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/format/previous_input.erb +13 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/headings/original_task.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/graph/context/headings/previous_inputs.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/mcp/errors/request_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/mcp/errors/transformation_unserializable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/mcp/tools/fallback/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/output/truncation/marker.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/mount_changed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/secure_traversal_unavailable.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/errors/workspace_root_changed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/directory_listing_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/entry_required.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/file_read_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/invalid_utf8.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_escape.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/mount_missing.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/multiply_linked.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_directory.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/not_file.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/null_byte.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/outside_scope.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/parent_escape.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_missing.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/path_traverses_symlink.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/read_only.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/replace_empty.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/symlink_path.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_multiple.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/text_not_found.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_parent_missing.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/filesystem/feedback/write_target_symlink.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/policy/feedback/feature_denied.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/errors/program_supervisor_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/command_timed_out.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/executable_required.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_memory_exceeded.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_output_exceeded.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/sandbox/process/feedback/program_timed_out.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/skills/discovery/instructions.erb +18 -0
- data/lib/little_ghost/prompts/little_ghost/skills/feedback/unknown.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/skills/resources/notices/truncated.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/content.erb +28 -0
- data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/skills/tools/activate/inputs/name/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/persistence/format/redaction.erb +12 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/invalid_result.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/repair/feedback/schema_errors.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/repair/request.erb +14 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/tools/result/description.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/invalid_json.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/missing_tool.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/multiple_tools.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_complex.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_deep.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/too_large.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/structured_output/validation/feedback/tool_not_exclusive.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/create_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/previous_turn_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/restore_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/errors/turn_failed.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_ids.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/duplicate_path.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/identity_capacity.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/inactive.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_chars_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/interjection_unsupported.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_cursor.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/invalid_mode.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/list_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/message_type.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/not_running.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/queue_capacity.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_invalid.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/task_name_limit.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/terminal_identity.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/turn_capacity.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_id.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/feedback/unknown_kind.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/notices/turn_cancelled.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/active_id/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/interject/inputs/message/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/list/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/id/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/message/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/send/inputs/send_mode/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/kind/description.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/spawn_mode/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/spawn/inputs/task_name/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/subagents/tools/wait/inputs/ids/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/swarm/handoff/notice.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/swarm/handoff/request.erb +16 -0
- data/lib/little_ghost/prompts/little_ghost/swarm/tools/handoff/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/list_files/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/read_file/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/replace_in_file/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/shell/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_file/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/description.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/single_progress.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/built_in/write_todos/feedback/unique_ids.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/errors/unexpected_failure.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/errors/unserializable_result.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/feedback/invalid_input.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/feedback/unknown.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/final_warning.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/termination.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/loop/notices/warning.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/additional_property.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/enum.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_format.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/invalid_pattern.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_items.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/max_length.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/maximum.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_items.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/min_length.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/minimum.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/required.erb +10 -0
- data/lib/little_ghost/prompts/little_ghost/tools/validation/schema/type.erb +11 -0
- data/lib/little_ghost/prompts/little_ghost/workspace/feedback/path_changed.erb +10 -0
- data/lib/little_ghost/provider_registry.rb +1 -0
- data/lib/little_ghost/providers/lm_studio/catalog_source.rb +201 -0
- data/lib/little_ghost/providers/lm_studio.rb +52 -0
- data/lib/little_ghost/providers/openai.rb +3 -75
- data/lib/little_ghost/providers/openai_compatible.rb +78 -1
- data/lib/little_ghost/runtime/hooks/artifacts.rb +58 -22
- data/lib/little_ghost/runtime.rb +8 -3
- data/lib/little_ghost/sandbox/environment_policy.rb +29 -4
- data/lib/little_ghost/sandbox/filesystem.rb +51 -33
- data/lib/little_ghost/sandbox/policy.rb +28 -6
- data/lib/little_ghost/sandbox/process_runner.rb +6 -2
- data/lib/little_ghost/sandbox/process_session.rb +8 -4
- data/lib/little_ghost/sandbox/scope.rb +4 -1
- data/lib/little_ghost/sandboxes/seatbelt.rb +3 -1
- data/lib/little_ghost/sandboxes/unrestricted.rb +17 -9
- data/lib/little_ghost/skills/catalog.rb +41 -36
- data/lib/little_ghost/structured_output.rb +6 -6
- data/lib/little_ghost/subagents/control_tool.rb +58 -0
- data/lib/little_ghost/subagents/manager.rb +99 -74
- data/lib/little_ghost/support/output_truncation.rb +28 -4
- data/lib/little_ghost/swarm.rb +20 -7
- data/lib/little_ghost/tool.rb +47 -20
- data/lib/little_ghost/tool_registry.rb +2 -2
- data/lib/little_ghost/tools/filesystem.rb +12 -4
- data/lib/little_ghost/tools/shell.rb +5 -1
- data/lib/little_ghost/tools/write_todos.rb +9 -9
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +199 -27
- data/lib/little_ghost/workspace.rb +2 -2
- data/lib/little_ghost.rb +6 -2
- metadata +222 -7
- data/lib/little_ghost/mcp/types.rb +0 -216
data/docs/guides/skills.md
CHANGED
|
@@ -107,9 +107,7 @@ LittleGhost.configure do |config|
|
|
|
107
107
|
|
|
108
108
|
config.sandbox = {
|
|
109
109
|
provider: :native,
|
|
110
|
-
files: {root: :read_write, skills: :read_only}
|
|
111
|
-
root_filesystem: :isolated,
|
|
112
|
-
network: :none
|
|
110
|
+
files: {root: :read_write, skills: :read_only}
|
|
113
111
|
}
|
|
114
112
|
end
|
|
115
113
|
```
|
data/docs/guides/tools.md
CHANGED
|
@@ -79,6 +79,8 @@ end
|
|
|
79
79
|
Prefer `available_if` on an individual Tool when only that operation is
|
|
80
80
|
conditional. Use a provider when the collection itself owns discovery,
|
|
81
81
|
construction, or shared setup for an application or remote service.
|
|
82
|
+
The [MCP guide](mcp.md) shows how a Model Context Protocol server provides a
|
|
83
|
+
Toolset through the official Ruby client.
|
|
82
84
|
|
|
83
85
|
LittleGhost does not enforce class names. A useful application convention is
|
|
84
86
|
to end one model-callable operation with `Tool` and a provider of multiple
|
data/exe/little_ghost
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
lib = File.expand_path("../lib", __dir__)
|
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
|
+
|
|
7
|
+
require "little_ghost/cli"
|
|
8
|
+
|
|
9
|
+
source_path = File.expand_path("..", __dir__)
|
|
10
|
+
source_path = nil unless File.file?(File.join(source_path, "little_ghost.gemspec"))
|
|
11
|
+
|
|
12
|
+
exit LittleGhost::CLI.new(ARGV, source_path:).run
|
|
@@ -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:
|
|
185
|
+
Message.new(role: :system, content: render_framework_prompt("context_management/summary/system_instruction")),
|
|
197
186
|
*messages,
|
|
198
|
-
Message.new(role: :user, content: "
|
|
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.
|
|
21
|
-
#
|
|
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
|
-
#
|
|
95
|
-
#
|
|
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 =
|
|
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] = "
|
|
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 = (
|
|
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],
|
data/lib/little_ghost/agent.rb
CHANGED
|
@@ -39,18 +39,17 @@ module LittleGhost
|
|
|
39
39
|
# an explicitly isolated setup.
|
|
40
40
|
#
|
|
41
41
|
# Agent declarations are inherited. Define a short prompt inline, or place a
|
|
42
|
-
# growing prompt in <tt>app/prompts/customer_support/
|
|
42
|
+
# growing prompt in <tt>app/prompts/customer_support/system_prompt.erb</tt> for
|
|
43
43
|
# +CustomerSupportAgent+. The {Prompts as Views guide}[rdoc-ref:docs/guides/prompt_views.md]
|
|
44
|
-
# explains conventional lookup,
|
|
45
|
-
# as skills, context management, loop
|
|
46
|
-
# until their DSL is used.
|
|
44
|
+
# explains conventional lookup, values prepared by the Agent, locals, and
|
|
45
|
+
# partials. Optional features such as skills, context management, loop
|
|
46
|
+
# detection, and delegation stay inactive until their DSL is used.
|
|
47
47
|
#
|
|
48
48
|
# Models may return text or locally validated structured data. LittleGhost
|
|
49
49
|
# hides unexpected Tool exception messages from the model. See
|
|
50
50
|
# Run[rdoc-ref:LittleGhost::Run] for outcomes, cancellation, and cleanup, and
|
|
51
51
|
# Assembly[rdoc-ref:LittleGhost::Assembly] for the advanced run-scoped form.
|
|
52
52
|
class Agent < Assembly
|
|
53
|
-
DEFAULT_SYSTEM_PROMPT = "You are a helpful agent." # :nodoc:
|
|
54
53
|
DEFAULT_MAX_TOOL_RESULT_TOKENS = 10_000 # :nodoc:
|
|
55
54
|
MAX_STRUCTURED_RESULT_BYTES = 1_000_000 # :nodoc:
|
|
56
55
|
MAX_STRUCTURED_RESULT_DEPTH = 64 # :nodoc:
|
|
@@ -84,7 +83,6 @@ module LittleGhost
|
|
|
84
83
|
class_attribute :system_prompt_builder_value
|
|
85
84
|
class_attribute :tool_declarations_value, default: []
|
|
86
85
|
class_attribute :code_mode_configuration_value
|
|
87
|
-
class_attribute :prompt_local_values, default: {}
|
|
88
86
|
class_attribute :callback_values, default: Support::Callbacks.new(*CALLBACKS)
|
|
89
87
|
|
|
90
88
|
class << self
|
|
@@ -284,16 +282,6 @@ module LittleGhost
|
|
|
284
282
|
|
|
285
283
|
def code_mode_configuration = code_mode_configuration_value # :nodoc:
|
|
286
284
|
|
|
287
|
-
# Adds a named value or resolver to every prompt rendered for the agent.
|
|
288
|
-
def prompt_local(name, *values, &resolver)
|
|
289
|
-
raise ArgumentError, "Provide a prompt local value or block" if values.empty? && !resolver
|
|
290
|
-
raise ArgumentError, "Provide a prompt local value or block, not both" unless values.empty? || !resolver
|
|
291
|
-
|
|
292
|
-
self.prompt_local_values = prompt_local_values.merge(name.to_sym => resolver || values.fetch(0))
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
def prompt_local_resolvers = prompt_local_values # :nodoc:
|
|
296
|
-
|
|
297
285
|
def callbacks = callback_values # :nodoc:
|
|
298
286
|
|
|
299
287
|
CALLBACKS.each do |name|
|
|
@@ -539,7 +527,8 @@ module LittleGhost
|
|
|
539
527
|
# The first form is the application-facing entrypoint. It may be reused for
|
|
540
528
|
# independent concurrent calls and creates a fresh Run for each one. The
|
|
541
529
|
# second form is run-scoped; Runtime builders supply its dependencies and it
|
|
542
|
-
# must not outlive
|
|
530
|
+
# must not outlive its owning Run. Calls on a run-scoped instance must be
|
|
531
|
+
# sequential. An overlapping call raises AgentBusyError when its stream begins.
|
|
543
532
|
def initialize(
|
|
544
533
|
model: nil,
|
|
545
534
|
runtime: nil,
|
|
@@ -560,6 +549,8 @@ module LittleGhost
|
|
|
560
549
|
standalone = model.nil? && run.nil?
|
|
561
550
|
super(run:, runtime:, workspace:, sandbox:, standalone:)
|
|
562
551
|
if standalone
|
|
552
|
+
@framework_prompts = FrameworkPrompts.new(paths: template_paths)
|
|
553
|
+
@framework_prompt_invocation_paths = [].freeze
|
|
563
554
|
@owns_resources = true
|
|
564
555
|
@closed = false
|
|
565
556
|
@close_mutex = Mutex.new
|
|
@@ -570,6 +561,12 @@ module LittleGhost
|
|
|
570
561
|
|
|
571
562
|
@model = model
|
|
572
563
|
@runtime = runtime || run&.runtime
|
|
564
|
+
@framework_prompts = if @runtime
|
|
565
|
+
FrameworkPrompts.for_runtime(@runtime)
|
|
566
|
+
else
|
|
567
|
+
FrameworkPrompts.new(paths: template_paths)
|
|
568
|
+
end
|
|
569
|
+
@framework_prompt_invocation_paths = [].freeze
|
|
573
570
|
@run = run
|
|
574
571
|
@workspace = workspace || run&.workspace
|
|
575
572
|
@sandbox = sandbox || run&.sandbox
|
|
@@ -606,6 +603,8 @@ module LittleGhost
|
|
|
606
603
|
@assembly_transitions = {}
|
|
607
604
|
@assembly_tool_batch_sizes = {}
|
|
608
605
|
@assembly_transition = nil
|
|
606
|
+
@_little_ghost_invocation_mutex = Mutex.new
|
|
607
|
+
@_little_ghost_invocation_active = false
|
|
609
608
|
raise ArgumentError, "max_turns must be at least 1" if @max_turns < 1
|
|
610
609
|
raise ArgumentError, "max_tool_calls must be at least 1" if @max_tool_calls < 1
|
|
611
610
|
raise ArgumentError, "max_tool_result_tokens must be at least 1" if @max_tool_result_tokens < 1
|
|
@@ -613,6 +612,7 @@ module LittleGhost
|
|
|
613
612
|
resolved_runtime.runtime_hooks.find { |hook| hook.is_a?(Runtime::Hooks::Artifacts) }
|
|
614
613
|
end
|
|
615
614
|
apply_cancellation_decision!(run_callbacks(:after_initialize, self))
|
|
615
|
+
@_little_ghost_prompt_assign_baseline = prompt_application_state.freeze
|
|
616
616
|
rescue
|
|
617
617
|
@tool_registry&.close
|
|
618
618
|
@code_mode_runtime&.close
|
|
@@ -643,7 +643,7 @@ module LittleGhost
|
|
|
643
643
|
def request_assembly_transition(value, context:) # :nodoc:
|
|
644
644
|
@assembly_transitions_mutex.synchronize do
|
|
645
645
|
unless @assembly_tool_batch_sizes[context] == 1
|
|
646
|
-
raise ToolError, "
|
|
646
|
+
raise ToolError, render_framework_prompt("assembly/feedback/transition_only")
|
|
647
647
|
end
|
|
648
648
|
if @assembly_transitions.key?(context)
|
|
649
649
|
raise ProtocolError, "Multiple assembly transitions were requested in one agent turn"
|
|
@@ -658,7 +658,7 @@ module LittleGhost
|
|
|
658
658
|
|
|
659
659
|
# Adds an interjection and returns the model's immediate result details.
|
|
660
660
|
#
|
|
661
|
-
#
|
|
661
|
+
# +target_operation_id+ may identify the active model operation.
|
|
662
662
|
# Messages may contain only text, image, or document content. The returned
|
|
663
663
|
# result value exposes +text+, +tool_calls?+, +interjection_ids+, and
|
|
664
664
|
# +batch_key+; tool calls may continue after this result. Depend on these
|
|
@@ -729,11 +729,12 @@ module LittleGhost
|
|
|
729
729
|
# Streams one invocation as StreamEvent objects.
|
|
730
730
|
#
|
|
731
731
|
# Agents built inside a run accept history, JSON-like context, cancellation,
|
|
732
|
-
# deadlines, settings, and
|
|
732
|
+
# deadlines, settings, and invocation-specific prompt roots. An Agent
|
|
733
733
|
# instance may be streamed only by its owning Run. Every template path must
|
|
734
|
-
# be an application-created TrustedPath
|
|
735
|
-
#
|
|
736
|
-
#
|
|
734
|
+
# be an application-created TrustedPath, never a value selected by a request
|
|
735
|
+
# or model. A run-scoped Agent accepts one active invocation; enumerating an
|
|
736
|
+
# overlapping stream raises AgentBusyError. The instance may be invoked
|
|
737
|
+
# again after the first stream completes or fails.
|
|
737
738
|
def stream(
|
|
738
739
|
input = nil,
|
|
739
740
|
history: nil,
|
|
@@ -778,18 +779,21 @@ module LittleGhost
|
|
|
778
779
|
end
|
|
779
780
|
settings = @model_settings.merge(settings)
|
|
780
781
|
Enumerator.new do |events|
|
|
782
|
+
invocation_acquired = false
|
|
781
783
|
interjections = AgentInterjections.new
|
|
782
|
-
run_context = RunContext.new(
|
|
783
|
-
state: context,
|
|
784
|
-
cancellation_token: cancellation_token,
|
|
785
|
-
deadline: deadline,
|
|
786
|
-
metadata: {agent_id: self.class.agent_id},
|
|
787
|
-
checkpoint:,
|
|
788
|
-
conversation_id:,
|
|
789
|
-
interjection_metadata:,
|
|
790
|
-
interjection_ids:
|
|
791
|
-
)
|
|
792
784
|
begin
|
|
785
|
+
begin_invocation!
|
|
786
|
+
invocation_acquired = true
|
|
787
|
+
run_context = RunContext.new(
|
|
788
|
+
state: context,
|
|
789
|
+
cancellation_token: cancellation_token,
|
|
790
|
+
deadline: deadline,
|
|
791
|
+
metadata: {agent_id: self.class.agent_id},
|
|
792
|
+
checkpoint:,
|
|
793
|
+
conversation_id:,
|
|
794
|
+
interjection_metadata:,
|
|
795
|
+
interjection_ids:
|
|
796
|
+
)
|
|
793
797
|
with_invocation(run_context) do
|
|
794
798
|
execute(
|
|
795
799
|
input,
|
|
@@ -808,23 +812,55 @@ module LittleGhost
|
|
|
808
812
|
interjections.close(error)
|
|
809
813
|
raise
|
|
810
814
|
ensure
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
815
|
+
cleanup_error = nil
|
|
816
|
+
begin
|
|
817
|
+
@code_mode_runtime&.close(context: run_context) if run_context
|
|
818
|
+
rescue => error
|
|
819
|
+
cleanup_error = error
|
|
820
|
+
end
|
|
821
|
+
begin
|
|
822
|
+
interjections.close(AgentInterjectionError.new("Agent finished before the interjection was delivered"))
|
|
823
|
+
rescue => error
|
|
824
|
+
cleanup_error ||= error
|
|
825
|
+
ensure
|
|
826
|
+
begin
|
|
827
|
+
unregister_interjections(interjections)
|
|
828
|
+
ensure
|
|
829
|
+
end_invocation! if invocation_acquired
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
raise cleanup_error if cleanup_error
|
|
814
833
|
end
|
|
815
834
|
end
|
|
816
835
|
end
|
|
817
836
|
|
|
818
|
-
#
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
837
|
+
# :call-seq:
|
|
838
|
+
# system_prompt() -> Object
|
|
839
|
+
#
|
|
840
|
+
# Prepares application values for a file-backed system prompt. Override this
|
|
841
|
+
# method, assign instance variables, and read them from the ERB view:
|
|
842
|
+
#
|
|
843
|
+
# def system_prompt
|
|
844
|
+
# @company_name = "Northstar"
|
|
845
|
+
# end
|
|
846
|
+
#
|
|
847
|
+
# In <tt>app/prompts/customer_support/system_prompt.erb</tt>:
|
|
848
|
+
#
|
|
849
|
+
# You help customers of <%= @company_name %>.
|
|
850
|
+
#
|
|
851
|
+
# LittleGhost calls the action once before rendering the top-level prompt
|
|
852
|
+
# view. Partials share its prepared values without calling it again. Its
|
|
853
|
+
# return value is ignored. If it raises, the invocation fails with that
|
|
854
|
+
# exception. An inline prompt declared on the Agent class takes precedence
|
|
855
|
+
# and does not call this method.
|
|
856
|
+
#
|
|
857
|
+
# Each render starts with the application instance variables captured after
|
|
858
|
+
# the Agent's +after_initialize+ callbacks finish. LittleGhost copies the
|
|
859
|
+
# prepared values into the view, then restores the Agent's live instance
|
|
860
|
+
# variables, even when this method raises. Objects are not duplicated, so
|
|
861
|
+
# treat mutable values as read-only or assign a copy. Subclasses may call
|
|
862
|
+
# +super+ before preparing additional values.
|
|
863
|
+
def system_prompt
|
|
828
864
|
end
|
|
829
865
|
|
|
830
866
|
# The Tool registry available during this Agent run.
|
|
@@ -887,6 +923,73 @@ module LittleGhost
|
|
|
887
923
|
|
|
888
924
|
private
|
|
889
925
|
|
|
926
|
+
PROTECTED_PROMPT_INSTANCE_VARIABLES = %i[
|
|
927
|
+
@active_assemblies @active_interjections @agent_path @agent_stream_path
|
|
928
|
+
@artifact_lifecycle @assembly_closed @assembly_definition @assembly_mutex
|
|
929
|
+
@assembly_tool_batch_sizes @assembly_transition @assembly_transitions
|
|
930
|
+
@assembly_transitions_mutex @close_mutex @closed @code_mode_declaration
|
|
931
|
+
@code_mode_runtime @delegation_activity @exclusive_tools_mutex @executor
|
|
932
|
+
@interjections_mutex @max_tool_calls @max_tool_result_tokens @max_turns
|
|
933
|
+
@model @model_settings @owns_resources @run @runtime @sandbox @standalone
|
|
934
|
+
@framework_prompts @framework_prompt_invocation_paths
|
|
935
|
+
@structured_output_strategy @task_runner @template_resolver @tool_loop_except
|
|
936
|
+
@tool_loop_fallback @tool_loop_mutex @tool_loop_runs @tool_loop_terminate_at
|
|
937
|
+
@tool_loop_warning_at @tool_registry @workspace
|
|
938
|
+
@_little_ghost_invocation_active @_little_ghost_invocation_mutex
|
|
939
|
+
].freeze # :nodoc:
|
|
940
|
+
|
|
941
|
+
def begin_invocation!
|
|
942
|
+
@_little_ghost_invocation_mutex.synchronize do
|
|
943
|
+
if @_little_ghost_invocation_active
|
|
944
|
+
raise AgentBusyError,
|
|
945
|
+
"Agent is already handling an invocation; use the Agent class entrypoint or a fresh Agent for independent calls"
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
@_little_ghost_invocation_active = true
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
def end_invocation!
|
|
953
|
+
@_little_ghost_invocation_mutex.synchronize { @_little_ghost_invocation_active = false }
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
def prompt_view_assigns
|
|
957
|
+
names = prompt_application_instance_variables
|
|
958
|
+
names.to_h do |name|
|
|
959
|
+
[name.to_s.delete_prefix("@").to_sym, instance_variable_get(name)]
|
|
960
|
+
end
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
def prepare_prompt_view_assigns
|
|
964
|
+
saved = prompt_application_state
|
|
965
|
+
replace_prompt_application_state(@_little_ghost_prompt_assign_baseline)
|
|
966
|
+
begin
|
|
967
|
+
system_prompt
|
|
968
|
+
prompt_view_assigns
|
|
969
|
+
ensure
|
|
970
|
+
replace_prompt_application_state(saved)
|
|
971
|
+
end
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
def prompt_application_instance_variables
|
|
975
|
+
instance_variables.reject do |name|
|
|
976
|
+
PROTECTED_PROMPT_INSTANCE_VARIABLES.include?(name) || name.to_s.start_with?("@_little_ghost_")
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
def prompt_application_state
|
|
981
|
+
prompt_application_instance_variables.to_h do |name|
|
|
982
|
+
[name, instance_variable_get(name)]
|
|
983
|
+
end
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
def replace_prompt_application_state(state)
|
|
987
|
+
prompt_application_instance_variables.each { |name| remove_instance_variable(name) }
|
|
988
|
+
state.each do |name, value|
|
|
989
|
+
instance_variable_set(name, value)
|
|
990
|
+
end
|
|
991
|
+
end
|
|
992
|
+
|
|
890
993
|
def entrypoint_payload(input, options)
|
|
891
994
|
return options if input.nil?
|
|
892
995
|
return input.merge(options) if input.is_a?(Hash)
|
|
@@ -911,12 +1014,7 @@ module LittleGhost
|
|
|
911
1014
|
Message.new(
|
|
912
1015
|
role: :user,
|
|
913
1016
|
content: [
|
|
914
|
-
Content::Text.new(text:
|
|
915
|
-
Agent interjection:
|
|
916
|
-
|
|
917
|
-
Respond briefly in ordinary text before any tool calls, then continue the current task unless this
|
|
918
|
-
interjection asks you to finish.
|
|
919
|
-
MESSAGE
|
|
1017
|
+
Content::Text.new(text: render_framework_prompt("agent/interjections/instructions")),
|
|
920
1018
|
*source.content
|
|
921
1019
|
],
|
|
922
1020
|
metadata: source.metadata.merge(interjection.metadata).merge(
|
|
@@ -974,6 +1072,8 @@ module LittleGhost
|
|
|
974
1072
|
interjections:,
|
|
975
1073
|
interject_ready:
|
|
976
1074
|
)
|
|
1075
|
+
previous_framework_prompt_invocation_paths = @framework_prompt_invocation_paths
|
|
1076
|
+
@framework_prompt_invocation_paths = template_paths.freeze
|
|
977
1077
|
started_at = monotonic_time
|
|
978
1078
|
operation_id = SecureRandom.uuid
|
|
979
1079
|
events = agent_stream_events(
|
|
@@ -1322,6 +1422,8 @@ module LittleGhost
|
|
|
1322
1422
|
metadata = model.details.to_h.merge(model_role: model.role)
|
|
1323
1423
|
emit(events, :invocation_error, error:, usage: context.usage, metadata:)
|
|
1324
1424
|
raise
|
|
1425
|
+
ensure
|
|
1426
|
+
@framework_prompt_invocation_paths = previous_framework_prompt_invocation_paths
|
|
1325
1427
|
end
|
|
1326
1428
|
|
|
1327
1429
|
def invoke_model(
|
|
@@ -1343,7 +1445,14 @@ module LittleGhost
|
|
|
1343
1445
|
StructuredOutput.validate_tool_collision!(strategy, ordinary_tools) if strategy
|
|
1344
1446
|
request = ModelRequest.new(
|
|
1345
1447
|
messages: messages,
|
|
1346
|
-
tools: model_tools(
|
|
1448
|
+
tools: model_tools(
|
|
1449
|
+
strategy ? strategy.tools(
|
|
1450
|
+
ordinary_tools,
|
|
1451
|
+
description: render_framework_prompt("structured_output/tools/result/description")
|
|
1452
|
+
) : ordinary_tools,
|
|
1453
|
+
context:,
|
|
1454
|
+
turn:
|
|
1455
|
+
),
|
|
1347
1456
|
settings: settings,
|
|
1348
1457
|
output_schema: strategy&.output_schema,
|
|
1349
1458
|
tool_choice: strategy&.tool_choice(repair: structured_result_repair_due),
|
|
@@ -1573,7 +1682,11 @@ module LittleGhost
|
|
|
1573
1682
|
}
|
|
1574
1683
|
)
|
|
1575
1684
|
if tool.is_a?(ToolError)
|
|
1576
|
-
result = build_tool_result(
|
|
1685
|
+
result = build_tool_result(
|
|
1686
|
+
tool_use_id: tool_use.id,
|
|
1687
|
+
content: render_framework_prompt("tools/feedback/unknown", name: tool_use.name),
|
|
1688
|
+
status: :error
|
|
1689
|
+
)
|
|
1577
1690
|
finish_instrumentation(
|
|
1578
1691
|
tool_handle,
|
|
1579
1692
|
operation_id:,
|
|
@@ -1659,6 +1772,7 @@ module LittleGhost
|
|
|
1659
1772
|
tool_result,
|
|
1660
1773
|
tool_use:,
|
|
1661
1774
|
run: @run,
|
|
1775
|
+
agent: self,
|
|
1662
1776
|
workspace:,
|
|
1663
1777
|
context:
|
|
1664
1778
|
)
|
|
@@ -1686,7 +1800,12 @@ module LittleGhost
|
|
|
1686
1800
|
)
|
|
1687
1801
|
ExecutedTool.new(result:, execution_result: tool_result)
|
|
1688
1802
|
rescue ToolError => error
|
|
1689
|
-
|
|
1803
|
+
content = if error.framework_prompt
|
|
1804
|
+
render_framework_prompt(error.framework_prompt.key, **error.framework_prompt.locals)
|
|
1805
|
+
else
|
|
1806
|
+
error.message
|
|
1807
|
+
end
|
|
1808
|
+
result = build_tool_result(tool_use_id: tool_use.id, content:, status: :error)
|
|
1690
1809
|
finish_instrumentation(
|
|
1691
1810
|
tool_handle,
|
|
1692
1811
|
operation_id:,
|
|
@@ -1746,7 +1865,7 @@ module LittleGhost
|
|
|
1746
1865
|
content = if references.empty?
|
|
1747
1866
|
result.content
|
|
1748
1867
|
else
|
|
1749
|
-
|
|
1868
|
+
[result.content, render_framework_prompt("artifacts/presentation/format/references", references:)].join("\n\n")
|
|
1750
1869
|
end
|
|
1751
1870
|
|
|
1752
1871
|
Tool::ExecutionResult.new(
|
|
@@ -1763,8 +1882,12 @@ module LittleGhost
|
|
|
1763
1882
|
artifacts.filter_map do |artifact|
|
|
1764
1883
|
next unless artifact.reference.is_a?(String) && artifact.bytes
|
|
1765
1884
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1885
|
+
render_framework_prompt(
|
|
1886
|
+
"artifacts/presentation/format/reference",
|
|
1887
|
+
reference: artifact.reference,
|
|
1888
|
+
media_type: artifact.media_type,
|
|
1889
|
+
bytes: artifact.bytes
|
|
1890
|
+
)
|
|
1768
1891
|
end
|
|
1769
1892
|
end
|
|
1770
1893
|
|
|
@@ -1852,7 +1975,10 @@ module LittleGhost
|
|
|
1852
1975
|
configuration = self.class.result_schema
|
|
1853
1976
|
schema_name = configuration.fetch(:name)
|
|
1854
1977
|
validate_structured_result_limits!(value)
|
|
1855
|
-
errors = Tool::SchemaValidator.new(
|
|
1978
|
+
errors = Tool::SchemaValidator.new(
|
|
1979
|
+
configuration.fetch(:schema),
|
|
1980
|
+
prompt_renderer: method(:render_framework_prompt)
|
|
1981
|
+
).validate(value)
|
|
1856
1982
|
return "Structured result does not match its schema: #{errors.join("; ")}" unless errors.empty?
|
|
1857
1983
|
|
|
1858
1984
|
context.submit_structured_result(
|
|
@@ -1873,7 +1999,7 @@ module LittleGhost
|
|
|
1873
1999
|
tool_uses.map do |tool_use|
|
|
1874
2000
|
build_tool_result(
|
|
1875
2001
|
tool_use_id: tool_use.id,
|
|
1876
|
-
content: "
|
|
2002
|
+
content: render_framework_prompt("structured_output/repair/feedback/invalid_result"),
|
|
1877
2003
|
status: :error
|
|
1878
2004
|
)
|
|
1879
2005
|
end
|
|
@@ -1910,7 +2036,10 @@ module LittleGhost
|
|
|
1910
2036
|
def redact_structured_result_message(message)
|
|
1911
2037
|
Message.new(
|
|
1912
2038
|
role: message.role,
|
|
1913
|
-
content:
|
|
2039
|
+
content: render_framework_prompt(
|
|
2040
|
+
"structured_output/persistence/format/redaction",
|
|
2041
|
+
schema_name: self.class.result_schema.fetch(:name)
|
|
2042
|
+
),
|
|
1914
2043
|
metadata: message.metadata
|
|
1915
2044
|
)
|
|
1916
2045
|
end
|
|
@@ -1995,14 +2124,15 @@ module LittleGhost
|
|
|
1995
2124
|
end
|
|
1996
2125
|
|
|
1997
2126
|
def structured_result_repair_message
|
|
1998
|
-
requirement = if @structured_output_strategy&.tool?
|
|
1999
|
-
"Call #{@structured_output_strategy.schema_name} exactly once as your only tool call."
|
|
2000
|
-
else
|
|
2001
|
-
"Your final response must be JSON matching the configured output schema."
|
|
2002
|
-
end
|
|
2003
2127
|
Message.new(
|
|
2004
2128
|
role: :user,
|
|
2005
|
-
content:
|
|
2129
|
+
content: render_framework_prompt(
|
|
2130
|
+
"structured_output/repair/request",
|
|
2131
|
+
tool: @structured_output_strategy&.tool?,
|
|
2132
|
+
schema_name: @structured_output_strategy&.schema_name,
|
|
2133
|
+
repairs_remaining: 1,
|
|
2134
|
+
feedback: render_framework_prompt("structured_output/repair/feedback/invalid_result")
|
|
2135
|
+
)
|
|
2006
2136
|
)
|
|
2007
2137
|
end
|
|
2008
2138
|
|
|
@@ -2110,14 +2240,17 @@ module LittleGhost
|
|
|
2110
2240
|
prompt = prompt.call(locals) if prompt.respond_to?(:call)
|
|
2111
2241
|
return append_code_mode_instructions(prompt) if prompt
|
|
2112
2242
|
template = self.class.system_template
|
|
2113
|
-
|
|
2243
|
+
if instance_of?(Agent) && run && !template
|
|
2244
|
+
return append_code_mode_instructions(render_framework_prompt("agent/system/default"))
|
|
2245
|
+
end
|
|
2114
2246
|
|
|
2115
|
-
template ||= "#{self.class.logical_path}/
|
|
2247
|
+
template ||= "#{self.class.logical_path}/system_prompt" if run
|
|
2116
2248
|
return nil unless template
|
|
2117
2249
|
|
|
2118
2250
|
append_code_mode_instructions(@template_resolver.render(
|
|
2119
2251
|
template,
|
|
2120
2252
|
locals: locals,
|
|
2253
|
+
assigns: prepare_prompt_view_assigns,
|
|
2121
2254
|
invocation_paths: invocation_paths
|
|
2122
2255
|
))
|
|
2123
2256
|
end
|
|
@@ -2146,6 +2279,27 @@ module LittleGhost
|
|
|
2146
2279
|
LittleGhost::PromptResolver.new(paths:)
|
|
2147
2280
|
end
|
|
2148
2281
|
|
|
2282
|
+
public
|
|
2283
|
+
|
|
2284
|
+
def render_framework_prompt(key, **locals) # :nodoc:
|
|
2285
|
+
@framework_prompts.render(
|
|
2286
|
+
key,
|
|
2287
|
+
locals:,
|
|
2288
|
+
invocation_paths: @framework_prompt_invocation_paths,
|
|
2289
|
+
agent_path: self.class.logical_path
|
|
2290
|
+
)
|
|
2291
|
+
end
|
|
2292
|
+
|
|
2293
|
+
def framework_prompt_scope # :nodoc:
|
|
2294
|
+
{
|
|
2295
|
+
framework_prompts: @framework_prompts,
|
|
2296
|
+
invocation_paths: @framework_prompt_invocation_paths,
|
|
2297
|
+
agent_path: self.class.logical_path
|
|
2298
|
+
}.freeze
|
|
2299
|
+
end
|
|
2300
|
+
|
|
2301
|
+
private
|
|
2302
|
+
|
|
2149
2303
|
def apply_cancellation_decision!(decision)
|
|
2150
2304
|
raise CancelledError, decision.reason if decision.cancel?
|
|
2151
2305
|
end
|
|
@@ -2305,7 +2459,13 @@ module LittleGhost
|
|
|
2305
2459
|
end
|
|
2306
2460
|
|
|
2307
2461
|
def truncated_tool_result(content)
|
|
2308
|
-
Support::OutputTruncation.truncate_middle_with_token_budget(
|
|
2462
|
+
Support::OutputTruncation.truncate_middle_with_token_budget(
|
|
2463
|
+
content,
|
|
2464
|
+
@max_tool_result_tokens,
|
|
2465
|
+
framework_prompts: @framework_prompts,
|
|
2466
|
+
invocation_paths: @framework_prompt_invocation_paths,
|
|
2467
|
+
agent_path: self.class.logical_path
|
|
2468
|
+
).first
|
|
2309
2469
|
end
|
|
2310
2470
|
|
|
2311
2471
|
def monotonic_time
|