ltcai 8.9.0 → 9.0.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 (89) hide show
  1. package/README.md +28 -39
  2. package/auto_setup.py +11 -62
  3. package/docs/CHANGELOG.md +76 -237
  4. package/docs/COMMUNITY_AND_PLUGINS.md +2 -2
  5. package/docs/DEVELOPMENT.md +8 -8
  6. package/docs/LEGACY_COMPATIBILITY.md +1 -1
  7. package/docs/ONBOARDING.md +2 -2
  8. package/docs/ROADMAP_RECOMMENDATIONS.md +61 -36
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +2 -2
  11. package/docs/kg-schema.md +1 -1
  12. package/lattice_brain/__init__.py +1 -1
  13. package/lattice_brain/archive.py +4 -9
  14. package/lattice_brain/embeddings.py +38 -2
  15. package/lattice_brain/graph/_kg_common.py +27 -462
  16. package/lattice_brain/graph/_kg_constants.py +243 -0
  17. package/lattice_brain/graph/_kg_fsutil.py +297 -0
  18. package/lattice_brain/graph/discovery.py +0 -948
  19. package/lattice_brain/graph/discovery_index.py +972 -0
  20. package/lattice_brain/graph/retrieval.py +0 -570
  21. package/lattice_brain/graph/retrieval_docgen.py +210 -0
  22. package/lattice_brain/graph/retrieval_vector.py +460 -0
  23. package/lattice_brain/graph/store.py +6 -0
  24. package/lattice_brain/ingestion.py +68 -0
  25. package/lattice_brain/portability.py +1 -9
  26. package/lattice_brain/quality.py +98 -4
  27. package/lattice_brain/runtime/agent_runtime.py +166 -0
  28. package/lattice_brain/runtime/multi_agent.py +1 -1
  29. package/lattice_brain/utils.py +28 -0
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/api/chat.py +340 -407
  32. package/latticeai/api/chat_helpers.py +227 -0
  33. package/latticeai/api/computer_use.py +149 -31
  34. package/latticeai/api/marketplace.py +11 -0
  35. package/latticeai/api/permissions.py +3 -3
  36. package/latticeai/api/tools.py +1 -0
  37. package/latticeai/app_factory.py +82 -360
  38. package/latticeai/core/io_utils.py +37 -0
  39. package/latticeai/core/legacy_compatibility.py +1 -1
  40. package/latticeai/core/local_embeddings.py +2 -4
  41. package/latticeai/core/marketplace.py +33 -2
  42. package/latticeai/core/mcp_catalog.py +450 -0
  43. package/latticeai/core/mcp_registry.py +2 -441
  44. package/latticeai/core/users.py +4 -9
  45. package/latticeai/core/workspace_os.py +1 -1
  46. package/latticeai/core/workspace_os_utils.py +3 -17
  47. package/latticeai/integrations/telegram_bot.py +7 -2
  48. package/latticeai/models/model_providers.py +111 -0
  49. package/latticeai/models/router.py +58 -136
  50. package/latticeai/runtime/audit_runtime.py +27 -16
  51. package/latticeai/runtime/automation_runtime.py +9 -0
  52. package/latticeai/runtime/history_runtime.py +163 -0
  53. package/latticeai/runtime/namespace_runtime.py +173 -0
  54. package/latticeai/runtime/network_config_runtime.py +56 -0
  55. package/latticeai/runtime/sso_config_runtime.py +128 -0
  56. package/latticeai/runtime/user_key_runtime.py +106 -0
  57. package/latticeai/services/architecture_readiness.py +2 -2
  58. package/latticeai/services/memory_service.py +213 -0
  59. package/latticeai/services/platform_runtime.py +9 -1
  60. package/latticeai/services/product_readiness.py +11 -11
  61. package/latticeai/services/review_queue.py +64 -11
  62. package/latticeai/services/run_executor.py +21 -0
  63. package/latticeai/services/setup_detection.py +80 -0
  64. package/latticeai/services/tool_dispatch.py +1 -1
  65. package/package.json +1 -1
  66. package/setup_wizard.py +11 -55
  67. package/src-tauri/Cargo.lock +1 -1
  68. package/src-tauri/Cargo.toml +1 -1
  69. package/src-tauri/tauri.conf.json +1 -1
  70. package/static/app/asset-manifest.json +11 -11
  71. package/static/app/assets/{Act-fZokUnC0.js → Act-21lIXx2E.js} +1 -1
  72. package/static/app/assets/Brain-BqUd5UJJ.js +321 -0
  73. package/static/app/assets/{Capture-D5KV3Cu7.js → Capture-BA7Z2Q1u.js} +1 -1
  74. package/static/app/assets/{Library-C9kyFkSt.js → Library-bFMtyni3.js} +1 -1
  75. package/static/app/assets/System-K6krGCqn.js +1 -0
  76. package/static/app/assets/index-C4R3ws30.js +17 -0
  77. package/static/app/assets/index-ChSeOB02.css +2 -0
  78. package/static/app/assets/primitives-sQU3it5I.js +1 -0
  79. package/static/app/assets/{textarea-CD8UNKIy.js → textarea-DK3Fd_lR.js} +1 -1
  80. package/static/app/index.html +2 -2
  81. package/static/css/tokens.css +4 -2
  82. package/static/sw.js +1 -1
  83. package/tools/local_files.py +6 -0
  84. package/latticeai/runtime/sso_runtime.py +0 -52
  85. package/static/app/assets/Brain-DtyuWubr.js +0 -321
  86. package/static/app/assets/System-VbChmX7r.js +0 -1
  87. package/static/app/assets/index-DCh5AoXt.css +0 -2
  88. package/static/app/assets/index-DPdcPoF0.js +0 -17
  89. package/static/app/assets/primitives-DFeanEV6.js +0 -1
