pynydus 0.0.4__tar.gz

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 (44) hide show
  1. pynydus-0.0.4/PKG-INFO +138 -0
  2. pynydus-0.0.4/README.md +118 -0
  3. pynydus-0.0.4/pynydus/__init__.py +33 -0
  4. pynydus-0.0.4/pynydus/agents/__init__.py +1 -0
  5. pynydus-0.0.4/pynydus/agents/letta/README.md +46 -0
  6. pynydus-0.0.4/pynydus/agents/letta/__init__.py +1 -0
  7. pynydus-0.0.4/pynydus/agents/letta/hatcher.py +230 -0
  8. pynydus-0.0.4/pynydus/agents/letta/spawner.py +658 -0
  9. pynydus-0.0.4/pynydus/agents/openclaw/README.md +33 -0
  10. pynydus-0.0.4/pynydus/agents/openclaw/__init__.py +1 -0
  11. pynydus-0.0.4/pynydus/agents/openclaw/hatcher.py +152 -0
  12. pynydus-0.0.4/pynydus/agents/openclaw/spawner.py +221 -0
  13. pynydus-0.0.4/pynydus/agents/zeroclaw/README.md +36 -0
  14. pynydus-0.0.4/pynydus/agents/zeroclaw/__init__.py +1 -0
  15. pynydus-0.0.4/pynydus/agents/zeroclaw/hatcher.py +155 -0
  16. pynydus-0.0.4/pynydus/agents/zeroclaw/spawner.py +349 -0
  17. pynydus-0.0.4/pynydus/api/__init__.py +4 -0
  18. pynydus-0.0.4/pynydus/api/errors.py +34 -0
  19. pynydus-0.0.4/pynydus/api/raw_types.py +65 -0
  20. pynydus-0.0.4/pynydus/api/schemas.py +347 -0
  21. pynydus-0.0.4/pynydus/api/skill_format.py +222 -0
  22. pynydus-0.0.4/pynydus/client/__init__.py +5 -0
  23. pynydus-0.0.4/pynydus/client/client.py +265 -0
  24. pynydus-0.0.4/pynydus/cmd/__init__.py +1 -0
  25. pynydus-0.0.4/pynydus/cmd/main.py +666 -0
  26. pynydus-0.0.4/pynydus/engine/__init__.py +1 -0
  27. pynydus-0.0.4/pynydus/engine/differ.py +154 -0
  28. pynydus-0.0.4/pynydus/engine/hatcher.py +307 -0
  29. pynydus-0.0.4/pynydus/engine/merger.py +213 -0
  30. pynydus-0.0.4/pynydus/engine/nydusfile.py +400 -0
  31. pynydus-0.0.4/pynydus/engine/packager.py +413 -0
  32. pynydus-0.0.4/pynydus/engine/pipeline.py +642 -0
  33. pynydus-0.0.4/pynydus/engine/refinement.py +501 -0
  34. pynydus-0.0.4/pynydus/engine/validator.py +79 -0
  35. pynydus-0.0.4/pynydus/pkg/__init__.py +1 -0
  36. pynydus-0.0.4/pynydus/pkg/config.py +90 -0
  37. pynydus-0.0.4/pynydus/pkg/connector_utils.py +113 -0
  38. pynydus-0.0.4/pynydus/pkg/credential_scanner.py +185 -0
  39. pynydus-0.0.4/pynydus/pkg/llm.py +170 -0
  40. pynydus-0.0.4/pynydus/pkg/presidio.py +296 -0
  41. pynydus-0.0.4/pynydus/pkg/signing.py +183 -0
  42. pynydus-0.0.4/pynydus/remote/__init__.py +5 -0
  43. pynydus-0.0.4/pynydus/remote/registry.py +342 -0
  44. pynydus-0.0.4/pyproject.toml +70 -0
