feat(channel): propagate sender user ID into channel system prompt#5526
Conversation
Include the platform-specific sender ID (e.g. Mattermost user_id) in the channel system prompt so the LLM can identify and distinguish individual users. This lays the groundwork for future RBAC-based access control by making the sender identity available to the model. Changes: - Add sender parameter to build_channel_system_prompt() - Inject sender=<id> into the Channel context block - Pass msg.sender at the call site in process_channel_message() - Add three unit tests for the new behavior
theonlyhennygod
left a comment
There was a problem hiding this comment.
Comprehension Summary
What: This PR adds the existing msg.sender field (platform-specific user ID) to the channel system prompt by threading a new sender parameter through build_channel_system_prompt(). The sender ID is injected into the "Channel context" block alongside the existing channel and reply_target fields. Three regression tests are added.
Why: Mattermost (and other channel) sender IDs are available at runtime but were not propagated into the LLM prompt context. This prevents the model from distinguishing which user sent a message — a prerequisite for future RBAC and user-scoped behavior.
Blast radius: The prompt text changes for all channel integrations where reply_target is non-empty, not just Mattermost. The sender field is already populated by every channel implementation (ChannelMessage.sender in traits.rs), so no upstream changes were needed. Models may begin referencing sender IDs more explicitly in replies.
Review Summary
Gate Checks — All Pass
- Template: Fully completed, all required sections present.
- CI: 20/20 checks pass (Lint, Test, Build x3, Security Audit, Strict Delta Lint, Benchmarks, Docs Quality, all gates).
- Size: XS (1 file, +40/−3). Appropriate scope.
- Privacy: Test fixtures use neutral placeholders (
user_abc123,user_aaa,user_bbb). No PII, credentials, or identity leakage. Pass. - Duplicates: No overlapping open PRs (other Mattermost PRs address unrelated features).
- Architecture: No new dependencies, no trait bypass, no config changes. Uses the existing
ChannelMessage.senderfield already populated by all channel implementations. - Supersedes/Attribution: N/A.
Code Review — No Issues Found
- The
senderparameter is correctly threaded through the function signature and call site. - Sender injection is gated behind the existing
!reply_target.is_empty()check, so it only appears when channel context is relevant. - No
unwrap()in library code. No unnecessary allocations — the sender is interpolated into the existingformat!()call. - Naming follows project conventions (
sender, notuser_idorauthor). - The prompt text addition is clear and well-structured.
Regression Analysis
build_channel_system_prompthas exactly one call site (process_channel_message, line 2744). No other callers are affected.- All channel implementations already populate
msg.sender, so no channel is broken by the new parameter. - The prompt change applies to all channels with non-empty
reply_target, not just Mattermost. The contributor explicitly acknowledges this in blast radius and has mitigated via targeted tests.
Test Coverage
- Three new unit tests cover: (1) sender ID inclusion in prompt, (2) sender omission when
reply_targetis empty, (3) different senders produce different prompts. - Tests are well-named, focused, and use neutral placeholders.
Security Assessment
No security impact identified. Platform-internal sender IDs (e.g., Mattermost user_id) are opaque identifiers, not secrets. They are already present in the runtime and are now additionally forwarded to the model provider as prompt context. No tokens, profile metadata, or credentials are exposed.
Performance Assessment
No performance impact identified. The change adds one string interpolation to an existing format!() call in a non-hot path (prompt construction happens once per channel message).
Thank you @titulus for a clean, well-scoped contribution with thorough validation evidence and thoughtful blast-radius documentation. This PR is ready for maintainer merge.
Port of zeroclaw-labs/zeroclaw#5526. The sender's platform-specific user ID is now included in the channel context block of the system prompt when reply_target is present, enabling the model to distinguish between different users in channel conversations. Co-authored-by: titulus <titulus@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
) * feat(channel): propagate sender user ID into channel system prompt Port of zeroclaw-labs/zeroclaw#5526. The sender's platform-specific user ID is now included in the channel context block of the system prompt when reply_target is present, enabling the model to distinguish between different users in channel conversations. Co-authored-by: titulus <titulus@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(channel): sanitise metadata before prompt interpolation Strip newlines and carriage returns from channel_name, reply_target, and sender before injecting into the system prompt to prevent control characters from breaking prompt structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: titulus <titulus@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eroclaw-labs#5526) Include the platform-specific sender ID (e.g. Mattermost user_id) in the channel system prompt so the LLM can identify and distinguish individual users. This lays the groundwork for future RBAC-based access control by making the sender identity available to the model. Changes: - Add sender parameter to build_channel_system_prompt() - Inject sender=<id> into the Channel context block - Pass msg.sender at the call site in process_channel_message() - Add three unit tests for the new behavior
Summary
masterfor all contributions): mastersenderto the channel system prompt context, passedmsg.senderat prompt build time, and added regression tests plus a live Mattermost verification.Label Snapshot (required)
risk: low|medium|high): risk: mediumsize: XS|S|M|L|XL, auto-managed/read-only): autocore|agent|channel|config|cron|daemon|doctor|gateway|health|heartbeat|integration|memory|observability|onboard|provider|runtime|security|service|skillforge|skills|tool|tunnel|docs|dependencies|ci|tests|scripts|dev, comma-separated): channel,tests<module>: <component>, for examplechannel: telegram,provider: kimi,tool: shell): channel: mattermosttrusted contributor|experienced contributor|principal contributor|distinguished contributor, auto-managed/read-only; author merged PRs >=5/10/20/50): autoChange Metadata
bug|feature|refactor|docs|security|chore): featureruntime|provider|channel|memory|security|ci|docs|multi): channelLinked Issue
Supersede Attribution (required when
Supersedes #is used)#<pr> by @<author>, one per line): N/ACo-authored-bytrailers added for materially incorporated contributors? (Yes/No): NoNo, explain why (for example: inspiration-only, no direct code/design carry-over): not superseding any PR\n): (Pass/Fail): PassValidation Evidence (required)
Commands and result summary:
cargo fmt --all -- --check CARGO_BUILD_JOBS=1 cargo clippy --all-targets -- -D warnings CARGO_BUILD_JOBS=1 cargo test cargo build --releaseSecurity Impact (required)
Yes/No): NoYes/No): NoYes/No): NoYes/No): NoYes, describe risk and mitigation: N/APrivacy and Data Hygiene (required)
pass|needs-follow-up): passCompatibility / Migration
Yes/No): YesYes/No): NoYes/No): Noi18n Follow-Through (required when docs or user-facing wording changes)
Yes/No): NoYes, locale navigation parity updated inREADME*,docs/README*, anddocs/SUMMARY.mdfor supported locales (en,zh-CN,ja,ru,fr,vi)? (Yes/No): N/AYes, localized runtime-contract docs updated where equivalents exist (minimum forfr/vi:commands-reference,config-reference,troubleshooting)? (Yes/No/N.A.): N/AYes, Vietnamese canonical docs underdocs/i18n/vi/**synced and compatibility shims underdocs/*.vi.mdvalidated? (Yes/No/N.A.): N/ANo/N.A., link follow-up issue/PR and explain scope decision: N/AHuman Verification (required)
What was personally validated beyond CI:
nhkmck65yf8tir6p618b1wac1y; the bot replied with that exact sender ID; human repeated with a second prompt and received the same correct IDsender=whenreply_targetis present; prompt omits channel context whenreply_targetis empty; different senders produce different promptsSide Effects / Blast Radius (required)
reply_targetis present; Mattermost runtime behavior verified liveAgent Collaboration Notes (recommended)
msg.senderalready existed in runtime, patched prompt construction at the root cause, added regression tests, ran full local validation, then verified live in MattermostAGENTS.md+CONTRIBUTING.md): YesRollback Plan (required)
11da7df1Risks and Mitigations
Risk: prompt contract changed for channel runs that include
reply_target, not just MattermostMitigation: kept the change minimal and localized to the existing context block; added regression tests that pin the new behavior
Risk: platform user IDs are now forwarded to the model provider as part of prompt context
Mitigation: only the existing platform sender ID is added, with no secrets or extra profile metadata; this is required for user distinction and future RBAC work