@@ -0,0 +1,173 @@
1
+ """Runtime namespace filtering for legacy server_app compatibility.
2
+
3
+ The app factory still constructs a broad local namespace for historical
4
+ ``server_app`` attribute access. This module keeps that surface deliberate:
5
+ public runtime objects and known legacy helpers stay visible, while internal
6
+ assembly scratch values do not leak into ``server_app.__getattr__``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, fields
12
+ from types import ModuleType
13
+ from typing import Any, Dict, Mapping
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class RuntimeBundle:
18
+ """Typed migration target for app-factory runtime dependencies."""
19
+
20
+ app: Any
21
+ CONFIG: Any
22
+ KNOWLEDGE_GRAPH: Any
23
+ INGESTION_PIPELINE: Any
24
+ AGENT_RUNTIME: Any
25
+ HOOKS_REGISTRY: Any
26
+ REVIEW_QUEUE: Any
27
+ AGENT_REGISTRY: Any
28
+ model_router: Any
29
+ build_runtime: Any
30
+ get_shared_runtime: Any
31
+ create_app: Any
32
+
33
+ def as_legacy_dict(self) -> Dict[str, Any]:
34
+ return {field.name: getattr(self, field.name) for field in fields(self)}
35
+
36
+
37
+ INTERNAL_RUNTIME_NAMES = {
38
+ "config",
39
+ "logging",
40
+ "os",
41
+ "threading",
42
+ "Path",
43
+ "mx",
44
+ "uvicorn",
45
+ "HTTPException",
46
+ "Request",
47
+ "BaseModel",
48
+ "keyring",
49
+ "datetime",
50
+ "runtime_bundle",
51
+ }
52
+
53
+ LEGACY_UNDERSCORE_EXPORTS = {
54
+ "_LOCAL_WRITE_BLOCKED_PREFIXES",
55
+ "_RATE_LIMIT_ENABLED",
56
+ "_SESSION_TTL",
57
+ "_TOOL_CATALOG_BRIEF",
58
+ "_TOOL_GOVERNANCE_DEFAULT",
59
+ "_agent_risk",
60
+ "_allowed_workspaces_for",
61
+ "_build_admin_audit_report",
62
+ "_build_sensitivity_report",
63
+ "_bytes_match_extension",
64
+ "_check_ip_rate_limit",
65
+ "_check_rate_limit",
66
+ "_check_tool_role",
67
+ "_classify_sensitive_message",
68
+ "_client_ip",
69
+ "_create_security_router",
70
+ "_embedding_info",
71
+ "_fetch_skills_marketplace",
72
+ "_get_audit_log",
73
+ "_get_sso_discovery",
74
+ "_graph_stats_safe",
75
+ "_host_is_loopback",
76
+ "_list_compat_profiles",
77
+ "_llm_generate_sync",
78
+ "_product_hardening_status",
79
+ "_recent_chat_context",
80
+ "_redact_secret_text",
81
+ "_require_graph",
82
+ "_scoped_hybrid_search",
83
+ "_security_audit_events_safe",
84
+ "_security_list_uploaded_files",
85
+ "_spawn",
86
+ "_tool_response",
87
+ "_user_id_for_email",
88
+ "_workspace_graph",
89
+ "_workspace_models_payload",
90
+ "_workspace_scope_from_request",
91
+ "_workspace_settings_payload",
92
+ }
93
+
94
+ LEGACY_PUBLIC_EXPORTS = {
95
+ "ENGINE_MODEL_CATALOG",
96
+ "TOOL_GOVERNANCE",
97
+ "enforce_rate_limit",
98
+ "filter_lower_family_versions",
99
+ "hash_password",
100
+ "normalize_local_model_request",
101
+ "verify_password",
102
+ }
103
+
104
+
105
+ def _is_internal_runtime_dict(name: str, value: Any) -> bool:
106
+ if not isinstance(value, dict):
107
+ return False
108
+ if name in {"_RUNTIME_BUNDLE"}:
109
+ return False
110
+ return (
111
+ name.endswith("_runtime")
112
+ or name.endswith("_router_bundle")
113
+ or name.endswith("_rt")
114
+ or name
115
+ in {
116
+ "_mcp_state",
117
+ "_garden_import",
118
+ "_foundation_router_bundle",
119
+ "_static_routes_bundle",
120
+ "_vpc_runtime",
121
+ "_sso_runtime",
122
+ "_security_runtime",
123
+ "_session_runtime",
124
+ "_config_runtime",
125
+ "_context_runtime",
126
+ "_brain_runtime",
127
+ "_hooks_runtime",
128
+ "_history_query_runtime",
129
+ "_persistence_runtime",
130
+ "_platform_automation_runtime",
131
+ "_user_key_runtime",
132
+ "_web_runtime",
133
+ }
134
+ )
135
+
136
+
137
+ def build_runtime_namespace(
138
+ local_namespace: Mapping[str, Any],
139
+ *,
140
+ runtime_bundle: RuntimeBundle | Mapping[str, Any],
141
+ ) -> Dict[str, Any]:
142
+ """Return the compatibility namespace without assembly scratch values."""
143
+ legacy_bundle = (
144
+ runtime_bundle.as_legacy_dict()
145
+ if isinstance(runtime_bundle, RuntimeBundle)
146
+ else dict(runtime_bundle)
147
+ )
148
+ exported: Dict[str, Any] = dict(legacy_bundle)
149
+ allowed = set(legacy_bundle) | LEGACY_PUBLIC_EXPORTS | LEGACY_UNDERSCORE_EXPORTS
150
+ for name in allowed:
151
+ if name in exported:
152
+ continue
153
+ if name in INTERNAL_RUNTIME_NAMES:
154
+ continue
155
+ value = local_namespace.get(name)
156
+ if value is None:
157
+ continue
158
+ if isinstance(value, ModuleType):
159
+ continue
160
+ if _is_internal_runtime_dict(name, value):
161
+ continue
162
+ exported[name] = value
163
+ exported["RUNTIME_BUNDLE"] = runtime_bundle
164
+ exported["_RUNTIME_BUNDLE"] = legacy_bundle
165
+ return exported
166
+
167
+
168
+ __all__ = [
169
+ "INTERNAL_RUNTIME_NAMES",
170
+ "LEGACY_UNDERSCORE_EXPORTS",
171
+ "RuntimeBundle",
172
+ "build_runtime_namespace",
173
+ ]
@@ -0,0 +1,56 @@
1
+ """VPC network-profile config seam extracted from the legacy app factory.
2
+
3
+ ``DEFAULT_VPC_CONFIG`` and the load/save helpers used to live inline in
4
+ ``app_factory._build``. Behaviour and exported names are preserved exactly for
5
+ the legacy ``server_app`` compatibility namespace; the only change is that they
6
+ now live behind a builder so the factory stays a wiring path.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Any, Dict
16
+
17
+
18
+ def build_vpc_runtime(*, vpc_file: Path, logging: Any) -> Dict[str, Any]:
19
+ """Return ``DEFAULT_VPC_CONFIG`` and the VPC load/save helpers."""
20
+
21
+ default_vpc_config: Dict[str, Any] = {
22
+ "provider": "AWS",
23
+ "region": "ap-northeast-2",
24
+ "cidr_block": "10.42.0.0/16",
25
+ "private_subnets": ["10.42.10.0/24", "10.42.20.0/24"],
26
+ "endpoint": "ltcai-private.local",
27
+ "vpn_status": "standby",
28
+ "peering_status": "not_configured",
29
+ "notes": "로컬 MLX 브릿지를 프라이빗 서브넷 또는 VPN 뒤에서 운영할 때 쓰는 네트워크 프로필입니다.",
30
+ "updated_at": None,
31
+ }
32
+
33
+ def load_vpc_config() -> Dict:
34
+ if not os.path.exists(vpc_file):
35
+ return default_vpc_config.copy()
36
+ try:
37
+ with open(vpc_file, "r", encoding="utf-8") as f:
38
+ stored = json.load(f)
39
+ return {**default_vpc_config, **stored}
40
+ except Exception as e:
41
+ logging.warning("load_vpc_config failed (using defaults): %s", e)
42
+ return default_vpc_config.copy()
43
+
44
+ def save_vpc_config(config: Dict) -> None:
45
+ config["updated_at"] = datetime.now().isoformat()
46
+ with open(vpc_file, "w", encoding="utf-8") as f:
47
+ json.dump(config, f, ensure_ascii=False, indent=2)
48
+
49
+ return {
50
+ "DEFAULT_VPC_CONFIG": default_vpc_config,
51
+ "load_vpc_config": load_vpc_config,
52
+ "save_vpc_config": save_vpc_config,
53
+ }
54
+
55
+
56
+ __all__ = ["build_vpc_runtime"]
@@ -0,0 +1,128 @@
1
+ """SSO / OIDC config + discovery seam extracted from the app factory.
2
+
3
+ Owns the whole SSO surface that used to be inline in ``app_factory._build``:
4
+ the env-default resolution, the ``sso_config.json`` load/save, the public
5
+ (secret-stripped) view, and the OIDC discovery document cache.
6
+
7
+ Unlike the previous split (config inline in the factory + a separate discovery
8
+ cache in ``sso_runtime``), the discovery cache now lives in the *same* closure
9
+ as ``save_sso_config``, so saving a new SSO config actually invalidates the
10
+ cached discovery document. The old inline ``save_sso_config`` reassigned a
11
+ factory-local cache variable that ``_get_sso_discovery`` never read, so the
12
+ invalidation was a silent no-op — this consolidation fixes that.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from pathlib import Path
19
+ from typing import Any, Dict, Optional
20
+
21
+
22
+ def build_sso_config_runtime(
23
+ *,
24
+ sso_file: Path,
25
+ discovery_url: str,
26
+ client_id: str,
27
+ client_secret: str,
28
+ redirect_uri: str,
29
+ provider_name: str,
30
+ logging: Any,
31
+ ) -> Dict[str, Any]:
32
+ """Return the SSO config helpers + discovery accessor as a name → value dict."""
33
+
34
+ # Single shared discovery cache: save_sso_config and _get_sso_discovery both
35
+ # close over it, so a config change is observed on the next discovery fetch.
36
+ _discovery_cache: Dict[str, Any] = {"data": None, "url": ""}
37
+ _sso_states: Dict[str, float] = {}
38
+
39
+ def _sso_env_defaults() -> Dict[str, object]:
40
+ return {
41
+ "enabled": bool(discovery_url and client_id and client_secret),
42
+ "provider_name": provider_name,
43
+ "discovery_url": discovery_url,
44
+ "client_id": client_id,
45
+ "client_secret": client_secret,
46
+ "redirect_uri": redirect_uri,
47
+ "scopes": "openid email profile",
48
+ }
49
+
50
+ def load_sso_config() -> Dict[str, object]:
51
+ config = _sso_env_defaults()
52
+ if sso_file.exists():
53
+ try:
54
+ data = json.loads(sso_file.read_text(encoding="utf-8"))
55
+ if isinstance(data, dict):
56
+ config.update({k: v for k, v in data.items() if v is not None})
57
+ except Exception as e:
58
+ logging.warning("load_sso_config failed (using env/defaults): %s", e)
59
+ config["provider_name"] = str(config.get("provider_name") or "SSO")
60
+ config["discovery_url"] = str(config.get("discovery_url") or "")
61
+ config["client_id"] = str(config.get("client_id") or "")
62
+ config["client_secret"] = str(config.get("client_secret") or "")
63
+ config["redirect_uri"] = str(config.get("redirect_uri") or redirect_uri)
64
+ config["scopes"] = str(config.get("scopes") or "openid email profile")
65
+ config["enabled"] = bool(config.get("enabled")) and bool(
66
+ config["discovery_url"] and config["client_id"] and config["client_secret"]
67
+ )
68
+ return config
69
+
70
+ def get_sso_settings() -> Dict[str, object]:
71
+ return load_sso_config()
72
+
73
+ def public_sso_config(config: Optional[Dict[str, object]] = None) -> Dict[str, object]:
74
+ cfg = config or get_sso_settings()
75
+ return {
76
+ "enabled": bool(cfg.get("enabled")),
77
+ "provider_name": cfg.get("provider_name") or "",
78
+ "discovery_url": cfg.get("discovery_url") or "",
79
+ "client_id": cfg.get("client_id") or "",
80
+ "redirect_uri": cfg.get("redirect_uri") or redirect_uri,
81
+ "scopes": cfg.get("scopes") or "openid email profile",
82
+ "secret_configured": bool(cfg.get("client_secret")),
83
+ }
84
+
85
+ def save_sso_config(update: Dict[str, object]) -> Dict[str, object]:
86
+ current = load_sso_config()
87
+ if update.get("client_secret") == "":
88
+ update.pop("client_secret", None)
89
+ current.update({k: v for k, v in update.items() if v is not None})
90
+ current["enabled"] = bool(current.get("enabled")) and bool(
91
+ current.get("discovery_url") and current.get("client_id") and current.get("client_secret")
92
+ )
93
+ sso_file.write_text(json.dumps(current, ensure_ascii=False, indent=2), encoding="utf-8")
94
+ _discovery_cache["data"] = None
95
+ _discovery_cache["url"] = ""
96
+ return current
97
+
98
+ async def _get_sso_discovery() -> Optional[Dict]:
99
+ settings = get_sso_settings()
100
+ url = settings.get("discovery_url", "")
101
+ if _discovery_cache["data"] and _discovery_cache["url"] == url:
102
+ return _discovery_cache["data"]
103
+ if not url:
104
+ return None
105
+ try:
106
+ import httpx as _httpx
107
+ async with _httpx.AsyncClient() as c:
108
+ r = await c.get(url, timeout=10)
109
+ r.raise_for_status()
110
+ _discovery_cache["data"] = r.json()
111
+ _discovery_cache["url"] = url
112
+ except Exception as e:
113
+ logging.warning("SSO discovery failed: %s", e)
114
+ return None
115
+ return _discovery_cache["data"]
116
+
117
+ return {
118
+ "_sso_env_defaults": _sso_env_defaults,
119
+ "load_sso_config": load_sso_config,
120
+ "get_sso_settings": get_sso_settings,
121
+ "public_sso_config": public_sso_config,
122
+ "save_sso_config": save_sso_config,
123
+ "_get_sso_discovery": _get_sso_discovery,
124
+ "_sso_states": _sso_states,
125
+ }
126
+
127
+
128
+ __all__ = ["build_sso_config_runtime"]
@@ -0,0 +1,106 @@
1
+ """User profile + provider API-key helpers extracted from the app factory.
2
+
3
+ The helpers close over the user store, keyring adapter, identity migration, and
4
+ plaintext-key policy. Returning callables preserves the legacy ``server_app``
5
+ namespace while keeping ``app_factory._build`` focused on wiring.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, Optional
11
+
12
+
13
+ def build_user_key_runtime(
14
+ *,
15
+ load_users: Any,
16
+ save_users: Any,
17
+ ensure_user_identity: Any,
18
+ keyring: Any,
19
+ allow_plaintext_api_keys: bool,
20
+ logging: Any,
21
+ http_exception: Any,
22
+ ) -> Dict[str, Any]:
23
+ """Return history-user and provider API-key helpers."""
24
+
25
+ def get_history_user(email: Optional[str], nickname: Optional[str] = None) -> Dict:
26
+ if not email:
27
+ return {"user_email": None, "user_nickname": nickname or None}
28
+ users = load_users()
29
+ user = users.get(email, {})
30
+ return {
31
+ "user_email": email,
32
+ "user_nickname": nickname or user.get("nickname") or user.get("name") or email,
33
+ }
34
+
35
+ def get_user_api_key(email: Optional[str], provider: str) -> Optional[str]:
36
+ if not email:
37
+ return None
38
+ keyring_key = f"{email}:{provider}"
39
+ if keyring is not None:
40
+ try:
41
+ key = keyring.get_password("LatticeAI", keyring_key)
42
+ if key:
43
+ return key.strip()
44
+ except Exception as exc:
45
+ logging.warning("keyring read failed for %s: %s", provider, exc)
46
+ users = load_users()
47
+ user = users.get(email) or {}
48
+ api_keys = user.get("api_keys") or {}
49
+ key = api_keys.get(provider)
50
+ if isinstance(key, str) and key.strip() and allow_plaintext_api_keys:
51
+ return key.strip()
52
+ return None
53
+
54
+ def set_user_api_key(email: str, provider: str, key: str) -> None:
55
+ keyring_key = f"{email}:{provider}"
56
+ if keyring is not None:
57
+ try:
58
+ keyring.set_password("LatticeAI", keyring_key, key)
59
+ users = load_users()
60
+ user = users.get(email)
61
+ if user and "api_keys" in user:
62
+ user["api_keys"].pop(provider, None)
63
+ if not user["api_keys"]:
64
+ user.pop("api_keys", None)
65
+ save_users(users)
66
+ return
67
+ except Exception as exc:
68
+ logging.warning("keyring write failed for %s: %s", provider, exc)
69
+ if not allow_plaintext_api_keys:
70
+ raise http_exception(
71
+ status_code=500,
72
+ detail="OS keyring에 API 키를 저장하지 못했습니다. keyring 설정을 확인하거나 LATTICEAI_ALLOW_PLAINTEXT_API_KEYS=true를 명시적으로 설정하세요.",
73
+ )
74
+
75
+ if not allow_plaintext_api_keys:
76
+ raise http_exception(
77
+ status_code=500,
78
+ detail="keyring 패키지를 사용할 수 없어 API 키를 안전하게 저장할 수 없습니다.",
79
+ )
80
+
81
+ users = load_users()
82
+ user = users.get(email)
83
+ if not user:
84
+ user = {
85
+ "password_hash": "",
86
+ "salt": "",
87
+ "name": email,
88
+ "nickname": email,
89
+ "role": "user",
90
+ "disabled": False,
91
+ }
92
+ ensure_user_identity(email, user)
93
+ api_keys = user.get("api_keys") or {}
94
+ api_keys[provider] = key
95
+ user["api_keys"] = api_keys
96
+ users[email] = user
97
+ save_users(users)
98
+
99
+ return {
100
+ "get_history_user": get_history_user,
101
+ "get_user_api_key": get_user_api_key,
102
+ "set_user_api_key": set_user_api_key,
103
+ }
104
+
105
+
106
+ __all__ = ["build_user_key_runtime"]
@@ -1,6 +1,6 @@
1
1
  """Machine-checkable architecture readiness gates for release work.
2
2
 
3
- 8.9.0 keeps the major architecture priorities under an explicit release
3
+ 9.0.0 keeps the major architecture priorities under an explicit release
4
4
  contract while product maturity work reduces visible beta seams. AgentRuntime, ToolRegistry,
5
5
  central Config, decomposed server runtime, and Knowledge Graph stabilization
6
6
  must remain discoverable, ordered, and backed by tests before the release can be
@@ -17,7 +17,7 @@ from typing import Any, Dict, List
17
17
  from latticeai.core.legacy_compatibility import legacy_shim_report
18
18
 
19
19
 
20
- ARCHITECTURE_VERSION_TARGET = "8.9.0"
20
+ ARCHITECTURE_VERSION_TARGET = "9.0.0"
21
21
 
22
22
  PREFERRED_REFACTORING_ORDER = [
23
23
  "agent-runtime",