Bedrock's Converse API multiplexes Claude, Llama, Titan, Mistral and
others through a single content-block protocol. None of those families
expose a per-message author identity in a unified way, so prefixing in
the text content block keeps multi-user attribution consistent
regardless of which underlying model the request lands on.
buildUserContent now passes msg through messageutil.ApplyUserNamePrefix
before constructing the text ContentBlockMemberText. Tool result paths
go through a separate code path (toolResultBlock) and are unaffected.
Image media handling is unchanged.
Tests:
- buildUserContent with Name set produces the prefixed text block
- buildUserContent without Name preserves byte-identical behavior
- existing image and base64 cases continue to pass
The Bedrock build is gated behind //go:build bedrock so this path is
only exercised in builds that opt in via that tag.
Refs #2702.
Sibling change to the anthropic_messages adapter, this time for the
SDK-based path that talks to the official Anthropic endpoint via
anthropic-sdk-go. The Messages API there is the same shape, so the
fix is identical: wrap user content with messageutil.ApplyUserNamePrefix
when the message carries Name attribution; tool result blocks pass
through unchanged.
The prefix is applied at marshal time only; the persisted message is
not mutated. Existing single-user / direct-channel flows are
unaffected because Name is empty there.
Existing provider tests continue to pass; behavior is exercised by
the messageutil unit tests (which cover ApplyUserNamePrefix
exhaustively) and the agent-level integration tests added earlier.
Refs #2702.
Anthropic's Messages API has no per-message author identity, so the
attribution from Message.Name needs to be rendered into content for
the model to see it. This adapter handles the HTTP path used by
self-hosted and custom Anthropic-compatible endpoints.
User content is wrapped via messageutil.ApplyUserNamePrefix at marshal
time, giving wire payloads like
{"role": "user", "content": "[U_alice] My name is Alice"}
The persisted Message is never mutated; the prefix exists only in the
outgoing request body.
Tool result blocks (msg.ToolCallID set) are explicitly not prefixed
because they are function outputs, not user utterances. Direct
single-user channels see no change because Name is empty there.
Tests cover the four relevant cases: prefix applied, no-name no-prefix,
tool result untouched, and a multi-user history scenario reproducing
the issue #2702 example.
Refs #2702.
SerializeMessages is the wire serializer shared by every OpenAI-style
backend in the project (openai_compat, azure, deepseek, glm, qwen,
groq, and other endpoints that accept the OpenAI Chat Completions
schema). The OpenAI spec defines an optional `name` field on each
message that disambiguates participants in multi-user conversations.
This change adds the field to the openaiMessage wire struct (with
omitempty) and the multipart map branch (only when set), so any
Message.Name produced upstream is forwarded to the model. Sanitization
upstream guarantees the wire-level constraint ^[a-zA-Z0-9_-]{1,64}$,
so backends that strictly enforce it will not reject the request.
Tests cover three properties:
- name appears on the plain-text path when set
- name appears on the multipart-with-media path when set
- both paths omit the field entirely when Name is empty (legacy
single-user channels stay byte-identical on the wire)
Refs #2702.
Connects the inbound SenderID, which was already carried end-to-end via
InboundContext to PromptBuildRequest, into the per-turn user message so
historical messages remember who said what.
- userPromptMessage gains a senderID parameter. Real human IDs are
sanitized via SanitizeMessageName; synthetic triggers (cron,
heartbeat, async:*) are filtered out via IsSystemSenderID so they
do not surface as distinct users.
- pipeline_setup.go now passes ts.opts.Dispatch.SenderID() and uses
AddFullMessage unconditionally. The previous AddMessage(role,
content) shortcut for media-less turns silently dropped the new
Name field, breaking history disambiguation.
- context.go (BuildMessagesFromPrompt) propagates req.SenderID onto
the current-turn user message that goes to the LLM.
After this commit, sessions persist sender attribution and provider
adapters receive it on each user message. Adapters that consume Name
(OpenAI native field, Anthropic-style content prefix) land in
follow-up commits.
The existing "## Current Sender" block in buildDynamicContext is
unchanged; it continues to describe the latest speaker in the system
prompt while Name covers the per-message channel for history.
Refs #2702.
Two more helpers needed for end-to-end multi-user attribution:
- IsSystemSenderID filters synthetic trigger sources (cron, heartbeat,
async:* tool callbacks, system channel) so internal events do not
surface as distinct human users in the conversation. Without this,
cron-triggered messages would carry attribution like cron, producing
a confusing [cron] [System: cron] ... double prefix on adapters that
fall back to in-content rendering.
- ApplyUserNamePrefix renders msg.Name as a [name] prefix on user
content for adapters whose APIs do not support a per-message author
identity (Anthropic, Bedrock, OpenAI Responses). The persisted
message is never mutated; only the wire payload carries the prefix.
Tool result messages and non-user roles pass through unchanged.
These helpers are pure and independently testable; they will be wired
into the agent and provider adapters in subsequent commits.
Refs #2702.
OpenAI's Chat Completions name field requires ^[a-zA-Z0-9_-]{1,64}$.
Real-world sender IDs (Discord alice#1234, numeric Telegram IDs, Slack
U07AB12C3DEF, free-form CLI names) often violate this, so a sanitizer
sits between the raw inbound ID and any wire payload.
Disallowed characters collapse into a single underscore, leading and
trailing underscores are trimmed, and the result is byte-safely
truncated to 64 (output is pure ASCII).
This helper will be wired into the agent pipeline in a later commit.
Refs #2702.
Adds an optional Name field on providers.Message so per-message sender
attribution can be carried through the persistence layer and onto the
wire. The field is JSON-tagged `name,omitempty`, so:
- the OpenAI Chat Completions wire format will be able to emit the
native `name` field that disambiguates participants in multi-user
conversations;
- session stores that round-trip messages via json.Marshal preserve
the field with no extra plumbing;
- existing session files load cleanly with Name = "".
This is the schema-only change. Adapters and the agent pipeline are
updated in subsequent commits.
Refs #2702.
* fix(docker): restore `make docker-build` by adding build directives and fixing Go version
docker-compose.yml only had `image:` references with no `build:` sections,
so `docker compose build` had nothing to build. Also fixed golang:1.26.0-alpine
(nonexistent) to golang:1.25-alpine in Dockerfile.full/heavy, and removed
LICENSE from .dockerignore since scripts/copydir.go needs it as a repo-root anchor
during `go generate`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(docker): inject version metadata ldflags in Dockerfile.launcher
Mirror the ldflags from web/Makefile (Version, GitCommit, BuildTime,
GoVersion) into the picoclaw-launcher go build command so Docker-built
launcher images include proper version/build metadata.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(tools): add cross-platform serial hardware tool
* feat(config): wire serial tool into runtime and dashboard
* hardware/serial: tighten validation and error handling
* hardware/serial: improve unix cancellation and timeout polling
* hardware/serial: improve windows I/O handling
* hardware/serial: fix darwin cross-compilation build
* docs(design): summarize hardware support and serial limits
* build: keep go generate on host during cross builds
* onboard: drop unrelated go generate change from serial work
* style(tools): wrap serial lines for golines
* refactor(pico): unify message kind handling of tool_calls and thought
* fix(pico): add legacy compatibility for thought payload in Send method
Co-authored-by: Copilot <copilot@github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Two-phase strategy: 7 days inactive → stale warning, 7 more days → close.
Exempt labels: pinned, keep-open, wip, do-not-close, type: roadmap.
Draft PRs are also exempt. Runs daily at 03:00 JST.
Scan oldest items first (ascending: true) with 500 ops budget to avoid
backlog starvation.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>