anima-core 1.0.2 → 1.1.1

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.reek.yml +51 -0
  4. data/README.md +63 -29
  5. data/anima-core.gemspec +4 -1
  6. data/app/channels/session_channel.rb +30 -11
  7. data/app/decorators/tool_call_decorator.rb +32 -3
  8. data/app/decorators/tool_decorator.rb +57 -0
  9. data/app/decorators/tool_response_decorator.rb +12 -4
  10. data/app/decorators/web_get_tool_decorator.rb +102 -0
  11. data/app/jobs/agent_request_job.rb +93 -23
  12. data/app/jobs/mneme_job.rb +51 -0
  13. data/app/jobs/passive_recall_job.rb +29 -0
  14. data/app/models/concerns/event/broadcasting.rb +4 -0
  15. data/app/models/event.rb +10 -0
  16. data/app/models/goal.rb +27 -0
  17. data/app/models/goal_pinned_event.rb +11 -0
  18. data/app/models/pinned_event.rb +41 -0
  19. data/app/models/session.rb +402 -6
  20. data/app/models/snapshot.rb +76 -0
  21. data/bin/jobs +5 -0
  22. data/config/initializers/event_subscribers.rb +12 -3
  23. data/config/initializers/fts5_schema_dump.rb +21 -0
  24. data/config/queue.yml +0 -1
  25. data/db/migrate/20260321080000_create_mneme_schema.rb +32 -0
  26. data/db/migrate/20260321120000_create_pinned_events.rb +27 -0
  27. data/db/migrate/20260321140000_create_events_fts_index.rb +77 -0
  28. data/db/migrate/20260321140100_add_recalled_event_ids_to_sessions.rb +10 -0
  29. data/lib/agent_loop.rb +63 -20
  30. data/lib/analytical_brain/runner.rb +158 -65
  31. data/lib/analytical_brain/tools/assign_nickname.rb +76 -0
  32. data/lib/analytical_brain/tools/finish_goal.rb +6 -1
  33. data/lib/anima/cli.rb +32 -9
  34. data/lib/anima/installer.rb +11 -24
  35. data/lib/anima/settings.rb +59 -0
  36. data/lib/anima/spinner.rb +75 -0
  37. data/lib/anima/version.rb +1 -1
  38. data/lib/environment_probe.rb +4 -4
  39. data/lib/events/bounce_back.rb +37 -0
  40. data/lib/events/subscribers/persister.rb +19 -0
  41. data/lib/events/subscribers/subagent_message_router.rb +102 -0
  42. data/lib/events/subscribers/transient_broadcaster.rb +36 -0
  43. data/lib/events/tool_call.rb +5 -3
  44. data/lib/llm/client.rb +19 -9
  45. data/lib/mneme/compressed_viewport.rb +200 -0
  46. data/lib/mneme/l2_runner.rb +138 -0
  47. data/lib/mneme/passive_recall.rb +69 -0
  48. data/lib/mneme/runner.rb +254 -0
  49. data/lib/mneme/search.rb +150 -0
  50. data/lib/mneme/tools/attach_events_to_goals.rb +107 -0
  51. data/lib/mneme/tools/everything_ok.rb +24 -0
  52. data/lib/mneme/tools/save_snapshot.rb +68 -0
  53. data/lib/mneme.rb +29 -0
  54. data/lib/providers/anthropic.rb +57 -13
  55. data/lib/shell_session.rb +194 -63
  56. data/lib/tasks/fts5.rake +6 -0
  57. data/lib/tools/base.rb +2 -1
  58. data/lib/tools/bash.rb +4 -2
  59. data/lib/tools/registry.rb +22 -3
  60. data/lib/tools/remember.rb +179 -0
  61. data/lib/tools/request_feature.rb +3 -1
  62. data/lib/tools/spawn_specialist.rb +21 -9
  63. data/lib/tools/spawn_subagent.rb +22 -11
  64. data/lib/tools/subagent_prompts.rb +20 -3
  65. data/lib/tools/web_get.rb +21 -10
  66. data/lib/tui/app.rb +222 -125
  67. data/lib/tui/decorators/base_decorator.rb +165 -0
  68. data/lib/tui/decorators/bash_decorator.rb +20 -0
  69. data/lib/tui/decorators/edit_decorator.rb +19 -0
  70. data/lib/tui/decorators/read_decorator.rb +24 -0
  71. data/lib/tui/decorators/think_decorator.rb +36 -0
  72. data/lib/tui/decorators/web_get_decorator.rb +19 -0
  73. data/lib/tui/decorators/write_decorator.rb +19 -0
  74. data/lib/tui/flash.rb +139 -0
  75. data/lib/tui/formatting.rb +28 -0
  76. data/lib/tui/height_map.rb +93 -0
  77. data/lib/tui/message_store.rb +97 -8
  78. data/lib/tui/performance_logger.rb +90 -0
  79. data/lib/tui/screens/chat.rb +358 -133
  80. data/templates/config.toml +47 -0
  81. data/templates/soul.md +1 -1
  82. metadata +83 -4
  83. data/CHANGELOG.md +0 -80
  84. data/Gemfile +0 -17
  85. data/lib/tools/return_result.rb +0 -81
