From 691da501c6e0417d18fed66e0302e9801fcef26b Mon Sep 17 00:00:00 2001 From: Vitaly Zabershinsky Date: Sun, 19 Jul 2026 16:46:30 +0300 Subject: [PATCH 1/2] feat(sparc-service): add SPARC_LOG_REQUESTS and SPARC_STRIP_TOOL_ARG_KEYS env vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SPARC_LOG_REQUESTS=true logs the full incoming ReflectRequest JSON at INFO level so unexpected tool argument keys can be diagnosed without rebuilding. SPARC_STRIP_TOOL_ARG_KEYS= removes the named keys from every tool_calls[].function.arguments before the request reaches SPARC. Needed as a configurable hotfix for Exgentic sending session_id in tool arguments — a key not declared in the tool spec that causes SPARC to reject the call. Both vars are no-ops when unset. No image rebuild required to toggle them; set via kubectl set env or the sparc-service ConfigMap. Co-Authored-By: Claude Sonnet 4.6 --- authbridge/sparc-service/sparc_service/api.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/authbridge/sparc-service/sparc_service/api.py b/authbridge/sparc-service/sparc_service/api.py index 67df2c24..c120b1a1 100644 --- a/authbridge/sparc-service/sparc_service/api.py +++ b/authbridge/sparc-service/sparc_service/api.py @@ -8,7 +8,9 @@ from __future__ import annotations +import json import logging +import os from fastapi import FastAPI, HTTPException from fastapi.concurrency import run_in_threadpool @@ -19,6 +21,37 @@ log = logging.getLogger(__name__) +# When SPARC_LOG_REQUESTS=true, log the full incoming ReflectRequest JSON so +# you can inspect exactly what the caller sends (useful for diagnosing +# unexpected tool argument keys). Disabled by default — payloads can be large. +_LOG_REQUESTS = os.getenv("SPARC_LOG_REQUESTS", "").strip().lower() in {"1", "true", "yes"} + +# When SPARC_STRIP_TOOL_ARG_KEYS is set (comma-separated key names), those keys +# are removed from every tool_calls[].function.arguments JSON object before the +# request reaches SPARC. Use to drop agent-injected keys that are not in the +# tool spec and would cause SPARC to reject the call. +# Example: SPARC_STRIP_TOOL_ARG_KEYS=session_id,request_id +_STRIP_KEYS: frozenset[str] = frozenset( + k.strip() for k in os.getenv("SPARC_STRIP_TOOL_ARG_KEYS", "").split(",") if k.strip() +) + + +def _strip_tool_arg_keys(tool_calls: list[dict], keys: frozenset[str]) -> list[dict]: + """Return a copy of tool_calls with the named argument keys removed.""" + result = [] + for tc in tool_calls: + fn = tc.get("function", {}) + raw_args = fn.get("arguments", "") + try: + args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args + if isinstance(args, dict): + args = {k: v for k, v in args.items() if k not in keys} + new_args = json.dumps(args) if isinstance(args, dict) else raw_args + except (json.JSONDecodeError, TypeError): + new_args = raw_args + result.append({**tc, "function": {**fn, "arguments": new_args}}) + return result + def create_app(engine: ReflectionEngine | None = None) -> FastAPI: """Build the FastAPI app. Inject ``engine`` in tests; defaults to env config.""" @@ -51,6 +84,16 @@ def readyz() -> dict[str, object]: @app.post("/reflect", response_model=ReflectResponse) async def reflect(request: ReflectRequest) -> ReflectResponse: + if _LOG_REQUESTS: + log.info("incoming reflect request: %s", request.model_dump_json()) + + if _STRIP_KEYS and request.tool_calls: + request = request.model_copy( + update={"tool_calls": _strip_tool_arg_keys(request.tool_calls, _STRIP_KEYS)} + ) + if _LOG_REQUESTS: + log.info("after strip (%s): tool_calls=%s", sorted(_STRIP_KEYS), request.tool_calls) + # SPARCReflectionComponent.process is synchronous (and CPU/IO bound on the # LLM call); run it off the event loop so the service stays responsive. try: From 91262d0c55e0236704efadf7429c39587941390f Mon Sep 17 00:00:00 2001 From: Vitaly Zabershinsky Date: Sun, 19 Jul 2026 17:08:35 +0300 Subject: [PATCH 2/2] fix(sparc-service): configure root logger so application log.info() output is visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uvicorn.run() with log_level="info" only configures the uvicorn logger, not the Python root logger — application loggers (sparc_service.api) had no handler and were silently dropped. Adding basicConfig before uvicorn.run() ensures all INFO+ log lines reach stdout. Co-Authored-By: Claude Sonnet 4.6 --- authbridge/sparc-service/sparc_service/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/authbridge/sparc-service/sparc_service/__main__.py b/authbridge/sparc-service/sparc_service/__main__.py index a0f387a4..65d7ba23 100644 --- a/authbridge/sparc-service/sparc_service/__main__.py +++ b/authbridge/sparc-service/sparc_service/__main__.py @@ -8,6 +8,8 @@ def main() -> None: + import logging + logging.basicConfig(level=logging.INFO) settings = Settings.from_env() uvicorn.run("sparc_service.api:app", host=settings.host, port=settings.port, log_level="info")