diff --git a/agent/background_review.py b/agent/background_review.py index 985888693b3c..3b829e5e327f 100644 --- a/agent/background_review.py +++ b/agent/background_review.py @@ -87,12 +87,18 @@ def _resolve_review_runtime(agent: Any) -> Dict[str, Any]: explicit_api_key=task_api_key, explicit_base_url=task_base_url, ) + # Forward the per-provider output cap (custom_providers + # max_output_tokens) so the routed fork doesn't fall back to the + # model-family output limit (e.g. qwen3 -> 65,536), which can exceed + # the local server's real context budget. Mirrors gateway/run.py. + _rt_max_tokens = rp.get("max_output_tokens") return { "provider": rp.get("provider") or task_provider, "model": task_model, "api_key": rp.get("api_key"), "base_url": rp.get("base_url"), "api_mode": rp.get("api_mode"), + "max_tokens": _rt_max_tokens if isinstance(_rt_max_tokens, int) and _rt_max_tokens > 0 else None, "routed": True, } except Exception as e: @@ -647,6 +653,7 @@ def _run_review_in_thread( # (The runtime whitelist below still restricts dispatch.) review_agent = AIAgent( model=_rt.get("model") or agent.model, + max_tokens=_rt.get("max_tokens"), max_iterations=16, quiet_mode=True, platform=agent.platform, diff --git a/cron/scheduler.py b/cron/scheduler.py index 39f4ed82c003..59a72cf5b9c0 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -2751,6 +2751,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: # example DeepSeek) for cron jobs that do not pin provider/model. runtime_kwargs = { "requested": job.get("provider"), + "target_model": model or None, } if job.get("base_url"): runtime_kwargs["explicit_base_url"] = job.get("base_url") @@ -2871,8 +2872,15 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: job_id, _mcp_exc, ) + # Forward the per-provider output cap (custom_providers + # max_output_tokens) so cron agents don't fall back to the + # model-family output limit (e.g. qwen3 -> 65,536), which can + # exceed the local server's real context budget. Mirrors + # agent/background_review.py. + _rt_max_tokens = runtime.get("max_output_tokens") agent = AIAgent( model=model, + max_tokens=_rt_max_tokens if isinstance(_rt_max_tokens, int) and _rt_max_tokens > 0 else None, api_key=runtime.get("api_key"), base_url=runtime.get("base_url"), provider=runtime.get("provider"), diff --git a/plugins/memory/openviking/__init__.py b/plugins/memory/openviking/__init__.py index 5aef3b908503..c13cdfac6dc9 100644 --- a/plugins/memory/openviking/__init__.py +++ b/plugins/memory/openviking/__init__.py @@ -3116,7 +3116,14 @@ class OpenVikingMemoryProvider(MemoryProvider): json.dumps(payload, ensure_ascii=False), ) try: - client.post(f"/api/v1/sessions/{sid}/messages/batch", payload) + # The OpenViking server caps batches at 100 messages; + # chunk so long sessions keep structured sync instead + # of degrading to the 4k-char text fallback. + for _start in range(0, len(batch_messages), 100): + client.post( + f"/api/v1/sessions/{sid}/messages/batch", + {"messages": batch_messages[_start:_start + 100]}, + ) return except Exception as batch_error: logger.warning(