@@ -39,6 +39,10 @@ mcp_response = 60
39
39
  # Web fetch request timeout.
40
40
  web_request = 10
41
41
 
42
+ # Per-tool-call timeout. The agent can override per call via the timeout parameter.
43
+ # Also used as the default deadline for orphan detection in heal_orphaned_tool_calls!.
44
+ tool = 180
45
+
42
46
  # ─── Shell ──────────────────────────────────────────────────────
43
47
 
44
48
  [shell]
@@ -94,6 +98,9 @@ soul = "{{ANIMA_HOME}}/soul.md"
94
98
 
95
99
  [session]
96
100
 
101
+ # View mode for new sessions: "basic", "verbose", or "debug".
102
+ default_view_mode = "basic"
103
+
97
104
  # Regenerate session name every N messages.
98
105
  name_generation_interval = 30
99
106
 
@@ -114,3 +121,43 @@ blocking_on_agent_message = false
114
121
 
115
122
  # Number of recent events to include in the analytical brain's context window.
116
123
  event_window = 20
124
+
125
+ # ─── Mneme (Memory Department) ──────────────────────────────────
126
+
127
+ [mneme]
128
+
129
+ # Maximum tokens per Mneme LLM response.
130
+ max_tokens = 2048
131
+
132
+ # Fraction of the main viewport token budget allocated to Mneme's viewport.
133
+ # Mneme sees roughly this fraction of the total context when triggered.
134
+ viewport_fraction = 0.33
135
+
136
+ # Fraction of the main viewport token budget reserved for Level 1 snapshots
137
+ # (hourly summaries). Snapshots appear above the sliding window.
138
+ l1_budget_fraction = 0.15
139
+
140
+ # Fraction of the main viewport token budget reserved for Level 2 snapshots
141
+ # (daily/weekly meta-summaries). Displayed above L1 snapshots.
142
+ l2_budget_fraction = 0.05
143
+
144
+ # Number of uncovered Level 1 snapshots that triggers Level 2 compression.
145
+ l2_snapshot_threshold = 5
146
+
147
+ # Fraction of the main viewport token budget reserved for pinned events.
148
+ # Pinned events appear between snapshots and the sliding window in the Goals section.
149
+ pinned_budget_fraction = 0.05
150
+
151
+ # ─── Recall (Associative Memory) ─────────────────────────────────
152
+
153
+ [recall]
154
+
155
+ # Maximum search results returned per FTS5 query.
156
+ max_results = 5
157
+
158
+ # Fraction of the main viewport token budget reserved for recalled memories.
159
+ # Recalled memories appear between snapshots and the sliding window.
160
+ budget_fraction = 0.05
161
+
162
+ # Maximum tokens per individual recall snippet.
163
+ max_snippet_tokens = 512
data/templates/soul.md CHANGED
@@ -17,7 +17,7 @@ config, credentials, skills, workflows, agents.
17
17
  Find your source code README:
18
18
 
19
19
  ```bash
20
- cat $(bundle show anima-core)/README.md
20
+ cat $(gem contents anima-core | grep README.md)
21
21
  ```
22
22
 
23
23
  ## What to do first
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anima-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yevhenii Hurin
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: certifi
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: draper
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +135,20 @@ dependencies:
121
135
  - - "~>"
122
136
  - !ruby/object:Gem::Version
123
137
  version: '1.4'
