From 67d6871bf8a866512a3f20e8ffdbb23c9b232a1a Mon Sep 17 00:00:00 2001 From: PatrickNoFilter Date: Fri, 10 Jul 2026 12:05:31 +0700 Subject: [PATCH 1/4] Fix handle_before_tool dict-truthy bug + MCP response size limit Three fixes for Zero integration: 1. handle_before_tool: brain.recall() returns a dict, which is ALWAYS truthy. Changed to check result.get('merged', []) so the hook only reports when there are actual recalled items. Also fixed len() to count merged results instead of dict keys (always 3). 2. handle_after_tool: Consolidated to create a single Brain() instance instead of two (one for file edits, one for tool observations). 3. as_brain MCP server: Added 50KB response size limit to ok() helper. Without this, brain_recall returns the full per_layer dict which can grow very large with accumulated facts, causing provider context overflow and 'provider agent crashes' in Zero. Fixes: https://github.com/PatrickNoFilter/eling/issues --- src/eling/as_brain/mcp_server.py | 3 +++ src/eling/zero_plugin/eling-hook.py | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/eling/as_brain/mcp_server.py b/src/eling/as_brain/mcp_server.py index 3ae597f..f8217b1 100644 --- a/src/eling/as_brain/mcp_server.py +++ b/src/eling/as_brain/mcp_server.py @@ -468,6 +468,9 @@ def _handle_tool_call(rid: int | str | None, params: dict) -> dict: def ok(data: Any) -> dict: try: text = json.dumps(data, default=str) + # Limit response to 50KB to prevent provider context overflow + if len(text) > 50_000: + text = json.dumps({"warning": "response truncated (50KB limit)", "truncated": True}, default=str) except Exception: text = json.dumps({"error": "result not serializable", "raw": str(data)[:500]}) return { diff --git a/src/eling/zero_plugin/eling-hook.py b/src/eling/zero_plugin/eling-hook.py index 2c9fe7a..2d21916 100644 --- a/src/eling/zero_plugin/eling-hook.py +++ b/src/eling/zero_plugin/eling-hook.py @@ -42,8 +42,9 @@ def handle_before_tool(payload: dict) -> str | None: try: brain = get_brain() results = brain.recall(query, limit=3) - if results: - return f"eling: recalled {len(results)} memory items for {tool}" + merged = results.get("merged", []) + if merged: + return f"eling: recalled {len(merged)} memory items for {tool}" except Exception as e: log.warning("before_tool recall failed: %s", e) return None @@ -58,6 +59,7 @@ def handle_after_tool(payload: dict) -> str | None: result = payload.get("result", "") or payload.get("output", "") msgs = [] + brain = None # Remember file edits as facts if changed_files and status in ("success", "ok", "", None): @@ -78,7 +80,8 @@ def handle_after_tool(payload: dict) -> str | None: # Remember tool results as observations if result and tool and status in ("success", "ok", "", None): try: - brain = get_brain() + if brain is None: + brain = get_brain() summary = str(result)[:300] brain.remember( f"Tool [{tool}] returned: {summary}", From ef6276e14c0d91d1a78abb3447626ec0c3a571ff Mon Sep 17 00:00:00 2001 From: PatrickNoFilter Date: Fri, 10 Jul 2026 12:12:20 +0700 Subject: [PATCH 2/4] Fix: notion.available catches ImportError instead of raising notion.available property and _has_httpx() both called _require_httpx() which raises RuntimeError when httpx is not installed. Default recall layers now include 'notion' (main branch changed from ['builtin', 'facts', 'kb', 'code'] to include 'notion'). This means any call to brain.recall() without explicit layers triggers notion.available, which crashed the benchmark job when httpx is absent. Fix: wrap _require_httpx() in try/except so available=property returns False instead of raising. Same fix for _has_httpx(). Fixes benchmark CI failure for PR #2. --- src/eling/layers/notion.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/eling/layers/notion.py b/src/eling/layers/notion.py index 743ea65..2cbf34a 100644 --- a/src/eling/layers/notion.py +++ b/src/eling/layers/notion.py @@ -48,12 +48,18 @@ def __init__( @property def available(self) -> bool: if _HAS_HTTPX is None: - _require_httpx() + try: + _require_httpx() + except RuntimeError: + _HAS_HTTPX = False return _HAS_HTTPX and bool(self.api_key) def _has_httpx(self) -> bool: if _HAS_HTTPX is None: - _require_httpx() + try: + _require_httpx() + except RuntimeError: + _HAS_HTTPX = False return bool(_HAS_HTTPX) def _get_client(self) -> "httpx.Client": From 636e6ee5fd863d01329e7d5f35d73bcc98d88baf Mon Sep 17 00:00:00 2001 From: PatrickNoFilter Date: Fri, 10 Jul 2026 12:14:23 +0700 Subject: [PATCH 3/4] fix: add global _HAS_HTTPX to available property and _has_httpx The try/except around _require_httpx() assigns _HAS_HTTPX, but without a 'global' declaration Python treats it as a local variable, causing UnboundLocalError. Add global declarations to available and _has_httpx. --- src/eling/layers/notion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/eling/layers/notion.py b/src/eling/layers/notion.py index 2cbf34a..1707376 100644 --- a/src/eling/layers/notion.py +++ b/src/eling/layers/notion.py @@ -47,6 +47,7 @@ def __init__( @property def available(self) -> bool: + global _HAS_HTTPX if _HAS_HTTPX is None: try: _require_httpx() @@ -55,6 +56,7 @@ def available(self) -> bool: return _HAS_HTTPX and bool(self.api_key) def _has_httpx(self) -> bool: + global _HAS_HTTPX if _HAS_HTTPX is None: try: _require_httpx() From 3b12198db35ad6d26cbd67baa3fffdee9e2bee8f Mon Sep 17 00:00:00 2001 From: PatrickNoFilter Date: Fri, 10 Jul 2026 13:00:12 +0700 Subject: [PATCH 4/4] fix: add pull-requests: write permission to benchmark job The 'Comment benchmark on PR' step uses GITHUB_TOKEN to call issues.createComment, which requires pull-requests: write scope. Without it, runs fail with 'HttpError: Resource not accessible by integration'. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15370dd..632003c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,9 @@ jobs: timeout-minutes: 10 needs: [test] if: github.event_name == 'pull_request' + permissions: + pull-requests: write + contents: read steps: - uses: actions/checkout@v4