personal memory agent
at main 144 lines 4.3 kB view raw
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4"""CLI command for chatting with the journal agent.""" 5 6from __future__ import annotations 7 8import argparse 9import sys 10import threading 11 12from think.callosum import CallosumConnection 13from think.cortex_client import cortex_request, read_agent_events 14from think.utils import setup_cli 15 16 17def main() -> None: 18 """Entry point for ``sol chat``.""" 19 parser = argparse.ArgumentParser( 20 prog="sol chat", 21 description="Chat with your journal", 22 ) 23 parser.add_argument("message", nargs="*", help="Chat message") 24 parser.add_argument("--facet", help="Facet context") 25 parser.add_argument("--provider", help="AI provider override") 26 parser.add_argument( 27 "--talent", default="unified", help="Talent agent name (default: unified)" 28 ) 29 args = setup_cli(parser) 30 31 from think.awareness import ensure_sol_directory 32 33 ensure_sol_directory() 34 35 if not args.message: 36 parser.print_help() 37 return 38 39 message = " ".join(args.message).strip() 40 41 config: dict[str, str] = {} 42 if args.facet: 43 config["facet"] = args.facet 44 45 agent_id = cortex_request( 46 prompt=message, 47 name=args.talent, 48 provider=args.provider, 49 config=config if config else None, 50 ) 51 if agent_id is None: 52 print( 53 "Error: failed to connect to cortex (is the stack running?)", 54 file=sys.stderr, 55 ) 56 sys.exit(1) 57 58 result: dict[str, str] = {} 59 done = threading.Event() 60 listener = CallosumConnection() 61 62 def on_event(msg: dict) -> None: 63 if msg.get("tract") != "cortex": 64 return 65 if msg.get("agent_id") != agent_id: 66 return 67 68 event_type = msg.get("event") 69 if event_type == "start": 70 if args.verbose: 71 print( 72 f"Agent started (model={msg.get('model')}, provider={msg.get('provider')})", 73 file=sys.stderr, 74 ) 75 elif event_type == "thinking": 76 if args.verbose: 77 print( 78 f"Thinking: {msg.get('summary', '')[:200]}", 79 file=sys.stderr, 80 ) 81 elif event_type == "tool_start": 82 if args.verbose: 83 print(f"Tool: {msg.get('tool', 'unknown')}", file=sys.stderr) 84 elif event_type == "tool_end": 85 if args.verbose: 86 print(f"Tool done: {msg.get('tool', '')}", file=sys.stderr) 87 elif event_type == "finish": 88 result["text"] = msg.get("result", "") 89 done.set() 90 elif event_type == "error": 91 result["error"] = msg.get("error", "Unknown error") 92 done.set() 93 94 listener.start(callback=on_event) 95 96 if not args.verbose: 97 print("Thinking...", end="", file=sys.stderr, flush=True) 98 99 try: 100 done.wait(timeout=600) 101 except KeyboardInterrupt: 102 print("\nInterrupted.", file=sys.stderr) 103 listener.stop() 104 sys.exit(1) 105 106 listener.stop() 107 108 if not args.verbose: 109 print("\r \r", end="", file=sys.stderr, flush=True) 110 111 if "error" in result: 112 print(f"Error: {result['error']}", file=sys.stderr) 113 sys.exit(1) 114 115 if "text" in result and result["text"].strip(): 116 print(result["text"]) 117 return 118 119 if "text" in result: 120 print("Error: agent returned an empty result.", file=sys.stderr) 121 sys.exit(1) 122 123 try: 124 events = read_agent_events(agent_id) 125 for event in reversed(events): 126 event_type = event.get("event") 127 if event_type == "finish": 128 text = event.get("result", "") 129 if str(text).strip(): 130 print(text) 131 return 132 print("Error: agent returned an empty result.", file=sys.stderr) 133 sys.exit(1) 134 if event_type == "error": 135 print( 136 f"Error: {event.get('error', 'Unknown error')}", 137 file=sys.stderr, 138 ) 139 sys.exit(1) 140 except FileNotFoundError: 141 pass 142 143 print("Error: request timed out.", file=sys.stderr) 144 sys.exit(1)