agentic-threat-hunting-framework 0.2.4__py3-none-any.whl → 0.3.1__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-threat-hunting-framework
3
- Version: 0.2.4
3
+ Version: 0.3.1
4
4
  Summary: Agentic Threat Hunting Framework - Memory and AI for threat hunters
5
5
  Author-email: Sydney Marrone <athf@nebulock.io>
6
6
  Maintainer-email: Sydney Marrone <athf@nebulock.io>
@@ -33,6 +33,7 @@ Requires-Dist: click>=8.0.0
33
33
  Requires-Dist: pyyaml>=6.0
34
34
  Requires-Dist: rich>=10.0.0
35
35
  Requires-Dist: jinja2>=3.0.0
36
+ Requires-Dist: importlib_resources>=5.0.0; python_version < "3.9"
36
37
  Provides-Extra: dev
37
38
  Requires-Dist: pytest>=7.0.0; extra == "dev"
38
39
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -76,6 +77,7 @@ ATHF provides structure and persistence for threat hunting programs. It's a mark
76
77
  - Maintains a searchable repository of past investigations
77
78
  - Enables AI assistants to reference your environment and previous work
78
79
  - Works with any SIEM/EDR platform
80
+ - **NEW:** Includes AI-powered research and hypothesis generation agents (v0.3.0+)
79
81
 
80
82
  ## The Problem
81
83
 
@@ -115,8 +117,8 @@ ATHF defines a simple maturity model. Each level builds on the previous one.
115
117
  | **0** | Ad-hoc | Hunts exist in Slack, tickets, or analyst notes |
116
118
  | **1** | Documented | Persistent hunt records using LOCK |
117
119
  | **2** | Searchable | AI reads and recalls your hunts |
118
- | **3** | Generative | AI executes queries via MCP tools |
119
- | **4** | Agentic | Autonomous agents monitor and act |
120
+ | **3** | Generative | AI executes queries via MCP tools, conducts research |
121
+ | **4** | Agentic | Autonomous agents monitor and act, generate hypotheses |
120
122
 
121
123
  **Level 1:** Operational within a day
122
124
  **Level 2:** Operational within a week
@@ -136,8 +138,11 @@ pip install agentic-threat-hunting-framework
136
138
  # Initialize your hunt program
137
139
  athf init
138
140
 
139
- # Create your first hunt
140
- athf hunt new --technique T1003.001 --title "LSASS Credential Dumping"
141
+ # NEW: Conduct research before hunting (5-skill methodology)
142
+ athf research new --topic "LSASS dumping" --technique T1003.001
143
+
144
+ # Create your first hunt (link to research)
145
+ athf hunt new --technique T1003.001 --title "LSASS Credential Dumping" --research R-0001
141
146
  ```
142
147
 
143
148
  ### Option 2: Install from Source (Development)
@@ -161,7 +166,8 @@ git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
161
166
  cd agentic-threat-hunting-framework
162
167
 
163
168
  # Copy a template and start documenting
164
- cp templates/HUNT_LOCK.md hunts/H-0001.md
169
+ mkdir -p hunts
170
+ cp athf/data/templates/HUNT_LOCK.md hunts/H-0001.md
165
171
 
166
172
  # Customize AGENTS.md with your environment
167
173
  # Add your SIEM, EDR, and data sources
@@ -182,6 +188,23 @@ athf init # Interactive setup
182
188
  athf init --non-interactive # Use defaults
183
189
  ```
184
190
 
191
+ ### Research & Hypothesis Generation (NEW in v0.3.0)
192
+
193
+ ```bash
194
+ # Conduct thorough pre-hunt research (15-20 min)
195
+ athf research new --topic "LSASS dumping" --technique T1003.001
196
+
197
+ # Quick research for urgent hunts (5 min)
198
+ athf research new --topic "Pass-the-Hash" --depth basic
199
+
200
+ # Generate AI-powered hypothesis from threat intel
201
+ athf agent run hypothesis-generator --threat-intel "APT29 targeting SaaS"
202
+
203
+ # List research and agents
204
+ athf research list
205
+ athf agent list
206
+ ```
207
+
185
208
  ### Create Hunts
