anima-core 1.4.0 → 1.5.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.
- checksums.yaml +4 -4
- data/.reek.yml +18 -20
- data/README.md +61 -95
- data/agents/thoughts-analyzer.md +12 -7
- data/anima-core.gemspec +1 -0
- data/app/channels/session_channel.rb +38 -58
- data/app/decorators/agent_message_decorator.rb +7 -2
- data/app/decorators/message_decorator.rb +31 -100
- data/app/decorators/pending_from_melete_decorator.rb +36 -0
- data/app/decorators/pending_from_melete_goal_decorator.rb +13 -0
- data/app/decorators/pending_from_melete_skill_decorator.rb +19 -0
- data/app/decorators/pending_from_melete_workflow_decorator.rb +13 -0
- data/app/decorators/pending_from_mneme_decorator.rb +44 -0
- data/app/decorators/pending_message_decorator.rb +94 -0
- data/app/decorators/pending_subagent_decorator.rb +46 -0
- data/app/decorators/pending_tool_response_decorator.rb +51 -0
- data/app/decorators/pending_user_message_decorator.rb +22 -0
- data/app/decorators/system_message_decorator.rb +5 -0
- data/app/decorators/tool_call_decorator.rb +13 -2
- data/app/decorators/tool_response_decorator.rb +2 -2
- data/app/decorators/user_message_decorator.rb +7 -2
- data/app/jobs/count_tokens_job.rb +23 -0
- data/app/jobs/drain_job.rb +169 -0
- data/app/jobs/melete_enrichment_job/goal_change_listener.rb +52 -0
- data/app/jobs/melete_enrichment_job.rb +48 -0
- data/app/jobs/mneme_enrichment_job.rb +46 -0
- data/app/jobs/tool_execution_job.rb +87 -0
- data/app/models/concerns/token_estimation.rb +54 -0
- data/app/models/goal.rb +21 -10
- data/app/models/message.rb +47 -36
- data/app/models/pending_message.rb +276 -29
- data/app/models/pinned_message.rb +8 -3
- data/app/models/session.rb +474 -432
- data/app/models/snapshot.rb +11 -21
- data/bin/inspect-cassette +17 -4
- data/config/application.rb +1 -0
- data/config/initializers/event_subscribers.rb +71 -4
- data/config/initializers/inflections.rb +3 -1
- data/db/cable_structure.sql +3 -3
- data/db/migrate/20260407170803_remove_viewport_message_ids_from_sessions.rb +5 -0
- data/db/migrate/20260407180400_remove_mneme_snapshot_pointer_columns_from_sessions.rb +6 -0
- data/db/migrate/20260411120553_add_token_count_to_pinned_messages.rb +5 -0
- data/db/migrate/20260411172926_remove_active_skills_and_workflow_from_sessions.rb +6 -0
- data/db/migrate/20260412110625_replace_processing_with_aasm_state.rb +6 -0
- data/db/migrate/20260418150323_add_kind_and_message_type_to_pending_messages.rb +6 -0
- data/db/migrate/20260419120000_add_drain_fields_to_pending_messages.rb +7 -0
- data/db/migrate/20260419130000_drop_pending_messages_kind_default.rb +5 -0
- data/db/migrate/20260419140000_add_drain_indexes_to_pending_messages.rb +8 -0
- data/db/migrate/20260420100000_add_hud_visibility_to_sessions.rb +15 -0
- data/db/queue_structure.sql +13 -13
- data/db/structure.sql +44 -31
- data/lib/agents/registry.rb +1 -1
- data/lib/anima/settings.rb +7 -33
- data/lib/anima/version.rb +1 -1
- data/lib/aoide/phantom_call_filter.rb +49 -0
- data/lib/{analytical_brain.rb → aoide.rb} +6 -3
- data/lib/events/authentication_required.rb +24 -0
- data/lib/events/bounce_back.rb +4 -4
- data/lib/events/eviction_completed.rb +28 -0
- data/lib/events/goal_created.rb +28 -0
- data/lib/events/goal_updated.rb +32 -0
- data/lib/events/llm_responded.rb +35 -0
- data/lib/events/message_created.rb +27 -0
- data/lib/events/message_updated.rb +25 -0
- data/lib/events/session_state_changed.rb +30 -0
- data/lib/events/skill_activated.rb +28 -0
- data/lib/events/start_melete.rb +36 -0
- data/lib/events/start_mneme.rb +33 -0
- data/lib/events/start_processing.rb +32 -0
- data/lib/events/subagent_evicted.rb +31 -0
- data/lib/events/subscribers/active_state_broadcaster.rb +27 -0
- data/lib/events/subscribers/authentication_broadcaster.rb +34 -0
- data/lib/events/subscribers/drain_kickoff.rb +20 -0
- data/lib/events/subscribers/eviction_broadcaster.rb +26 -0
- data/lib/events/subscribers/llm_response_handler.rb +145 -0
- data/lib/events/subscribers/melete_kickoff.rb +24 -0
- data/lib/events/subscribers/message_broadcaster.rb +34 -0
- data/lib/events/subscribers/mneme_kickoff.rb +24 -0
- data/lib/events/subscribers/mneme_scheduler.rb +21 -0
- data/lib/events/subscribers/persister.rb +6 -8
- data/lib/events/subscribers/session_state_broadcaster.rb +33 -0
- data/lib/events/subscribers/subagent_message_router.rb +26 -29
- data/lib/events/subscribers/subagent_visibility_broadcaster.rb +33 -0
- data/lib/events/subscribers/tool_response_creator.rb +33 -0
- data/lib/events/subscribers/transient_broadcaster.rb +1 -1
- data/lib/events/tool_executed.rb +34 -0
- data/lib/events/workflow_activated.rb +27 -0
- data/lib/llm/client.rb +41 -201
- data/lib/mcp/client_manager.rb +41 -46
- data/lib/mcp/stdio_transport.rb +9 -5
- data/lib/{analytical_brain → melete}/runner.rb +63 -68
- data/lib/{analytical_brain → melete}/tools/activate_skill.rb +1 -1
- data/lib/{analytical_brain → melete}/tools/assign_nickname.rb +2 -2
- data/lib/{analytical_brain → melete}/tools/everything_is_ready.rb +2 -2
- data/lib/{analytical_brain → melete}/tools/finish_goal.rb +3 -3
- data/lib/{analytical_brain → melete}/tools/goal_messaging.rb +4 -3
- data/lib/{analytical_brain → melete}/tools/read_workflow.rb +2 -2
- data/lib/{analytical_brain → melete}/tools/rename_session.rb +3 -3
- data/lib/{analytical_brain → melete}/tools/set_goal.rb +1 -1
- data/lib/{analytical_brain → melete}/tools/update_goal.rb +4 -4
- data/lib/melete.rb +26 -0
- data/lib/mneme/base_runner.rb +121 -0
- data/lib/mneme/l2_runner.rb +14 -20
- data/lib/mneme/recall_runner.rb +132 -0
- data/lib/mneme/runner.rb +118 -171
- data/lib/mneme/search.rb +104 -62
- data/lib/mneme/tools/nothing_to_surface.rb +25 -0
- data/lib/mneme/tools/save_snapshot.rb +2 -10
- data/lib/mneme/tools/surface_memory.rb +89 -0
- data/lib/mneme.rb +11 -5
- data/lib/shell_session.rb +303 -612
- data/lib/skills/definition.rb +2 -2
- data/lib/skills/registry.rb +1 -1
- data/lib/tools/base.rb +16 -0
- data/lib/tools/bash.rb +25 -57
- data/lib/tools/edit.rb +2 -0
- data/lib/tools/read.rb +2 -0
- data/lib/tools/registry.rb +79 -3
- data/lib/tools/{recall.rb → search_messages.rb} +19 -21
- data/lib/tools/spawn_specialist.rb +20 -10
- data/lib/tools/spawn_subagent.rb +24 -14
- data/lib/tools/subagent_prompts.rb +15 -4
- data/lib/tools/think.rb +1 -1
- data/lib/tools/{remember.rb → view_messages.rb} +10 -10
- data/lib/tools/write.rb +2 -0
- data/lib/tui/app.rb +5 -4
- data/lib/tui/braille_spinner.rb +7 -7
- data/lib/tui/decorators/base_decorator.rb +24 -3
- data/lib/tui/message_store.rb +93 -44
- data/lib/tui/screens/chat.rb +94 -20
- data/lib/tui/settings.rb +9 -2
- data/lib/workflows/definition.rb +3 -3
- data/lib/workflows/registry.rb +1 -1
- data/skills/github.md +38 -0
- data/templates/config.toml +4 -23
- data/workflows/review_pr.md +18 -14
- metadata +88 -28
- data/app/jobs/agent_request_job.rb +0 -199
- data/app/jobs/analytical_brain_job.rb +0 -33
- data/app/jobs/count_message_tokens_job.rb +0 -39
- data/app/jobs/passive_recall_job.rb +0 -24
- data/app/models/concerns/message/broadcasting.rb +0 -86
- data/lib/agent_loop.rb +0 -215
- data/lib/analytical_brain/tools/deactivate_skill.rb +0 -40
- data/lib/analytical_brain/tools/deactivate_workflow.rb +0 -35
- data/lib/events/agent_message.rb +0 -25
- data/lib/events/subscribers/message_collector.rb +0 -64
- data/lib/events/tool_call.rb +0 -31
- data/lib/events/tool_response.rb +0 -33
- data/lib/mneme/compressed_viewport.rb +0 -204
- data/lib/mneme/passive_recall.rb +0 -138
data/skills/github.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github
|
|
3
|
+
description: "GitHub operations — issues, pull requests, releases, CI runs, comments. Activate when an issue or PR number comes up, or when the conversation turns to repo state."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitHub
|
|
7
|
+
|
|
8
|
+
`gh` is installed and authenticated.
|
|
9
|
+
|
|
10
|
+
## Sub-issues — REST only
|
|
11
|
+
|
|
12
|
+
Not in top-level `gh` commands. Use the issue's *global ID* (not its number):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
16
|
+
ISSUE_ID=$(gh api repos/$REPO/issues/42 --jq '.id')
|
|
17
|
+
|
|
18
|
+
# add
|
|
19
|
+
gh api repos/$REPO/issues/36/sub_issues -X POST -F sub_issue_id=$ISSUE_ID
|
|
20
|
+
|
|
21
|
+
# list
|
|
22
|
+
gh api repos/$REPO/issues/36/sub_issues
|
|
23
|
+
|
|
24
|
+
# reorder (move one to sit after another)
|
|
25
|
+
gh api repos/$REPO/issues/36/sub_issues/priority -X PATCH \
|
|
26
|
+
-F sub_issue_id=$ISSUE_ID -F after_id=$AFTER_ID
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Reading JSON
|
|
30
|
+
|
|
31
|
+
`gh` JSON output is token-expensive. Pipe through `toon` — a lossless LLM-optimized format that round-trips back to JSON:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gh pr view 459 --json title,body,reviewDecision | toon
|
|
35
|
+
gh api repos/$REPO/pulls/459/comments | toon
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Skip `toon` when the output is going into further tooling that needs raw JSON (jq, scripts).
|
data/templates/config.toml
CHANGED
|
@@ -102,16 +102,6 @@ max_tool_response_chars = 6_000
|
|
|
102
102
|
# ~24000 chars ≈ ~8000 tokens.
|
|
103
103
|
max_subagent_response_chars = 24_000
|
|
104
104
|
|
|
105
|
-
# ─── Environment ──────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
[environment]
|
|
108
|
-
|
|
109
|
-
# Files to scan for in the working directory (at root and up to project_files_max_depth subdirectories deep).
|
|
110
|
-
project_files = ["CLAUDE.md", "AGENTS.md", "README.md", "CONTRIBUTING.md"]
|
|
111
|
-
|
|
112
|
-
# Maximum directory depth for project file scanning.
|
|
113
|
-
project_files_max_depth = 3
|
|
114
|
-
|
|
115
105
|
# ─── GitHub ─────────────────────────────────────────────────────
|
|
116
106
|
|
|
117
107
|
[github]
|
|
@@ -137,25 +127,16 @@ soul = "{{ANIMA_HOME}}/soul.md"
|
|
|
137
127
|
# View mode for new sessions: "basic", "verbose", or "debug".
|
|
138
128
|
default_view_mode = "basic"
|
|
139
129
|
|
|
140
|
-
# Regenerate session name every N messages.
|
|
141
|
-
name_generation_interval = 30
|
|
142
130
|
|
|
143
|
-
# ───
|
|
131
|
+
# ─── Melete ─────────────────────────────────────────
|
|
144
132
|
|
|
145
|
-
[
|
|
133
|
+
[melete]
|
|
146
134
|
|
|
147
|
-
# Maximum tokens per
|
|
135
|
+
# Maximum tokens per Melete response.
|
|
148
136
|
# Must accommodate multiple tool calls (rename + goals + skills + ready).
|
|
149
137
|
max_tokens = 4096
|
|
150
138
|
|
|
151
|
-
#
|
|
152
|
-
# Ensures activated skills are available for the current response.
|
|
153
|
-
blocking_on_user_message = true
|
|
154
|
-
|
|
155
|
-
# Run the analytical brain asynchronously after the main agent completes.
|
|
156
|
-
blocking_on_agent_message = false
|
|
157
|
-
|
|
158
|
-
# Number of recent messages to include in the analytical brain's context window.
|
|
139
|
+
# Number of recent messages to include in Melete's context window.
|
|
159
140
|
message_window = 20
|
|
160
141
|
|
|
161
142
|
# ─── Mneme (Memory Department) ──────────────────────────────────
|
data/workflows/review_pr.md
CHANGED
|
@@ -124,37 +124,38 @@ Read the diff from: /tmp/pr_<number>_diff.txt
|
|
|
124
124
|
- REST conventions and route design
|
|
125
125
|
- ActiveRecord patterns (associations, validations placement)
|
|
126
126
|
- Service object patterns and naming
|
|
127
|
+
- Security-adjacent AR patterns: raw SQL interpolation, mass assignment gaps, missing tenant/org scoping on shared-model queries
|
|
127
128
|
|
|
128
129
|
Output: List findings tagged [major], [minor], or [nit] with file:line references."
|
|
129
130
|
```
|
|
130
131
|
|
|
131
|
-
#### Subagent 2:
|
|
132
|
+
#### Subagent 2: TicketDelivery
|
|
132
133
|
|
|
133
134
|
```
|
|
134
|
-
Prompt: "
|
|
135
|
+
Prompt: "Your role: verify this PR delivers the ticket. Code-quality subagents judge how the work was done; you judge whether the work was done.
|
|
135
136
|
|
|
136
137
|
Read the diff from: /tmp/pr_<number>_diff.txt
|
|
137
138
|
|
|
138
|
-
|
|
139
|
+
The ticket defines 'done'. The PR description is how the author frames their work — useful context, not authority. When the two disagree, the ticket wins and the disagreement itself is a finding.
|
|
139
140
|
|
|
140
|
-
## Ticket
|
|
141
|
-
<ticket title,
|
|
141
|
+
## Ticket (verbatim — do not summarize)
|
|
142
|
+
<full ticket title, description, every Task, every Acceptance Criterion>
|
|
143
|
+
|
|
144
|
+
## PR description (verbatim)
|
|
145
|
+
<PR body from Step 1>
|
|
142
146
|
|
|
143
147
|
## Historical Context
|
|
144
148
|
<output from thoughts-analyzer>
|
|
145
149
|
|
|
146
150
|
<any additional instructions from user input>
|
|
147
151
|
|
|
148
|
-
##
|
|
149
|
-
- SQL injection (raw queries, interpolation in where clauses)
|
|
150
|
-
- XSS vulnerabilities (unescaped output, html_safe misuse)
|
|
151
|
-
- CSRF protection gaps
|
|
152
|
-
- Mass assignment vulnerabilities (permit params)
|
|
153
|
-
- Authentication/authorization bypasses
|
|
154
|
-
- Secrets or credentials in code
|
|
155
|
-
- Insecure direct object references
|
|
152
|
+
## How to work
|
|
156
153
|
|
|
157
|
-
|
|
154
|
+
Map each requirement in the ticket — Tasks, Acceptance Criteria, named targets — to evidence in the diff. For each, produce one line: ✅ delivered, ⚠️ partial, or ❌ missing, with file:line references.
|
|
155
|
+
|
|
156
|
+
A requirement is delivered when the code does what the ticket asked for in meaning, not merely in mention. Match semantics against the ticket's verbs: 'add Y' needs Y; 'replace X with Y' needs Y and no X. When the ticket lists multiple targets, verify each separately.
|
|
157
|
+
|
|
158
|
+
Output: the verification table first. Then findings tagged [major], [minor], or [nit] with file:line references."
|
|
158
159
|
```
|
|
159
160
|
|
|
160
161
|
#### Subagent 3: PerfPro
|
|
@@ -182,6 +183,7 @@ Read the diff from: /tmp/pr_<number>_diff.txt
|
|
|
182
183
|
- Memory bloat (loading large datasets)
|
|
183
184
|
- Missing caching opportunities
|
|
184
185
|
- Background job considerations (should this be async?)
|
|
186
|
+
- Cross-tenant data leakage in aggregation (missing organization_id scope on joins, unscoped WHERE in reports)
|
|
185
187
|
|
|
186
188
|
Output: List findings tagged [major], [minor], or [nit] with file:line references."
|
|
187
189
|
```
|
|
@@ -210,6 +212,7 @@ Read the diff from: /tmp/pr_<number>_diff.txt
|
|
|
210
212
|
- Test isolation issues (shared state, missing cleanup)
|
|
211
213
|
- Assertion quality (testing behavior vs implementation)
|
|
212
214
|
- Missing edge case coverage
|
|
215
|
+
- Missing coverage for authorization boundaries (cross-org access denial, role-based access denied, unauthenticated request rejected)
|
|
213
216
|
|
|
214
217
|
Output: List findings tagged [major], [minor], or [nit] with file:line references."
|
|
215
218
|
```
|
|
@@ -235,6 +238,7 @@ Read the diff from: /tmp/pr_<number>_diff.txt
|
|
|
235
238
|
- Complex logic lacking explanatory comments
|
|
236
239
|
- Misleading or outdated comments
|
|
237
240
|
- Magic numbers or strings needing constants
|
|
241
|
+
- Secrets, tokens, or credentials appearing in logs, comments, error messages, or test fixtures; permission-gating magic constants that should be named
|
|
238
242
|
|
|
239
243
|
Output: List findings tagged [major], [minor], or [nit] with file:line references."
|
|
240
244
|
```
|
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.
|
|
4
|
+
version: 1.5.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: aasm
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.5'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.5'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: certifi
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -260,20 +274,31 @@ files:
|
|
|
260
274
|
- app/decorators/agent_message_decorator.rb
|
|
261
275
|
- app/decorators/application_decorator.rb
|
|
262
276
|
- app/decorators/message_decorator.rb
|
|
277
|
+
- app/decorators/pending_from_melete_decorator.rb
|
|
278
|
+
- app/decorators/pending_from_melete_goal_decorator.rb
|
|
279
|
+
- app/decorators/pending_from_melete_skill_decorator.rb
|
|
280
|
+
- app/decorators/pending_from_melete_workflow_decorator.rb
|
|
281
|
+
- app/decorators/pending_from_mneme_decorator.rb
|
|
282
|
+
- app/decorators/pending_message_decorator.rb
|
|
283
|
+
- app/decorators/pending_subagent_decorator.rb
|
|
284
|
+
- app/decorators/pending_tool_response_decorator.rb
|
|
285
|
+
- app/decorators/pending_user_message_decorator.rb
|
|
263
286
|
- app/decorators/system_message_decorator.rb
|
|
264
287
|
- app/decorators/tool_call_decorator.rb
|
|
265
288
|
- app/decorators/tool_decorator.rb
|
|
266
289
|
- app/decorators/tool_response_decorator.rb
|
|
267
290
|
- app/decorators/user_message_decorator.rb
|
|
268
291
|
- app/decorators/web_get_tool_decorator.rb
|
|
269
|
-
- app/jobs/agent_request_job.rb
|
|
270
|
-
- app/jobs/analytical_brain_job.rb
|
|
271
292
|
- app/jobs/application_job.rb
|
|
272
|
-
- app/jobs/
|
|
293
|
+
- app/jobs/count_tokens_job.rb
|
|
294
|
+
- app/jobs/drain_job.rb
|
|
295
|
+
- app/jobs/melete_enrichment_job.rb
|
|
296
|
+
- app/jobs/melete_enrichment_job/goal_change_listener.rb
|
|
297
|
+
- app/jobs/mneme_enrichment_job.rb
|
|
273
298
|
- app/jobs/mneme_job.rb
|
|
274
|
-
- app/jobs/
|
|
299
|
+
- app/jobs/tool_execution_job.rb
|
|
275
300
|
- app/models/application_record.rb
|
|
276
|
-
- app/models/concerns/
|
|
301
|
+
- app/models/concerns/token_estimation.rb
|
|
277
302
|
- app/models/goal.rb
|
|
278
303
|
- app/models/goal_pinned_message.rb
|
|
279
304
|
- app/models/message.rb
|
|
@@ -337,27 +362,23 @@ files:
|
|
|
337
362
|
- db/migrate/20260401180000_add_api_metrics_to_messages.rb
|
|
338
363
|
- db/migrate/20260401210935_remove_recalled_message_ids_from_sessions.rb
|
|
339
364
|
- db/migrate/20260403080031_add_initial_cwd_to_sessions.rb
|
|
365
|
+
- db/migrate/20260407170803_remove_viewport_message_ids_from_sessions.rb
|
|
366
|
+
- db/migrate/20260407180400_remove_mneme_snapshot_pointer_columns_from_sessions.rb
|
|
367
|
+
- db/migrate/20260411120553_add_token_count_to_pinned_messages.rb
|
|
368
|
+
- db/migrate/20260411172926_remove_active_skills_and_workflow_from_sessions.rb
|
|
369
|
+
- db/migrate/20260412110625_replace_processing_with_aasm_state.rb
|
|
370
|
+
- db/migrate/20260418150323_add_kind_and_message_type_to_pending_messages.rb
|
|
371
|
+
- db/migrate/20260419120000_add_drain_fields_to_pending_messages.rb
|
|
372
|
+
- db/migrate/20260419130000_drop_pending_messages_kind_default.rb
|
|
373
|
+
- db/migrate/20260419140000_add_drain_indexes_to_pending_messages.rb
|
|
374
|
+
- db/migrate/20260420100000_add_hud_visibility_to_sessions.rb
|
|
340
375
|
- db/queue_schema.rb
|
|
341
376
|
- db/queue_structure.sql
|
|
342
377
|
- db/seeds.rb
|
|
343
378
|
- db/structure.sql
|
|
344
379
|
- exe/anima
|
|
345
|
-
- lib/agent_loop.rb
|
|
346
380
|
- lib/agents/definition.rb
|
|
347
381
|
- lib/agents/registry.rb
|
|
348
|
-
- lib/analytical_brain.rb
|
|
349
|
-
- lib/analytical_brain/runner.rb
|
|
350
|
-
- lib/analytical_brain/tools/activate_skill.rb
|
|
351
|
-
- lib/analytical_brain/tools/assign_nickname.rb
|
|
352
|
-
- lib/analytical_brain/tools/deactivate_skill.rb
|
|
353
|
-
- lib/analytical_brain/tools/deactivate_workflow.rb
|
|
354
|
-
- lib/analytical_brain/tools/everything_is_ready.rb
|
|
355
|
-
- lib/analytical_brain/tools/finish_goal.rb
|
|
356
|
-
- lib/analytical_brain/tools/goal_messaging.rb
|
|
357
|
-
- lib/analytical_brain/tools/read_workflow.rb
|
|
358
|
-
- lib/analytical_brain/tools/rename_session.rb
|
|
359
|
-
- lib/analytical_brain/tools/set_goal.rb
|
|
360
|
-
- lib/analytical_brain/tools/update_goal.rb
|
|
361
382
|
- lib/anima.rb
|
|
362
383
|
- lib/anima/cli.rb
|
|
363
384
|
- lib/anima/cli/mcp.rb
|
|
@@ -367,35 +388,73 @@ files:
|
|
|
367
388
|
- lib/anima/settings.rb
|
|
368
389
|
- lib/anima/spinner.rb
|
|
369
390
|
- lib/anima/version.rb
|
|
391
|
+
- lib/aoide.rb
|
|
392
|
+
- lib/aoide/phantom_call_filter.rb
|
|
370
393
|
- lib/credential_store.rb
|
|
371
|
-
- lib/events/
|
|
394
|
+
- lib/events/authentication_required.rb
|
|
372
395
|
- lib/events/base.rb
|
|
373
396
|
- lib/events/bounce_back.rb
|
|
374
397
|
- lib/events/bus.rb
|
|
398
|
+
- lib/events/eviction_completed.rb
|
|
399
|
+
- lib/events/goal_created.rb
|
|
400
|
+
- lib/events/goal_updated.rb
|
|
401
|
+
- lib/events/llm_responded.rb
|
|
402
|
+
- lib/events/message_created.rb
|
|
403
|
+
- lib/events/message_updated.rb
|
|
404
|
+
- lib/events/session_state_changed.rb
|
|
405
|
+
- lib/events/skill_activated.rb
|
|
406
|
+
- lib/events/start_melete.rb
|
|
407
|
+
- lib/events/start_mneme.rb
|
|
408
|
+
- lib/events/start_processing.rb
|
|
409
|
+
- lib/events/subagent_evicted.rb
|
|
375
410
|
- lib/events/subscriber.rb
|
|
376
|
-
- lib/events/subscribers/
|
|
411
|
+
- lib/events/subscribers/active_state_broadcaster.rb
|
|
412
|
+
- lib/events/subscribers/authentication_broadcaster.rb
|
|
413
|
+
- lib/events/subscribers/drain_kickoff.rb
|
|
414
|
+
- lib/events/subscribers/eviction_broadcaster.rb
|
|
415
|
+
- lib/events/subscribers/llm_response_handler.rb
|
|
416
|
+
- lib/events/subscribers/melete_kickoff.rb
|
|
417
|
+
- lib/events/subscribers/message_broadcaster.rb
|
|
418
|
+
- lib/events/subscribers/mneme_kickoff.rb
|
|
419
|
+
- lib/events/subscribers/mneme_scheduler.rb
|
|
377
420
|
- lib/events/subscribers/persister.rb
|
|
421
|
+
- lib/events/subscribers/session_state_broadcaster.rb
|
|
378
422
|
- lib/events/subscribers/subagent_message_router.rb
|
|
423
|
+
- lib/events/subscribers/subagent_visibility_broadcaster.rb
|
|
424
|
+
- lib/events/subscribers/tool_response_creator.rb
|
|
379
425
|
- lib/events/subscribers/transient_broadcaster.rb
|
|
380
426
|
- lib/events/system_message.rb
|
|
381
|
-
- lib/events/
|
|
382
|
-
- lib/events/tool_response.rb
|
|
427
|
+
- lib/events/tool_executed.rb
|
|
383
428
|
- lib/events/user_message.rb
|
|
429
|
+
- lib/events/workflow_activated.rb
|
|
384
430
|
- lib/llm/client.rb
|
|
385
431
|
- lib/mcp/client_manager.rb
|
|
386
432
|
- lib/mcp/config.rb
|
|
387
433
|
- lib/mcp/health_check.rb
|
|
388
434
|
- lib/mcp/secrets.rb
|
|
389
435
|
- lib/mcp/stdio_transport.rb
|
|
436
|
+
- lib/melete.rb
|
|
437
|
+
- lib/melete/runner.rb
|
|
438
|
+
- lib/melete/tools/activate_skill.rb
|
|
439
|
+
- lib/melete/tools/assign_nickname.rb
|
|
440
|
+
- lib/melete/tools/everything_is_ready.rb
|
|
441
|
+
- lib/melete/tools/finish_goal.rb
|
|
442
|
+
- lib/melete/tools/goal_messaging.rb
|
|
443
|
+
- lib/melete/tools/read_workflow.rb
|
|
444
|
+
- lib/melete/tools/rename_session.rb
|
|
445
|
+
- lib/melete/tools/set_goal.rb
|
|
446
|
+
- lib/melete/tools/update_goal.rb
|
|
390
447
|
- lib/mneme.rb
|
|
391
|
-
- lib/mneme/
|
|
448
|
+
- lib/mneme/base_runner.rb
|
|
392
449
|
- lib/mneme/l2_runner.rb
|
|
393
|
-
- lib/mneme/
|
|
450
|
+
- lib/mneme/recall_runner.rb
|
|
394
451
|
- lib/mneme/runner.rb
|
|
395
452
|
- lib/mneme/search.rb
|
|
396
453
|
- lib/mneme/tools/attach_messages_to_goals.rb
|
|
397
454
|
- lib/mneme/tools/everything_ok.rb
|
|
455
|
+
- lib/mneme/tools/nothing_to_surface.rb
|
|
398
456
|
- lib/mneme/tools/save_snapshot.rb
|
|
457
|
+
- lib/mneme/tools/surface_memory.rb
|
|
399
458
|
- lib/providers/anthropic.rb
|
|
400
459
|
- lib/shell_session.rb
|
|
401
460
|
- lib/skills/definition.rb
|
|
@@ -408,14 +467,14 @@ files:
|
|
|
408
467
|
- lib/tools/mcp_tool.rb
|
|
409
468
|
- lib/tools/open_issue.rb
|
|
410
469
|
- lib/tools/read.rb
|
|
411
|
-
- lib/tools/recall.rb
|
|
412
470
|
- lib/tools/registry.rb
|
|
413
|
-
- lib/tools/remember.rb
|
|
414
471
|
- lib/tools/response_truncator.rb
|
|
472
|
+
- lib/tools/search_messages.rb
|
|
415
473
|
- lib/tools/spawn_specialist.rb
|
|
416
474
|
- lib/tools/spawn_subagent.rb
|
|
417
475
|
- lib/tools/subagent_prompts.rb
|
|
418
476
|
- lib/tools/think.rb
|
|
477
|
+
- lib/tools/view_messages.rb
|
|
419
478
|
- lib/tools/web_get.rb
|
|
420
479
|
- lib/tools/write.rb
|
|
421
480
|
- lib/tui/app.rb
|
|
@@ -540,6 +599,7 @@ files:
|
|
|
540
599
|
- skills/draper-decorators/references/patterns.md
|
|
541
600
|
- skills/draper-decorators/references/testing.md
|
|
542
601
|
- skills/gh-issue.md
|
|
602
|
+
- skills/github.md
|
|
543
603
|
- skills/mcp-server/SKILL.md
|
|
544
604
|
- skills/mcp-server/examples/dynamic_tools.rb
|
|
545
605
|
- skills/mcp-server/examples/file_manager_tool.rb
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Executes an LLM agent loop as a background job with retry logic
|
|
4
|
-
# for transient failures (network errors, rate limits, server errors).
|
|
5
|
-
#
|
|
6
|
-
# Supports two modes:
|
|
7
|
-
#
|
|
8
|
-
# **Immediate Persist (message_id provided):** The user message was already
|
|
9
|
-
# persisted and broadcast by the caller (e.g. {SessionChannel#speak}).
|
|
10
|
-
# The job verifies LLM delivery — if the first API call fails, the
|
|
11
|
-
# message is deleted and a {Events::BounceBack} is emitted so clients
|
|
12
|
-
# can restore the text to the input field.
|
|
13
|
-
#
|
|
14
|
-
# **Standard (no message_id):** Processes already-persisted messages (e.g.
|
|
15
|
-
# after pending message promotion). Uses ActiveJob retry/discard for
|
|
16
|
-
# error handling.
|
|
17
|
-
#
|
|
18
|
-
# @example Immediate Persist — message already saved by SessionChannel
|
|
19
|
-
# AgentRequestJob.perform_later(session.id, message_id: 42)
|
|
20
|
-
#
|
|
21
|
-
# @example Standard — pending message processing
|
|
22
|
-
# AgentRequestJob.perform_later(session.id)
|
|
23
|
-
class AgentRequestJob < ApplicationJob
|
|
24
|
-
queue_as :default
|
|
25
|
-
|
|
26
|
-
# ActionCable action signaling clients to prompt for an API token.
|
|
27
|
-
AUTH_REQUIRED_ACTION = "authentication_required"
|
|
28
|
-
|
|
29
|
-
# Standard path only — immediate persist handles its own errors.
|
|
30
|
-
retry_on Providers::Anthropic::TransientError,
|
|
31
|
-
wait: :polynomially_longer, attempts: 5 do |job, error|
|
|
32
|
-
Events::Bus.emit(Events::SystemMessage.new(
|
|
33
|
-
content: "Failed after multiple retries: #{error.message}",
|
|
34
|
-
session_id: job.arguments.first
|
|
35
|
-
))
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
discard_on ActiveRecord::RecordNotFound
|
|
39
|
-
discard_on Providers::Anthropic::AuthenticationError do |job, error|
|
|
40
|
-
session_id = job.arguments.first
|
|
41
|
-
Events::Bus.emit(Events::SystemMessage.new(
|
|
42
|
-
content: "Authentication failed: #{error.message}",
|
|
43
|
-
session_id: session_id
|
|
44
|
-
))
|
|
45
|
-
ActionCable.server.broadcast(
|
|
46
|
-
"session_#{session_id}",
|
|
47
|
-
{"action" => AUTH_REQUIRED_ACTION, "message" => error.message}
|
|
48
|
-
)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# @param session_id [Integer] ID of the session to process
|
|
52
|
-
# @param message_id [Integer, nil] ID of a pre-persisted user message (triggers delivery verification)
|
|
53
|
-
def perform(session_id, message_id: nil)
|
|
54
|
-
session = Session.find(session_id)
|
|
55
|
-
|
|
56
|
-
# Atomic: only one job processes a session at a time.
|
|
57
|
-
return unless claim_processing(session_id)
|
|
58
|
-
|
|
59
|
-
run_analytical_brain_blocking(session)
|
|
60
|
-
|
|
61
|
-
agent_loop = AgentLoop.new(session: session)
|
|
62
|
-
|
|
63
|
-
if message_id
|
|
64
|
-
deliver_persisted_message(session, message_id, agent_loop)
|
|
65
|
-
else
|
|
66
|
-
agent_loop.run
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Process any pending messages that arrived after the last tool round.
|
|
70
|
-
loop do
|
|
71
|
-
promoted = session.promote_pending_messages!
|
|
72
|
-
break if promoted[:texts].empty? && promoted[:pairs].empty?
|
|
73
|
-
agent_loop.run
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
session.schedule_analytical_brain!
|
|
77
|
-
ensure
|
|
78
|
-
release_processing(session_id)
|
|
79
|
-
clear_interrupt(session_id)
|
|
80
|
-
agent_loop&.finalize
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
-
# Verifies LLM delivery for a pre-persisted user message.
|
|
86
|
-
#
|
|
87
|
-
# The message was already created and broadcast by the caller, so
|
|
88
|
-
# the user sees their message immediately. This method makes the
|
|
89
|
-
# first LLM API call — if it fails, the message is deleted and a
|
|
90
|
-
# {Events::BounceBack} notifies clients to remove the phantom
|
|
91
|
-
# message and restore the text to the input field. For
|
|
92
|
-
# {Providers::Anthropic::AuthenticationError}, an additional
|
|
93
|
-
# +authentication_required+ broadcast prompts the client to show
|
|
94
|
-
# the token entry dialog.
|
|
95
|
-
#
|
|
96
|
-
# Unlike the standard path (which uses +retry_on+ / +discard_on+),
|
|
97
|
-
# all errors here are caught and swallowed after emitting a
|
|
98
|
-
# BounceBack — the job completes normally so ActiveJob does not
|
|
99
|
-
# retry a message the user will re-send manually.
|
|
100
|
-
#
|
|
101
|
-
# After successful delivery, continues the agent loop (tool
|
|
102
|
-
# execution, subsequent API calls).
|
|
103
|
-
#
|
|
104
|
-
# @param session [Session] the conversation session
|
|
105
|
-
# @param message_id [Integer] database ID of the pre-persisted user message
|
|
106
|
-
# @param agent_loop [AgentLoop] agent loop instance (reused for continuation)
|
|
107
|
-
def deliver_persisted_message(session, message_id, agent_loop)
|
|
108
|
-
message = Message.find_by(id: message_id, session_id: session.id)
|
|
109
|
-
# Message may have been deleted between SessionChannel#speak and job
|
|
110
|
-
# execution (e.g. user recalled the message). Exit silently — there
|
|
111
|
-
# is nothing to deliver or bounce back.
|
|
112
|
-
return unless message
|
|
113
|
-
|
|
114
|
-
content = message.payload["content"]
|
|
115
|
-
|
|
116
|
-
begin
|
|
117
|
-
agent_loop.deliver!
|
|
118
|
-
rescue => error
|
|
119
|
-
message.destroy!
|
|
120
|
-
Events::Bus.emit(Events::BounceBack.new(
|
|
121
|
-
content: content,
|
|
122
|
-
error: error.message,
|
|
123
|
-
session_id: session.id,
|
|
124
|
-
message_id: message_id
|
|
125
|
-
))
|
|
126
|
-
broadcast_auth_required(session.id, error) if error.is_a?(Providers::Anthropic::AuthenticationError)
|
|
127
|
-
return
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
agent_loop.run
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def broadcast_auth_required(session_id, error)
|
|
134
|
-
ActionCable.server.broadcast(
|
|
135
|
-
"session_#{session_id}",
|
|
136
|
-
{"action" => AUTH_REQUIRED_ACTION, "message" => error.message}
|
|
137
|
-
)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Runs the analytical brain synchronously before the main agent loop.
|
|
141
|
-
# Respects the blocking_on_user_message setting and session guards
|
|
142
|
-
# (skips sub-agents and sessions with too few messages).
|
|
143
|
-
def run_analytical_brain_blocking(session)
|
|
144
|
-
return unless Anima::Settings.analytical_brain_blocking_on_user_message
|
|
145
|
-
return if session.sub_agent?
|
|
146
|
-
|
|
147
|
-
AnalyticalBrain::Runner.new(session).call
|
|
148
|
-
rescue => error
|
|
149
|
-
# The analytical brain is best-effort: skill activation enhances the
|
|
150
|
-
# response but the main agent must still reply even if it fails.
|
|
151
|
-
msg = "FAILED (blocking) session=#{session.id}: #{error.class}: #{error.message}"
|
|
152
|
-
Rails.logger.error("Analytical brain #{msg}")
|
|
153
|
-
AnalyticalBrain.logger.error("#{msg}\n#{error.backtrace&.first(10)&.join("\n")}")
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Sets the session's processing flag atomically. Returns true if this
|
|
157
|
-
# job claimed the lock, false if another job already holds it.
|
|
158
|
-
# Broadcasts +session_state: llm_generating+ and the state change to
|
|
159
|
-
# the parent session's HUD.
|
|
160
|
-
def claim_processing(session_id)
|
|
161
|
-
claimed = Session.where(id: session_id, processing: false).update_all(processing: true) == 1
|
|
162
|
-
if claimed
|
|
163
|
-
session = Session.find_by(id: session_id)
|
|
164
|
-
session&.broadcast_session_state("llm_generating")
|
|
165
|
-
session&.broadcast_children_update_to_parent
|
|
166
|
-
end
|
|
167
|
-
claimed
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Clears the processing flag so the session can accept new jobs.
|
|
171
|
-
# Broadcasts +session_state: idle+ to the session stream (replaces
|
|
172
|
-
# the old +processing_stopped+ action) and +children_updated+ to the
|
|
173
|
-
# parent session's HUD.
|
|
174
|
-
def release_processing(session_id)
|
|
175
|
-
Session.where(id: session_id).update_all(processing: false)
|
|
176
|
-
session = Session.find_by(id: session_id)
|
|
177
|
-
session&.broadcast_session_state("idle")
|
|
178
|
-
session&.broadcast_children_update_to_parent
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Safety-net clearing of the interrupt flag.
|
|
182
|
-
def clear_interrupt(session_id)
|
|
183
|
-
Session.where(id: session_id, interrupt_requested: true).update_all(interrupt_requested: false)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Emits a system message before each retry so the user sees
|
|
187
|
-
# "retrying..." instead of nothing.
|
|
188
|
-
def retry_job(options = {})
|
|
189
|
-
error = options[:error]
|
|
190
|
-
wait = options[:wait]
|
|
191
|
-
|
|
192
|
-
Events::Bus.emit(Events::SystemMessage.new(
|
|
193
|
-
content: "#{error.message} — retrying in #{wait.to_i}s...",
|
|
194
|
-
session_id: arguments.first
|
|
195
|
-
))
|
|
196
|
-
|
|
197
|
-
super
|
|
198
|
-
end
|
|
199
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Runs the analytical brain — a phantom LLM loop that observes the main
|
|
4
|
-
# session and performs background maintenance (currently: session naming).
|
|
5
|
-
#
|
|
6
|
-
# Replaces {GenerateSessionNameJob} with a tool-based architecture that
|
|
7
|
-
# future tickets will expand with skill activation, goal tracking, etc.
|
|
8
|
-
#
|
|
9
|
-
# Scheduling guards live in {Session#schedule_analytical_brain!} — this
|
|
10
|
-
# job always runs when called.
|
|
11
|
-
#
|
|
12
|
-
# @example
|
|
13
|
-
# AnalyticalBrainJob.perform_later(session.id)
|
|
14
|
-
class AnalyticalBrainJob < ApplicationJob
|
|
15
|
-
queue_as :default
|
|
16
|
-
|
|
17
|
-
retry_on Providers::Anthropic::TransientError,
|
|
18
|
-
wait: :polynomially_longer, attempts: 3
|
|
19
|
-
|
|
20
|
-
discard_on ActiveRecord::RecordNotFound
|
|
21
|
-
discard_on Providers::Anthropic::AuthenticationError
|
|
22
|
-
|
|
23
|
-
# @param session_id [Integer] the main Session to analyze
|
|
24
|
-
def perform(session_id)
|
|
25
|
-
brain_log = AnalyticalBrain.logger
|
|
26
|
-
session = Session.find(session_id)
|
|
27
|
-
brain_log.info("async job started for session=#{session_id}")
|
|
28
|
-
AnalyticalBrain::Runner.new(session).call
|
|
29
|
-
rescue => error
|
|
30
|
-
brain_log.error("FAILED (async) session=#{session_id}: #{error.class}: #{error.message}")
|
|
31
|
-
raise
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Counts tokens in a message's payload via the Anthropic API and
|
|
4
|
-
# caches the result on the message record. Enqueued automatically
|
|
5
|
-
# after each LLM message is created.
|
|
6
|
-
class CountMessageTokensJob < ApplicationJob
|
|
7
|
-
queue_as :default
|
|
8
|
-
|
|
9
|
-
retry_on Providers::Anthropic::Error, wait: :polynomially_longer, attempts: 3
|
|
10
|
-
discard_on ActiveRecord::RecordNotFound
|
|
11
|
-
|
|
12
|
-
# @param message_id [Integer] the Message record to count tokens for
|
|
13
|
-
def perform(message_id)
|
|
14
|
-
message = Message.find(message_id)
|
|
15
|
-
return if already_counted?(message)
|
|
16
|
-
|
|
17
|
-
provider = Providers::Anthropic.new
|
|
18
|
-
api_messages = [{role: message.api_role, content: message.payload["content"].to_s}]
|
|
19
|
-
|
|
20
|
-
token_count = provider.count_tokens(
|
|
21
|
-
model: Anima::Settings.model,
|
|
22
|
-
messages: api_messages
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# Guard against parallel jobs: reload and re-check before writing.
|
|
26
|
-
# Uses update! (not update_all) so {Message::Broadcasting} after_update_commit
|
|
27
|
-
# broadcasts the updated token count to connected clients.
|
|
28
|
-
message.reload
|
|
29
|
-
return if already_counted?(message)
|
|
30
|
-
|
|
31
|
-
message.update!(token_count: token_count)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def already_counted?(message)
|
|
37
|
-
message.token_count > 0
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Runs passive recall after goal updates — searches message history for
|
|
4
|
-
# context relevant to active goals and injects phantom tool_call/tool_response
|
|
5
|
-
# pairs into the session's message stream.
|
|
6
|
-
#
|
|
7
|
-
# Phantom pairs ride the conveyor belt like regular messages, getting
|
|
8
|
-
# cached, evicted, and compressed by Mneme naturally.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# PassiveRecallJob.perform_later(session.id)
|
|
12
|
-
class PassiveRecallJob < ApplicationJob
|
|
13
|
-
queue_as :default
|
|
14
|
-
|
|
15
|
-
discard_on ActiveRecord::RecordNotFound
|
|
16
|
-
|
|
17
|
-
# @param session_id [Integer]
|
|
18
|
-
def perform(session_id)
|
|
19
|
-
session = Session.find(session_id)
|
|
20
|
-
count = Mneme::PassiveRecall.new(session).call
|
|
21
|
-
|
|
22
|
-
Mneme.logger.info("session=#{session_id} — passive recall injected #{count} phantom pairs") if count > 0
|
|
23
|
-
end
|
|
24
|
-
end
|