138
+ - !ruby/object:Gem::Dependency
139
+ name: reverse_markdown
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '3.0'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.0'
124
152
  - !ruby/object:Gem::Dependency
125
153
  name: solid_cable
126
154
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +205,20 @@ dependencies:
177
205
  - - "~>"
178
206
  - !ruby/object:Gem::Version
179
207
  version: '4.0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: toon-ruby
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - "~>"
213
+ - !ruby/object:Gem::Version
214
+ version: '0.1'
215
+ type: :runtime
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - "~>"
220
+ - !ruby/object:Gem::Version
221
+ version: '0.1'
180
222
  - !ruby/object:Gem::Dependency
181
223
  name: websocket-client-simple
182
224
  requirement: !ruby/object:Gem::Requirement
@@ -198,9 +240,8 @@ executables:
198
240
  extensions: []
199
241
  extra_rdoc_files: []
200
242
  files:
243
+ - ".gitattributes"
201
244
  - ".reek.yml"
202
- - CHANGELOG.md
203
- - Gemfile
204
245
  - LICENSE.txt
205
246
  - Procfile
206
247
  - Procfile.dev
@@ -221,17 +262,24 @@ files:
221
262
  - app/decorators/event_decorator.rb
222
263
  - app/decorators/system_message_decorator.rb
223
264
  - app/decorators/tool_call_decorator.rb
265
+ - app/decorators/tool_decorator.rb
224
266
  - app/decorators/tool_response_decorator.rb
225
267
  - app/decorators/user_message_decorator.rb
268
+ - app/decorators/web_get_tool_decorator.rb
226
269
  - app/jobs/agent_request_job.rb
227
270
  - app/jobs/analytical_brain_job.rb
228
271
  - app/jobs/application_job.rb
229
272
  - app/jobs/count_event_tokens_job.rb
273
+ - app/jobs/mneme_job.rb
274
+ - app/jobs/passive_recall_job.rb
230
275
  - app/models/application_record.rb
231
276
  - app/models/concerns/event/broadcasting.rb
232
277
  - app/models/event.rb
233
278
  - app/models/goal.rb
279
+ - app/models/goal_pinned_event.rb
280
+ - app/models/pinned_event.rb
234
281
  - app/models/session.rb
282
+ - app/models/snapshot.rb
235
283
  - bin/jobs
236
284
  - bin/rails
237
285
  - bin/rake
@@ -245,6 +293,7 @@ files:
245
293
  - config/environments/production.rb
246
294
  - config/environments/test.rb
247
295
  - config/initializers/event_subscribers.rb
296
+ - config/initializers/fts5_schema_dump.rb
248
297
  - config/initializers/inflections.rb
249
298
  - config/puma.rb
250
299
  - config/queue.yml
@@ -270,6 +319,10 @@ files:
270
319
  - db/migrate/20260315144837_add_completed_at_to_goals.rb
271
320
  - db/migrate/20260315191105_add_active_workflow_to_sessions.rb
272
321
  - db/migrate/20260316094817_add_interrupt_requested_to_sessions.rb
322
+ - db/migrate/20260321080000_create_mneme_schema.rb
323
+ - db/migrate/20260321120000_create_pinned_events.rb
324
+ - db/migrate/20260321140000_create_events_fts_index.rb
325
+ - db/migrate/20260321140100_add_recalled_event_ids_to_sessions.rb
273
326
  - db/queue_schema.rb
274
327
  - db/seeds.rb
275
328
  - exe/anima
@@ -279,6 +332,7 @@ files:
279
332
  - lib/analytical_brain.rb
280
333
  - lib/analytical_brain/runner.rb
281
334
  - lib/analytical_brain/tools/activate_skill.rb
335
+ - lib/analytical_brain/tools/assign_nickname.rb
282
336
  - lib/analytical_brain/tools/deactivate_skill.rb
283
337
  - lib/analytical_brain/tools/deactivate_workflow.rb
284
338
  - lib/analytical_brain/tools/everything_is_ready.rb
@@ -294,15 +348,19 @@ files:
294
348
  - lib/anima/config_migrator.rb
295
349
  - lib/anima/installer.rb
296
350
  - lib/anima/settings.rb
351
+ - lib/anima/spinner.rb
297
352
  - lib/anima/version.rb
298
353
  - lib/credential_store.rb
