tylor-mcp 1.0.0 β†’ 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +165 -146
  2. package/bin/tylor.js +37 -2
  3. package/package.json +1 -1
  4. package/server/config.py +8 -13
  5. package/server/server.log +1 -0
  6. package/server/storage/dynamo.py +33 -0
  7. package/server/storage/json_store.py +49 -2
  8. package/server/tests/test_install.py +18 -4
  9. package/server/tests/test_ui_server.py +57 -19
  10. package/server/tests/test_ui_shader_background.py +14 -17
  11. package/server/tests/test_ui_story_6_3.py +64 -71
  12. package/server/tools/agents.py +278 -10
  13. package/server/tools/executor.py +38 -1
  14. package/server/tools/harness.py +119 -14
  15. package/server/tools/help.py +21 -1
  16. package/server/tools/hooks.py +19 -6
  17. package/server/tools/registry.py +50 -3
  18. package/server/tools/security.py +162 -0
  19. package/server/tools/skill_installer.py +34 -1
  20. package/server/tools/summarizer.py +3 -1
  21. package/server/tools/tests/test_agents.py +17 -4
  22. package/server/tools/tests/test_executor.py +39 -0
  23. package/server/tools/tests/test_help_agent101.py +20 -13
  24. package/server/tools/tests/test_registry_client.py +26 -12
  25. package/server/tools/tests/test_skill_installer.py +19 -0
  26. package/server/tools/tests/test_spawn_agent_harness.py +225 -0
  27. package/server/tools/tests/test_thread_command_skills.py +6 -0
  28. package/server/tools/tests/test_tier1_schema.py +17 -3
  29. package/server/tools/tylor.py +11 -10
  30. package/server/ui_server.py +160 -11
  31. package/server/validate.py +29 -15
  32. package/skills/ecc-data/SKILL.md +32 -0
  33. package/skills/ecc-diagrams/SKILL.md +31 -0
  34. package/skills/ecc-pipeline/SKILL.md +31 -0
  35. package/skills/ecc-presentation/SKILL.md +31 -0
  36. package/skills/ecc-web/SKILL.md +31 -0
  37. package/skills/help-agent101/SKILL.md +1 -0
  38. package/skills/open-threads-ui/SKILL.md +33 -0
  39. package/ui/index.html +760 -889