186
209
 
187
210
  ```bash
@@ -189,7 +212,8 @@ athf hunt new # Interactive mode
189
212
  athf hunt new \
190
213
  --technique T1003.001 \
191
214
  --title "LSASS Dumping Detection" \
192
- --platform windows
215
+ --platform windows \
216
+ --research R-0001 # Link to research document
193
217
  ```
194
218
 
195
219
  ### List & Search
@@ -199,6 +223,7 @@ athf hunt list # Show all hunts
199
223
  athf hunt list --status completed # Filter by status
200
224
  athf hunt list --output json # JSON output
201
225
  athf hunt search "kerberoasting" # Full-text search
226
+ athf research search "credential" # Search research docs
202
227
  ```
203
228
 
204
229
  ### Validate & Stats
@@ -208,6 +233,7 @@ athf hunt validate # Validate all hunts
208
233
  athf hunt validate H-0001 # Validate specific hunt
209
234
  athf hunt stats # Show statistics
210
235
  athf hunt coverage # MITRE ATT&CK coverage
236
+ athf research stats # Research metrics
211
237
  ```
212
238
 
213
239
  **Full documentation:** [CLI Reference](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/CLI_REFERENCE.md)
@@ -222,35 +248,12 @@ Watch ATHF in action: initialize a workspace, create hunts, and explore your thr
222
248
 
223
249
  ## Installation
224
250
 