pynydus-0.0.4/PKG-INFO ADDED
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.3
2
+ Name: pynydus
3
+ Version: 0.0.4
4
+ Summary: Portable state transport for AI agents
5
+ Author: Nydus Team
6
+ Author-email: Nydus Team <contact@nydus.ag>
7
+ License: MIT
8
+ Requires-Dist: cryptography>=43.0.0
9
+ Requires-Dist: pydantic>=2.0
10
+ Requires-Dist: typer>=0.15
11
+ Requires-Dist: rich>=13.0
12
+ Requires-Dist: presidio-analyzer>=2.2
13
+ Requires-Dist: presidio-anonymizer>=2.2
14
+ Requires-Dist: instructor>=1.0
15
+ Requires-Dist: anthropic>=0.40
16
+ Requires-Dist: pyyaml>=6.0
17
+ Requires-Dist: tomli>=2.0 ; python_full_version < '3.11'
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ <p align="center">
22
+ <img src="https://raw.githubusercontent.com/NydusAI/nydus/main/assets/logo.png" alt="Nydus" width="420">
23
+ </p>
24
+
25
+ <p align="center">
26
+ <strong>Portable state transport for AI agents</strong><br>
27
+ Transport agent state between any two frameworks without manual migration
28
+ </p>
29
+
30
+ <p align="center">
31
+ <a href="https://pypi.org/project/pynydus/"><img src="https://img.shields.io/pypi/v/pynydus?color=blue" alt="PyPI"></a>
32
+ <a href="https://pypi.org/project/pynydus/"><img src="https://img.shields.io/pypi/pyversions/pynydus" alt="Python"></a>
33
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
34
+ <a href="#status"><img src="https://img.shields.io/badge/status-early%20development-orange" alt="Status"></a>
35
+ </p>
36
+
37
+ <p align="center">
38
+ <a href="#install">Install</a> •
39
+ <a href="#how-it-works">How it works</a> •
40
+ <a href="#cli">CLI</a> •
41
+ <a href="#python-sdk">Python SDK</a>
42
+ </p>
43
+
44
+ ---
45
+
46
+ ## How it works
47
+
48
+ - **Spawn**: read source agent artifacts, redact PII, and package everything into a portable **Egg** (`.egg` archive)
49
+ - **Hatch**: decode an Egg into any supported target runtime, with optional LLM refinement and secret injection
50
+
51
+ ```
52
+ Source artifacts → Spawn → Egg (.egg) → Hatch → Target-native files
53
+ ```
54
+
55
+ ### The Egg format
56
+
57
+ An `.egg` file is a signed ZIP archive containing:
58
+
59
+ ```
60
+ manifest.json # metadata, source type, versions
61
+ memory.json # labeled memory records
62
+ secrets.json # PII placeholders + secret requirements
63
+ skills/<slug>/SKILL.md # portable skill definitions (agentskills.io)
64
+ mcp/<server>.json # MCP server configs
65
+ raw/... # redacted source files (for pass-through hatch)
66
+ ```
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ pip install pynydus
72
+ ```
73
+
74
+ ## CLI
75
+
76
+ ```bash
77
+ # Create a Nydusfile in your project directory
78
+ cat > Nydusfile << 'EOF'
79
+ SOURCE openclaw ./my-agent/
80
+ REDACT pii
81
+ EOF
82
+
83
+ # Spawn an egg
84
+ nydus spawn -o agent.egg
85
+
86
+ # Inspect and validate
87
+ nydus inspect agent.egg
88
+ nydus inspect agent.egg --secrets --logs
89
+ nydus validate agent.egg
90
+
91
+ # Generate a template .env from the egg's secret requirements
92
+ nydus env agent.egg -o agent.env
93
+
94
+ # Hatch into a target runtime
95
+ nydus hatch agent.egg --target letta --secrets agent.env
96
+
97
+ # Compare two eggs
98
+ nydus diff v1.egg v2.egg
99
+
100
+ # Signing
101
+ nydus keygen
102
+ nydus spawn -o signed.egg # auto-signs if key exists
103
+ ```
104
+
105
+ ## Python SDK
106
+
107
+ ```python
108
+ from pynydus.client import Nydus
109
+
110
+ ny = Nydus()
111
+
112
+ # Spawn from a Nydusfile in the current directory
113
+ egg_path = ny.spawn()
114
+
115
+ # Hatch into a target runtime with secret injection
116
+ result = ny.hatch(egg_path, target="letta", secrets="agent.env")
117
+ print(result.output_dir, result.files_created)
118
+ ```
119
+
120
+ ## Project structure
121
+
122
+ ```
123
+ pynydus/
124
+ api/ # Egg data model, schemas, errors
125
+ agents/ # Per-platform spawners + hatchers
126
+ openclaw/
127
+ zeroclaw/
128
+ letta/
129
+ engine/ # Core pipelines: spawn, hatch, pack, validate, diff, refine
130
+ cmd/ # Typer CLI
131
+ client/ # Python SDK
132
+ pkg/ # Utilities: presidio, LLM, signing, config
133
+ remote/ # Nest registry client
134
+ ```
135
+
136
+ ## License
137
+
138
+ MIT
@@ -0,0 +1,118 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/NydusAI/nydus/main/assets/logo.png" alt="Nydus" width="420">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <strong>Portable state transport for AI agents</strong><br>
7
+ Transport agent state between any two frameworks without manual migration
8
+ </p>
9
+
10
+ <p align="center">
11
+ <a href="https://pypi.org/project/pynydus/"><img src="https://img.shields.io/pypi/v/pynydus?color=blue" alt="PyPI"></a>
12
+ <a href="https://pypi.org/project/pynydus/"><img src="https://img.shields.io/pypi/pyversions/pynydus" alt="Python"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
14
+ <a href="#status"><img src="https://img.shields.io/badge/status-early%20development-orange" alt="Status"></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="#install">Install</a> •
19
+ <a href="#how-it-works">How it works</a> •
20
+ <a href="#cli">CLI</a> •
21
+ <a href="#python-sdk">Python SDK</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ ## How it works
27
+
28
+ - **Spawn**: read source agent artifacts, redact PII, and package everything into a portable **Egg** (`.egg` archive)
29
+ - **Hatch**: decode an Egg into any supported target runtime, with optional LLM refinement and secret injection
30
+
31
+ ```
32
+ Source artifacts → Spawn → Egg (.egg) → Hatch → Target-native files
33
+ ```
34
+
35
+ ### The Egg format
36
+
37
+ An `.egg` file is a signed ZIP archive containing:
38
+
39
+ ```
40
+ manifest.json # metadata, source type, versions
41
+ memory.json # labeled memory records
42
+ secrets.json # PII placeholders + secret requirements
43
+ skills/<slug>/SKILL.md # portable skill definitions (agentskills.io)
44
+ mcp/<server>.json # MCP server configs
45
+ raw/... # redacted source files (for pass-through hatch)
46
+ ```
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install pynydus
52
+ ```
53
+
54
+ ## CLI
55
+
56
+ ```bash
57
+ # Create a Nydusfile in your project directory
58
+ cat > Nydusfile << 'EOF'
59
+ SOURCE openclaw ./my-agent/
60
+ REDACT pii
61
+ EOF
62
+
63
+ # Spawn an egg
64
+ nydus spawn -o agent.egg
65
+
66
+ # Inspect and validate
67
+ nydus inspect agent.egg
68
+ nydus inspect agent.egg --secrets --logs
69
+ nydus validate agent.egg
70
+
71
+ # Generate a template .env from the egg's secret requirements
72
+ nydus env agent.egg -o agent.env
73
+
74
+ # Hatch into a target runtime
75
+ nydus hatch agent.egg --target letta --secrets agent.env
76
+
77
+ # Compare two eggs
78
+ nydus diff v1.egg v2.egg
79
+
80
+ # Signing
81
+ nydus keygen
82
+ nydus spawn -o signed.egg # auto-signs if key exists
83
+ ```
84
+
85
+ ## Python SDK
86
+
87
+ ```python
88
+ from pynydus.client import Nydus
89
+
90
+ ny = Nydus()
91
+
92
+ # Spawn from a Nydusfile in the current directory
93
+ egg_path = ny.spawn()
94
+
95
+ # Hatch into a target runtime with secret injection
96
+ result = ny.hatch(egg_path, target="letta", secrets="agent.env")
97
+ print(result.output_dir, result.files_created)
98
+ ```
99
+
100
+ ## Project structure
101
+
102
+ ```
103
+ pynydus/
104
+ api/ # Egg data model, schemas, errors
105
+ agents/ # Per-platform spawners + hatchers
106
+ openclaw/
107
+ zeroclaw/
108
+ letta/
109
+ engine/ # Core pipelines: spawn, hatch, pack, validate, diff, refine
110
+ cmd/ # Typer CLI
111
+ client/ # Python SDK
112
+ pkg/ # Utilities: presidio, LLM, signing, config
113
+ remote/ # Nest registry client
114
+ ```
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,33 @@
1
+ """pynydus — Portable state transport for AI agents."""
2
+
3
+ __version__ = "0.1.0"
4
+ EGG_SPEC_VERSION = "2.0"
5
+
6
+ from pynydus.api.schemas import (
7
+ DiffReport,
8
+ Egg,
9
+ HatchResult,
10
+ Manifest,
11
+ McpServerConfig,
12
+ MemoryRecord,
13
+ SecretRecord,
14
+ SkillRecord,
15
+ SpawnAttachments,
16
+ ValidationReport,
17
+ )
18
+ from pynydus.client.client import Nydus
19
+
20
+ __all__ = [
21
+ "DiffReport",
22
+ "EGG_SPEC_VERSION",
23
+ "Egg",
24
+ "HatchResult",
25
+ "Manifest",
26
+ "McpServerConfig",
27
+ "MemoryRecord",
28
+ "Nydus",
29
+ "SecretRecord",
30
+ "SkillRecord",
31
+ "SpawnAttachments",
32
+ "ValidationReport",
33
+ ]
@@ -0,0 +1 @@
1
+ """Agent connectors — one sub-package per framework (spawner + hatcher)."""
@@ -0,0 +1,46 @@
1
+ # Letta Agent
2
+
3
+ ## Spawner: source files → Egg
4
+
5
+ Supports both directory-based and SQLite database inputs.
6
+
7
+ ### Directory mode
8
+
9
+ | Source file | Egg field | Label |
10
+ |---|---|---|
11
+ | `agent_state.json` → `system` | `MemoryRecord` | `FLOW` |
12
+ | `agent_state.json` → `memory.persona` | `MemoryRecord` | `PERSONA` |
13
+ | `agent_state.json` → `memory.human` | `MemoryRecord` | `CONTEXT` |
14
+ | `agent_state.json` → `memory.<other>` | `MemoryRecord` | `CONTEXT` |
15
+ | `agent_state.json` → `tools[]` | `SkillRecord` | |
16
+ | `system_prompt.md` / `.txt` | `MemoryRecord` (fallback if no system in state) | `FLOW` |
17
+ | `archival_memory.json` | `MemoryRecord` | `STATE` |
18
+ | `archival/*` | `MemoryRecord` | `STATE` |
19
+ | `tools/*.py` | `SkillRecord` | |
20
+ | `.letta/config.json` | `SecretRecord` | |
21
+
22
+ ### Database mode (`agent.db`)
23
+
24
+ | DB table | Egg field | Label |
25
+ |---|---|---|
26
+ | `blocks` (persona) | `MemoryRecord` | `PERSONA` |
27
+ | `blocks` (human) | `MemoryRecord` | `CONTEXT` |
28
+ | `blocks` (system) | `MemoryRecord` | `FLOW` |
29
+ | `blocks` (other) | `MemoryRecord` | `CONTEXT` |
30
+ | `archival_memory` | `MemoryRecord` | `STATE` |
31
+ | `tools` | `SkillRecord` | |
32
+ | `agents` → config JSON | `SecretRecord` | |
33
+
34
+ Detection: directory with `agent_state.json`, `.letta/`, `tools/`, or
35
+ `agent.db`, or a `.db` file directly.
36
+
37
+ ## Hatcher: Egg → target files
38
+
39
+ | Egg field | Target file | Notes |
40
+ |---|---|---|
41
+ | `MemoryRecord` (`PERSONA`) | `agent_state.json` → `memory.persona` | Appended to block value |
42
+ | `MemoryRecord` (`CONTEXT`) | `agent_state.json` → `memory.human` | Appended to block value |
43
+ | `MemoryRecord` (`FLOW`) | `agent_state.json` → `system` + `system_prompt.md` | Duplicate for convenience |
44
+ | `MemoryRecord` (`STATE`) | `archival_memory.json` | Array of `{text, timestamp}` |
45
+ | `SkillRecord` | `tools/<name>.py` + `agent_state.json` → `tools[]` | Source code in both |
46
+ | `SecretRecord` (`CREDENTIAL`) | `.letta/config.json` | `{name: placeholder}` |
@@ -0,0 +1 @@
1
+ """Letta agent connector."""
@@ -0,0 +1,230 @@
1
+ """Letta hatcher connector. Spec §10.3.
2
+
3
+ Produces a valid Letta agent project directory from an Egg:
4
+ - agent_state.json <- memory blocks + agent config skeleton
5
+ - tools/ <- skill records as Python tool files
6
+ - archival_memory.json <- state memory
7
+ - system_prompt.md <- flow memory
8
+
9
+ All 4 MemoryLabel values have explicit mappings:
10
+ - PERSONA -> memory.persona block in agent_state.json
11
+ - CONTEXT -> memory.human block in agent_state.json
12
+ - FLOW -> system field + system_prompt.md
13
+ - STATE -> archival_memory.json
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from pathlib import Path
20
+
21
+ from pynydus.api.errors import HatchError
22
+ from pynydus.api.raw_types import RenderResult
23
+ from pynydus.api.schemas import (
24
+ Egg,
25
+ HatchResult,
26
+ MemoryLabel,
27
+ SecretKind,
28
+ ValidationIssue,
29
+ ValidationReport,
30
+ )
31
+ from pynydus.pkg.connector_utils import skill_to_filename as _skill_to_filename
32
+
33
+ _LABEL_TO_BLOCK: dict[MemoryLabel, str] = {
34
+ MemoryLabel.PERSONA: "persona",
35
+ MemoryLabel.CONTEXT: "human",
36
+ MemoryLabel.FLOW: "system",
37
+ }
38
+
39
+
40
+ class LettaHatcher:
41
+ """Produce a valid Letta agent directory from an Egg."""
42
+
43
+ def render(self, egg: Egg) -> RenderResult:
44
+ """Render Egg records into target file contents.
45
+
46
+ Returns a dict of ``filename -> content`` with ``{{SECRET_NNN}}``
47
+ and ``{{PII_NNN}}`` placeholders intact.
48
+ """
49
+ files: dict[str, str] = {}
50
+ warnings: list[str] = []
51
+
52
+ # --- agent_state.json ---
53
+ agent_state = self._build_agent_state(egg)
54
+ files["agent_state.json"] = json.dumps(agent_state, indent=2) + "\n"
55
+
56
+ # --- system_prompt.md ---
57
+ system_records = [
58
+ m for m in egg.memory.memory if m.label == MemoryLabel.FLOW
59
+ ]
60
+ if system_records:
61
+ files["system_prompt.md"] = "\n\n".join(r.text for r in system_records) + "\n"
62
+
63
+ # --- tools/ directory ---
64
+ if egg.skills.skills:
65
+ for skill in egg.skills.skills:
66
+ fname = _skill_to_filename(skill.name)
67
+ files[f"tools/{fname}"] = skill.content + "\n"
68
+
69
+ # --- archival_memory.json (state) ---
70
+ state_records = [
71
+ m for m in egg.memory.memory if m.label == MemoryLabel.STATE
72
+ ]
73
+ if state_records:
74
+ entries = []
75
+ for rec in state_records:
76
+ entry: dict[str, str | None] = {"text": rec.text}
77
+ entry["timestamp"] = rec.timestamp.isoformat() if rec.timestamp else None
78
+ entries.append(entry)
79
+ files["archival_memory.json"] = json.dumps(entries, indent=2) + "\n"
80
+
81
+ # --- .letta/config.json (credential placeholders) ---
82
+ credentials = [
83
+ s for s in egg.secrets.secrets if s.kind == SecretKind.CREDENTIAL
84
+ ]
85
+ if credentials:
86
+ config = {s.name: s.placeholder for s in credentials}
87
+ files[".letta/config.json"] = json.dumps(config, indent=2) + "\n"
88
+
89
+ if not files:
90
+ raise HatchError("Egg produced no output files for Letta target")
91
+
92
+ return RenderResult(files=files, warnings=warnings)
93
+
94
+ def hatch(self, egg: Egg, output_dir: Path) -> HatchResult:
95
+ """Generate Letta project files from an Egg.
96
+
97
+ .. deprecated::
98
+ Use :meth:`render` instead. The pipeline now handles disk I/O.
99
+ """
100
+ result = self.render(egg)
101
+
102
+ output_dir.mkdir(parents=True, exist_ok=True)
103
+ files_created: list[str] = []
104
+ for fname, content in result.files.items():
105
+ fpath = output_dir / fname
106
+ fpath.parent.mkdir(parents=True, exist_ok=True)
107
+ fpath.write_text(content)
108
+ files_created.append(fname)
109
+
110
+ return HatchResult(
111
+ target="letta",
112
+ output_dir=output_dir,
113
+ files_created=files_created,
114
+ warnings=list(result.warnings),
115
+ )
116
+
117
+ def validate(self, result: HatchResult) -> ValidationReport:
118
+ """Validate generated Letta output."""
119
+ issues: list[ValidationIssue] = []
120
+
121
+ if "agent_state.json" not in result.files_created:
122
+ issues.append(
123
+ ValidationIssue(
124
+ level="warning",
125
+ message="agent_state.json was not generated",
126
+ location=str(result.output_dir),
127
+ )
128
+ )
129
+
130
+ for fname in result.files_created:
131
+ fpath = result.output_dir / fname
132
+ if not fpath.exists():
133
+ issues.append(
134
+ ValidationIssue(
135
+ level="error",
136
+ message=f"Expected file not found: {fname}",
137
+ location=str(fpath),
138
+ )
139
+ )
140
+
141
+ state_path = result.output_dir / "agent_state.json"
142
+ if state_path.exists():
143
+ try:
144
+ data = json.loads(state_path.read_text())
145
+ if not isinstance(data, dict):
146
+ issues.append(
147
+ ValidationIssue(
148
+ level="error",
149
+ message="agent_state.json is not a JSON object",
150
+ location=str(state_path),
151
+ )
152
+ )
153
+ elif "memory" not in data and "system" not in data:
154
+ issues.append(
155
+ ValidationIssue(
156
+ level="warning",
157
+ message="agent_state.json has no memory blocks or system prompt",
158
+ location=str(state_path),
159
+ )
160
+ )
161
+ except json.JSONDecodeError:
162
+ issues.append(
163
+ ValidationIssue(
164
+ level="error",
165
+ message="agent_state.json is not valid JSON",
166
+ location=str(state_path),
167
+ )
168
+ )
169
+
170
+ return ValidationReport(
171
+ valid=not any(i.level == "error" for i in issues),
172
+ issues=issues,
173
+ )
174
+
175
+ # ------------------------------------------------------------------
176
+ # Helpers
177
+ # ------------------------------------------------------------------
178
+
179
+ def _build_agent_state(self, egg: Egg) -> dict:
180
+ """Build the agent_state.json content from Egg data."""
181
+ state: dict = {
182
+ "name": "nydus_agent",
183
+ "memory": {},
184
+ "tools": [],
185
+ }
186
+
187
+ for mem_rec in egg.memory.memory:
188
+ if mem_rec.label in _LABEL_TO_BLOCK:
189
+ block_name = _LABEL_TO_BLOCK[mem_rec.label]
190
+ if block_name in state["memory"]:
191
+ state["memory"][block_name]["value"] += "\n\n" + mem_rec.text
192
+ else:
193
+ state["memory"][block_name] = {
194
+ "value": mem_rec.text,
195
+ "limit": 5000,
196
+ }
197
+
198
+ flow_records = [
199
+ m for m in egg.memory.memory if m.label == MemoryLabel.FLOW
200
+ ]
201
+ if flow_records:
202
+ state["system"] = "\n\n".join(r.text for r in flow_records)
203
+
204
+ for skill in egg.skills.skills:
205
+ state["tools"].append({
206
+ "name": _skill_to_module_name(skill.name),
207
+ "source_code": skill.content,
208
+ })
209
+
210
+ if egg.manifest.source_metadata:
211
+ state["metadata"] = {
212
+ "nydus_source": egg.manifest.source_connector,
213
+ "nydus_version": egg.manifest.nydus_version,
214
+ }
215
+
216
+ return state
217
+
218
+
219
+ # ---------------------------------------------------------------------------
220
+ # Utility functions
221
+ # ---------------------------------------------------------------------------
222
+
223
+
224
+ def _skill_to_module_name(name: str) -> str:
225
+ """Convert a skill display name to a Python module name."""
226
+ module = name.lower().strip().replace(" ", "_").replace("-", "_")
227
+ module = "".join(c for c in module if c.isalnum() or c == "_")
228
+ if module and module[0].isdigit():
229
+ module = f"tool_{module}"
230
+ return module or "tool"