loki-mode 6.83.1 → 7.0.2
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/SKILL.md +62 -11
- package/VERSION +1 -1
- package/agents/managed_registry.py +246 -0
- package/agents/types.json +330 -0
- package/autonomy/completion-council.sh +226 -0
- package/autonomy/loki +346 -15
- package/autonomy/run.sh +408 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +235 -0
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/managed_tools.py +245 -0
- package/mcp/server.py +22 -0
- package/memory/managed_memory/__init__.py +9 -0
- package/memory/managed_memory/retrieve.py +237 -1
- package/package.json +4 -2
- package/providers/managed.py +789 -0
- package/skills/00-index.md +1 -0
- package/skills/memory.md +187 -0
|
@@ -284,6 +284,234 @@ def hydrate_patterns(
|
|
|
284
284
|
return merged
|
|
285
285
|
|
|
286
286
|
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# Hydrate procedural skills
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def hydrate_skills(
|
|
293
|
+
local_mtime_floor: float,
|
|
294
|
+
target_dir: Optional[str] = None,
|
|
295
|
+
) -> int:
|
|
296
|
+
"""
|
|
297
|
+
Pull procedural skills from the managed store and merge them into
|
|
298
|
+
.loki/memory/skills/{name}.json (one file per skill). Returns the number
|
|
299
|
+
of skill files written. Returns 0 on disabled / error.
|
|
300
|
+
|
|
301
|
+
Only skills whose remote timestamp is newer than `local_mtime_floor` are
|
|
302
|
+
merged. Local wins on conflict: a skill whose filename already exists is
|
|
303
|
+
NOT overwritten.
|
|
304
|
+
"""
|
|
305
|
+
if not is_enabled():
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
client = _get_client()
|
|
310
|
+
except ManagedDisabled as e:
|
|
311
|
+
emit_managed_event(
|
|
312
|
+
"managed_agents_fallback",
|
|
313
|
+
{"reason": "client_unavailable", "detail": str(e), "op": "hydrate_skills"},
|
|
314
|
+
)
|
|
315
|
+
return 0
|
|
316
|
+
except Exception as e: # pragma: no cover
|
|
317
|
+
emit_managed_event(
|
|
318
|
+
"managed_agents_fallback",
|
|
319
|
+
{"reason": "client_error", "detail": str(e), "op": "hydrate_skills"},
|
|
320
|
+
)
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
store = client.stores_get_or_create(
|
|
325
|
+
name=_store_name(),
|
|
326
|
+
description="Loki Mode RARV-C shadow-write store (v6.83.0)",
|
|
327
|
+
scope="project",
|
|
328
|
+
)
|
|
329
|
+
store_id = store.get("id") or store.get("store_id")
|
|
330
|
+
if not store_id:
|
|
331
|
+
return 0
|
|
332
|
+
entries = client.memories_list(store_id=store_id, path_prefix="skills/")
|
|
333
|
+
except Exception as e:
|
|
334
|
+
emit_managed_event(
|
|
335
|
+
"managed_agents_fallback",
|
|
336
|
+
{"reason": "list_error", "detail": str(e), "op": "hydrate_skills"},
|
|
337
|
+
)
|
|
338
|
+
return 0
|
|
339
|
+
|
|
340
|
+
target_dir = target_dir or os.environ.get("LOKI_TARGET_DIR") or os.getcwd()
|
|
341
|
+
skills_dir = Path(target_dir) / ".loki" / "memory" / "skills"
|
|
342
|
+
skills_dir.mkdir(parents=True, exist_ok=True)
|
|
343
|
+
|
|
344
|
+
merged = 0
|
|
345
|
+
for e in entries:
|
|
346
|
+
content = e.get("content")
|
|
347
|
+
if not content:
|
|
348
|
+
continue
|
|
349
|
+
try:
|
|
350
|
+
skill = json.loads(content)
|
|
351
|
+
except (TypeError, json.JSONDecodeError):
|
|
352
|
+
continue
|
|
353
|
+
sid = skill.get("id") or skill.get("skill_id")
|
|
354
|
+
name = skill.get("name") or sid
|
|
355
|
+
if not name:
|
|
356
|
+
continue
|
|
357
|
+
|
|
358
|
+
# Sanitize filename (mirror MemoryStorage.save_skill).
|
|
359
|
+
safe_name = "".join(
|
|
360
|
+
c if c.isalnum() or c in "-_" else "_" for c in str(name)
|
|
361
|
+
)
|
|
362
|
+
skill_path = skills_dir / f"{safe_name}.json"
|
|
363
|
+
if skill_path.exists():
|
|
364
|
+
# Local wins on conflict.
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
# Optional mtime gate.
|
|
368
|
+
ts = skill.get("updated_at") or skill.get("created_at")
|
|
369
|
+
if ts and local_mtime_floor:
|
|
370
|
+
try:
|
|
371
|
+
if isinstance(ts, (int, float)) and float(ts) < local_mtime_floor:
|
|
372
|
+
continue
|
|
373
|
+
except (TypeError, ValueError):
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
from memory.storage import MemoryStorage # type: ignore
|
|
378
|
+
|
|
379
|
+
storage = MemoryStorage(str(skills_dir.parent))
|
|
380
|
+
storage._atomic_write(skill_path, skill)
|
|
381
|
+
except Exception:
|
|
382
|
+
import tempfile
|
|
383
|
+
|
|
384
|
+
fd, tmp = tempfile.mkstemp(
|
|
385
|
+
dir=str(skills_dir), prefix=".tmp_", suffix=".json"
|
|
386
|
+
)
|
|
387
|
+
try:
|
|
388
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
389
|
+
json.dump(skill, f, indent=2, default=str)
|
|
390
|
+
os.replace(tmp, skill_path)
|
|
391
|
+
except Exception as ex:
|
|
392
|
+
if os.path.exists(tmp):
|
|
393
|
+
os.unlink(tmp)
|
|
394
|
+
emit_managed_event(
|
|
395
|
+
"managed_agents_fallback",
|
|
396
|
+
{"reason": "atomic_write_failed", "detail": str(ex), "op": "hydrate_skills"},
|
|
397
|
+
)
|
|
398
|
+
continue
|
|
399
|
+
merged += 1
|
|
400
|
+
|
|
401
|
+
emit_managed_event(
|
|
402
|
+
"managed_memory_hydrate_skills",
|
|
403
|
+
{"merged": merged, "candidates": len(entries)},
|
|
404
|
+
)
|
|
405
|
+
return merged
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ---------------------------------------------------------------------------
|
|
409
|
+
# Session hydrate (patterns + skills) with idempotency guard
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
_HYDRATE_SENTINEL = ".loki/managed/hydrate.lock"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _already_hydrated_this_session(target_dir: str) -> bool:
|
|
417
|
+
"""Idempotent: once we write the sentinel file, a second hydrate is no-op."""
|
|
418
|
+
sentinel = Path(target_dir) / _HYDRATE_SENTINEL
|
|
419
|
+
return sentinel.exists()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _mark_hydrated(target_dir: str) -> None:
|
|
423
|
+
sentinel = Path(target_dir) / _HYDRATE_SENTINEL
|
|
424
|
+
sentinel.parent.mkdir(parents=True, exist_ok=True)
|
|
425
|
+
try:
|
|
426
|
+
sentinel.write_text(str(int(time.time())), encoding="utf-8")
|
|
427
|
+
except OSError:
|
|
428
|
+
pass
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def hydrate(
|
|
432
|
+
namespace: Optional[str] = None,
|
|
433
|
+
mtime_floor: Optional[float] = None,
|
|
434
|
+
target_dir: Optional[str] = None,
|
|
435
|
+
) -> Dict[str, int]:
|
|
436
|
+
"""
|
|
437
|
+
Session-boot hydrate: pull semantic patterns AND procedural skills from
|
|
438
|
+
the managed store and merge them into local .loki/memory/. Emits a single
|
|
439
|
+
`managed_memory_hydrate` event with counts.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
namespace: Optional logical namespace label; reserved for multi-tenant
|
|
443
|
+
stores (not yet used by the backend). Included in the event for
|
|
444
|
+
observability.
|
|
445
|
+
mtime_floor: Only merge remote entries updated after this epoch
|
|
446
|
+
timestamp. Defaults to 0.0 (pull everything not already local).
|
|
447
|
+
target_dir: Override .loki root; defaults to LOKI_TARGET_DIR or cwd.
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
{"patterns": N, "skills": M, "skipped": bool}. Disabled flags / errors
|
|
451
|
+
return {"patterns": 0, "skills": 0, "skipped": True/False}.
|
|
452
|
+
|
|
453
|
+
Idempotent: a second call within the same session (while the lock file
|
|
454
|
+
exists) short-circuits and returns zero counts with skipped=True.
|
|
455
|
+
"""
|
|
456
|
+
target_dir = target_dir or os.environ.get("LOKI_TARGET_DIR") or os.getcwd()
|
|
457
|
+
|
|
458
|
+
if not is_enabled():
|
|
459
|
+
return {"patterns": 0, "skills": 0, "skipped": True}
|
|
460
|
+
|
|
461
|
+
if _already_hydrated_this_session(target_dir):
|
|
462
|
+
emit_managed_event(
|
|
463
|
+
"managed_memory_hydrate",
|
|
464
|
+
{
|
|
465
|
+
"patterns": 0,
|
|
466
|
+
"skills": 0,
|
|
467
|
+
"skipped": True,
|
|
468
|
+
"reason": "already_hydrated_this_session",
|
|
469
|
+
"namespace": namespace or "",
|
|
470
|
+
},
|
|
471
|
+
)
|
|
472
|
+
return {"patterns": 0, "skills": 0, "skipped": True}
|
|
473
|
+
|
|
474
|
+
floor = float(mtime_floor) if mtime_floor is not None else 0.0
|
|
475
|
+
|
|
476
|
+
patterns_merged = 0
|
|
477
|
+
skills_merged = 0
|
|
478
|
+
try:
|
|
479
|
+
patterns_merged = hydrate_patterns(
|
|
480
|
+
local_mtime_floor=floor, target_dir=target_dir
|
|
481
|
+
)
|
|
482
|
+
except Exception as e: # pragma: no cover - defensive
|
|
483
|
+
emit_managed_event(
|
|
484
|
+
"managed_agents_fallback",
|
|
485
|
+
{"reason": "hydrate_patterns_error", "detail": str(e), "op": "hydrate"},
|
|
486
|
+
)
|
|
487
|
+
try:
|
|
488
|
+
skills_merged = hydrate_skills(
|
|
489
|
+
local_mtime_floor=floor, target_dir=target_dir
|
|
490
|
+
)
|
|
491
|
+
except Exception as e: # pragma: no cover - defensive
|
|
492
|
+
emit_managed_event(
|
|
493
|
+
"managed_agents_fallback",
|
|
494
|
+
{"reason": "hydrate_skills_error", "detail": str(e), "op": "hydrate"},
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
_mark_hydrated(target_dir)
|
|
498
|
+
|
|
499
|
+
emit_managed_event(
|
|
500
|
+
"managed_memory_hydrate",
|
|
501
|
+
{
|
|
502
|
+
"patterns": patterns_merged,
|
|
503
|
+
"skills": skills_merged,
|
|
504
|
+
"skipped": False,
|
|
505
|
+
"namespace": namespace or "",
|
|
506
|
+
},
|
|
507
|
+
)
|
|
508
|
+
return {
|
|
509
|
+
"patterns": patterns_merged,
|
|
510
|
+
"skills": skills_merged,
|
|
511
|
+
"skipped": False,
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
|
|
287
515
|
# ---------------------------------------------------------------------------
|
|
288
516
|
# Module CLI
|
|
289
517
|
# ---------------------------------------------------------------------------
|
|
@@ -319,7 +547,15 @@ def _main(argv: Optional[list] = None) -> int:
|
|
|
319
547
|
floor = 0.0
|
|
320
548
|
if args.since_seconds and args.since_seconds > 0:
|
|
321
549
|
floor = time.time() - args.since_seconds
|
|
322
|
-
|
|
550
|
+
# Phase 2: session-boot hydrate covers patterns + skills and is
|
|
551
|
+
# idempotent (sentinel-guarded). Prints a one-line summary to
|
|
552
|
+
# stdout so callers can log counts without parsing JSON.
|
|
553
|
+
result = hydrate(mtime_floor=floor)
|
|
554
|
+
print(
|
|
555
|
+
f"[managed] hydrate patterns={result.get('patterns', 0)} "
|
|
556
|
+
f"skills={result.get('skills', 0)} "
|
|
557
|
+
f"skipped={result.get('skipped', False)}"
|
|
558
|
+
)
|
|
323
559
|
return 0
|
|
324
560
|
|
|
325
561
|
query = args.query or ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"VERSION",
|
|
68
68
|
"autonomy/",
|
|
69
69
|
"providers/",
|
|
70
|
+
"agents/",
|
|
70
71
|
"skills/",
|
|
71
72
|
"references/",
|
|
72
73
|
"docs/**/*.md",
|
|
@@ -105,7 +106,8 @@
|
|
|
105
106
|
"test:visual": "node --experimental-vm-modules node_modules/jest/bin/jest.js dashboard-ui/tests/visual-regression.test.js",
|
|
106
107
|
"test:parity": "node --experimental-vm-modules dashboard-ui/scripts/check-parity.js",
|
|
107
108
|
"test:parity:json": "node --experimental-vm-modules dashboard-ui/scripts/check-parity.js --json",
|
|
108
|
-
"test:dashboard": "npm run test:visual && npm run test:parity"
|
|
109
|
+
"test:dashboard": "npm run test:visual && npm run test:parity",
|
|
110
|
+
"test:integration": "bash tests/integration/run_integration_suite.sh"
|
|
109
111
|
},
|
|
110
112
|
"engines": {
|
|
111
113
|
"node": ">=18.0.0"
|