Skip to content

Commit df42b97

Browse files
authored
Merge branch 'main' into feature/entity-sharing-full-mcp
2 parents 332cbcd + 6f79732 commit df42b97

66 files changed

Lines changed: 5227 additions & 788 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Required: API key for LLM access
22
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"
33

4-
# Optional: Evolve Backend Configuration
5-
# EVOLVE_BACKEND=filesystem
4+
# Optional: Evolve Backend Configuration (default: filesystem)
5+
# EVOLVE_BACKEND=filesystem # Options: filesystem, postgres, milvus
66
# EVOLVE_NAMESPACE_ID=evolve
77

88
# Optional: LLM Configuration
@@ -11,4 +11,17 @@ OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"
1111
# OPENAI_BASE_URL=https://api.openai.com/v1
1212

1313
# Optional: Advanced Settings
14-
# EVOLVE_CLUSTERING_THRESHOLD=0.80
14+
# EVOLVE_CLUSTERING_THRESHOLD=0.80
15+
16+
# Optional: Postgres backend (requires: pip install altk-evolve[pgvector])
17+
# EVOLVE_BACKEND=postgres
18+
# EVOLVE_PG_HOST=127.0.0.1
19+
# EVOLVE_PG_PORT=5432
20+
# EVOLVE_PG_USER=postgres
21+
# EVOLVE_PG_PASSWORD=postgres # pragma: allowlist secret
22+
# EVOLVE_PG_DBNAME=evolve
23+
# EVOLVE_PG_AUTO_CREATE_DB=true
24+
# EVOLVE_PG_BOOTSTRAP_DB=postgres
25+
26+
# Optional: Milvus backend (requires: pip install altk-evolve[milvus])
27+
# EVOLVE_BACKEND=milvus

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
# See https://pre-commit.com for more information
33

44
repos:
5+
# Mypy - Python static type checking (using uv)
6+
- repo: local
7+
hooks:
8+
- id: mypy
9+
name: mypy
10+
entry: uv run mypy .
11+
language: system
12+
pass_filenames: false
13+
types_or: [python, pyi]
14+
515
# Ruff - Python linting and formatting (using uv)
616
- repo: https://github.com/astral-sh/ruff-pre-commit
717
rev: v0.14.3

.secrets.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "^.secrets.baseline$|package-lock\\.json$",
44
"lines": null
55
},
6-
"generated_at": "2026-04-16T17:29:52Z",
6+
"generated_at": "2026-04-20T15:55:53Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -82,7 +82,7 @@
8282
"hashed_secret": "ec3810e10fb78db55ce38b9c18d1c3eb1db739e0",
8383
"is_secret": false,
8484
"is_verified": false,
85-
"line_number": 58,
85+
"line_number": 74,
8686
"type": "Secret Keyword",
8787
"verified_result": null
8888
}

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
<!-- version list -->
44

