From f1fe41dac70f5d893e76340faae1a0a919265cd8 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sat, 25 Apr 2026 13:15:15 +0200 Subject: [PATCH] Adding changes to docker compose --- agents/gerhard-hermes/channel_directory.json | 2 +- agents/gerhard-hermes/gateway.lock | 1 - agents/gerhard-hermes/gateway_state.json | 2 +- agents/gerhard-hermes/logs/agent.log | 46 ++++ agents/gerhard-hermes/logs/errors.log | 12 + agents/gerhard-hermes/state.db-shm | Bin 0 -> 32768 bytes agents/gerhard-hermes/state.db-wal | 0 docker-compose.yml | 6 +- plugins/festinger/festinger/main.py | 225 +++++++++++++++++++ plugins/festinger/festinger/recollection.py | 20 +- 10 files changed, 308 insertions(+), 6 deletions(-) delete mode 100644 agents/gerhard-hermes/gateway.lock create mode 100644 agents/gerhard-hermes/state.db-shm create mode 100644 agents/gerhard-hermes/state.db-wal diff --git a/agents/gerhard-hermes/channel_directory.json b/agents/gerhard-hermes/channel_directory.json index aa6348f..97de673 100644 --- a/agents/gerhard-hermes/channel_directory.json +++ b/agents/gerhard-hermes/channel_directory.json @@ -1,5 +1,5 @@ { - "updated_at": "2026-04-25T09:38:38.117239", + "updated_at": "2026-04-25T11:03:49.746150", "platforms": { "telegram": [], "discord": [], diff --git a/agents/gerhard-hermes/gateway.lock b/agents/gerhard-hermes/gateway.lock deleted file mode 100644 index 7f4fdbc..0000000 --- a/agents/gerhard-hermes/gateway.lock +++ /dev/null @@ -1 +0,0 @@ -{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 28571} \ No newline at end of file diff --git a/agents/gerhard-hermes/gateway_state.json b/agents/gerhard-hermes/gateway_state.json index b384a22..616ac0d 100644 --- a/agents/gerhard-hermes/gateway_state.json +++ b/agents/gerhard-hermes/gateway_state.json @@ -1 +1 @@ -{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 28571, "gateway_state": "stopped", "exit_reason": null, "restart_requested": false, "active_agents": 0, "platforms": {}, "updated_at": "2026-04-25T09:40:22.622059+00:00"} \ No newline at end of file +{"pid": 7, "kind": "hermes-gateway", "argv": ["/opt/hermes/.venv/bin/hermes", "gateway", "run"], "start_time": 539712, "gateway_state": "stopped", "exit_reason": null, "restart_requested": false, "active_agents": 0, "platforms": {}, "updated_at": "2026-04-25T11:13:41.592795+00:00"} \ No newline at end of file diff --git a/agents/gerhard-hermes/logs/agent.log b/agents/gerhard-hermes/logs/agent.log index f99dd95..2d18d4e 100644 --- a/agents/gerhard-hermes/logs/agent.log +++ b/agents/gerhard-hermes/logs/agent.log @@ -32,3 +32,49 @@ 2026-04-25 09:40:22,622 INFO gateway.run: Gateway stopped 2026-04-25 09:40:22,622 INFO gateway.run: Cron ticker stopped 2026-04-25 09:40:22,997 INFO gateway.run: Exiting with code 1 (signal-initiated shutdown without restart request) so systemd Restart=on-failure can revive the gateway. +2026-04-25 10:48:29,979 INFO hermes_cli.plugins: Plugin 'openai' registered image_gen provider: openai +2026-04-25 10:48:29,981 INFO hermes_cli.plugins: Plugin 'openai-codex' registered image_gen provider: openai-codex +2026-04-25 10:48:30,118 INFO hermes_cli.plugins: Plugin 'xai' registered image_gen provider: xai +2026-04-25 10:48:30,326 INFO hermes_cli.plugins: Plugin discovery complete: 5 found, 4 enabled +2026-04-25 10:48:30,987 INFO hermes_cli.web_server: Mounted plugin API routes: /api/plugins/example/ +2026-04-25 10:48:30,988 WARNING hermes_cli.web_server: Binding to 0.0.0.0 with --insecure — the dashboard has no robust authentication. Only use on trusted networks. +2026-04-25 10:48:31,628 INFO gateway.run: Starting Hermes Gateway... +2026-04-25 10:48:31,629 INFO gateway.run: Session storage: /opt/data/sessions +2026-04-25 10:48:31,636 WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id). +2026-04-25 10:48:31,640 INFO gateway.run: Previous gateway exited cleanly — skipping session suspension +2026-04-25 10:48:31,640 WARNING gateway.run: No messaging platforms enabled. +2026-04-25 10:48:31,641 INFO gateway.run: Gateway will continue running for cron job execution. +2026-04-25 10:48:31,642 INFO gateway.run: 1 hook(s) loaded +2026-04-25 10:48:31,645 INFO gateway.run: Channel directory built: 0 target(s) +2026-04-25 10:48:31,645 INFO gateway.run: Press Ctrl+C to stop +2026-04-25 10:48:31,686 INFO gateway.run: Cron ticker started (interval=60s) +2026-04-25 11:03:44,640 INFO gateway.run: Received SIGTERM/SIGINT — initiating shutdown +2026-04-25 11:03:44,686 WARNING gateway.run: Shutdown diagnostic — other hermes processes running: + root 1 0.0 0.0 2288 1032 ? Ss 10:48 0:00 /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh gateway run + hermes 35 0.0 0.0 6504 3484 ? R 11:03 0:00 ps aux +2026-04-25 11:03:44,695 INFO gateway.run: Stopping gateway... +2026-04-25 11:03:45,314 INFO gateway.run: Gateway stopped +2026-04-25 11:03:45,315 INFO gateway.run: Cron ticker stopped +2026-04-25 11:03:47,883 INFO hermes_cli.plugins: Plugin 'openai' registered image_gen provider: openai +2026-04-25 11:03:47,886 INFO hermes_cli.plugins: Plugin 'openai-codex' registered image_gen provider: openai-codex +2026-04-25 11:03:48,041 INFO hermes_cli.plugins: Plugin 'xai' registered image_gen provider: xai +2026-04-25 11:03:48,333 INFO hermes_cli.plugins: Plugin discovery complete: 5 found, 4 enabled +2026-04-25 11:03:49,226 INFO hermes_cli.web_server: Mounted plugin API routes: /api/plugins/example/ +2026-04-25 11:03:49,226 WARNING hermes_cli.web_server: Binding to 0.0.0.0 with --insecure — the dashboard has no robust authentication. Only use on trusted networks. +2026-04-25 11:03:49,730 INFO gateway.run: Starting Hermes Gateway... +2026-04-25 11:03:49,730 INFO gateway.run: Session storage: /opt/data/sessions +2026-04-25 11:03:49,737 WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id). +2026-04-25 11:03:49,742 INFO gateway.run: Previous gateway exited cleanly — skipping session suspension +2026-04-25 11:03:49,742 WARNING gateway.run: No messaging platforms enabled. +2026-04-25 11:03:49,742 INFO gateway.run: Gateway will continue running for cron job execution. +2026-04-25 11:03:49,743 INFO gateway.run: 1 hook(s) loaded +2026-04-25 11:03:49,747 INFO gateway.run: Channel directory built: 0 target(s) +2026-04-25 11:03:49,747 INFO gateway.run: Press Ctrl+C to stop +2026-04-25 11:03:49,793 INFO gateway.run: Cron ticker started (interval=60s) +2026-04-25 11:13:40,708 INFO gateway.run: Received SIGTERM/SIGINT — initiating shutdown +2026-04-25 11:13:40,720 WARNING gateway.run: Shutdown diagnostic — other hermes processes running: + root 1 0.0 0.0 2288 1108 ? Ss 11:03 0:00 /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh gateway run + hermes 35 0.0 0.0 6504 3488 ? R 11:13 0:00 ps aux +2026-04-25 11:13:40,723 INFO gateway.run: Stopping gateway... +2026-04-25 11:13:41,593 INFO gateway.run: Gateway stopped +2026-04-25 11:13:41,593 INFO gateway.run: Cron ticker stopped diff --git a/agents/gerhard-hermes/logs/errors.log b/agents/gerhard-hermes/logs/errors.log index bde3b6e..4a40c2d 100644 --- a/agents/gerhard-hermes/logs/errors.log +++ b/agents/gerhard-hermes/logs/errors.log @@ -3,3 +3,15 @@ 2026-04-25 09:40:22,113 WARNING gateway.run: Shutdown diagnostic — other hermes processes running: root 1 0.0 0.0 2288 1104 ? Ss 09:38 0:00 /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh gateway run hermes 37 0.0 0.0 6504 3488 ? R 09:40 0:00 ps aux +2026-04-25 10:48:30,988 WARNING hermes_cli.web_server: Binding to 0.0.0.0 with --insecure — the dashboard has no robust authentication. Only use on trusted networks. +2026-04-25 10:48:31,636 WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id). +2026-04-25 10:48:31,640 WARNING gateway.run: No messaging platforms enabled. +2026-04-25 11:03:44,686 WARNING gateway.run: Shutdown diagnostic — other hermes processes running: + root 1 0.0 0.0 2288 1032 ? Ss 10:48 0:00 /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh gateway run + hermes 35 0.0 0.0 6504 3484 ? R 11:03 0:00 ps aux +2026-04-25 11:03:49,226 WARNING hermes_cli.web_server: Binding to 0.0.0.0 with --insecure — the dashboard has no robust authentication. Only use on trusted networks. +2026-04-25 11:03:49,737 WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied. Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id). +2026-04-25 11:03:49,742 WARNING gateway.run: No messaging platforms enabled. +2026-04-25 11:13:40,720 WARNING gateway.run: Shutdown diagnostic — other hermes processes running: + root 1 0.0 0.0 2288 1108 ? Ss 11:03 0:00 /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh gateway run + hermes 35 0.0 0.0 6504 3488 ? R 11:13 0:00 ps aux diff --git a/agents/gerhard-hermes/state.db-shm b/agents/gerhard-hermes/state.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3 Resp return await _handle_ollama_generate(request, agent_name=agent_id.lower()) +# --------------------------------------------------------------------------- +# /scan — gutask integration: scan task / letter text and return recollection +# --------------------------------------------------------------------------- + +# Saliency encounter weight per context type. +# Facts stated in tasks are stronger signals than chat overheard in passing. +_CONTEXT_WEIGHT: dict[str, float] = { + "task": 2.0, + "letter": 1.5, + "chat": 1.0, +} + + +@app.post("/scan") +async def scan_text(request: Request) -> dict: + """ + Scan a block of plain text for domain concepts and return a recollection block. + + Called by gutask when an agent reads a task or a letter, so that the agent + sees relevant knowledge-graph context alongside the task/letter content. + + Body: + text (str) — the task description, letter body, or any free text + agent (str) — agent name (for logging; optional) + context (str) — "task" | "letter" | "chat" (default: "task") + + Returns: + recollection_block (str | null) — the block, + or null if nothing salient was found + salient_tokens (list[str]) — concepts that triggered the block + cues_found (int) — number of URD cues extracted from the text + """ + pool = request.app.state.pool + data = await request.json() + + text: str = data.get("text", "").strip() + agent_name: str = data.get("agent", "").strip().lower() + context_type: str = data.get("context", "task").strip().lower() + weight: float = _CONTEXT_WEIGHT.get(context_type, 1.0) + + if not text: + return {"recollection_block": None, "salient_tokens": [], "cues_found": 0} + + read_threshold = float(await get_config(pool, "saliency_read_threshold", "0.5")) + conf_floor = float(await get_config(pool, "recollection_confidence_floor", "0.6")) + recency_days = int(await get_config(pool, "recollection_recency_days", "90")) + + # 1. Cue scanner — extract explicit relationship assertions and enqueue them. + cues = list(scan_cues(text)) + cues_found = len(cues) + for cue in cues: + await enqueue_cue(cue) + + # 2. Token loop — find salient concepts and record weighted encounters. + tokens = tokenize(text) + salient_ids: list[int] = [] + + for token in tokens: + row = cache.soas_by_token.get(token) + if row is None or row.saliency == 0.0: + continue + # Weight the encounter: task/letter mentions count more than chat. + # We stage fractional deltas; flush_encounter_deltas rounds to int, + # so accumulate weight as repeated single increments for simplicity. + increments = max(1, round(weight)) + for _ in range(increments): + cache.record_encounter(row.id) + if row.saliency >= read_threshold: + salient_ids.append(row.id) + + if not salient_ids: + log.debug("scan | agent=%s context=%s cues=%d → no salient concepts", + agent_name or "(none)", context_type, cues_found) + return {"recollection_block": None, "salient_tokens": [], "cues_found": cues_found} + + # 3. Build recollection block (session boost not applicable here — no system message). + block = build_recollection_block(salient_ids, conf_floor, recency_days) + + salient_tokens = [cache.soas_by_id.get(cid, str(cid)) for cid in salient_ids] + log.info( + "scan | agent=%s context=%s cues=%d salient=%s\n%s", + agent_name or "(none)", context_type, cues_found, salient_tokens, + block or "(no block)", + ) + + return { + "recollection_block": block, + "salient_tokens": salient_tokens, + "cues_found": cues_found, + } + + +# --------------------------------------------------------------------------- +# /recall/{concept} — gutask recall backend +# --------------------------------------------------------------------------- + +@app.get("/recall/{concept:path}") +async def recall_concept( + concept: str, + request: Request, + depth: str = "brief", +) -> dict: + """ + Return what Festinger knows about a concept, formatted for agent display. + + depth: + brief — URD edges only (same as recollection block, more readable) + detailed — edges + saliency stats + related concepts in same dimensions + everything — detailed + full write log history + """ + pool = request.app.state.pool + concept = concept.lower().strip() + + row = cache.soas_by_token.get(concept) + if row is None: + # Try DB in case cache is stale + async with pool.acquire() as conn: + db_row = await conn.fetchrow( + "SELECT id, token, saliency, novelty, encounter_count, " + "first_seen_context, last_seen FROM soas WHERE token = $1", + concept, + ) + if not db_row: + return {"concept": concept, "found": False, "text": f"No knowledge about '{concept}' in Festinger."} + from .cache import SoasRow + row = SoasRow( + id=db_row["id"], token=db_row["token"], + saliency=db_row["saliency"], novelty=db_row["novelty"], + encounter_count=db_row["encounter_count"], + first_seen_context=db_row["first_seen_context"] or "", + last_seen=db_row["last_seen"], + ) + + edges = cache.urd_by_concept.get(row.id, []) + reverse_edges = cache.urd_by_parent.get(row.id, []) + + lines = [f"── {concept} {'─' * max(0, 50 - len(concept))}"] + + if depth in ("detailed", "everything"): + from .recollection import recency_decay, centrality_bonus, effective_score + decay = recency_decay(row) + score = effective_score(row.id) + last_seen_str = ( + row.last_seen.strftime("%Y-%m-%d") if row.last_seen else "never" + ) + lines.append(f" saliency: {row.saliency:.2f} encounters: {row.encounter_count}" + f" score: {score:.2f} last seen: {last_seen_str}") + if row.first_seen_context: + lines.append(f" first seen: \"{row.first_seen_context[:80]}\"") + if row.id in cache.pending_conflicts: + lines.append(" ⚠ has pending conflict in resolution queue") + lines.append("") + + if edges: + lines.append(" Relationships (outgoing):") + for e in edges: + conf_str = f" conf={e.confidence:.2f}" if e.confidence < 1.0 else "" + lines.append(f" [{e.dim_token}] → {e.parent_token}{conf_str}") + else: + lines.append(" No outgoing relationships stored.") + + if reverse_edges: + lines.append("") + lines.append(" Referenced by:") + for e in reverse_edges[:10]: + child_token = cache.soas_by_id.get(e.concept_id, str(e.concept_id)) + lines.append(f" [{e.dim_token}] ← {child_token}") + if len(reverse_edges) > 10: + lines.append(f" … and {len(reverse_edges) - 10} more") + + if depth in ("detailed", "everything") and edges: + # Siblings: other concepts sharing the same parent in the same dimension + siblings: dict[str, list[str]] = {} + for e in edges: + peer_edges = cache.urd_by_parent.get(e.parent_id, []) + peers = [ + cache.soas_by_id.get(pe.concept_id, str(pe.concept_id)) + for pe in peer_edges + if pe.concept_id != row.id and pe.dim_id == e.dim_id + ][:5] + if peers: + siblings[f"{e.dim_token}/{e.parent_token}"] = peers + if siblings: + lines.append("") + lines.append(" Siblings (same parent/dimension):") + for label, peers in siblings.items(): + lines.append(f" {label}: {', '.join(peers)}") + + if depth == "everything": + async with pool.acquire() as conn: + log_rows = await conn.fetch( + """ + SELECT op, parent_token, dim_token, is_isa, source, created_at + FROM kg_write_log WHERE concept_id = $1 + ORDER BY created_at DESC LIMIT 20 + """, + row.id, + ) + if log_rows: + lines.append("") + lines.append(" Write history:") + for lr in log_rows: + ts = lr["created_at"].strftime("%Y-%m-%d") + isa = "is-a" if lr["is_isa"] else "is-part-of" + lines.append( + f" {ts} {lr['op']:<10} [{lr['dim_token']}] {isa} {lr['parent_token']}" + f" ({lr['source']})" + ) + + text = "\n".join(lines) + return { + "concept": concept, + "found": True, + "depth": depth, + "saliency": row.saliency, + "encounter_count": row.encounter_count, + "edges": [ + {"dim": e.dim_token, "parent": e.parent_token, + "is_isa": e.is_isa, "confidence": e.confidence} + for e in edges + ], + "text": text, + } + + # --------------------------------------------------------------------------- # /iknowthat — manual write path # --------------------------------------------------------------------------- diff --git a/plugins/festinger/festinger/recollection.py b/plugins/festinger/festinger/recollection.py index 009a12e..3bacc25 100644 --- a/plugins/festinger/festinger/recollection.py +++ b/plugins/festinger/festinger/recollection.py @@ -178,7 +178,25 @@ def build_recollection_block( if not lines: return None - return f"\n" + "\n".join(lines) + "\n" + # Footer: list the concepts that have actual URD data so the agent knows + # it can dig deeper via gutask recall. + hit_tokens = [ + cache.soas_by_id.get(cid, str(cid)) + for _, cid in scored + if query_edges(cid, confidence_floor) or cache.urd_by_parent.get(cid) + ][:6] + footer_lines = [] + if hit_tokens: + footer_lines.append( + "To recall more: gutask recall brief|detailed|everything" + ) + footer_lines.append(" concepts: " + ", ".join(hit_tokens)) + + body = "\n".join(lines) + if footer_lines: + body += "\n" + "\n".join(footer_lines) + + return "\n" + body + "\n" # ---------------------------------------------------------------------------