aegis-memory 1.2.0__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.
- aegis_memory/__init__.py +47 -0
- aegis_memory/cli/__init__.py +9 -0
- aegis_memory/cli/commands/__init__.py +29 -0
- aegis_memory/cli/commands/config.py +208 -0
- aegis_memory/cli/commands/export_import.py +205 -0
- aegis_memory/cli/commands/features.py +334 -0
- aegis_memory/cli/commands/memory.py +353 -0
- aegis_memory/cli/commands/playbook.py +132 -0
- aegis_memory/cli/commands/progress.py +304 -0
- aegis_memory/cli/commands/stats.py +164 -0
- aegis_memory/cli/commands/status.py +110 -0
- aegis_memory/cli/commands/vote.py +76 -0
- aegis_memory/cli/main.py +84 -0
- aegis_memory/cli/utils/__init__.py +43 -0
- aegis_memory/cli/utils/auth.py +108 -0
- aegis_memory/cli/utils/config.py +159 -0
- aegis_memory/cli/utils/errors.py +166 -0
- aegis_memory/cli/utils/output.py +205 -0
- aegis_memory/client.py +1116 -0
- aegis_memory/integrations/__init__.py +22 -0
- aegis_memory/integrations/crewai.py +318 -0
- aegis_memory/integrations/langchain.py +268 -0
- aegis_memory-1.2.0.dist-info/METADATA +303 -0
- aegis_memory-1.2.0.dist-info/RECORD +27 -0
- aegis_memory-1.2.0.dist-info/WHEEL +4 -0
- aegis_memory-1.2.0.dist-info/entry_points.txt +2 -0
- aegis_memory-1.2.0.dist-info/licenses/LICENSE +190 -0
aegis_memory/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aegis Memory - Agent-native memory fabric for AI agents.
|
|
3
|
+
|
|
4
|
+
Aegis Memory is an open-source, self-hostable memory engine for LLM agents with:
|
|
5
|
+
- Agent-native semantics (namespace, scope, multi-agent ACLs)
|
|
6
|
+
- First-class multi-agent support (cross-agent queries, structured handoffs)
|
|
7
|
+
- Context-engineering patterns (ACE-style voting, deltas, reflections, playbooks)
|
|
8
|
+
- Production-oriented design (FastAPI + Postgres + pgvector)
|
|
9
|
+
|
|
10
|
+
Quick Start:
|
|
11
|
+
from aegis_memory import AegisClient
|
|
12
|
+
|
|
13
|
+
client = AegisClient(api_key="your-key", base_url="http://localhost:8000")
|
|
14
|
+
|
|
15
|
+
# Add a memory
|
|
16
|
+
result = client.add("User prefers dark mode", agent_id="ui-agent")
|
|
17
|
+
|
|
18
|
+
# Query memories
|
|
19
|
+
memories = client.query("user preferences", agent_id="ui-agent")
|
|
20
|
+
|
|
21
|
+
# Cross-agent query with access control
|
|
22
|
+
memories = client.query_cross_agent(
|
|
23
|
+
"user settings",
|
|
24
|
+
requesting_agent_id="settings-agent"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
For more examples, see: https://github.com/quantifylabs/aegis-memory/tree/main/examples
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
__version__ = "1.2.0"
|
|
31
|
+
|
|
32
|
+
from aegis_memory.client import (
|
|
33
|
+
AegisClient,
|
|
34
|
+
Memory,
|
|
35
|
+
PlaybookEntry,
|
|
36
|
+
SessionProgress,
|
|
37
|
+
Feature,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AegisClient",
|
|
42
|
+
"Memory",
|
|
43
|
+
"PlaybookEntry",
|
|
44
|
+
"SessionProgress",
|
|
45
|
+
"Feature",
|
|
46
|
+
"__version__",
|
|
47
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aegis CLI Commands
|
|
3
|
+
|
|
4
|
+
All command implementations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from aegis_memory.cli.commands.config import app as config_app
|
|
8
|
+
from aegis_memory.cli.commands import (
|
|
9
|
+
status,
|
|
10
|
+
stats,
|
|
11
|
+
memory,
|
|
12
|
+
vote,
|
|
13
|
+
progress,
|
|
14
|
+
features,
|
|
15
|
+
playbook,
|
|
16
|
+
export_import,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"config_app",
|
|
21
|
+
"status",
|
|
22
|
+
"stats",
|
|
23
|
+
"memory",
|
|
24
|
+
"vote",
|
|
25
|
+
"progress",
|
|
26
|
+
"features",
|
|
27
|
+
"playbook",
|
|
28
|
+
"export_import",
|
|
29
|
+
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aegis CLI Config Command
|
|
3
|
+
|
|
4
|
+
Configuration management: init, show, set, use profiles.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.prompt import Prompt, Confirm
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
|
|
13
|
+
from aegis_memory.cli.utils.config import (
|
|
14
|
+
load_config,
|
|
15
|
+
save_config,
|
|
16
|
+
load_credentials,
|
|
17
|
+
save_credentials,
|
|
18
|
+
get_config_path,
|
|
19
|
+
get_credentials_path,
|
|
20
|
+
get_active_profile,
|
|
21
|
+
set_nested_value,
|
|
22
|
+
get_nested_value,
|
|
23
|
+
)
|
|
24
|
+
from aegis_memory.cli.utils.auth import get_client, get_api_key
|
|
25
|
+
from aegis_memory.cli.utils.output import print_success, print_error, print_warning
|
|
26
|
+
|
|
27
|
+
app = typer.Typer(help="Configuration management")
|
|
28
|
+
console = Console()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def init(
|
|
33
|
+
non_interactive: bool = typer.Option(False, "--non-interactive", "-y", help="Use defaults without prompting"),
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Interactive first-run setup.
|
|
37
|
+
|
|
38
|
+
Creates configuration and credentials files.
|
|
39
|
+
"""
|
|
40
|
+
console.print("\n[bold]Welcome to Aegis Memory CLI![/bold]\n")
|
|
41
|
+
|
|
42
|
+
config = load_config()
|
|
43
|
+
credentials = load_credentials()
|
|
44
|
+
|
|
45
|
+
if non_interactive:
|
|
46
|
+
# Use defaults
|
|
47
|
+
api_url = "http://localhost:8000"
|
|
48
|
+
api_key = "dev-key"
|
|
49
|
+
namespace = "default"
|
|
50
|
+
agent_id = "cli-user"
|
|
51
|
+
else:
|
|
52
|
+
# Interactive prompts
|
|
53
|
+
api_url = Prompt.ask(
|
|
54
|
+
"Server URL",
|
|
55
|
+
default="http://localhost:8000"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
api_key = Prompt.ask(
|
|
59
|
+
"API Key",
|
|
60
|
+
password=True,
|
|
61
|
+
default="dev-key"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
namespace = Prompt.ask(
|
|
65
|
+
"Default namespace",
|
|
66
|
+
default="default"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
agent_id = Prompt.ask(
|
|
70
|
+
"Default agent ID",
|
|
71
|
+
default="cli-user"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Update config
|
|
75
|
+
config["profiles"]["local"] = {
|
|
76
|
+
"api_url": api_url,
|
|
77
|
+
"api_key_env": "AEGIS_API_KEY",
|
|
78
|
+
"default_namespace": namespace,
|
|
79
|
+
"default_agent_id": agent_id,
|
|
80
|
+
}
|
|
81
|
+
config["default_profile"] = "local"
|
|
82
|
+
|
|
83
|
+
# Update credentials
|
|
84
|
+
if "profiles" not in credentials:
|
|
85
|
+
credentials["profiles"] = {}
|
|
86
|
+
credentials["profiles"]["local"] = {"api_key": api_key}
|
|
87
|
+
|
|
88
|
+
# Save
|
|
89
|
+
save_config(config)
|
|
90
|
+
save_credentials(credentials)
|
|
91
|
+
|
|
92
|
+
print_success(f"Configuration saved to {get_config_path()}")
|
|
93
|
+
print_success(f"Credentials saved to {get_credentials_path()}")
|
|
94
|
+
|
|
95
|
+
# Test connection
|
|
96
|
+
if not non_interactive:
|
|
97
|
+
if Confirm.ask("\nTest connection?", default=True):
|
|
98
|
+
_test_connection()
|
|
99
|
+
else:
|
|
100
|
+
_test_connection()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _test_connection():
|
|
104
|
+
"""Test connection to server."""
|
|
105
|
+
try:
|
|
106
|
+
client = get_client()
|
|
107
|
+
if client is None:
|
|
108
|
+
print_error("No API key configured")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
response = client.client.get("/health")
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
health = response.json()
|
|
114
|
+
|
|
115
|
+
version = health.get("version", "unknown")
|
|
116
|
+
status = health.get("status", "unknown")
|
|
117
|
+
|
|
118
|
+
if status == "healthy":
|
|
119
|
+
print_success(f"Connected to Aegis v{version} ({status})")
|
|
120
|
+
else:
|
|
121
|
+
print_warning(f"Connected to Aegis v{version} ({status})")
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print_error(f"Connection failed: {str(e)}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command()
|
|
128
|
+
def show():
|
|
129
|
+
"""Show current configuration."""
|
|
130
|
+
config = load_config()
|
|
131
|
+
profile_name = config.get("default_profile", "local")
|
|
132
|
+
profile = get_active_profile(config)
|
|
133
|
+
|
|
134
|
+
console.print(f"\n[bold]Profile:[/bold] {profile_name} (active)")
|
|
135
|
+
console.print(f"[bold]API URL:[/bold] {profile.get('api_url', 'not set')}")
|
|
136
|
+
console.print(f"[bold]Namespace:[/bold] {profile.get('default_namespace', 'default')}")
|
|
137
|
+
console.print(f"[bold]Agent ID:[/bold] {profile.get('default_agent_id', 'cli-user')}")
|
|
138
|
+
|
|
139
|
+
output_config = config.get("output", {})
|
|
140
|
+
console.print(f"[bold]Output:[/bold] {output_config.get('format', 'table')}")
|
|
141
|
+
|
|
142
|
+
# Show API key status (masked)
|
|
143
|
+
api_key = get_api_key(config)
|
|
144
|
+
if api_key:
|
|
145
|
+
masked = api_key[:4] + "..." + api_key[-4:] if len(api_key) > 8 else "****"
|
|
146
|
+
console.print(f"[bold]API Key:[/bold] {masked}")
|
|
147
|
+
else:
|
|
148
|
+
console.print("[bold]API Key:[/bold] [red]not configured[/red]")
|
|
149
|
+
|
|
150
|
+
console.print(f"\n[dim]Config: {get_config_path()}[/dim]")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@app.command("set")
|
|
154
|
+
def set_value(
|
|
155
|
+
key: str = typer.Argument(..., help="Config key (e.g., profiles.local.api_url)"),
|
|
156
|
+
value: str = typer.Argument(..., help="Value to set"),
|
|
157
|
+
):
|
|
158
|
+
"""Set a configuration value."""
|
|
159
|
+
config = load_config()
|
|
160
|
+
|
|
161
|
+
keys = key.split(".")
|
|
162
|
+
set_nested_value(config, keys, value)
|
|
163
|
+
save_config(config)
|
|
164
|
+
|
|
165
|
+
print_success(f"Set {key} = {value}")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@app.command()
|
|
169
|
+
def use(
|
|
170
|
+
profile: str = typer.Argument(..., help="Profile name to switch to"),
|
|
171
|
+
):
|
|
172
|
+
"""Switch to a different profile."""
|
|
173
|
+
config = load_config()
|
|
174
|
+
|
|
175
|
+
if profile not in config.get("profiles", {}):
|
|
176
|
+
print_error(f"Profile '{profile}' not found")
|
|
177
|
+
console.print("\nAvailable profiles:")
|
|
178
|
+
for name in config.get("profiles", {}).keys():
|
|
179
|
+
console.print(f" - {name}")
|
|
180
|
+
raise typer.Exit(1)
|
|
181
|
+
|
|
182
|
+
config["default_profile"] = profile
|
|
183
|
+
save_config(config)
|
|
184
|
+
|
|
185
|
+
print_success(f"Switched to profile: {profile}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.command()
|
|
189
|
+
def profiles():
|
|
190
|
+
"""List all configured profiles."""
|
|
191
|
+
config = load_config()
|
|
192
|
+
current = config.get("default_profile", "local")
|
|
193
|
+
|
|
194
|
+
console.print("\n[bold]Configured Profiles[/bold]\n")
|
|
195
|
+
|
|
196
|
+
for name, profile in config.get("profiles", {}).items():
|
|
197
|
+
marker = "[green]●[/green]" if name == current else "[dim]○[/dim]"
|
|
198
|
+
console.print(f" {marker} {name}")
|
|
199
|
+
console.print(f" URL: {profile.get('api_url', 'not set')}")
|
|
200
|
+
console.print(f" Namespace: {profile.get('default_namespace', 'default')}")
|
|
201
|
+
console.print()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@app.command()
|
|
205
|
+
def path():
|
|
206
|
+
"""Show configuration file paths."""
|
|
207
|
+
console.print(f"[bold]Config:[/bold] {get_config_path()}")
|
|
208
|
+
console.print(f"[bold]Credentials:[/bold] {get_credentials_path()}")
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aegis CLI Export/Import Commands
|
|
3
|
+
|
|
4
|
+
Data export and import for backup and migration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import typer
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
+
|
|
15
|
+
from aegis_memory.cli.utils.auth import get_client, get_default_namespace
|
|
16
|
+
from aegis_memory.cli.utils.output import print_json, print_success, print_error
|
|
17
|
+
from aegis_memory.cli.utils.errors import wrap_errors, require_client, handle_api_error
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@wrap_errors
|
|
23
|
+
def export(
|
|
24
|
+
namespace: Optional[str] = typer.Option(None, "--namespace", "-n", help="Filter by namespace (default: all)"),
|
|
25
|
+
agent: Optional[str] = typer.Option(None, "--agent", "-a", help="Filter by agent ID"),
|
|
26
|
+
format: str = typer.Option("jsonl", "--format", "-f", help="Format: jsonl or json"),
|
|
27
|
+
include_embeddings: bool = typer.Option(False, "--include-embeddings", help="Include embedding vectors"),
|
|
28
|
+
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Output file (default: stdout)"),
|
|
29
|
+
limit: Optional[int] = typer.Option(None, "--limit", help="Max memories to export"),
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Export memories for backup or migration.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
aegis export > backup.jsonl
|
|
36
|
+
aegis export -o backup.jsonl
|
|
37
|
+
aegis export -n production -f json -o prod-backup.json
|
|
38
|
+
aegis export --include-embeddings -o full-backup.jsonl
|
|
39
|
+
"""
|
|
40
|
+
if format not in ("jsonl", "json"):
|
|
41
|
+
print_error("Format must be 'jsonl' or 'json'")
|
|
42
|
+
raise typer.Exit(1)
|
|
43
|
+
|
|
44
|
+
client = require_client()
|
|
45
|
+
|
|
46
|
+
# Build request
|
|
47
|
+
params = {"format": format}
|
|
48
|
+
if namespace:
|
|
49
|
+
params["namespace"] = namespace
|
|
50
|
+
if agent:
|
|
51
|
+
params["agent_id"] = agent
|
|
52
|
+
if include_embeddings:
|
|
53
|
+
params["include_embeddings"] = True
|
|
54
|
+
if limit:
|
|
55
|
+
params["limit"] = limit
|
|
56
|
+
|
|
57
|
+
with Progress(
|
|
58
|
+
SpinnerColumn(),
|
|
59
|
+
TextColumn("[progress.description]{task.description}"),
|
|
60
|
+
console=console,
|
|
61
|
+
transient=True,
|
|
62
|
+
) as progress:
|
|
63
|
+
progress.add_task(description="Exporting memories...", total=None)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
response = client.client.post("/memories/export", json=params)
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
except Exception as e:
|
|
69
|
+
handle_api_error(e, "export")
|
|
70
|
+
|
|
71
|
+
# Handle output
|
|
72
|
+
if format == "jsonl":
|
|
73
|
+
# Streaming response
|
|
74
|
+
content = response.text
|
|
75
|
+
lines = content.strip().split("\n") if content.strip() else []
|
|
76
|
+
count = len(lines)
|
|
77
|
+
|
|
78
|
+
if output:
|
|
79
|
+
output.write_text(content)
|
|
80
|
+
print_success(f"Exported {count} memories to {output}")
|
|
81
|
+
else:
|
|
82
|
+
# Output to stdout
|
|
83
|
+
console.print(content, end="")
|
|
84
|
+
if sys.stdout.isatty():
|
|
85
|
+
console.print(f"\n[dim]Exported {count} memories[/dim]", err=True)
|
|
86
|
+
else:
|
|
87
|
+
# JSON response
|
|
88
|
+
data = response.json()
|
|
89
|
+
memories = data.get("memories", [])
|
|
90
|
+
count = len(memories)
|
|
91
|
+
|
|
92
|
+
if output:
|
|
93
|
+
with open(output, "w") as f:
|
|
94
|
+
json.dump(data, f, indent=2, default=str)
|
|
95
|
+
print_success(f"Exported {count} memories to {output}")
|
|
96
|
+
else:
|
|
97
|
+
console.print(json.dumps(data, indent=2, default=str))
|
|
98
|
+
if sys.stdout.isatty():
|
|
99
|
+
console.print(f"\n[dim]Exported {count} memories[/dim]", err=True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@wrap_errors
|
|
103
|
+
def import_memories(
|
|
104
|
+
file: Path = typer.Argument(..., help="File to import from"),
|
|
105
|
+
namespace: Optional[str] = typer.Option(None, "--namespace", "-n", help="Override namespace"),
|
|
106
|
+
agent: Optional[str] = typer.Option(None, "--agent", "-a", help="Override agent ID"),
|
|
107
|
+
skip_duplicates: bool = typer.Option(True, "--skip-duplicates/--no-skip-duplicates", help="Skip content duplicates"),
|
|
108
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Validate without importing"),
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Import memories from export file.
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
aegis import backup.jsonl
|
|
115
|
+
aegis import backup.json -n staging
|
|
116
|
+
aegis import backup.jsonl --dry-run
|
|
117
|
+
"""
|
|
118
|
+
if not file.exists():
|
|
119
|
+
print_error(f"File not found: {file}")
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
|
|
122
|
+
client = require_client()
|
|
123
|
+
|
|
124
|
+
# Detect format
|
|
125
|
+
content = file.read_text()
|
|
126
|
+
|
|
127
|
+
if file.suffix == ".json" or content.strip().startswith("{"):
|
|
128
|
+
# JSON format
|
|
129
|
+
try:
|
|
130
|
+
data = json.loads(content)
|
|
131
|
+
memories = data.get("memories", [])
|
|
132
|
+
except json.JSONDecodeError as e:
|
|
133
|
+
print_error(f"Invalid JSON: {e}")
|
|
134
|
+
raise typer.Exit(1)
|
|
135
|
+
else:
|
|
136
|
+
# JSONL format
|
|
137
|
+
memories = []
|
|
138
|
+
for line in content.strip().split("\n"):
|
|
139
|
+
if line.strip():
|
|
140
|
+
try:
|
|
141
|
+
memories.append(json.loads(line))
|
|
142
|
+
except json.JSONDecodeError as e:
|
|
143
|
+
print_error(f"Invalid JSONL line: {e}")
|
|
144
|
+
raise typer.Exit(1)
|
|
145
|
+
|
|
146
|
+
if not memories:
|
|
147
|
+
print_error("No memories found in file")
|
|
148
|
+
raise typer.Exit(1)
|
|
149
|
+
|
|
150
|
+
if dry_run:
|
|
151
|
+
console.print(f"\n[bold]Dry run[/bold] - would import {len(memories)} memories")
|
|
152
|
+
|
|
153
|
+
# Show sample
|
|
154
|
+
if memories:
|
|
155
|
+
sample = memories[0]
|
|
156
|
+
console.print(f"\nSample memory:")
|
|
157
|
+
console.print(f" Content: {sample.get('content', '')[:60]}...")
|
|
158
|
+
console.print(f" Agent: {namespace or sample.get('agent_id', '-')}")
|
|
159
|
+
console.print(f" Namespace: {agent or sample.get('namespace', 'default')}")
|
|
160
|
+
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Import memories
|
|
164
|
+
imported = 0
|
|
165
|
+
skipped = 0
|
|
166
|
+
errors = 0
|
|
167
|
+
|
|
168
|
+
with Progress(
|
|
169
|
+
SpinnerColumn(),
|
|
170
|
+
TextColumn("[progress.description]{task.description}"),
|
|
171
|
+
console=console,
|
|
172
|
+
) as progress:
|
|
173
|
+
task = progress.add_task(description="Importing...", total=len(memories))
|
|
174
|
+
|
|
175
|
+
for mem in memories:
|
|
176
|
+
try:
|
|
177
|
+
# Override namespace/agent if specified
|
|
178
|
+
mem_namespace = namespace or mem.get("namespace", "default")
|
|
179
|
+
mem_agent = agent or mem.get("agent_id")
|
|
180
|
+
|
|
181
|
+
result = client.add(
|
|
182
|
+
content=mem.get("content", ""),
|
|
183
|
+
agent_id=mem_agent,
|
|
184
|
+
user_id=mem.get("user_id"),
|
|
185
|
+
namespace=mem_namespace,
|
|
186
|
+
scope=mem.get("scope"),
|
|
187
|
+
metadata=mem.get("metadata"),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if result.deduped_from and skip_duplicates:
|
|
191
|
+
skipped += 1
|
|
192
|
+
else:
|
|
193
|
+
imported += 1
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
errors += 1
|
|
197
|
+
|
|
198
|
+
progress.update(task, advance=1)
|
|
199
|
+
|
|
200
|
+
# Summary
|
|
201
|
+
print_success(f"Imported {imported} memories")
|
|
202
|
+
if skipped:
|
|
203
|
+
console.print(f" [dim]Skipped (duplicate): {skipped}[/dim]")
|
|
204
|
+
if errors:
|
|
205
|
+
console.print(f" [yellow]Errors: {errors}[/yellow]")
|