Run/RunStream took the new turn as a bare string, which had nowhere
to carry ContentPart attachments. Both now take an llm.Message
(Role is forced to RoleUser regardless of what the caller sets), so a
caller building a multimodal turn just fills in Content/Parts on it
instead of the loop needing a second, parallel parameter.
subagent.go and every test call site are updated to wrap their string
prompt as llm.Message{Role: llm.RoleUser, Content: ...} — SubAgent.Run
itself is untouched, it still takes a plain task string.
Message gains an optional Parts []ContentPart alongside the existing
plain-text Content, so a turn can carry text plus image/video
attachments. Content stays the single source of truth for every
existing text-only caller (sidebar.go, memory_tools.go, etc. are
untouched); Parts only matters to a provider client when non-empty.
openai and llamacpp (both OpenAI-compatible) serialize Parts into the
standard text/image_url content-array shape; llamacpp additionally
passes video through as a best-effort video_url part, since llama.cpp
itself has no video support but the whole point of this client is the
user's own OpenAI-compatible server sitting in front of a
video-capable model — the server decides whether it understands it,
not this client. anthropic converts image parts to its base64 image
content block, and rejects a video part outright with a clear error:
the Messages API has no video block type at all, so sending one would
just produce a confusing 400 instead.
ProviderCapabilities gains SupportsVideo, true only for llamacpp.
Two failure modes seen live with Qwen3.6 on llama.cpp ended turns silently
mid-task:
- The model writes its tool call as plain text inside its reasoning, the
server never parses it, and the round ends with nothing executed. The
loop now detects the markers and nudges the model to re-issue the call
for real (max 2 per turn).
- llama.cpp silently ignores the max_thinking_tokens field, so a model in
a reasoning spiral ran until max_tokens (seen live: 25k+ tokens of
nonstop thinking, ~20 min). The llamacpp client now enforces the budget
client-side during Stream: once exceeded while the round is still pure
reasoning, it cuts with FinishThinkingBudget and aborts the request
(freeing the server slot); the loop answers with its own corrective
nudge, on a separate counter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NetworkPolicy validates scheme/host and re-validates resolved IPs at dial
time and on redirects (DNS-rebinding defense), with cloud metadata
endpoints always blocked. Redact masks known credential shapes (OpenAI/
Anthropic/GitHub/AWS/Slack/Google keys, PEM blocks, JWTs) in tool output.
WrapUntrusted fences fetched web content against prompt injection, paired
with UntrustedContentInstruction for the system prompt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fragments now carry a memory type in metadata (legacy fragments count as
procedural) with SearchByType filtering, and EpisodeCapture summarizes a
finished turn with the local LLM and stores it as episodic memory, so the
agent can answer "what did we do yesterday?". Includes an E2E test against
a live llama.cpp server (gated) and taxonomy unit tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Formatting only (struct field alignment, import ordering) across the
files that didn't comply — no semantic changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>