299
354
  - lib/environment_probe.rb
300
355
  - lib/events/agent_message.rb
301
356
  - lib/events/base.rb
357
+ - lib/events/bounce_back.rb
302
358
  - lib/events/bus.rb
303
359
  - lib/events/subscriber.rb
304
360
  - lib/events/subscribers/message_collector.rb
305
361
  - lib/events/subscribers/persister.rb
362
+ - lib/events/subscribers/subagent_message_router.rb
363
+ - lib/events/subscribers/transient_broadcaster.rb
306
364
  - lib/events/system_message.rb
307
365
  - lib/events/tool_call.rb
308
366
  - lib/events/tool_response.rb
@@ -313,18 +371,28 @@ files:
313
371
  - lib/mcp/health_check.rb
314
372
  - lib/mcp/secrets.rb
315
373
  - lib/mcp/stdio_transport.rb
374
+ - lib/mneme.rb
375
+ - lib/mneme/compressed_viewport.rb
376
+ - lib/mneme/l2_runner.rb
377
+ - lib/mneme/passive_recall.rb
378
+ - lib/mneme/runner.rb
379
+ - lib/mneme/search.rb
380
+ - lib/mneme/tools/attach_events_to_goals.rb
381
+ - lib/mneme/tools/everything_ok.rb
382
+ - lib/mneme/tools/save_snapshot.rb
316
383
  - lib/providers/anthropic.rb
317
384
  - lib/shell_session.rb
318
385
  - lib/skills/definition.rb
319
386
  - lib/skills/registry.rb
387
+ - lib/tasks/fts5.rake
320
388
  - lib/tools/base.rb
321
389
  - lib/tools/bash.rb
322
390
  - lib/tools/edit.rb
323
391
  - lib/tools/mcp_tool.rb
324
392
  - lib/tools/read.rb
325
393
  - lib/tools/registry.rb
394
+ - lib/tools/remember.rb
326
395
  - lib/tools/request_feature.rb
327
- - lib/tools/return_result.rb
328
396
  - lib/tools/spawn_specialist.rb
329
397
  - lib/tools/spawn_subagent.rb
330
398
  - lib/tools/subagent_prompts.rb
@@ -333,8 +401,19 @@ files:
333
401
  - lib/tools/write.rb
334
402
  - lib/tui/app.rb
335
403
  - lib/tui/cable_client.rb
404
+ - lib/tui/decorators/base_decorator.rb
405
+ - lib/tui/decorators/bash_decorator.rb
406
+ - lib/tui/decorators/edit_decorator.rb
407
+ - lib/tui/decorators/read_decorator.rb
408
+ - lib/tui/decorators/think_decorator.rb
409
+ - lib/tui/decorators/web_get_decorator.rb
410
+ - lib/tui/decorators/write_decorator.rb
411
+ - lib/tui/flash.rb
412
+ - lib/tui/formatting.rb
413
+ - lib/tui/height_map.rb
336
414
  - lib/tui/input_buffer.rb
337
415
  - lib/tui/message_store.rb
416
+ - lib/tui/performance_logger.rb
338
417
  - lib/tui/screens/chat.rb
339
418
  - lib/workflows/definition.rb
340
419
  - lib/workflows/registry.rb