package/README.md CHANGED
@@ -1,146 +1,165 @@
1
- <div align="center">
2
- <img src="assets/tylor_logo.png" alt="Tylor Logo" width="150">
3
- <h1>Tylor</h1>
4
- <p><strong>The Tailor to Your Threads</strong></p>
5
- <p><em>Give Claude Code persistent memory, laser-focused context, and an autonomous team of specialists.</em></p>
6
-
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
- [![Platform: Windows | macOS | Linux | WSL](https://img.shields.io/badge/Platform-Cross--Platform-success)](#)
9
- [![Claude Code](https://img.shields.io/badge/Integration-Claude_Code-orange)](#)
10
- [![GitHub Copilot](https://img.shields.io/badge/Integration-GitHub_Copilot-blue)](#)
11
- [![Antigravity](https://img.shields.io/badge/Integration-Antigravity-blueviolet)](#)
12
- </div>
13
-
14
- ---
15
-
16
- Tylor transforms your Claude Code experience from a single-shot terminal interaction into a **persistent, intelligent workspace**.
17
-
18
- Every time you open Claude Code, you normally start from zero. Tylor fixes that. It organizes your work into **threads**β€”isolated, named workspaces that survive restarts and reboots. It remembers every decision, every line of code, and every discussion, so you never have to repeat yourself.
19
-
20
- **No database. No cloud account. No configuration. Just install and go.**
21
-
22
- ---
23
-
24
- ## 🎨 How It Works
25
-
26
- <div align="center">
27
- <img src="assets/tylor_threads_concept.png" alt="Tylor Threads Architecture" width="800">
28
- <p><em>Tylor weaves parallel, persistent memory threads and orchestrates specialist sub-agents.</em></p>
29
- </div>
30
-
31
- ---
32
-
33
- ## ✨ Features
34
-
35
- ### 🧠 Persistent Memory
36
- Tylor completely eliminates the "context reset." Shut down your computer, close your terminal, and come back a week laterβ€”Claude will pick up exactly where you left off.
37
-
38
- ### πŸ—‚οΈ Context Isolation (Threads)
39
- Work in parallel without context bleed. Discuss frontend components in a `Frontend` thread and database schemas in a `Backend` thread. By isolating context, token usage stays low, and Claude's focus stays incredibly sharp.
40
-
41
- ### πŸ€– Intelligent Orchestration
42
- You don't need to micromanage. Claude acts as the orchestrator. If you ask it to review architecture, it will dynamically load its `cto` persona. If you ask it to write a PRD, it natively invokes the `bmad` skill framework to get the job done.
43
-
44
- ### πŸ”Œ Infinite Extensibility (Lazy-Loading)
45
- Tylor is built on a production-hardened ADK-pattern harness. You can register hundreds of domain-specific ECC skills (like `ecc/web`, `ecc/data`) via the `/add-skill` command. Tylor **lazy-loads** only the tools required for the current prompt, giving you massive capability scaling without ever blowing up Claude's token context window.
46
-
47
- ### πŸ—οΈ Autonomous AFK Sandboxing
48
- Declare a sandbox for your thread and let Claude work autonomously. Assign large, complex tasks and let Claude execute them while you step away from the keyboard.
49
-
50
- ### πŸ“Š Visual Dashboard
51
- Monitor your entire workspace through a beautiful, locally hosted web UI. Track active threads, review past conversations, and watch autonomous agent progress in real-time.
52
-
53
- ---
54
-
55
- ## πŸš€ Installation
56
-
57
- Tylor installs seamlessly into your Claude Code, Claude Desktop, GitHub Copilot, Antigravity, or VSCode Claude extension environment. Requires Python 3.8+.
58
-
59
- ### ⚑ Option 1: The One-Line Installer (Recommended)
60
-
61
- If you have Node.js installed, you can configure Tylor instantly across all your clients without manually cloning the repository. Simply run:
62
-
63
- ```bash
64
- npx tylor-mcp
65
- ```
66
-
67
- ### πŸ’» Option 2: Manual Git Clone
68
-
69
- **macOS / Linux / WSL:**
70
- ```bash
71
- git clone https://github.com/GunjanGrunge/tylor ~/.claude/plugins/GunjanGrunge/tylor
72
- python3 ~/.claude/plugins/GunjanGrunge/tylor/install.py
73
- ```
74
-
75
- **Windows:**
76
- ```powershell
77
- git clone https://github.com/GunjanGrunge/tylor %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor
78
- python %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor\install.py
79
- ```
80
-
81
- ### Step 3: Verify
82
-
83
- 1. Restart your Claude, GitHub Copilot, or Antigravity client completely (close the terminal/app and reopen it).
84
- 2. Type `/help-agent101` in your prompt (or use Copilot Chat / `/mcp show`).
85
- 3. If you see the capability index, Tylor is fully operational!
86
-
87
- ---
88
-
89
- ## πŸ•ΉοΈ Quick Start
90
-
91
- Creating your first persistent workflow is incredibly simple:
92
-
93
- ```text
94
- /new-thread Authentication ← Create a persistent workspace
95
- /run we need to implement JWT based authentication
96
-
97
- /new-thread Dashboard UI ← Create an isolated UI thread
98
- /run build a react dashboard with a sidebar
99
-
100
- /switch-thread Authentication ← Instantly switch context back to Auth
101
- /run add refresh token logic
102
-
103
- /list-threads ← View your workspace status
104
- /open-threads-ui ← Launch the visual dashboard
105
- ```
106
-
107
- ---
108
-
109
- ## πŸ› οΈ Command Reference
110
-
111
- Tylor exposes a suite of powerful commands directly within Claude:
112
-
113
- | Command | Description |
114
- |---|---|
115
- | `/new-thread <name>` | Create a named thread and seamlessly switch future work into it. |
116
- | `/switch-thread <name>` | Switch context to an existing thread (fuzzy matching supported). |
117
- | `/list-threads` | Show all available threads alongside their status and activity. |
118
- | `/kill-thread <name>` | Close a thread and dispatch asynchronous summarization. |
119
- | `/recall` | Search through the deep semantic memory of your active thread. |
120
- | `/add-skill` | Install a new skill package dynamically. |
121
- | `/open-threads-ui` | Open the live, local thread visualizer UI in your browser. |
122
- | `/set-sandbox <path>` | Declare specific filesystem roots for secure, autonomous execution. |
123
- | `/afk-status` | Get real-time progress reports on current autonomous background tasks. |
124
-
125
- > **Pro Tip:** You can also use shorthand aliases like `CT <name>` to create a thread or `SwThread <name>` to switch.
126
-
127
- ---
128
-
129
- ## 🎭 Sub-Agents & Personas
130
-
131
- Tylor comes pre-equipped with specialist sub-agents. Claude will **automatically invoke** these personas based on the nature of your queryβ€”no manual intervention required.
132
-
133
- * **`cto`**: System architecture, tradeoffs, platform strategy, and engineering standards.
134
- * **`code_agent`**: Senior software engineer laser-focused on shipping robust code and tests.
135
- * **`analyst`**: Market research, data synthesis, and technical decision support.
136
- * **`ceo`**: Product strategy, roadmap prioritization, and stakeholder framing.
137
-
138
- ---
139
-
140
- ## πŸ“„ License
141
-
142
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
143
-
144
- <div align="center">
145
- <p><em>Tylor β€” Tailoring the future of AI development.</em></p>
146
- </div>
1
+ <div align="center">
2
+ <img src="assets/tylor_logo.png" alt="Tylor Logo" width="150">
3
+ <h1>Tylor</h1>
4
+ <p><strong>The Tailor to Your Threads</strong></p>
5
+ <p><em>Give Claude Code persistent memory, laser-focused context, and an autonomous team of specialists.</em></p>
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+ [![Platform: Windows | macOS | Linux | WSL](https://img.shields.io/badge/Platform-Cross--Platform-success)](#)
9
+ [![Claude Code](https://img.shields.io/badge/Integration-Claude_Code-orange)](#)
10
+ [![GitHub Copilot](https://img.shields.io/badge/Integration-GitHub_Copilot-blue)](#)
11
+ [![Antigravity](https://img.shields.io/badge/Integration-Antigravity-blueviolet)](#)
12
+ </div>
13
+
14
+ ---
15
+
16
+ Tylor transforms your Claude Code experience from a single-shot terminal interaction into a **persistent, intelligent workspace**.
17
+
18
+ Every time you open Claude Code, you normally start from zero. Tylor fixes that. It organizes your work into **threads**β€”isolated, named workspaces that survive restarts and reboots. It remembers every decision, every line of code, and every discussion, so you never have to repeat yourself.
19
+
20
+ **No database. No cloud account. No configuration. Just install and go.**
21
+
22
+ ---
23
+
24
+ ## 🎨 How It Works
25
+
26
+ <div align="center">
27
+ <img src="assets/tylor_threads_concept.png" alt="Tylor Threads Architecture" width="800">
28
+ <p><em>Tylor weaves parallel, persistent memory threads and orchestrates specialist sub-agents.</em></p>
29
+ </div>
30
+
31
+ ---
32
+
33
+ ## ✨ Features
34
+
35
+ ### 🧠 Persistent Memory
36
+ Tylor completely eliminates the "context reset." Shut down your computer, close your terminal, and come back a week laterβ€”Claude will pick up exactly where you left off.
37
+
38
+ ### πŸ—‚οΈ Context Isolation (Threads)
39
+ Work in parallel without context bleed. Discuss frontend components in a `Frontend` thread and database schemas in a `Backend` thread. By isolating context, token usage stays low, and Claude's focus stays incredibly sharp.
40
+
41
+ ### πŸ€– Intelligent Orchestration
42
+ You don't need to micromanage. Claude acts as the orchestrator. If you ask it to review architecture, it will dynamically load its `cto` persona. If you ask it to write a PRD, it natively invokes the `bmad` skill framework to get the job done.
43
+
44
+ ### πŸ”Œ Infinite Extensibility (Lazy-Loading)
45
+ Tylor is built on a production-hardened ADK-pattern harness. You can register hundreds of domain-specific ECC skills (like `ecc/web`, `ecc/data`) via the `/add-skill` command. Tylor **lazy-loads** only the tools required for the current prompt, giving you massive capability scaling without ever blowing up Claude's token context window.
46
+
47
+ ### πŸ—οΈ Autonomous AFK Sandboxing
48
+ Declare a sandbox for your thread and let Claude work autonomously. Assign large, complex tasks and let Claude execute them while you step away from the keyboard.
49
+
50
+ ### πŸ“Š Visual Dashboard
51
+ Monitor your entire workspace through a beautiful, locally hosted web UI. Track active threads, review past conversations, and watch autonomous agent progress in real-time.
52
+
53
+ ---
54
+
55
+ ## πŸš€ Installation
56
+
57
+ Tylor installs seamlessly into your Claude Code, Claude Desktop, GitHub Copilot, Antigravity, or VSCode Claude extension environment. Requires Python 3.8+.
58
+
59
+ ### ⚑ Option 1: The One-Line Installer (Recommended)
60
+
61
+ If you have Node.js installed, you can configure Tylor instantly across all your clients without manually cloning the repository. Simply run:
62
+
63
+ ```bash
64
+ npx tylor-mcp
65
+ ```
66
+
67
+ ### πŸ’» Option 2: Manual Git Clone
68
+
69
+ **macOS / Linux / WSL:**
70
+ ```bash
71
+ git clone https://github.com/GunjanGrunge/tylor ~/.claude/plugins/GunjanGrunge/tylor
72
+ python3 ~/.claude/plugins/GunjanGrunge/tylor/install.py
73
+ ```
74
+
75
+ **Windows:**
76
+ ```powershell
77
+ git clone https://github.com/GunjanGrunge/tylor %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor
78
+ python %USERPROFILE%\.claude\plugins\GunjanGrunge\tylor\install.py
79
+ ```
80
+
81
+ ### Step 3: Verify
82
+
83
+ 1. Restart your Claude, GitHub Copilot, or Antigravity client completely (close the terminal/app and reopen it).
84
+ 2. Type `/help-agent101` in your prompt (or use Copilot Chat / `/mcp show`).
85
+ 3. If you see the capability index, Tylor is fully operational!
86
+
87
+ ---
88
+
89
+ ## πŸ•ΉοΈ Quick Start
90
+
91
+ Creating your first persistent workflow is incredibly simple:
92
+
93
+ ```text
94
+ /new-thread Authentication ← Create a persistent workspace
95
+ /run we need to implement JWT based authentication
96
+
97
+ /new-thread Dashboard UI ← Create an isolated UI thread
98
+ /run build a react dashboard with a sidebar
99
+
100
+ /switch-thread Authentication ← Instantly switch context back to Auth
101
+ /run add refresh token logic
102
+
103
+ /list-threads ← View your workspace status
104
+ /open-threads-ui ← Launch the visual dashboard
105
+ ```
106
+
107
+ ---
108
+
109
+ ## πŸ› οΈ Command Reference
110
+
111
+ Tylor exposes a suite of powerful commands directly within Claude:
112
+
113
+ | Command | Description |
114
+ |---|---|
115
+ | `/new-thread <name>` | Create a named thread and seamlessly switch future work into it. |
116
+ | `/switch-thread <name>` | Switch context to an existing thread (fuzzy matching supported). |
117
+ | `/list-threads` | Show all available threads alongside their status and activity. |
118
+ | `/kill-thread <name>` | Close a thread and dispatch asynchronous summarization. |
119
+ | `/recall` | Search through the deep semantic memory of your active thread. |
120
+ | `/add-skill` | Install a new skill package dynamically. |
121
+ | `/open-threads-ui` | Open the live, local thread visualizer UI in your browser. |
122
+ | `/set-sandbox <path>` | Declare specific filesystem roots for secure, autonomous execution. |
123
+ | `/afk-status` | Get real-time progress reports on current autonomous background tasks. |
124
+
125
+ > **Pro Tip:** You can also use shorthand aliases like `CT <name>` to create a thread or `SwThread <name>` to switch.
126
+
127
+ ---
128
+
129
+ ## πŸ”’ Bumblebee Security Gate
130
+
131
+ Tylor now includes a default, plugin-wide security gate powered by Bumblebee. When a risky command is detectedβ€”especially package installs, extension installs, skill/package additions, or MCP config changesβ€”Tylor will initiate a read-only Bumblebee scan before the command runs.
132
+
133
+ - Enabled by default for any command pattern that looks like `pip install`, `npm install`, editor/extension installs, or skill/config setup.
134
+ - If Bumblebee is missing, Tylor will flag the command and surface clear guidance instead of executing it blindly.
135
+ - If Bumblebee detects risk, execution is blocked and the user sees actionable alternatives.
136
+
137
+ Suggested responses from the gate include:
138
+
139
+ - Install Bumblebee or set `BUMBLEBEE_PATH` if the CLI is not found.
140
+ - Run `bumblebee scan --json` manually before retrying.
141
+ - Disable the gate temporarily with `BUMBLEBEE_ENABLED=false` only if you understand the risk.
142
+ - Review package metadata, MCP config changes, and AI tool integrations before proceeding.
143
+
144
+ This layer applies across the plugin, regardless of which thread or persona is active.
145
+
146
+ ---
147
+
148
+ ## 🎭 Sub-Agents & Personas
149
+
150
+ Tylor comes pre-equipped with specialist sub-agents. Claude will **automatically invoke** these personas based on the nature of your queryβ€”no manual intervention required.
151
+
152
+ * **`cto`**: System architecture, tradeoffs, platform strategy, and engineering standards.
153
+ * **`code_agent`**: Senior software engineer laser-focused on shipping robust code and tests.
154
+ * **`analyst`**: Market research, data synthesis, and technical decision support.
155
+ * **`ceo`**: Product strategy, roadmap prioritization, and stakeholder framing.
156
+
157
+ ---
158
+
159
+ ## πŸ“„ License
160
+
161
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
162
+
163
+ <div align="center">
164
+ <p><em>Tylor β€” Tailoring the future of AI development.</em></p>
165
+ </div>
package/bin/tylor.js CHANGED
@@ -1,7 +1,41 @@
1
1
  #!/usr/bin/env node
2
- const { spawnSync } = require('child_process');
2
+ const { spawnSync, execSync } = require('child_process');
3
3
  const path = require('path');
4
+ const https = require('https');
5
+ const fs = require('fs');
4
6
 
7
+ // ── Update check ──────────────────────────────────────────────────────────────
8
+ function checkForUpdate() {
9
+ try {
10
+ const pkgPath = path.join(__dirname, '..', 'package.json');
11
+ const localPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
12
+ const localVersion = localPkg.version;
13
+ const pkgName = localPkg.name;
14
+
15
+ const url = `https://registry.npmjs.org/${pkgName}/latest`;
16
+ const req = https.get(url, { timeout: 3000 }, (res) => {
17
+ let data = '';
18
+ res.on('data', chunk => data += chunk);
19
+ res.on('end', () => {
20
+ try {
21
+ const latest = JSON.parse(data).version;
22
+ if (latest && latest !== localVersion) {
23
+ console.log('');
24
+ console.log(` ⚠️ Update available: ${localVersion} β†’ ${latest}`);
25
+ console.log(` Run: npm update -g ${pkgName} (or npx ${pkgName}@latest)`);
26
+ console.log('');
27
+ }
28
+ } catch (_) { /* silent */ }
29
+ });
30
+ });
31
+ req.on('error', () => { /* silent β€” offline or registry down */ });
32
+ req.on('timeout', () => { req.destroy(); });
33
+ } catch (_) { /* silent */ }
34
+ }
35
+
36
+ checkForUpdate();
37
+
38
+ // ── Installer ─────────────────────────────────────────────────────────────────
5
39
  const installPy = path.join(__dirname, '..', 'install.py');
6
40
  const args = process.argv.slice(2);
7
41
 
@@ -13,7 +47,7 @@ let result = spawnSync('python', [installPy, ...args], { stdio: 'inherit' });
13
47
  // Fallback to `python3` if `python` fails (standard on macOS/Linux)
14
48
  if (result.error || result.status !== 0) {
15
49
  result = spawnSync('python3', [installPy, ...args], { stdio: 'inherit' });
16
-
50
+
17
51
  if (result.error) {
18
52
  console.error("❌ Failed to launch the Tylor installer. Please ensure Python 3.8+ is installed on your system.");
19
53
  process.exit(1);
@@ -21,3 +55,4 @@ if (result.error || result.status !== 0) {
21
55
  }
22
56
 
23
57
  process.exit(result.status);
58
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tylor-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Give Claude Code persistent memory, laser-focused context, and an autonomous team of specialists.",
5
5
  "main": "server/main.py",
6
6
  "bin": {
package/server/config.py CHANGED
@@ -1,8 +1,8 @@
1
1
  """
2
2
  server/config.py β€” Agent101 server configuration.
3
- Reads AWS profile, Bedrock region, and Platform on AWS key.
3
+ Reads local storage settings and optional cloud provider settings.
4
4
  Resolution order: env var β†’ ~/.agent101/config.json β†’ .env file β†’ defaults.
5
- Warns on missing optional config; never crashes on startup.
5
+ Missing cloud settings are normal in local-first mode; startup never crashes.
6
6
  """
7
7
  from __future__ import annotations
8
8
  import json
@@ -57,6 +57,7 @@ def _load() -> dict:
57
57
  cfg = {
58
58
  "aws_profile": _get("AWS_PROFILE"),
59
59
  "aws_access_key_id": _get("AWS_ACCESS_KEY_ID"),
60
+ "storage_mode": _get("AGENT101_STORAGE_MODE", default="local"),
60
61
  "bedrock_region": _get("BEDROCK_REGION", default="us-east-1"),
61
62
  "bedrock_opus_model": _get(
62
63
  "BEDROCK_OPUS_MODEL",
@@ -69,19 +70,13 @@ def _load() -> dict:
69
70
  "s3_bucket": _get("S3_BUCKET"),
70
71
  "opensearch_host": _get("OPENSEARCH_HOST"),
71
72
  "opensearch_port": _get("OPENSEARCH_PORT", default="9200"),
73
+ "bumblebee_enabled": str(_get("BUMBLEBEE_ENABLED", default="true")).strip().lower() in {"1", "true", "yes", "on"},
74
+ "bumblebee_path": _get("BUMBLEBEE_PATH"),
72
75
  }
73
76
 
74
- # Warn on optional keys that affect runtime features
75
- if not cfg["platform_key"]:
76
- logger.warning(
77
- "ANTHROPIC_PLATFORM_AWS_API_KEY/ANTHROPIC_AWS_API_KEY not set β€” "
78
- "token overflow fallback to Claude Platform on AWS is disabled"
79
- )
80
- if not cfg["opensearch_host"]:
81
- logger.warning(
82
- "OPENSEARCH_HOST not set β€” "
83
- "semantic memory recall (recall_memory) will be unavailable until configured"
84
- )
77
+ if cfg["storage_mode"] not in {"local", "aws"}:
78
+ logger.warning("Unknown AGENT101_STORAGE_MODE=%s; falling back to local mode", cfg["storage_mode"])
79
+ cfg["storage_mode"] = "local"
85
80
 
86
81
  return cfg
87
82
 
@@ -0,0 +1 @@
1
+ zsh: command not found: python
@@ -187,6 +187,29 @@ class DynamoClient:
187
187
  attributes["Task"] = task
188
188
  return self.put_item(sk=sk, attributes=attributes)
189
189
 
190
+ def put_agent_event(
191
+ self,
192
+ thread_id: str,
193
+ agent_id: str,
194
+ event_type: str,
195
+ content: str,
196
+ persona: str | None = None,
197
+ ) -> dict:
198
+ """Persist a streamed sub-agent event for live UI replay."""
199
+ self._validate_agent_id(agent_id)
200
+ sk = f"THREAD#{thread_id}#AGENT#{agent_id}#EVENT#{_unique_event_suffix()}"
201
+ self._assert_thread_isolation(thread_id, sk)
202
+ attributes = {
203
+ "ThreadId": thread_id,
204
+ "AgentId": agent_id,
205
+ "Type": "agent_event",
206
+ "EventType": event_type,
207
+ "Content": content,
208
+ }
209
+ if persona:
210
+ attributes["Persona"] = persona
211
+ return self.put_item(sk=sk, attributes=attributes)
212
+
190
213
  def put_agent_handoff(
191
214
  self,
192
215
  thread_id: str,
@@ -230,6 +253,16 @@ class DynamoClient:
230
253
  items = self.query_thread(thread_id, f"THREAD#{thread_id}#AGENT#")
231
254
  return [item for item in items if item.get("SK", "").endswith("#STATE")]
232
255
 
256
+ def query_agent_events(self, thread_id: str, agent_id: str | None = None) -> list:
257
+ """Return streamed sub-agent events for one thread, optionally one agent."""
258
+ prefix = f"THREAD#{thread_id}#AGENT#"
259
+ if agent_id:
260
+ self._validate_agent_id(agent_id)
261
+ prefix = f"{prefix}{agent_id}#EVENT#"
262
+ items = self.query_thread(thread_id, prefix)
263
+ events = [item for item in items if "#EVENT#" in item.get("SK", "")]
264
+ return sorted(events, key=lambda item: item.get("SK", ""))
265
+
233
266
  def get_thread_meta(self, thread_id: str) -> dict | None:
234
267
  """Return thread META item for the given thread_id."""
235
268
  return self.get_item(thread_id, f"THREAD#{thread_id}#META")
@@ -104,7 +104,7 @@ class JsonStore:
104
104
  "name": name, "status": "active",
105
105
  "created_at": now, "updated_at": now,
106
106
  "messages": [], "summary": None,
107
- "sandbox_roots": [], "agent_states": {}, "agent_outputs": [],
107
+ "sandbox_roots": [], "agent_states": {}, "agent_outputs": [], "agent_events": [],
108
108
  }
109
109
  data["threads"].append(thread)
110
110
  self._save(data)
@@ -159,6 +159,10 @@ class JsonStore:
159
159
  "updated_at": now,
160
160
  **({"project": item["Project"]} if item.get("Project") else {}),
161
161
  })
162
+ if "sandbox_roots" in item:
163
+ existing["sandbox_roots"] = list(item.get("sandbox_roots", []))
164
+ if "afk_session" in item:
165
+ existing["afk_session"] = item.get("afk_session")
162
166
  if "Summary" in item:
163
167
  existing["summary"] = item["Summary"]
164
168
  self._save(data)
@@ -170,7 +174,7 @@ class JsonStore:
170
174
  "project": item.get("Project", ""),
171
175
  "created_at": item.get("CreatedAt", now),
172
176
  "updated_at": now, "messages": [], "summary": None,
173
- "sandbox_roots": [], "agent_states": {}, "agent_outputs": [],
177
+ "sandbox_roots": [], "agent_states": {}, "agent_outputs": [], "agent_events": [],
174
178
  }
175
179
  data["threads"].append(thread)
176
180
  self._save(data)
@@ -249,9 +253,13 @@ class JsonStore:
249
253
  "CreatedAt": t.get("created_at", ""),
250
254
  "UpdatedAt": t.get("updated_at", ""),
251
255
  "Project": t.get("project", ""),
256
+ "sandbox_roots": list(t.get("sandbox_roots", [])),
257
+ **({"afk_session": t["afk_session"]} if "afk_session" in t else {}),
252
258
  }]
253
259
  for msg in t.get("messages", []):
254
260
  items.append(msg)
261
+ for event in t.get("agent_events", []):
262
+ items.append(event)
255
263
  return items
256
264
 
257
265
  def query_thread(self, thread_id: str, sk_prefix: str) -> list:
@@ -274,6 +282,8 @@ class JsonStore:
274
282
  "MessageCount": len(t.get("messages", [])),
275
283
  "CreatedAt": t.get("created_at", ""),
276
284
  "UpdatedAt": t.get("updated_at", ""),
285
+ "sandbox_roots": list(t.get("sandbox_roots", [])),
286
+ **({"afk_session": t["afk_session"]} if "afk_session" in t else {}),
277
287
  }
278
288
 
279
289
  def get_current_thread_marker(self) -> dict | None:
@@ -332,6 +342,33 @@ class JsonStore:
332
342
  self._save(data)
333
343
  return item
334
344
 
345
+ def put_agent_event(
346
+ self,
347
+ thread_id: str,
348
+ agent_id: str,
349
+ event_type: str,
350
+ content: str,
351
+ persona: str | None = None,
352
+ ) -> dict:
353
+ data = self._load()
354
+ t = self._require(data, thread_id)
355
+ now = _now_iso()
356
+ item = {
357
+ "SK": f"THREAD#{thread_id}#AGENT#{agent_id}#EVENT#{now}#{uuid.uuid4().hex}",
358
+ "ThreadId": thread_id,
359
+ "AgentId": agent_id,
360
+ "Type": "agent_event",
361
+ "EventType": event_type,
362
+ "Content": content,
363
+ "CreatedAt": now,
364
+ }
365
+ if persona:
366
+ item["Persona"] = persona
367
+ t.setdefault("agent_events", []).append(item)
368
+ t["updated_at"] = now
369
+ self._save(data)
370
+ return item
371
+
335
372
  def put_agent_handoff(self, thread_id: str, agent_id: str, handoff_state: dict) -> dict:
336
373
  data = self._load()
337
374
  t = self._require(data, thread_id)
@@ -357,3 +394,13 @@ class JsonStore:
357
394
  if not t:
358
395
  return []
359
396
  return [{"AgentId": aid, **s} for aid, s in t.get("agent_states", {}).items()]
397
+
398
+ def query_agent_events(self, thread_id: str, agent_id: str | None = None) -> list:
399
+ data = self._load()
400
+ t = self._find(data, thread_id)
401
+ if not t:
402
+ return []
403
+ events = list(t.get("agent_events", []))
404
+ if agent_id:
405
+ events = [e for e in events if e.get("AgentId") == agent_id]
406
+ return sorted(events, key=lambda e: e.get("SK", ""))
@@ -288,8 +288,6 @@ def test_platform_key_present_via_env():
288
288
  # --- AC8: run_all exits 0 even when all AWS checks fail ---
289
289
 
290
290
  def test_run_all_is_advisory(tmp_path, capsys):
291
- from botocore.exceptions import NoCredentialsError
292
-
293
291
  with (
294
292
  patch("server.validate.check_dynamodb", return_value=(False, " βœ— DynamoDB β€” no creds")),
295
293
  patch("server.validate.check_s3", return_value=(False, " βœ— S3 β€” no creds")),
@@ -299,8 +297,24 @@ def test_run_all_is_advisory(tmp_path, capsys):
299
297
  ):
300
298
  error_count = run_all(str(tmp_path))
301
299
 
302
- assert error_count == 3 # 3 AWS failures counted
303
- # Caller (install.sh) always exits 0 β€” this just returns the count
300
+ assert error_count == 0
301
+ # Local JSON storage is the default; AWS failures are irrelevant until opted in.
302
+
303
+
304
+ def test_run_all_counts_aws_failures_when_explicitly_enabled(tmp_path, monkeypatch):
305
+ monkeypatch.setenv("AGENT101_ENABLE_AWS", "1")
306
+ validate_module = run_all.__module__
307
+
308
+ with (
309
+ patch(f"{validate_module}.check_dynamodb", return_value=(False, " βœ— DynamoDB β€” no creds")),
310
+ patch(f"{validate_module}.check_s3", return_value=(False, " βœ— S3 β€” no creds")),
311
+ patch(f"{validate_module}.check_bedrock", return_value=(False, " βœ— Bedrock β€” no creds")),
312
+ patch(f"{validate_module}.check_opensearch", return_value=(True, " ⚠ OpenSearch β€” skipped")),
313
+ patch(f"{validate_module}.check_platform_key", return_value=(True, " ⚠ key not set")),
314
+ ):
315
+ error_count = run_all(str(tmp_path))
316
+
317
+ assert error_count == 3
304
318
 
305
319
 
306
320
  # ---------------------------------------------------------------------------