agent-devkit 0.2.0 → 0.3.1
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.
- package/README.md +66 -13
- package/bin/agent.mjs +133 -7
- package/package.json +1 -1
- package/runtime/README.md +205 -13
- package/runtime/agent +31 -5
- package/runtime/agents/README.md +18 -0
- package/runtime/agents/contribution-reviewer/AGENTS.md +8 -0
- package/runtime/agents/contribution-reviewer/README.md +8 -0
- package/runtime/agents/contribution-reviewer/agent.yaml +40 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/capability.yaml +27 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/plan-contribution-pr/workflow.md +6 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/capability.yaml +25 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/review-contribution/workflow.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/capability.yaml +26 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/decision-rules.md +5 -0
- package/runtime/agents/contribution-reviewer/capabilities/validate-local-contribution/workflow.md +6 -0
- package/runtime/agents/contribution-reviewer/infra/README.md +6 -0
- package/runtime/agents/contribution-reviewer/knowledge/context.md +8 -0
- package/runtime/agents/contribution-reviewer/knowledge/system.md +8 -0
- package/runtime/agents/contribution-reviewer/templates/README.md +3 -0
- package/runtime/agents/knowledge-author/AGENTS.md +7 -0
- package/runtime/agents/knowledge-author/README.md +7 -0
- package/runtime/agents/knowledge-author/agent.yaml +37 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/capability.yaml +30 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-author/capabilities/create-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-author/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-author/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-author/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-author/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-curator/AGENTS.md +7 -0
- package/runtime/agents/knowledge-curator/README.md +6 -0
- package/runtime/agents/knowledge-curator/agent.yaml +37 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/capability.yaml +29 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/decision-rules.md +6 -0
- package/runtime/agents/knowledge-curator/capabilities/curate-knowledge-base/workflow.md +7 -0
- package/runtime/agents/knowledge-curator/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-curator/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-curator/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-curator/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-infra-builder/AGENTS.md +8 -0
- package/runtime/agents/knowledge-infra-builder/README.md +8 -0
- package/runtime/agents/knowledge-infra-builder/agent.yaml +38 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/capability.yaml +30 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/decision-rules.md +6 -0
- package/runtime/agents/knowledge-infra-builder/capabilities/create-knowledge-base/workflow.md +7 -0
- package/runtime/agents/knowledge-infra-builder/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-infra-builder/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-infra-builder/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-infra-builder/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-owner/AGENTS.md +7 -0
- package/runtime/agents/knowledge-owner/README.md +6 -0
- package/runtime/agents/knowledge-owner/agent.yaml +37 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/capability.yaml +28 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-owner/capabilities/publish-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-owner/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-owner/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-owner/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-owner/templates/.gitkeep +1 -0
- package/runtime/agents/knowledge-reviewer/AGENTS.md +7 -0
- package/runtime/agents/knowledge-reviewer/README.md +7 -0
- package/runtime/agents/knowledge-reviewer/agent.yaml +36 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/capability.yaml +26 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/decision-rules.md +6 -0
- package/runtime/agents/knowledge-reviewer/capabilities/review-knowledge-snapshot/workflow.md +7 -0
- package/runtime/agents/knowledge-reviewer/infra/.gitkeep +1 -0
- package/runtime/agents/knowledge-reviewer/knowledge/context.md +4 -0
- package/runtime/agents/knowledge-reviewer/knowledge/system.md +4 -0
- package/runtime/agents/knowledge-reviewer/templates/.gitkeep +1 -0
- package/runtime/agents/local-memory-manager/AGENTS.md +5 -0
- package/runtime/agents/local-memory-manager/README.md +7 -0
- package/runtime/agents/local-memory-manager/agent.yaml +38 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/capability.yaml +19 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/decision-rules.md +5 -0
- package/runtime/agents/local-memory-manager/capabilities/curate-local-memory/workflow.md +6 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/capability.yaml +19 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/decision-rules.md +5 -0
- package/runtime/agents/local-memory-manager/capabilities/inspect-local-memory/workflow.md +5 -0
- package/runtime/agents/local-memory-manager/infra/.gitkeep +1 -0
- package/runtime/agents/local-memory-manager/knowledge/context.md +4 -0
- package/runtime/agents/local-memory-manager/knowledge/system.md +4 -0
- package/runtime/agents/local-memory-manager/templates/.gitkeep +1 -0
- package/runtime/agents/memory-sync-manager/AGENTS.md +7 -0
- package/runtime/agents/memory-sync-manager/README.md +7 -0
- package/runtime/agents/memory-sync-manager/agent.yaml +37 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/capability.yaml +29 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/decision-rules.md +6 -0
- package/runtime/agents/memory-sync-manager/capabilities/plan-memory-backup/workflow.md +7 -0
- package/runtime/agents/memory-sync-manager/infra/.gitkeep +1 -0
- package/runtime/agents/memory-sync-manager/knowledge/context.md +4 -0
- package/runtime/agents/memory-sync-manager/knowledge/system.md +4 -0
- package/runtime/agents/memory-sync-manager/templates/.gitkeep +1 -0
- package/runtime/agents/shared-memory-curator/AGENTS.md +5 -0
- package/runtime/agents/shared-memory-curator/README.md +6 -0
- package/runtime/agents/shared-memory-curator/agent.yaml +38 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/create-shared-memory/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/publish-shared-submission/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/capability.yaml +19 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/decision-rules.md +5 -0
- package/runtime/agents/shared-memory-curator/capabilities/review-shared-submission/workflow.md +5 -0
- package/runtime/agents/shared-memory-curator/infra/.gitkeep +1 -0
- package/runtime/agents/shared-memory-curator/knowledge/context.md +5 -0
- package/runtime/agents/shared-memory-curator/knowledge/system.md +4 -0
- package/runtime/agents/shared-memory-curator/templates/.gitkeep +1 -0
- package/runtime/cli/README.md +47 -8
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/agent_registry.py +4 -2
- package/runtime/cli/aikit/agentic_commands.py +158 -0
- package/runtime/cli/aikit/app_home.py +2 -0
- package/runtime/cli/aikit/audit.py +16 -6
- package/runtime/cli/aikit/catalog.py +278 -8
- package/runtime/cli/aikit/cli_dispatch.py +489 -13
- package/runtime/cli/aikit/cli_parser.py +146 -8
- package/runtime/cli/aikit/contribution.py +132 -2
- package/runtime/cli/aikit/doctor_runtime.py +85 -0
- package/runtime/cli/aikit/embedded_mini_brain.py +351 -0
- package/runtime/cli/aikit/eval.py +356 -10
- package/runtime/cli/aikit/human_output.py +310 -4
- package/runtime/cli/aikit/interactive_wizard.py +146 -0
- package/runtime/cli/aikit/knowledge_base.py +1067 -0
- package/runtime/cli/aikit/llm.py +40 -6
- package/runtime/cli/aikit/local_artifacts.py +444 -0
- package/runtime/cli/aikit/local_llm.py +176 -0
- package/runtime/cli/aikit/local_llm_operator.py +15 -5
- package/runtime/cli/aikit/main.py +15 -0
- package/runtime/cli/aikit/mcp_manifest.py +798 -0
- package/runtime/cli/aikit/mcp_tools.py +643 -5
- package/runtime/cli/aikit/memory.py +405 -0
- package/runtime/cli/aikit/mini_brain.py +56 -25
- package/runtime/cli/aikit/model_router.py +42 -9
- package/runtime/cli/aikit/natural_prompt_runtime.py +194 -2
- package/runtime/cli/aikit/ollama.py +64 -15
- package/runtime/cli/aikit/onboarding.py +551 -0
- package/runtime/cli/aikit/output.py +67 -0
- package/runtime/cli/aikit/prompt_injection.py +12 -1
- package/runtime/cli/aikit/review_gate.py +14 -2
- package/runtime/cli/aikit/roadmap_cli.py +1 -1
- package/runtime/cli/aikit/secrets.py +3 -2
- package/runtime/cli/aikit/setup_wizard_payload.py +3 -0
- package/runtime/cli/aikit/shared_memory.py +415 -0
- package/runtime/cli/aikit/specialist_readiness.py +152 -0
- package/runtime/cli/aikit/tasks.py +104 -1
- package/runtime/cli/aikit/team.py +380 -0
- package/runtime/cli/aikit/toolchain.py +7 -2
- package/runtime/cli/aikit/workflows.py +115 -14
- package/runtime/models/qwen2.5-0.5b-instruct/manifest.json +30 -0
- package/runtime/providers/knowledge-github.yaml +40 -0
- package/runtime/providers/knowledge-google-drive.yaml +32 -0
- package/runtime/providers/knowledge-local.yaml +26 -0
- package/runtime/providers/knowledge-notion.yaml +32 -0
- package/runtime/providers/knowledge-obsidian.yaml +24 -0
- package/runtime/providers/knowledge-onedrive.yaml +36 -0
- package/runtime/providers/knowledge-s3.yaml +45 -0
- package/runtime/providers/knowledge-sharepoint.yaml +39 -0
- package/runtime/providers/knowledge-supabase.yaml +43 -0
- package/runtime/providers/knowledge-vector.yaml +39 -0
- package/runtime/requirements.txt +6 -0
- package/runtime/scripts/docker-cli-qa.sh +453 -0
- package/runtime/scripts/release-catalog-snapshot.json +55 -4
- package/runtime/scripts/release-gate.py +54 -13
- package/runtime/tooling/toolchain.yaml +92 -0
- package/runtime/vendor/skills/napkin/napkin.md +21 -7
- package/runtime/workflows/azure-card-analysis/README.md +3 -0
- package/runtime/workflows/azure-card-analysis/workflow.yaml +30 -0
- package/runtime/workflows/daily-pr-review/README.md +3 -0
- package/runtime/workflows/daily-pr-review/workflow.yaml +31 -0
- package/runtime/workflows/incident-analysis/README.md +3 -0
- package/runtime/workflows/incident-analysis/workflow.yaml +33 -0
- package/runtime/workflows/release-prep/README.md +3 -0
- package/runtime/workflows/release-prep/workflow.yaml +30 -0
|
@@ -4,6 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
import shutil
|
|
7
|
+
import hashlib
|
|
8
|
+
import hmac
|
|
9
|
+
import base64
|
|
10
|
+
import io
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import tarfile
|
|
14
|
+
import tempfile
|
|
7
15
|
from datetime import datetime, timezone
|
|
8
16
|
from pathlib import Path
|
|
9
17
|
from typing import Any
|
|
@@ -95,12 +103,18 @@ Curated local runbook entries promoted from repeated use.
|
|
|
95
103
|
- Keep high-value reusable notes here.
|
|
96
104
|
""",
|
|
97
105
|
}
|
|
106
|
+
BACKUP_PACKAGE_SCHEMA_VERSION = "agent-devkit.memory-backup-package/v1"
|
|
107
|
+
BACKUP_PACKAGE_ALGORITHM = "PBKDF2-HMAC-SHA256/XOR-HMAC-SHA256"
|
|
98
108
|
|
|
99
109
|
|
|
100
110
|
def memory_home() -> Path:
|
|
101
111
|
return app_memory_home()
|
|
102
112
|
|
|
103
113
|
|
|
114
|
+
def memory_backups_home() -> Path:
|
|
115
|
+
return app_path("backups", "memory")
|
|
116
|
+
|
|
117
|
+
|
|
104
118
|
def ensure_memory() -> dict[str, Any]:
|
|
105
119
|
ensure_app_home()
|
|
106
120
|
home = memory_home()
|
|
@@ -193,6 +207,183 @@ def show_memory(root: Path, *, agent_id: str | None = None, source_id: str | Non
|
|
|
193
207
|
}
|
|
194
208
|
|
|
195
209
|
|
|
210
|
+
def create_memory_backup(
|
|
211
|
+
*,
|
|
212
|
+
title: str | None = None,
|
|
213
|
+
encrypted: bool = False,
|
|
214
|
+
passphrase_env: str | None = None,
|
|
215
|
+
) -> dict[str, Any]:
|
|
216
|
+
memory_paths = ensure_memory()
|
|
217
|
+
backups = memory_backups_home()
|
|
218
|
+
backups.mkdir(parents=True, exist_ok=True)
|
|
219
|
+
backup_id = unique_backup_id(title)
|
|
220
|
+
backup_root = backups / backup_id
|
|
221
|
+
backup_root.mkdir(parents=True, exist_ok=False)
|
|
222
|
+
backup_memory = backup_root / ("_memory-staging" if encrypted else "memory")
|
|
223
|
+
shutil.copytree(memory_home(), backup_memory)
|
|
224
|
+
|
|
225
|
+
files = backup_file_inventory(backup_memory)
|
|
226
|
+
manifest = {
|
|
227
|
+
"schema_version": "agent-devkit.memory-backup/v1",
|
|
228
|
+
"id": backup_id,
|
|
229
|
+
"title": title or "Memory backup",
|
|
230
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
231
|
+
"source_home": str(memory_home()),
|
|
232
|
+
"storage": "local-filesystem",
|
|
233
|
+
"remote_upload": False,
|
|
234
|
+
"encrypted": encrypted,
|
|
235
|
+
"sensitive_local_copy": not encrypted,
|
|
236
|
+
"files": files,
|
|
237
|
+
"package": None,
|
|
238
|
+
"notes": [
|
|
239
|
+
"This is a local backup only; no remote provider upload was executed.",
|
|
240
|
+
"Remote backup must be encrypted before upload and requires explicit opt-in.",
|
|
241
|
+
],
|
|
242
|
+
}
|
|
243
|
+
if encrypted:
|
|
244
|
+
package_path = backup_root / f"{backup_id}.adkmb"
|
|
245
|
+
passphrase = passphrase_from_env(passphrase_env)
|
|
246
|
+
write_encrypted_backup_package(package_path, backup_memory, manifest, passphrase)
|
|
247
|
+
manifest["package"] = str(package_path)
|
|
248
|
+
manifest["package_name"] = package_path.name
|
|
249
|
+
shutil.rmtree(backup_memory)
|
|
250
|
+
manifest_path = backup_root / "manifest.json"
|
|
251
|
+
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
252
|
+
next_steps = [
|
|
253
|
+
f"Restore with `agent memory backup restore {backup_id} --yes`.",
|
|
254
|
+
f"Delete with `agent memory backup delete {backup_id} --yes`.",
|
|
255
|
+
]
|
|
256
|
+
if encrypted:
|
|
257
|
+
next_steps[0] = f"Restore with `agent memory backup restore {backup_id} --passphrase-env {passphrase_env or 'AGENT_DEVKIT_BACKUP_PASSPHRASE'} --yes`."
|
|
258
|
+
next_steps.insert(1, f"Portable package: {manifest['package']}")
|
|
259
|
+
return {
|
|
260
|
+
"kind": "memory-backup",
|
|
261
|
+
"status": "created",
|
|
262
|
+
"backup": public_backup(manifest, backup_root),
|
|
263
|
+
"home": str(backups),
|
|
264
|
+
"memory_home": memory_paths["home"],
|
|
265
|
+
"next_steps": next_steps,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def list_memory_backups() -> dict[str, Any]:
|
|
270
|
+
backups = memory_backups_home()
|
|
271
|
+
items = []
|
|
272
|
+
if backups.exists():
|
|
273
|
+
for manifest_path in sorted(backups.glob("*/manifest.json")):
|
|
274
|
+
manifest = load_backup_manifest(manifest_path)
|
|
275
|
+
if manifest:
|
|
276
|
+
items.append(public_backup(manifest, manifest_path.parent))
|
|
277
|
+
return {
|
|
278
|
+
"kind": "memory-backups",
|
|
279
|
+
"status": "ok",
|
|
280
|
+
"home": str(backups),
|
|
281
|
+
"count": len(items),
|
|
282
|
+
"items": items,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def restore_memory_backup(
|
|
287
|
+
backup_id: str | None,
|
|
288
|
+
*,
|
|
289
|
+
yes: bool = False,
|
|
290
|
+
backup_file: str | None = None,
|
|
291
|
+
passphrase_env: str | None = None,
|
|
292
|
+
) -> dict[str, Any]:
|
|
293
|
+
backup_root, manifest = require_memory_backup(backup_id, backup_file=backup_file, passphrase_env=passphrase_env)
|
|
294
|
+
payload = {
|
|
295
|
+
"kind": "memory-backup-restore",
|
|
296
|
+
"backup": public_backup(manifest, backup_root),
|
|
297
|
+
"memory_home": str(memory_home()),
|
|
298
|
+
"requires_confirmation": True,
|
|
299
|
+
}
|
|
300
|
+
if not yes:
|
|
301
|
+
return {
|
|
302
|
+
**payload,
|
|
303
|
+
"status": "planned",
|
|
304
|
+
"executed": False,
|
|
305
|
+
"next_steps": [restore_next_step(manifest, backup_file=backup_file, passphrase_env=passphrase_env)],
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
ensure_app_home()
|
|
309
|
+
safety_backup: str | None = None
|
|
310
|
+
with decrypted_memory_source(backup_root, manifest, passphrase_env=passphrase_env) as source_memory:
|
|
311
|
+
if memory_home().exists():
|
|
312
|
+
safety_root = memory_backups_home() / f"pre-restore-{timestamp_id()}"
|
|
313
|
+
safety_root.mkdir(parents=True, exist_ok=False)
|
|
314
|
+
shutil.copytree(memory_home(), safety_root / "memory")
|
|
315
|
+
safety_backup = str(safety_root)
|
|
316
|
+
shutil.rmtree(memory_home())
|
|
317
|
+
shutil.copytree(source_memory, memory_home())
|
|
318
|
+
return {
|
|
319
|
+
**payload,
|
|
320
|
+
"status": "restored",
|
|
321
|
+
"executed": True,
|
|
322
|
+
"safety_backup": safety_backup,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class decrypted_memory_source:
|
|
327
|
+
def __init__(self, backup_root: Path, manifest: dict[str, Any], *, passphrase_env: str | None = None) -> None:
|
|
328
|
+
self.backup_root = backup_root
|
|
329
|
+
self.manifest = manifest
|
|
330
|
+
self.passphrase_env = passphrase_env
|
|
331
|
+
self.temp_dir: tempfile.TemporaryDirectory[str] | None = None
|
|
332
|
+
|
|
333
|
+
def __enter__(self) -> Path:
|
|
334
|
+
plain = self.backup_root / "memory"
|
|
335
|
+
if plain.exists():
|
|
336
|
+
return plain
|
|
337
|
+
package_path = package_path_from_manifest(self.backup_root, self.manifest)
|
|
338
|
+
if not package_path:
|
|
339
|
+
raise ValueError(f"memory backup has no restorable memory payload: {self.manifest.get('id')}")
|
|
340
|
+
passphrase = passphrase_from_env(self.passphrase_env)
|
|
341
|
+
self.temp_dir = tempfile.TemporaryDirectory(prefix="agent-devkit-memory-restore-")
|
|
342
|
+
target = Path(self.temp_dir.name)
|
|
343
|
+
extract_encrypted_backup_package(package_path, target, passphrase)
|
|
344
|
+
memory = target / "memory"
|
|
345
|
+
if not memory.exists():
|
|
346
|
+
raise ValueError("encrypted memory backup package did not contain memory/")
|
|
347
|
+
return memory
|
|
348
|
+
|
|
349
|
+
def __exit__(self, _exc_type: object, _exc: object, _tb: object) -> None:
|
|
350
|
+
if self.temp_dir:
|
|
351
|
+
self.temp_dir.cleanup()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def restore_next_step(manifest: dict[str, Any], *, backup_file: str | None, passphrase_env: str | None) -> str:
|
|
355
|
+
if manifest.get("encrypted"):
|
|
356
|
+
env_name = passphrase_env or "AGENT_DEVKIT_BACKUP_PASSPHRASE"
|
|
357
|
+
if backup_file:
|
|
358
|
+
return f"Re-run with `agent memory backup restore --file {backup_file} --passphrase-env {env_name} --yes` to restore local memory."
|
|
359
|
+
return f"Re-run with `agent memory backup restore {manifest['id']} --passphrase-env {env_name} --yes` to restore local memory."
|
|
360
|
+
if backup_file:
|
|
361
|
+
return f"Re-run with `agent memory backup restore --file {backup_file} --yes` to restore local memory."
|
|
362
|
+
return f"Re-run with `agent memory backup restore {manifest['id']} --yes` to restore local memory."
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def delete_memory_backup(backup_id: str | None, *, yes: bool = False) -> dict[str, Any]:
|
|
366
|
+
backup_root, manifest = require_memory_backup(backup_id)
|
|
367
|
+
payload = {
|
|
368
|
+
"kind": "memory-backup-delete",
|
|
369
|
+
"backup": public_backup(manifest, backup_root),
|
|
370
|
+
"requires_confirmation": True,
|
|
371
|
+
}
|
|
372
|
+
if not yes:
|
|
373
|
+
return {
|
|
374
|
+
**payload,
|
|
375
|
+
"status": "planned",
|
|
376
|
+
"executed": False,
|
|
377
|
+
"next_steps": [f"Re-run with `agent memory backup delete {manifest['id']} --yes` to remove this local backup."],
|
|
378
|
+
}
|
|
379
|
+
shutil.rmtree(backup_root)
|
|
380
|
+
return {
|
|
381
|
+
**payload,
|
|
382
|
+
"status": "deleted",
|
|
383
|
+
"executed": True,
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
196
387
|
def reset_memory(
|
|
197
388
|
*,
|
|
198
389
|
all_memory: bool = False,
|
|
@@ -246,6 +437,220 @@ def memory_path_payload() -> dict[str, Any]:
|
|
|
246
437
|
return ensure_memory()
|
|
247
438
|
|
|
248
439
|
|
|
440
|
+
def unique_backup_id(title: str | None) -> str:
|
|
441
|
+
base = sanitize_segment(title or "memory-backup")
|
|
442
|
+
candidate = f"{base}-{timestamp_id()}"
|
|
443
|
+
backups = memory_backups_home()
|
|
444
|
+
if not (backups / candidate).exists():
|
|
445
|
+
return candidate
|
|
446
|
+
suffix = 2
|
|
447
|
+
while (backups / f"{candidate}-{suffix}").exists():
|
|
448
|
+
suffix += 1
|
|
449
|
+
return f"{candidate}-{suffix}"
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def timestamp_id() -> str:
|
|
453
|
+
return datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def backup_file_inventory(root: Path) -> list[dict[str, Any]]:
|
|
457
|
+
items: list[dict[str, Any]] = []
|
|
458
|
+
for path in sorted(item for item in root.rglob("*") if item.is_file()):
|
|
459
|
+
relative = str(path.relative_to(root))
|
|
460
|
+
data = path.read_bytes()
|
|
461
|
+
items.append(
|
|
462
|
+
{
|
|
463
|
+
"path": relative,
|
|
464
|
+
"bytes": len(data),
|
|
465
|
+
"sha256": hashlib.sha256(data).hexdigest(),
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
return items
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def public_backup(manifest: dict[str, Any], root: Path) -> dict[str, Any]:
|
|
472
|
+
files = manifest.get("files") if isinstance(manifest.get("files"), list) else []
|
|
473
|
+
return {
|
|
474
|
+
"id": manifest.get("id"),
|
|
475
|
+
"title": manifest.get("title"),
|
|
476
|
+
"created_at": manifest.get("created_at"),
|
|
477
|
+
"path": str(root),
|
|
478
|
+
"storage": manifest.get("storage"),
|
|
479
|
+
"remote_upload": manifest.get("remote_upload") is True,
|
|
480
|
+
"encrypted": manifest.get("encrypted") is True,
|
|
481
|
+
"sensitive_local_copy": manifest.get("sensitive_local_copy") is True,
|
|
482
|
+
"package": manifest.get("package"),
|
|
483
|
+
"package_name": manifest.get("package_name"),
|
|
484
|
+
"file_count": len(files),
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def require_memory_backup(
|
|
489
|
+
backup_id: str | None,
|
|
490
|
+
*,
|
|
491
|
+
backup_file: str | None = None,
|
|
492
|
+
passphrase_env: str | None = None,
|
|
493
|
+
) -> tuple[Path, dict[str, Any]]:
|
|
494
|
+
if backup_file:
|
|
495
|
+
package = Path(backup_file).expanduser().resolve()
|
|
496
|
+
manifest = read_encrypted_backup_header(package, passphrase_env=passphrase_env)
|
|
497
|
+
return package.parent, manifest
|
|
498
|
+
item_id = sanitize_segment(backup_id or "")
|
|
499
|
+
if not item_id:
|
|
500
|
+
raise ValueError("memory backup requires a backup id")
|
|
501
|
+
root = memory_backups_home() / item_id
|
|
502
|
+
manifest_path = root / "manifest.json"
|
|
503
|
+
if not manifest_path.exists():
|
|
504
|
+
raise ValueError(f"memory backup not found: {item_id}")
|
|
505
|
+
manifest = load_backup_manifest(manifest_path)
|
|
506
|
+
if not manifest:
|
|
507
|
+
raise ValueError(f"invalid memory backup manifest: {item_id}")
|
|
508
|
+
return root, manifest
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def load_backup_manifest(path: Path) -> dict[str, Any] | None:
|
|
512
|
+
try:
|
|
513
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
514
|
+
except (OSError, json.JSONDecodeError):
|
|
515
|
+
return None
|
|
516
|
+
return payload if isinstance(payload, dict) else None
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def passphrase_from_env(passphrase_env: str | None) -> str:
|
|
520
|
+
env_name = passphrase_env or "AGENT_DEVKIT_BACKUP_PASSPHRASE"
|
|
521
|
+
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", env_name):
|
|
522
|
+
raise ValueError("--passphrase-env must be an environment variable name")
|
|
523
|
+
value = os.environ.get(env_name)
|
|
524
|
+
if not value:
|
|
525
|
+
raise ValueError(f"memory backup passphrase environment variable is not set: {env_name}")
|
|
526
|
+
if len(value) < 8:
|
|
527
|
+
raise ValueError("memory backup passphrase must have at least 8 characters")
|
|
528
|
+
return value
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def write_encrypted_backup_package(package_path: Path, memory_dir: Path, manifest: dict[str, Any], passphrase: str) -> None:
|
|
532
|
+
tar_bytes = memory_tar_bytes(memory_dir)
|
|
533
|
+
salt = os.urandom(16)
|
|
534
|
+
nonce = os.urandom(16)
|
|
535
|
+
key = derive_backup_key(passphrase, salt)
|
|
536
|
+
ciphertext = xor_bytes(tar_bytes, key, nonce)
|
|
537
|
+
header = {
|
|
538
|
+
"schema_version": BACKUP_PACKAGE_SCHEMA_VERSION,
|
|
539
|
+
"algorithm": BACKUP_PACKAGE_ALGORITHM,
|
|
540
|
+
"kdf": "PBKDF2-HMAC-SHA256",
|
|
541
|
+
"iterations": 200_000,
|
|
542
|
+
"salt": base64.b64encode(salt).decode("ascii"),
|
|
543
|
+
"nonce": base64.b64encode(nonce).decode("ascii"),
|
|
544
|
+
"manifest": {key: value for key, value in manifest.items() if key != "files"},
|
|
545
|
+
"files": manifest.get("files") or [],
|
|
546
|
+
}
|
|
547
|
+
header_bytes = json.dumps(header, ensure_ascii=False, sort_keys=True).encode("utf-8")
|
|
548
|
+
tag = hmac.new(key, header_bytes + ciphertext, hashlib.sha256).hexdigest()
|
|
549
|
+
envelope = {
|
|
550
|
+
"schema_version": BACKUP_PACKAGE_SCHEMA_VERSION,
|
|
551
|
+
"header": header,
|
|
552
|
+
"ciphertext": base64.b64encode(ciphertext).decode("ascii"),
|
|
553
|
+
"tag": tag,
|
|
554
|
+
}
|
|
555
|
+
package_path.write_text(json.dumps(envelope, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def read_encrypted_backup_header(package_path: Path, *, passphrase_env: str | None = None) -> dict[str, Any]:
|
|
559
|
+
envelope = read_backup_package(package_path)
|
|
560
|
+
header = envelope["header"]
|
|
561
|
+
manifest = header.get("manifest") if isinstance(header.get("manifest"), dict) else {}
|
|
562
|
+
payload = dict(manifest)
|
|
563
|
+
payload["files"] = header.get("files") if isinstance(header.get("files"), list) else []
|
|
564
|
+
payload["encrypted"] = True
|
|
565
|
+
payload["sensitive_local_copy"] = False
|
|
566
|
+
payload["package"] = str(package_path)
|
|
567
|
+
payload["package_name"] = package_path.name
|
|
568
|
+
payload.setdefault("id", package_path.stem)
|
|
569
|
+
payload.setdefault("title", package_path.stem)
|
|
570
|
+
return payload
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def extract_encrypted_backup_package(package_path: Path, target: Path, passphrase: str) -> None:
|
|
574
|
+
envelope = read_backup_package(package_path)
|
|
575
|
+
header = envelope["header"]
|
|
576
|
+
salt = base64.b64decode(header["salt"])
|
|
577
|
+
nonce = base64.b64decode(header["nonce"])
|
|
578
|
+
ciphertext = base64.b64decode(envelope["ciphertext"])
|
|
579
|
+
key = derive_backup_key(passphrase, salt)
|
|
580
|
+
header_bytes = json.dumps(header, ensure_ascii=False, sort_keys=True).encode("utf-8")
|
|
581
|
+
expected_tag = hmac.new(key, header_bytes + ciphertext, hashlib.sha256).hexdigest()
|
|
582
|
+
if not hmac.compare_digest(expected_tag, str(envelope.get("tag") or "")):
|
|
583
|
+
raise ValueError("memory backup package integrity check failed")
|
|
584
|
+
tar_bytes = xor_bytes(ciphertext, key, nonce)
|
|
585
|
+
extract_memory_tar(tar_bytes, target)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def read_backup_package(package_path: Path) -> dict[str, Any]:
|
|
589
|
+
if not package_path.exists():
|
|
590
|
+
raise ValueError(f"memory backup package not found: {package_path}")
|
|
591
|
+
try:
|
|
592
|
+
envelope = json.loads(package_path.read_text(encoding="utf-8"))
|
|
593
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
594
|
+
raise ValueError(f"invalid memory backup package: {package_path}") from exc
|
|
595
|
+
if not isinstance(envelope, dict) or envelope.get("schema_version") != BACKUP_PACKAGE_SCHEMA_VERSION:
|
|
596
|
+
raise ValueError(f"unsupported memory backup package: {package_path}")
|
|
597
|
+
header = envelope.get("header")
|
|
598
|
+
if not isinstance(header, dict) or header.get("algorithm") != BACKUP_PACKAGE_ALGORITHM:
|
|
599
|
+
raise ValueError(f"unsupported memory backup package algorithm: {package_path}")
|
|
600
|
+
return envelope
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def memory_tar_bytes(memory_dir: Path) -> bytes:
|
|
604
|
+
buffer = io.BytesIO()
|
|
605
|
+
with tarfile.open(fileobj=buffer, mode="w:gz") as archive:
|
|
606
|
+
for path in sorted(item for item in memory_dir.rglob("*") if item.is_file()):
|
|
607
|
+
archive.add(path, arcname=str(Path("memory") / path.relative_to(memory_dir)))
|
|
608
|
+
return buffer.getvalue()
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def extract_memory_tar(payload: bytes, target: Path) -> None:
|
|
612
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
613
|
+
with tarfile.open(fileobj=io.BytesIO(payload), mode="r:gz") as archive:
|
|
614
|
+
for member in archive.getmembers():
|
|
615
|
+
member_path = Path(member.name)
|
|
616
|
+
if member_path.is_absolute() or ".." in member_path.parts or not member.name.startswith("memory/"):
|
|
617
|
+
raise ValueError(f"unsafe path in memory backup package: {member.name}")
|
|
618
|
+
if not member.isfile():
|
|
619
|
+
continue
|
|
620
|
+
output = target / member_path
|
|
621
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
622
|
+
extracted = archive.extractfile(member)
|
|
623
|
+
if extracted is None:
|
|
624
|
+
continue
|
|
625
|
+
output.write_bytes(extracted.read())
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def derive_backup_key(passphrase: str, salt: bytes) -> bytes:
|
|
629
|
+
return hashlib.pbkdf2_hmac("sha256", passphrase.encode("utf-8"), salt, 200_000, dklen=32)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def xor_bytes(payload: bytes, key: bytes, nonce: bytes) -> bytes:
|
|
633
|
+
output = bytearray()
|
|
634
|
+
counter = 0
|
|
635
|
+
for index in range(0, len(payload), 32):
|
|
636
|
+
block = payload[index : index + 32]
|
|
637
|
+
stream = hmac.new(key, nonce + counter.to_bytes(8, "big"), hashlib.sha256).digest()
|
|
638
|
+
output.extend(byte ^ stream[offset] for offset, byte in enumerate(block))
|
|
639
|
+
counter += 1
|
|
640
|
+
return bytes(output)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def package_path_from_manifest(backup_root: Path, manifest: dict[str, Any]) -> Path | None:
|
|
644
|
+
raw = manifest.get("package")
|
|
645
|
+
if isinstance(raw, str) and raw:
|
|
646
|
+
return Path(raw).expanduser().resolve()
|
|
647
|
+
package_name = manifest.get("package_name")
|
|
648
|
+
if isinstance(package_name, str) and package_name:
|
|
649
|
+
return backup_root / package_name
|
|
650
|
+
candidates = sorted(backup_root.glob("*.adkmb"))
|
|
651
|
+
return candidates[0] if candidates else None
|
|
652
|
+
|
|
653
|
+
|
|
249
654
|
def increment_bucket(bucket: dict[str, Any], key: str, now: str) -> None:
|
|
250
655
|
item = bucket.setdefault(key, {"count": 0, "first_seen": now, "last_seen": now})
|
|
251
656
|
item["count"] = int(item.get("count") or 0) + 1
|
|
@@ -5,14 +5,20 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from cli.aikit.embedded_mini_brain import (
|
|
9
|
+
EMBEDDED_BACKEND_ID,
|
|
10
|
+
EMBEDDED_MODEL_ID,
|
|
11
|
+
embedded_mini_brain_status,
|
|
12
|
+
setup_embedded_mini_brain,
|
|
13
|
+
)
|
|
8
14
|
from cli.aikit.llm import BACKENDS, configure_backend, doctor_backend, load_config, save_config
|
|
9
|
-
from cli.aikit.ollama import
|
|
15
|
+
from cli.aikit.ollama import ollama_status
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
MINI_BRAIN_CONFIG_KEY = "mini_brain"
|
|
13
|
-
DEFAULT_HF_MODEL =
|
|
19
|
+
DEFAULT_HF_MODEL = EMBEDDED_MODEL_ID
|
|
14
20
|
DEFAULT_OLLAMA_MODEL = "qwen3:0.6b"
|
|
15
|
-
DEFAULT_PROVIDER =
|
|
21
|
+
DEFAULT_PROVIDER = EMBEDDED_BACKEND_ID
|
|
16
22
|
DEFAULT_BASE_URL = "http://localhost:11434/v1"
|
|
17
23
|
ALLOWED_TASKS = [
|
|
18
24
|
"setup_help",
|
|
@@ -50,14 +56,15 @@ def mini_brain_contract(
|
|
|
50
56
|
) -> dict[str, Any]:
|
|
51
57
|
config = load_config() if config is None else config
|
|
52
58
|
stored = config.get(MINI_BRAIN_CONFIG_KEY) if isinstance(config.get(MINI_BRAIN_CONFIG_KEY), dict) else {}
|
|
53
|
-
enabled = bool(stored.get("enabled"))
|
|
59
|
+
enabled = bool(stored.get("enabled", True))
|
|
54
60
|
provider = stored.get("provider") or stored.get("runtime") or DEFAULT_PROVIDER
|
|
55
61
|
hf_model = stored.get("hf_model") or stored.get("model") or DEFAULT_HF_MODEL
|
|
56
62
|
ollama_model = stored.get("ollama_model") or DEFAULT_OLLAMA_MODEL
|
|
63
|
+
embedded = embedded_mini_brain_status()
|
|
57
64
|
ollama_payload = ollama_status() if ollama_payload is None else ollama_payload
|
|
58
65
|
ollama_backend = doctor_backend(BACKENDS["ollama"], config) if ollama_backend is None else ollama_backend
|
|
59
|
-
|
|
60
|
-
runtime_available =
|
|
66
|
+
ollama_configured = ollama_backend.get("configured") is True
|
|
67
|
+
runtime_available = embedded.get("available") is True
|
|
61
68
|
available = enabled and provider == DEFAULT_PROVIDER and runtime_available
|
|
62
69
|
status = "ok" if available else "disabled" if not enabled else "unavailable"
|
|
63
70
|
return {
|
|
@@ -65,7 +72,8 @@ def mini_brain_contract(
|
|
|
65
72
|
"status": status,
|
|
66
73
|
"enabled": enabled,
|
|
67
74
|
"available": available,
|
|
68
|
-
"configured":
|
|
75
|
+
"configured": available,
|
|
76
|
+
"embedded_configured": provider == DEFAULT_PROVIDER,
|
|
69
77
|
"provider": provider,
|
|
70
78
|
"runtime": provider,
|
|
71
79
|
"hf_model": hf_model,
|
|
@@ -76,6 +84,7 @@ def mini_brain_contract(
|
|
|
76
84
|
"limits": dict_value(stored.get("limits"), DEFAULT_LIMITS),
|
|
77
85
|
"guardrails": list_value(stored.get("guardrails"), DEFAULT_GUARDRAILS),
|
|
78
86
|
"stored_secret": False,
|
|
87
|
+
"embedded": embedded,
|
|
79
88
|
"ollama": {
|
|
80
89
|
"status": ollama_payload.get("status"),
|
|
81
90
|
"daemon": (ollama_payload.get("daemon") or {}).get("status")
|
|
@@ -87,6 +96,7 @@ def mini_brain_contract(
|
|
|
87
96
|
"status": ollama_backend.get("status"),
|
|
88
97
|
"model": ollama_backend.get("model"),
|
|
89
98
|
"base_url": ollama_backend.get("base_url"),
|
|
99
|
+
"configured": ollama_configured,
|
|
90
100
|
},
|
|
91
101
|
}
|
|
92
102
|
|
|
@@ -98,47 +108,59 @@ def setup_mini_brain(
|
|
|
98
108
|
set_default: bool = False,
|
|
99
109
|
model: str = DEFAULT_OLLAMA_MODEL,
|
|
100
110
|
) -> dict[str, Any]:
|
|
111
|
+
embedded = embedded_mini_brain_status()
|
|
101
112
|
if dry_run or not yes:
|
|
102
113
|
status = "planned" if dry_run else "needs-confirmation"
|
|
114
|
+
needs_confirmation = not dry_run and not yes
|
|
103
115
|
return {
|
|
104
116
|
"kind": "mini-brain-setup",
|
|
105
117
|
"status": status,
|
|
106
118
|
"ok": bool(dry_run),
|
|
119
|
+
"exit_code": 2 if needs_confirmation else 0,
|
|
107
120
|
"dry_run": dry_run,
|
|
108
121
|
"yes": yes,
|
|
109
122
|
"stored_secret": False,
|
|
110
123
|
"mini_brain": planned_contract(model=model),
|
|
111
|
-
"
|
|
124
|
+
"embedded": embedded,
|
|
125
|
+
"embedded_install": setup_embedded_mini_brain(dry_run=True, yes=False),
|
|
126
|
+
"ollama_setup": {
|
|
127
|
+
"status": "skipped",
|
|
128
|
+
"ok": True,
|
|
129
|
+
"provider": "ollama",
|
|
130
|
+
"model": model,
|
|
131
|
+
"message": "Ollama is optional; use `agent local-llm install` to add local worker models.",
|
|
132
|
+
},
|
|
112
133
|
"next_steps": ["agent setup mini-brain --yes"],
|
|
113
|
-
"message": "Use --yes to
|
|
134
|
+
"message": "Use --yes to download and enable the embedded Qwen2.5-0.5B mini-brain.",
|
|
114
135
|
}
|
|
115
136
|
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
embedded_install = setup_embedded_mini_brain(dry_run=False, yes=True)
|
|
138
|
+
embedded = embedded_mini_brain_status()
|
|
139
|
+
if embedded_install.get("ok") is not True:
|
|
118
140
|
return {
|
|
119
141
|
"kind": "mini-brain-setup",
|
|
120
142
|
"status": "failed",
|
|
121
143
|
"ok": False,
|
|
144
|
+
"exit_code": embedded_install.get("exit_code", 1),
|
|
122
145
|
"dry_run": False,
|
|
123
146
|
"yes": True,
|
|
124
147
|
"stored_secret": False,
|
|
125
|
-
"mini_brain":
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
148
|
+
"mini_brain": mini_brain_contract(),
|
|
149
|
+
"embedded": embedded,
|
|
150
|
+
"embedded_install": embedded_install,
|
|
151
|
+
"ollama_setup": {
|
|
152
|
+
"status": "skipped",
|
|
153
|
+
"ok": True,
|
|
154
|
+
"provider": "ollama",
|
|
155
|
+
"model": model,
|
|
156
|
+
"message": "Ollama remains optional for additional local worker models.",
|
|
157
|
+
},
|
|
158
|
+
"message": "Embedded mini-brain setup failed before the backend could be enabled.",
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
existing_config = load_config()
|
|
132
|
-
existing_ollama = (
|
|
133
|
-
existing_config.get("llm", {}).get("backends", {}).get(DEFAULT_PROVIDER)
|
|
134
|
-
if isinstance(existing_config.get("llm"), dict)
|
|
135
|
-
else {}
|
|
136
|
-
)
|
|
137
|
-
existing_base_url = existing_ollama.get("base_url") if isinstance(existing_ollama, dict) else None
|
|
138
161
|
configured = configure_backend(
|
|
139
162
|
DEFAULT_PROVIDER,
|
|
140
|
-
|
|
141
|
-
model=model,
|
|
163
|
+
model=DEFAULT_HF_MODEL,
|
|
142
164
|
set_default=set_default,
|
|
143
165
|
)
|
|
144
166
|
config = load_config()
|
|
@@ -154,7 +176,15 @@ def setup_mini_brain(
|
|
|
154
176
|
"stored_secret": False,
|
|
155
177
|
"config_path": str(written_path),
|
|
156
178
|
"mini_brain": contract,
|
|
157
|
-
"
|
|
179
|
+
"embedded": embedded,
|
|
180
|
+
"embedded_install": embedded_install,
|
|
181
|
+
"ollama_setup": {
|
|
182
|
+
"status": "skipped",
|
|
183
|
+
"ok": True,
|
|
184
|
+
"provider": "ollama",
|
|
185
|
+
"model": model,
|
|
186
|
+
"message": "Ollama remains optional for additional local worker models.",
|
|
187
|
+
},
|
|
158
188
|
"llm_configure": configured,
|
|
159
189
|
"next_steps": ["Use low-risk setup, wizard and summary prompts normally."],
|
|
160
190
|
}
|
|
@@ -177,6 +207,7 @@ def planned_contract(*, model: str = DEFAULT_OLLAMA_MODEL) -> dict[str, Any]:
|
|
|
177
207
|
"limits": dict(DEFAULT_LIMITS),
|
|
178
208
|
"guardrails": list(DEFAULT_GUARDRAILS),
|
|
179
209
|
"stored_secret": False,
|
|
210
|
+
"embedded": embedded_mini_brain_status(),
|
|
180
211
|
}
|
|
181
212
|
|
|
182
213
|
|