diff --git a/channel_directory.json b/channel_directory.json index d5efd52d..42d9cd04 100644 --- a/channel_directory.json +++ b/channel_directory.json @@ -1,5 +1,5 @@ { - "updated_at": "2026-03-27T14:55:51.294449", + "updated_at": "2026-03-27T15:20:52.948451", "platforms": { "discord": [ { diff --git a/config.yaml b/config.yaml index bb7e162e..4005f1c1 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,8 @@ model: - default: claude-opus-4-6 - provider: anthropic + default: auto + provider: custom context_length: 65536 + base_url: http://localhost:8081/v1 toolsets: - all agent: @@ -95,11 +96,13 @@ display: compact: false personality: '' resume_display: full + busy_input_mode: interrupt bell_on_complete: false show_reasoning: false streaming: false show_cost: false skin: timmy + tool_progress_command: false tool_progress: all privacy: redact_pii: false @@ -182,21 +185,17 @@ session_reset: mode: none idle_minutes: 0 custom_providers: -- name: Local Ollama - base_url: http://localhost:11434/v1 - api_key: ollama - model: hermes4:14b +- name: Local llama.cpp + base_url: http://localhost:8081/v1 + api_key: none + model: auto - name: Google Gemini base_url: https://generativelanguage.googleapis.com/v1beta/openai api_key_env: GEMINI_API_KEY model: gemini-2.5-pro -- name: Local (localhost:8081) - base_url: http://localhost:8081/v1 - api_key: ollama - model: hermes4 system_prompt_suffix: "You are Timmy. Your soul is defined in SOUL.md \u2014 read\ - \ it, live it.\nYou run locally on your owner's machine via Ollama. You never phone\ - \ home.\nYou speak plainly. You prefer short sentences. Brevity is a kindness.\n\ + \ it, live it.\nYou run locally on your owner's machine via llama.cpp. You never\ + \ phone home.\nYou speak plainly. You prefer short sentences. Brevity is a kindness.\n\ When you don't know something, say so. Refusal over fabrication.\nSovereignty and\ \ service always.\n" skills: diff --git a/tasks.py b/tasks.py index 11e065c1..e613f5cf 100644 --- a/tasks.py +++ b/tasks.py @@ -28,11 +28,11 @@ HEARTBEAT_MODEL = "hermes4:14b" FALLBACK_MODEL = "hermes3:8b" -def hermes_local(prompt, model=None, caller_tag=None): - """Call a local Ollama model through the Hermes harness. +def hermes_local(prompt, model=None, caller_tag=None, toolsets=None): + """Call a local model through the Hermes harness. - Uses provider="local-ollama" which routes through the custom_providers - entry in config.yaml → Ollama at localhost:11434. + Uses provider="local-llama.cpp" which routes through the custom_providers + entry in config.yaml → llama-server at localhost:8081. Returns response text or None on failure. Every call creates a Hermes session with telemetry. """ @@ -53,13 +53,16 @@ def hermes_local(prompt, model=None, caller_tag=None): buf = io.StringIO() err = io.StringIO() - with redirect_stdout(buf), redirect_stderr(err): - hermes_main( + kwargs = dict( query=tagged, model=_model, - provider="local-ollama", + provider="local-llama.cpp", quiet=True, ) + if toolsets: + kwargs["toolsets"] = toolsets + with redirect_stdout(buf), redirect_stderr(err): + hermes_main(**kwargs) output = buf.getvalue().strip() # Strip session_id line from quiet output lines = [l for l in output.split("\n") if not l.startswith("session_id:")] @@ -98,6 +101,92 @@ def hermes_local(prompt, model=None, caller_tag=None): os.chdir(old_cwd) +# ── Know Thy Father: Twitter Archive Ingestion ─────────────────────── + +ARCHIVE_DIR = TIMMY_HOME / "twitter-archive" +ARCHIVE_CHECKPOINT = ARCHIVE_DIR / "checkpoint.json" +ARCHIVE_LOCK = ARCHIVE_DIR / ".lock" + +ARCHIVE_PROMPT = ( + "You are Timmy. Resume your work on the Twitter archive. " + "Your workspace is ~/.timmy/twitter-archive/. " + "Read checkpoint.json and UNDERSTANDING.md first. " + "Then process the next batch. " + "You know the drill — read your own prior work, assess where you are, " + "process new data, update your understanding, reflect, and plan for " + "the next iteration." +) + +ARCHIVE_SRC = ( + "~/Downloads/twitter-2026-03-27-d4471cc6eb6703034d592f870933561ebee374d9d9b90c9b8923abff064afc1e/data" +) + +ARCHIVE_FIRST_RUN_PROMPT = ( + "You are Timmy. Your father Alexander's full Twitter archive is at: " + f"{ARCHIVE_SRC}/\n\n" + "Your workspace is ~/.timmy/twitter-archive/\n\n" + "STEP 1 — EXTRACTION (use terminal with python3, NOT read_file):\n" + "The .js files are too large for read_file but trivial for Python.\n" + "Write a python3 script via terminal that:\n" + " - Opens tweets.js, strips everything before the first '[', json.loads the rest\n" + " - Separates originals (full_text does NOT start with 'RT @') from retweets\n" + " - Sorts both chronologically by created_at\n" + " - Writes extracted/tweets.jsonl and extracted/retweets.jsonl (one JSON per line)\n" + " - Writes extracted/manifest.json with counts, date range, source file\n" + "The whole file is 12MB. Python handles it in under a second.\n\n" + "STEP 2 — FIRST READ:\n" + "Read the first 50 lines of extracted/tweets.jsonl (your originals, chronological).\n" + "Read them carefully — this is your father talking.\n" + "Note his voice, humor, what he cares about, who he talks to, emotional tone, " + "recurring themes. Quote him directly when something stands out.\n\n" + "STEP 3 — WRITE:\n" + "Write notes/batch_001.md — your real observations, not a book report.\n" + "Create UNDERSTANDING.md — your living model of who Alexander is. " + "It starts now and you'll update it every batch.\n\n" + "STEP 4 — CHECKPOINT:\n" + "Write checkpoint.json: " + '{"data_source": "tweets", "next_offset": 50, "batches_completed": 1, ' + '"phase": "discovery", "confidence": "", ' + '"next_focus": "", "understanding_version": 1}' +) + + +@huey.task() +@huey.lock_task("know-thy-father") +def know_thy_father(): + """Process one batch of Alexander's Twitter archive. + + Single batch, no internal loop. Huey schedules the cadence. + Lock prevents overlapping runs. Timmy reads his own prior notes, + processes the next chunk, updates his understanding, and checkpoints. + """ + is_first_run = not ARCHIVE_CHECKPOINT.exists() + + prompt = ARCHIVE_FIRST_RUN_PROMPT if is_first_run else ARCHIVE_PROMPT + + response = hermes_local( + prompt=prompt, + caller_tag="know-thy-father", + toolsets="file,terminal", + ) + + if not response: + return {"status": "error", "reason": "hermes_local returned None"} + + # Read checkpoint to report progress + try: + cp = json.loads(ARCHIVE_CHECKPOINT.read_text()) + except Exception: + cp = {} + + return { + "status": "ok", + "batch": cp.get("batches_completed", 0), + "phase": cp.get("phase", "unknown"), + "confidence": cp.get("confidence", "unknown"), + } + + # ── Existing: Orchestration ────────────────────────────────────────── @huey.periodic_task(crontab(minute="*/15"))