5+
## v1.0.10 (2026-04-20)
6+
7+
### Bug Fixes
8+
9+
- **mcp**: Align metadata filters and harden SSE teardown
10+
([`a0bcc6d`](https://github.com/AgentToolkit/altk-evolve/commit/a0bcc6db5ac5fdb4808d6e11e451eb3156ba9596))
11+
12+
- **postgres**: Prevent ambiguous filter behavior across backends
13+
([`a0bcc6d`](https://github.com/AgentToolkit/altk-evolve/commit/a0bcc6db5ac5fdb4808d6e11e451eb3156ba9596))
14+
15+
516
## v1.0.9 (2026-04-17)
617

718
### Bug Fixes

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ From PyPI
5151
pip install altk-evolve
5252
```
5353

54+
**Optional Backend Dependencies:**
55+
56+
The default filesystem backend uses simple text matching and requires no additional dependencies. For semantic vector similarity search, install one of these backends:
57+
58+
For PostgreSQL with pgvector support (recommended for production):
59+
```bash
60+
uv sync --extra pgvector
61+
```
62+
63+
For Milvus support (optimized for large-scale vector search):
64+
```bash
65+
uv sync --extra milvus
66+
```
67+
68+
See the [Backend Configuration Guide](docs/guides/backend-configuration.md) for detailed comparison and setup instructions.
69+
5470
### Configuration
5571

5672
For direct OpenAI usage:
@@ -96,6 +112,13 @@ npx @modelcontextprotocol/inspector@latest http://127.0.0.1:8201/sse --cli --met
96112
- `create_entity(content: str, entity_type: str, metadata: str | None, enable_conflict_resolution: bool)`: Create a single entity in the namespace.
97113
- `delete_entity(entity_id: str)`: Delete a specific entity by its ID.
98114

115+
### Filter Migration Note
116+
Entity search filters reserve bare keys for top-level schema columns only: `id`, `type`, `content`, and `created_at`.
117+
118+
If you need to filter on JSON metadata, use the `metadata.<key>` form. For example, use `filters={"type": "trajectory", "metadata.task_id": "123"}` instead of `filters={"type": "trajectory", "task_id": "123"}`.
119+
120+
Existing integrations that stored custom fields in entity metadata should update filter writers to add the `metadata.` prefix for those keys.
121+
99122
## Features
100123
- **Proactive**: Learns how to recognize problems and their solutions, and generates guidelines that get automatically applied to new tasks.
101124
- **Conflict Resolution**: Update existing guidelines when new information contradicts them.
@@ -164,4 +187,4 @@ Evolve is an active project, and real‑world usage helps guide its direction.
164187

165188
If you’re experimenting with Evolve or exploring on‑the‑job learning for agents, feel free to open an issue or discussion to share use cases, ideas, or feedback.
166189

167-
See the [Contributing Guide](CONTRIBUTING.md) to understand our development process, or how to submit changes, report bugs, or propose features.
190+
See the [Contributing Guide](CONTRIBUTING.md) to understand our development process, or how to submit changes, report bugs, or propose features.

altk_evolve/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.9"
1+
__version__ = "1.0.10"

altk_evolve/backend/postgres.py

Lines changed: 116 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,13 @@ class PostgresEntityBackend(BaseEntityBackend):
4343
embedding_model: SentenceTransformer
4444
embedding_dim: int
4545
_settings: PostgresDBSettings
46+
_schema_filter_fields = {"id", "type", "content", "created_at"}
47+
_metadata_filter_prefix = "metadata."
4648

4749
def __init__(self, config: BaseSettings | None = None):
4850
super().__init__(config)
4951
self._settings = config if isinstance(config, type(postgres_db_settings)) else postgres_db_settings
50-
self.conn = psycopg.connect(
51-
host=self._settings.host,
52-
port=self._settings.port,
53-
user=self._settings.user,
54-
password=self._settings.password,
55-
dbname=self._settings.dbname,
56-
autocommit=True,
57-
)
52+
self.conn = self._connect_target_db()
5853
try:
5954
self._ensure_pgvector_extension()
6055
register_vector(self.conn)
@@ -70,6 +65,87 @@ def __init__(self, config: BaseSettings | None = None):
7065
self.conn.close()
7166
raise
7267

68+
def _connect(self, dbname: str) -> psycopg.Connection:
69+
return psycopg.connect(
70+
host=self._settings.host,
71+
port=self._settings.port,
72+
user=self._settings.user,
73+
password=self._settings.password,
74+
dbname=dbname,
75+
autocommit=True,
76+
)
77+
78+
def _is_missing_database_error(self, error: Exception) -> bool:
79+
# Check for SQL state code 3D000 (invalid catalog name)
80+
if getattr(error, "sqlstate", None) == "3D000":
81+
return True
82+
# Also check error message for database does not exist
83+
error_msg = str(error).lower()
84+
return "database" in error_msg and "does not exist" in error_msg
85+
86+
def _create_database(self) -> None:
87+
logger.info(
88+
"Database '%s' not found; attempting bootstrap via '%s'",
89+
self._settings.dbname,
90+
self._settings.bootstrap_db,
91+
)
92+
93+
# Try multiple common administrative databases as fallbacks
94+
bootstrap_candidates = [
95+
self._settings.bootstrap_db, # User-configured (default: "postgres")
96+
"template1", # PostgreSQL default template database
97+
self._settings.user, # User's default database (often created automatically)
98+
]
99+
100+
# Remove duplicates while preserving order
101+
seen = set()
102+
bootstrap_dbs = []
103+
for db in bootstrap_candidates:
104+
if db not in seen:
105+
seen.add(db)
106+
bootstrap_dbs.append(db)
107+
108+
last_error = None
109+
for bootstrap_db in bootstrap_dbs:
110+
try:
111+
logger.debug("Attempting to connect to bootstrap database: %s", bootstrap_db)
112+
admin_conn = self._connect(bootstrap_db)
113+
try:
114+
with admin_conn.cursor() as cur:
115+
cur.execute(sql.SQL("CREATE DATABASE {dbname}").format(dbname=sql.Identifier(self._settings.dbname)))
116+
logger.info("Successfully created database '%s' using bootstrap database '%s'", self._settings.dbname, bootstrap_db)
117+
return
118+
except Exception as create_error:
119+
# Check if database already exists (race condition with another process)
120+
sqlstate = getattr(create_error, "sqlstate", None)
121+
if sqlstate == "42P04": # duplicate_database
122+
logger.debug("Database '%s' already exists (created by another process)", self._settings.dbname)
123+
return # Treat as success
124+
raise # Re-raise other errors
125+
finally:
126+
if not admin_conn.closed:
127+
admin_conn.close()
128+
except Exception as e:
129+
last_error = e
130+
logger.debug("Failed to use bootstrap database '%s': %s", bootstrap_db, e)
131+
continue
132+
133+
# If all bootstrap databases failed, raise the last error with helpful message
134+
raise EvolveException(
135+
f"Failed to create database '{self._settings.dbname}'. "
136+
f"Tried bootstrap databases: {', '.join(bootstrap_dbs)}. "
137+
f"Last error: {last_error}"
138+
)
139+
140+
def _connect_target_db(self) -> psycopg.Connection:
141+
try:
142+
return self._connect(self._settings.dbname)
143+
except Exception as e:
144+
if not self._settings.auto_create_db or not self._is_missing_database_error(e):
145+
raise
146+
self._create_database()
147+
return self._connect(self._settings.dbname)
148+
73149
def _ensure_pgvector_extension(self):
74150
"""Ensure the pgvector extension is installed."""
75151
with self.conn.cursor() as cur:
@@ -214,11 +290,38 @@ def search_entities(
214290
table = self._table_name(namespace_id)
215291
filters = filters or {}
216292

217-
where_parts = []
218-
params: list = []
219-
for k, v in filters.items():
220-
where_parts.append(sql.SQL("{} = %s").format(sql.Identifier(k)))
221-
params.append(v)
293+
where_parts: list[sql.Composable] = []
294+
params: list[Any] = []
295+
for key, value in filters.items():
296+
if value is None:
297+
continue
298+
299+
if key in self._schema_filter_fields:
300+
where_parts.append(sql.SQL("{} = %s").format(sql.Identifier(key)))
301+
params.append(value)
302+
continue
303+
304+
if not key.startswith(self._metadata_filter_prefix):
305+
accepted_keys = sorted(self._schema_filter_fields)
306+
logger.error(
307+
"Invalid Postgres search filter key %r. Accepted schema keys: %s. "
308+
"Metadata filters must be prefixed with %r. filters=%r metadata_key=%r where_parts=%r params=%r",
309+
key,
310+
accepted_keys,
311+
self._metadata_filter_prefix,
312+
filters,
313+
None,
314+
[repr(part) for part in where_parts],
315+
params,
316+
)
317+
raise ValueError(
318+
f"Invalid filter key '{key}'. Accepted schema keys: {accepted_keys}. "
319+
f"Metadata filters must use the '{self._metadata_filter_prefix}<key>' form."
320+
)
321+
322+
metadata_key = key.split(".", 1)[1]
323+
where_parts.append(sql.SQL("metadata @> %s::jsonb"))
324+
params.append(json.dumps({metadata_key: value}))
222325
where_clause = sql.SQL(" AND ").join(where_parts) if where_parts else sql.SQL("TRUE")
223326

224327
if query is None:

altk_evolve/cli/cli.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
entities_app = typer.Typer(help="Entity management commands")
2424
sync_app = typer.Typer(help="Sync commands")
2525
skills_app = typer.Typer(help="Skill management commands")
26+
viz_app = typer.Typer(help="Visualization commands")
2627

2728
app.add_typer(namespaces_app, name="namespaces")
2829
app.add_typer(entities_app, name="entities")
2930
app.add_typer(sync_app, name="sync")
3031
app.add_typer(skills_app, name="skills")
32+
app.add_typer(viz_app, name="viz")
3133

3234
console = Console()
3335

@@ -544,5 +546,22 @@ def package_skills(
544546
sys.exit(1)
545547

546548

549+
# =============================================================================
550+
# Viz Commands
551+
# =============================================================================
552+
553+
554+
@viz_app.command("serve")
555+
def serve_viz(
556+
evolve_dir: Annotated[Path, typer.Option("--evolve-dir", "-d", help="Path to .evolve directory")] = Path(".evolve"),
557+
port: Annotated[int, typer.Option("--port", "-p", help="Port to serve on")] = 7891,
558+
no_browser: Annotated[bool, typer.Option("--no-browser", help="Don't open browser automatically")] = False,
559+
):
560+
"""Serve the Evolve Viz web interface for browsing entities and trajectories."""
561+
from altk_evolve.viz.server import serve
562+
563+
serve(evolve_dir=evolve_dir.resolve(), port=port, open_browser=not no_browser)
564+
565+
547566
if __name__ == "__main__":
548567
app()

altk_evolve/config/evolve.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class EvolveConfig(BaseSettings):
66
model_config = SettingsConfigDict(env_prefix="EVOLVE_")
7-
backend: Literal["milvus", "filesystem", "postgres"] = "milvus"
7+
backend: Literal["milvus", "filesystem", "postgres"] = "filesystem"
88
namespace_id: str = "evolve"
99
settings: BaseSettings | None = None
1010
clustering_threshold: float = 0.80

altk_evolve/config/postgres.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class PostgresDBSettings(BaseSettings):
1010
user: str = Field(default="postgres")
1111
password: str = Field(default="postgres")
1212
dbname: str = Field(default="evolve")
13+
auto_create_db: bool = Field(default=False)
14+
bootstrap_db: str = Field(default="postgres")
1315

1416

1517
# to reload settings call postgres_db_settings.__init__()

0 commit comments

Comments
 (0)