data/CHANGELOG.md DELETED
@@ -1,80 +0,0 @@
1
- ## [Unreleased]
2
-
3
- ### Added
4
- - `anima update` command — upgrades the gem and merges new config keys into existing `config.toml` without overwriting user-customized values — `--migrate-only` flag to skip gem upgrade (#155)
5
- - Directory-based skills format — `skills/skill-name/SKILL.md` with optional `references/` and `examples/` subdirectories alongside flat `.md` files (#152)
6
- - Import 6 marketplace skills: activerecord, rspec, draper-decorators, dragonruby, ratatui-ruby, mcp-server (#152)
7
- - Tmux-style focus switching — `Ctrl+A ↑` enters chat scrolling mode with yellow border, `Escape` returns to input; arrow keys and Page Up/Down scroll chat, mouse scroll works in both modes (#87)
8
- - Bash-style input history — press ↑ at top of input to recall previous messages, ↓ to navigate forward; original draft restored when exiting history (#87)
9
- - Real-time event broadcasting via `Event::Broadcasting` concern — `after_create_commit` and `after_update_commit` callbacks broadcast decorated payloads with database ID and action type to the session's ActionCable stream (#91)
10
- - TUI `MessageStore` ID-indexed updates — events with `action: "update"` replace existing entries in-place (O(1) lookup) without changing display order
11
- - `CountEventTokensJob` triggers broadcast — uses `update!` so token count updates push to connected clients in real time
12
- - Connection status constants in `CableClient` — replaces magic strings with named constants for protocol message types
13
-
14
- ### Changed
15
- - Connection status indicator simplified — emoji-only `🟢` for normal state, descriptive text only for abnormal states (#80)
16
- - `STATUS_STYLES` structure simplified from `{label, fg, bg}` to `{label, color}` (#80)
17
- - `ActionCableBridge` removed — broadcasting moved from EventBus subscriber to AR callbacks, eliminating the timing gap where events were broadcast before persistence
18
- - `SessionChannel` history includes event IDs for client-side correlation
19
-
20
- ### Fixed
21
- - TUI showed empty chat on reconnect — message store was cleared _after_ history arrived because `confirm_subscription` comes after `transmit` in Action Cable protocol; now clears on "subscribing" before history (#82)
22
-
23
- ## [0.2.1] - 2026-03-13
24
-
25
- ### Added
26
- - TUI view mode switching via `Ctrl+a → v` — cycle between Basic, Verbose, and Debug (#75)
27
- - Draper EventDecorator hierarchy — structured data decorators for all event types (#74)
28
- - Decorators return structured hashes (not strings) for transport-layer filtering (#86)
29
- - Basic mode tool call counter — inline `🔧 Tools: X/Y ✓` aggregation (#73)
30
- - Verbose view mode rendering — timestamps, tool call previews, system messages (#76)
31
- - Tool call previews: bash `$ command`, web_get `GET url`, generic JSON fallback
32
- - Tool response display: truncated to 3 lines, `↩` success / `❌` failure indicators
33
- - Debug view mode — token counts per message, full tool args/responses, tool use IDs (#77)
34
- - Estimated token indicator (`~` prefix) for events not yet counted by background job
35
- - View mode persisted on Session model — survives TUI disconnect/reconnect
36
- - Mode changes broadcast to all connected clients with re-decorated viewport
37
-
38
- ### Fixed
39
- - Newlines in LLM responses collapsed into single line in rendered view modes
40
- - Loading state stuck after view mode switch — input blocked with "Thinking..."
41
-
42
- ## [0.2.0] - 2026-03-10
43
-
44
- ### Added
45
- - Client-server architecture — Brain (Rails + Puma) runs as persistent service, TUI connects via WebSocket
46
- - Action Cable infrastructure with Solid Cable adapter for Brain/TUI WebSocket communication
47
- - `SessionChannel` — WebSocket channel for session management, message relay, and session switching
48
- - Graceful TUI reconnection with exponential backoff (up to 10 attempts, max 30s delay)
49
- - `AgentRequestJob` — background job for LLM agent loops with retry logic for transient failures (network errors, rate limits, server errors)
50
- - Provider error hierarchy — `TransientError`, `RateLimitError`, `ServerError` for retry classification; `AuthenticationError` for immediate discard
51
- - `AgentLoop#run` — retry-safe entry point for job callers; lets errors propagate for external retry handling
52
- - `AgentLoop` service — decouples LLM orchestration from TUI; callable from background jobs, Action Cable channels, or TUI directly
53
- - Session and event persistence to SQLite — conversations survive TUI restart
54
- - `Session` model — owns an ordered event stream
55
- - `Event` model — polymorphic type, JSON payload, auto-incrementing position
56
- - `Events::Subscribers::Persister` — writes all events to SQLite as they flow through the bus
57
- - TUI resumes last session on startup, `Ctrl+a > n` creates a new session
58
- - Event system using Rails Structured Event Reporter (`Rails.event`)
59
- - Five event types: `system_message`, `user_message`, `agent_message`, `tool_call`, `tool_response`
60
- - `Events::Bus` — thin wrapper around `Rails.event` for emitting and subscribing to Anima events
61
- - `Events::Subscribers::MessageCollector` — in-memory subscriber that collects displayable messages
62
- - Chat screen refactored from raw array to event-driven architecture
63
- - TUI chat screen with LLM integration — in-memory message array, threaded API calls
64
- - Chat input with character validation, backspace, Enter to submit
65
- - Loading indicator — "Thinking" status bar mode, grayed-out input during LLM calls
66
- - New session command (`Ctrl+a > n`) clears conversation
67
- - Error handling — API failures displayed inline as chat messages
68
- - Anthropic API subscription token authentication
69
- - LLM client (raw HTTP to Anthropic API)
70
- - TUI scaffold with RatatuiRuby — tmux-style `Ctrl+a` command mode, sidebar, status bar
71
- - Headless Rails 8.1 app (API-only, no views/assets)
72
- - `anima install` command — creates ~/.anima/ tree, per-environment credentials, systemd user service
73
- - `anima start` command — runs db:prepare and boots Rails via Foreman
74
- - Systemd user service — auto-enables and starts brain on `anima install`
75
- - SQLite databases, logs, tmp, and credentials stored in ~/.anima/
76
- - Environment validation (development, test, production)
77
-
78
- ## [0.0.1] - 2026-03-06
79
-
80
- - Initial gem scaffold with CI and RubyGems publishing
data/Gemfile DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in anima-core.gemspec
6
- gemspec
7
-
8
- gem "irb"
9
- gem "rake", "~> 13.0"
10
-
11
- gem "rspec", "~> 3.0"
12
- gem "rspec-rails", "~> 7.0"
13
-
14
- gem "reek", "~> 6.5"
15
- gem "standard", "~> 1.3"
16
-
17
- gem "webmock", "~> 3.23"
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tools
4
- # Sub-agent-only tool that delivers a completed result back to the
5
- # parent session as a tool_call/tool_response pair. The parent agent
6
- # sees it as if it called a tool itself — no custom event types needed.
7
- #
8
- # Never registered for main sessions — only sub-agents see this tool.
9
- class ReturnResult < Base
10
- def self.tool_name = "return_result"
11
-
12
- def self.description = "Return your completed result to the parent agent. " \
13
- "Call this when you have fulfilled the assigned task."
14
-
15
- def self.input_schema
16
- {
17
- type: "object",
18
- properties: {
19
- result: {
20
- type: "string",
21
- description: "The completed deliverable to send back to the parent agent"
22
- }
23
- },
24
- required: ["result"]
25
- }
26
- end
27
-
28
- # @param session [Session] the sub-agent session returning a result
29
- def initialize(session:, **)
30
- @session = session
31
- end
32
-
33
- # Emits a tool_call/tool_response pair in the parent session so the
34
- # parent agent sees the sub-agent result as a regular tool interaction.
35
- #
36
- # @param input [Hash<String, Object>] with "result" key
37
- # @return [String, Hash] confirmation message, or Hash with :error key on failure
38
- def execute(input)
39
- result = input["result"].to_s.strip
40
- return {error: "Result cannot be blank"} if result.empty?
41
-
42
- parent = @session.parent_session
43
- return {error: "No parent session — only sub-agents can return results"} unless parent
44
-
45
- tool_use_id = "toolu_subagent_#{@session.id}"
46
- task = extract_task
47
- # Specialists are spawned with a name from the registry; generic sub-agents have nil name.
48
- origin_tool = @session.name ? SpawnSpecialist.tool_name : SpawnSubagent.tool_name
49
-
50
- Events::Bus.emit(Events::ToolCall.new(
51
- content: "Sub-agent result (session #{@session.id})",
52
- tool_name: origin_tool,
53
- tool_input: {"task" => task, "session_id" => @session.id},
54
- tool_use_id: tool_use_id,
55
- session_id: parent.id
56
- ))
57
-
58
- Events::Bus.emit(Events::ToolResponse.new(
59
- content: result,
60
- tool_name: origin_tool,
61
- tool_use_id: tool_use_id,
62
- session_id: parent.id
63
- ))
64
-
65
- "Result delivered to parent session #{parent.id}."
66
- end
67
-
68
- private
69
-
70
- # Extracts the original task from the sub-agent's first user message.
71
- # @return [String]
72
- def extract_task
73
- @session.events
74
- .where(event_type: "user_message")
75
- .order(:id)
76
- .pick(:payload)
77
- &.dig("content")
78
- .to_s
79
- end
80
- end
81
- end