225
- ### Prerequisites
251
+ See the [Quick Start](#-quick-start) section above for installation options (PyPI, source, or pure markdown).
252
+
253
+ **Prerequisites:**
226
254
  - Python 3.8-3.13 (for CLI option)
227
255
  - Your favorite AI code assistant
228
256
 
229
- ### From PyPI (Recommended)
230
-
231
- ```bash
232
- pip install agentic-threat-hunting-framework
233
- athf init
234
- ```
235
-
236
- ### From Source (Development)
237
-
238
- ```bash
239
- git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
240
- cd agentic-threat-hunting-framework
241
- pip install -e .
242
- athf init
243
- ```
244
-
245
- ### Markdown-Only Setup (No Installation)
246
-
247
- ```bash
248
- git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
249
- cd agentic-threat-hunting-framework
250
- ```
251
-
252
- Start documenting hunts in the `hunts/` directory using the LOCK pattern.
253
-
254
257
  ## Documentation
255
258
 
256
259
  ### Core Concepts
@@ -297,21 +300,16 @@ Agentic threat hunting is not about replacing analysts. It's about building syst
297
300
 
298
301
  When your framework has memory, you stop losing knowledge to turnover or forgotten notes. When your AI assistant can reference that memory, it becomes a force multiplier.
299
302
 
300
- ## 💬 Community & Support
303
+ ## 💬 Community & Adoption
301
304
 
302
305
  - **GitHub Discussions:** [Ask questions, share hunts](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/discussions)
303
306
  - **Issues:** [Report bugs or request features](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/issues)
304
- - **Adoption Guide:** See [USING_ATHF.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/USING_ATHF.md) for how to use ATHF in your organization
305
307
  - **LinkedIn:** [Nebulock Inc.](https://www.linkedin.com/company/nebulock-inc) - Follow for updates
306
308
 
307
- ## 📖 Using ATHF
308
-
309
- ATHF is a framework to internalize, not a platform to extend. Fork it, customize it, make it yours.
309
+ **Using ATHF in Your Organization:** ATHF is a framework to internalize, not a platform to extend. Fork it, customize it, make it yours. See [USING_ATHF.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/USING_ATHF.md) for adoption guidance. Your hunts stay yours—sharing back is optional but appreciated.
310
310
 
311
311
  **Repository:** [https://github.com/Nebulock-Inc/agentic-threat-hunting-framework](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework)
312
312
 
313
- See [USING_ATHF.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/USING_ATHF.md) for adoption guidance. Your hunts stay yours—sharing back is optional but appreciated ([Discussions](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/discussions)).
314
-
315
313
  The goal is to help every threat hunting team move from ad-hoc memory to structured, agentic capability.
316
314
 
317
315
  ---
@@ -1,22 +1,31 @@
1
- agentic_threat_hunting_framework-0.2.4.dist-info/licenses/LICENSE,sha256=_KObErRfiKoolznt-DF0nJnr3U9Rdh7Z4Ba7G5qqckk,1071
1
+ agentic_threat_hunting_framework-0.3.1.dist-info/licenses/LICENSE,sha256=_KObErRfiKoolznt-DF0nJnr3U9Rdh7Z4Ba7G5qqckk,1071
2
2
  athf/__init__.py,sha256=OrjZe8P97_BTEkscapnwSsqKSjwXNP9d8-HtGr19Ni0,241
3
- athf/__version__.py,sha256=Cl7hGHiamJWMCcDKQ8Lcm6Vlc50BZ6GasydpyAoWkg8,59
4
- athf/cli.py,sha256=LSOazv6E_RChZFqoyMcdfDPH1hIJeZc1s95fssjDLZs,4515
5
- athf/commands/__init__.py,sha256=uDyr0bz-agpGO8fraXQl24wuQCxqbeCevZsJ2bDK29s,25
6
- athf/commands/context.py,sha256=WvOf0OuttAsEk_h4QDtdfqYI4CulDg2UCtq_5r5iJAA,12686
7
- athf/commands/env.py,sha256=AisRllJXbyCjK_2ii21qBBmCz9raxhBUemwM7BxqIYg,11859
8
- athf/commands/hunt.py,sha256=9ZEI11y8DUixUqw8-yR01K4hVz2JSJJokRwWk8tnNn4,22969
3
+ athf/__version__.py,sha256=BCXUHJGbpcGJIqpp7wAIo-a46Xl8TlClrdkqx9_kTW8,59
4
+ athf/cli.py,sha256=rkg_Nx9Yy_UqTXBOh-pwaiD-lXO0_IXQMA1SQpDj7g0,4639
5
+ athf/agents/__init__.py,sha256=iaSJpvnXm9rz4QS7gBrsaLEjm49uvsMs4BLPOJeyp78,346
6
+ athf/agents/base.py,sha256=lnVDIOQUOyP-Apa9UM2E1mRXUPnNJ4hVqQXOwVw2u4c,4286
7
+ athf/agents/llm/__init__.py,sha256=qSGA-NaInjsDkMpGQwnTz3S1OgCVlzetpMcDS_to1co,671
8
+ athf/agents/llm/hunt_researcher.py,sha256=dIyD2Izh3zdf62kCHug1DwXFgmWhOMQUTim7qM3UAIs,27071
9
+ athf/agents/llm/hypothesis_generator.py,sha256=XkbJz8IS4zwQjEy-ZD0zy2XW5uRnAy87Lii-5XTY0WU,8564
10
+ athf/commands/__init__.py,sha256=KbpUcLPjmltq5a_m1MjhrIe4sk3DvqsnAw1wCAZfZNo,85
11
+ athf/commands/agent.py,sha256=k-NWiLppt2oWbiJ-hx1inkK51jhfsAYiFhixbzzQmQI,16565
12
+ athf/commands/context.py,sha256=V-at81-OgKcLY-In48-AccTnHfTgdofmnjE8S5kypoI,12678
13
+ athf/commands/env.py,sha256=JPKRsv48cgsIAjSFaGJ1-Nu0nQKGSVg4AbiFxb9jVX4,11887
14
+ athf/commands/hunt.py,sha256=PcYz0Zj9qqB10s9mkbfHk-hl2IbcfJekeB6cA2exXPo,22991
9
15
  athf/commands/init.py,sha256=Qn0iETNyuQvM-ySqCeoDz-pPemeuzROX_karQF5yN_o,12685
10
16
  athf/commands/investigate.py,sha256=mK_id5vjfN_ukqB_-fyia0FNa0pBmtn0Xv6CKHQI1Qo,24663
11
- athf/commands/similar.py,sha256=lniOkSOn--ZIztsfTZS-afioJpqJEJQjmqfxsDy6xZQ,11790
17
+ athf/commands/research.py,sha256=FrLph4agaGQ_rIxMh0OQwh1MIGDFtj40zJ3E1ZFwaAw,18112
18
+ athf/commands/similar.py,sha256=FTTVr4zzP9bdJrirscp6pOxdQbE8zot6pa20-_TYiuo,11804
12
19
  athf/core/__init__.py,sha256=yG7C8ljx3UW4QZoYvDjUxsWHlbS8M-GLGB7Je7rRfqo,31
13
20
  athf/core/attack_matrix.py,sha256=QZKKmxckQ6-U7lqVdGUJoj2jEAhP3Juvr3sqaNx2oTw,3238
14
21
  athf/core/hunt_manager.py,sha256=PFsg8Ecg94NCpuFZpApo82lyORkgK5IfOIih-7-XsmM,11580
15
22
  athf/core/hunt_parser.py,sha256=FUj0yyBIcZnaS9aItMImeBDhegQwpkewIwUMNXW_ZWU,5122
16
23
  athf/core/investigation_parser.py,sha256=wbfjnq4gFgIc0a4bHIAnidVNPhbHDpIXWY1SGLk0Xls,6804
24
+ athf/core/research_manager.py,sha256=i4fUjuZJcAik8I4pwbLkQlu6cuxkWDlqaIRQrzAfB0s,14512
17
25
  athf/core/template_engine.py,sha256=vNTVhlxIXZpxU7VmQyrqCSt6ORS0IVjAV54TOmUDMTE,5636
18
- athf/data/__init__.py,sha256=eC5AiaYPQ7oYR3ktxTvRhUHVd_RB1zhQgcVPD3o-9Vw,364
19
- athf/data/docs/CHANGELOG.md,sha256=1dAondeKsQnGOn9esy9oZ29uG_oGgRuHxmkcmGQ1Cwo,5950
26
+ athf/core/web_search.py,sha256=B9IhmwH7gy2RVA6WSN3L7yGp3Q4L8OsiiwcEvnnZejU,10320
27
+ athf/data/__init__.py,sha256=QtgONloCaS3E9Ow995FMxyy6BbszpfmYeWpySQ2b9Mc,502
28
+ athf/data/docs/CHANGELOG.md,sha256=2omJArkID-VADL0gNDfBSS0_E9GnP9OfZLn9ls-l5eA,7074
20
29
  athf/data/docs/CLI_REFERENCE.md,sha256=zqUp-tu8OAcqzpOwx3XvzEq7UV6woDraUOcWasZI0a8,43748
21
30
  athf/data/docs/INSTALL.md,sha256=JOWxk6q2-rdpgCnWdSPb3-Cp8rX1y4nQm7ObKz2G0uM,13117
22
31
  athf/data/docs/README.md,sha256=rp-XQZeqteXJz7M2qKX3sl6o0AVfhGmz8GcNNKAt8pM,1061
@@ -40,8 +49,8 @@ athf/data/prompts/ai-workflow.md,sha256=rZtOcGuAEi35qx7182TwHJEORdz1-RxkZMBVkg61
40
49
  athf/data/prompts/basic-prompts.md,sha256=2bunpO35RoBdJWYthXVi40RNl2UWrfwOaFthBLHF5sU,8463
41
50
  athf/data/templates/HUNT_LOCK.md,sha256=zXxHaKMWbRDLewLTegYJMbXRM72s9gFFvjdwFfGNeJE,7386
42
51
  athf/utils/__init__.py,sha256=aEAPI1xnAsowOtc036cCb9ZOek5nrrfevu8PElhbNgk,30
43
- agentic_threat_hunting_framework-0.2.4.dist-info/METADATA,sha256=gcqEWImt2gBOrH2q5VUhafR5OiG_xIoCdpfbtEy1mt0,15472
44
- agentic_threat_hunting_framework-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
- agentic_threat_hunting_framework-0.2.4.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
46
- agentic_threat_hunting_framework-0.2.4.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
47
- agentic_threat_hunting_framework-0.2.4.dist-info/RECORD,,
52
+ agentic_threat_hunting_framework-0.3.1.dist-info/METADATA,sha256=KgED__EriZvPR42CCSDQHf7md832CFyd7RyRTbdtQbU,15838
53
+ agentic_threat_hunting_framework-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ agentic_threat_hunting_framework-0.3.1.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
55
+ agentic_threat_hunting_framework-0.3.1.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
56
+ agentic_threat_hunting_framework-0.3.1.dist-info/RECORD,,
athf/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for ATHF."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.3.1"
@@ -0,0 +1,14 @@
1
+ """ATHF Agent Framework.
2
+
3
+ This module provides base classes and implementations for ATHF agents.
4
+ Agents can be deterministic (Python-only) or LLM-powered (using Claude API).
5
+ """
6
+
7
+ from athf.agents.base import Agent, AgentResult, DeterministicAgent, LLMAgent
8
+
9
+ __all__ = [
10
+ "Agent",
11
+ "AgentResult",
12
+ "DeterministicAgent",
13
+ "LLMAgent",
14
+ ]
athf/agents/base.py ADDED
@@ -0,0 +1,141 @@
1
+ """Base classes for hunt-vault agents."""
2
+
3
+ import os
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, Generic, List, Optional, TypeVar
7
+
8
+ # Type variables for input/output
9
+ InputT = TypeVar("InputT")
10
+ OutputT = TypeVar("OutputT")
11
+
12
+
13
+ @dataclass
14
+ class AgentResult(Generic[OutputT]):
15
+ """Standard result format for all agents."""
16
+
17
+ success: bool
18
+ data: Optional[OutputT]
19
+ error: Optional[str] = None
20
+ warnings: List[str] = field(default_factory=list)
21
+ metadata: Dict[str, Any] = field(default_factory=dict)
22
+
23
+ @property
24
+ def is_success(self) -> bool:
25
+ """Check if the agent execution was successful."""
26
+ return self.success and self.error is None
27
+
28
+
29
+ class Agent(ABC, Generic[InputT, OutputT]):
30
+ """Base class for all agents."""
31
+
32
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
33
+ """Initialize agent with optional configuration.
34
+
35
+ Args:
36
+ config: Optional configuration dictionary
37
+ """
38
+ self.config = config or {}
39
+ self._setup()
40
+
41
+ def _setup(self) -> None:
42
+ """Optional setup method for subclasses."""
43
+ pass
44
+
45
+ @abstractmethod
46
+ def execute(self, input_data: InputT) -> AgentResult[OutputT]:
47
+ """Execute agent logic.
48
+
49
+ Args:
50
+ input_data: Input for the agent
51
+
52
+ Returns:
53
+ AgentResult with output data or error
54
+ """
55
+ pass
56
+
57
+ def __call__(self, input_data: InputT) -> AgentResult[OutputT]:
58
+ """Allow calling agent as a function."""
59
+ return self.execute(input_data)
60
+
61
+
62
+ class DeterministicAgent(Agent[InputT, OutputT]):
63
+ """Base class for deterministic Python agents (no LLM)."""
64
+
65
+ pass
66
+
67
+
68
+ class LLMAgent(Agent[InputT, OutputT]):
69
+ """Base class for LLM-powered agents."""
70
+
71
+ def __init__(self, config: Optional[Dict[str, Any]] = None, llm_enabled: bool = True):
72
+ """Initialize LLM agent.
73
+
74
+ Args:
75
+ config: Optional configuration dictionary
76
+ llm_enabled: Whether to enable LLM functionality
77
+ """
78
+ self.llm_enabled = llm_enabled
79
+ super().__init__(config)
80
+
81
+ def _log_llm_metrics(
82
+ self,
83
+ agent_name: str,
84
+ model_id: str,
85
+ input_tokens: int,
86
+ output_tokens: int,
87
+ cost_usd: float,
88
+ duration_ms: int,
89
+ ) -> None:
90
+ """Log LLM call metrics to centralized tracker.
91
+
92
+ Args:
93
+ agent_name: Name of the agent (e.g., "hypothesis-generator")
94
+ model_id: Bedrock model ID
95
+ input_tokens: Number of input tokens
96
+ output_tokens: Number of output tokens
97
+ cost_usd: Estimated cost in USD
98
+ duration_ms: Call duration in milliseconds
99
+ """
100
+ try:
101
+ from athf.core.metrics_tracker import MetricsTracker
102
+
103
+ MetricsTracker.get_instance().log_bedrock_call(
104
+ agent=agent_name,
105
+ model_id=model_id,
106
+ input_tokens=input_tokens,
107
+ output_tokens=output_tokens,
108
+ cost_usd=cost_usd,
109
+ duration_ms=duration_ms,
110
+ )
111
+ except Exception:
112
+ pass # Never fail agent execution due to metrics logging
113
+
114
+ def _get_llm_client(self) -> Any:
115
+ """Get AWS Bedrock runtime client for Claude models.
116
+
117
+ Returns:
118
+ Bedrock runtime client instance or None if LLM is disabled
119
+
120
+ Raises:
121
+ ValueError: If AWS credentials are not configured
122
+ ImportError: If boto3 package is not installed
123
+ """
124
+ if not self.llm_enabled:
125
+ return None
126
+
127
+ try:
128
+ import boto3
129
+
130
+ # Get AWS region from environment or use default
131
+ region = os.getenv("AWS_REGION", os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
132
+
133
+ # Create Bedrock runtime client
134
+ # Uses AWS credentials from environment, ~/.aws/credentials, or IAM role
135
+ client = boto3.client(service_name="bedrock-runtime", region_name=region)
136
+
137
+ return client
138
+ except ImportError:
139
+ raise ImportError("boto3 package not installed. Run: pip install boto3")
140
+ except Exception as e:
141
+ raise ValueError(f"Failed to create Bedrock client: {e}")
@@ -0,0 +1,27 @@
1
+ """LLM-powered agents for ATHF.
2
+
3
+ These agents use Claude API for creative and analytical tasks.
4
+ All LLM agents have fallback to deterministic methods when LLM is disabled.
5
+ """
6
+
7
+ from athf.agents.llm.hunt_researcher import (
8
+ HuntResearcherAgent,
9
+ ResearchInput,
10
+ ResearchOutput,
11
+ ResearchSkillOutput,
12
+ )
13
+ from athf.agents.llm.hypothesis_generator import (
14
+ HypothesisGenerationInput,
15
+ HypothesisGenerationOutput,
16
+ HypothesisGeneratorAgent,
17
+ )
18
+
19
+ __all__ = [
20
+ "HypothesisGeneratorAgent",
21
+ "HypothesisGenerationInput",
22
+ "HypothesisGenerationOutput",
23
+ "HuntResearcherAgent",
24
+ "ResearchInput",
25
+ "ResearchOutput",
26
+ "ResearchSkillOutput",
27
+ ]