replica-omnisciente/scripts/sync-agents-to-brain.py
Raphael Cautus (Maestro) c96b6631ce
Some checks failed
Aurélio Sync & Conscience Upgrade / Upgrade Réplica Conscience (push) Failing after 27s
feat(scripts): agent importer — new adapters, RL trajectory output
Extends sync-agents-to-brain.py with:
- cursor, zed, continue, aider, cline adapters (11 total now)
- RL trajectory extraction (state/action/observation triples)
- Improved content flattening for OpenAI/Anthropic multipart content
- Engine: stable per-message timestamps, session_memory.md rendering

Co-authored-by: Álvaro de Campos <campos@portugalfuturista.org>
2026-07-31 00:08:26 +01:00

172 lines
7.1 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Unified agent → Réplica Omnisciente brain importer.
Imports session history, plans and artifacts from every coding agent in the
fleet into the unified brain at <replica>/.aurelio/brain/, mirroring the Kimi
bridge (`scripts/sync-kimi-to-brain.py`) but for all sources:
kimi ~/.kimi/sessions/**/context.jsonl (+ plans)
hermes ~/.hermes/sessions/{saved,request_dump_*}.json
claude-code ~/.claude/projects/<hash>/<session>.jsonl (+ plans)
antigravity ~/.gemini/antigravity-{cli,ide}/conversations/*.db (sqlite)
qwen-code ~/.qwen[-code]/**/{context.jsonl,*.jsonl} (no-op if absent)
mimocode ./.mimocode/plans/*.md (+ future *.jsonl)
pi ~/.pi/agent/sessions/**/*.jsonl
opencode ~/.local/share/opencode/opencode.db (sqlite)
Output contract per session (see agent_importers/engine.py):
.aurelio/brain/session-<source>-<id>/{session.jsonl, session_memory.md,
.system_generated/chat_history.json, .system_generated/logs/overview.txt}
Idempotent: re-running writes only when content changes; timestamps are derived
from the source artifact so identical inputs produce byte-identical outputs.
Usage:
python3 scripts/sync-agents-to-brain.py [--source NAME]... [--dry-run]
[--skip-active] [--replica-root PATH]
[--summary]
# --source may be repeated; default = all known sources.
"""
from __future__ import annotations
import argparse
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Any
# Make `agent_importers` importable whether run as a module or a script.
sys.path.insert(0, str(Path(__file__).resolve().parent))
from agent_importers import ( # noqa: E402
ADAPTERS,
REPLICA_ROOT,
write_session,
derive_title,
)
from agent_importers.adapters import ( # noqa: E402
discover_kimi,
discover_hermes,
discover_claude_code,
discover_antigravity_cli,
discover_qwen_code,
discover_mimocode,
discover_pi,
discover_opencode,
)
def _is_recent(path_str: str, seconds: int = 60) -> bool:
try:
return (datetime.now().timestamp() - Path(path_str).stat().st_mtime) < seconds
except OSError:
return False
def _title_for(sess) -> str:
return sess.title or derive_title(sess.messages, sess.workspace_path, f"{sess.source} session")
def run(args: argparse.Namespace) -> dict[str, Any]:
replica_root = Path(args.replica_root).resolve() if args.replica_root else REPLICA_ROOT
# Patch the module-level roots used by the engine/writer.
import agent_importers.engine as eng
eng.REPLICA_ROOT = replica_root
eng.BRAIN_DIR = replica_root / ".aurelio" / "brain"
requested = args.source or list(ADAPTERS.keys())
summary: dict[str, Any] = {
"replica_root": str(replica_root),
"brain_dir": str(eng.BRAIN_DIR),
"dry_run": args.dry_run,
"sources": {},
"totals": {"sessions": 0, "written": 0, "skipped_empty": 0, "skipped_active": 0},
}
dispatch = {
"kimi": lambda: discover_kimi(Path(args.kimi_dir) if args.kimi_dir else None),
"hermes": lambda: discover_hermes(Path(args.hermes_dir) if args.hermes_dir else None),
"claude-code": lambda: discover_claude_code(Path(args.claude_dir) if args.claude_dir else None),
"antigravity": lambda: discover_antigravity_cli(Path(args.gemini_dir) if args.gemini_dir else None),
"qwen-code": lambda: discover_qwen_code(Path(args.qwen_dir) if args.qwen_dir else None),
"mimocode": lambda: discover_mimocode(repo_root=replica_root),
"pi": lambda: discover_pi(Path(args.pi_dir) if args.pi_dir else None),
"opencode": lambda: discover_opencode(Path(args.opencode_db) if args.opencode_db else None),
}
for name in requested:
if name not in ADAPTERS:
print(f"unknown source: {name}", file=sys.stderr)
continue
src_stats = {"sessions": 0, "written": 0, "skipped_empty": 0, "skipped_active": 0, "files": []}
try:
sessions = list(dispatch[name]())
except Exception as exc: # defensive: one bad source must not abort the run
print(f"[{name}] discovery failed: {exc}", file=sys.stderr)
sessions = []
for sess in sessions:
if not sess.title:
sess.title = _title_for(sess)
if args.skip_active and _is_recent(sess.source_locator):
src_stats["skipped_active"] += 1
continue
res = write_session(sess, dry_run=args.dry_run)
src_stats["sessions"] += 1
if res.get("skipped_empty"):
src_stats["skipped_empty"] += 1
wrote = res.get("written") or res.get("would_write") or []
if wrote:
src_stats["written"] += len(wrote)
if args.verbose:
src_stats["files"].append({res["brain_id"]: wrote})
summary["sources"][name] = src_stats
summary["totals"]["sessions"] += src_stats["sessions"]
summary["totals"]["written"] += src_stats["written"]
summary["totals"]["skipped_empty"] += src_stats["skipped_empty"]
summary["totals"]["skipped_active"] += src_stats["skipped_active"]
return summary
def main() -> int:
ap = argparse.ArgumentParser(description="Import all coding-agent artifacts into the Réplica brain.")
ap.add_argument("--source", action="append", choices=sorted(ADAPTERS.keys()),
help="Limit to one or more sources (repeatable). Default: all.")
ap.add_argument("--dry-run", action="store_true", help="Report what would be written without touching disk.")
ap.add_argument("--skip-active", action="store_true", help="Skip artifacts modified in the last 60s.")
ap.add_argument("--replica-root", default=None, help="Override replica-omnisciente root.")
ap.add_argument("--kimi-dir", default=None)
ap.add_argument("--hermes-dir", default=None)
ap.add_argument("--claude-dir", default=None)
ap.add_argument("--gemini-dir", default=None)
ap.add_argument("--qwen-dir", default=None)
ap.add_argument("--pi-dir", default=None)
ap.add_argument("--opencode-db", default=None)
ap.add_argument("--summary", action="store_true", help="Print JSON summary.")
ap.add_argument("--verbose", "-v", action="store_true")
args = ap.parse_args()
summary = run(args)
if args.summary:
print(json.dumps(summary, indent=2, ensure_ascii=False))
else:
t = summary["totals"]
print(f"sources: {', '.join(summary['sources']) or '(none)'}")
print(f"sessions: {t['sessions']} files_written: {t['written']} "
f"skipped_empty: {t['skipped_empty']} skipped_active: {t['skipped_active']}")
if args.dry_run:
print("(dry-run: nothing written)")
for name, st in summary["sources"].items():
print(f" {name:14s} sessions={st['sessions']:4d} wrote={st['written']:4d} "
f"empty={st['skipped_empty']:3d} active={st['skipped_active']:3d}")
return 0
if __name__ == "__main__":
raise SystemExit(main())