Skip to content

fix(slack): make thread images/files visible to the agent - #70188

Merged
teknium1 merged 4 commits into
mainfrom
slack/c1i-image-context
Jul 23, 2026
Merged

fix(slack): make thread images/files visible to the agent#70188
teknium1 merged 4 commits into
mainfrom
slack/c1i-image-context

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Images and files posted earlier in a Slack thread are now visible to the agent: every thread-context path annotates them with typed markers ([image: chart.png], [file: report.pdf (application/pdf)]), and on cold-start the thread-root image is downloaded and delivered so "look at this chart" mentions actually work.

Changes

  • _render_message_text appends sanitized media markers for every file attachment — covers cold-start hydrate, delta refresh, restart rehydration, and reply_to_text; hostile filenames (newlines/brackets) sanitized.
  • _collect_thread_root_images() on the cold-start path only: reads the root from the just-populated context cache (zero extra API calls), downloads image/* via the existing _download_slack_file, delivers as PHOTO. Capped at 4; download failure degrades to the marker. One-time by construction — no gateway/session plumbing.

Credits

Salvaged from #32315 (@yemi-lagosinternationalmarket — first implementation of context markers) and adapted from #69185 (@KCAYAAI — thread-root delivery design; the full 2,400-line MessageEvent plumbing across gateway core was judged overreach for the user-visible goal). #66136 (lazy file loading) deferred as complementary.
Supersedes #69185, #32315.

Validation

Check Result
15 new regression tests (A/B verified: 14/15 fail with fix reverted) green
tests/gateway/ -q -k slack 776 passed, 0 failed (re-verified post-rebase)

Infographic

slack-thread-media

…context

Images and files posted in a Slack thread before the bot joins were
invisible to the agent: _fetch_thread_context renders text only, and a
caption-less image post was dropped from context entirely (empty text →
skip). "@bot what do you think of the chart above?" read as a question
about nothing.

_render_message_text now appends a compact, sanitized marker per file
attachment — [image: chart.png], [video: demo.mp4], [audio: note.m4a],
[file: report.pdf (application/pdf)] — so the agent can SEE that prior
thread messages carried attachments and ask for a re-share when it needs
the bytes. Filenames are stripped of newlines/brackets so a hostile name
can't fake context structure. Because both thread-context formatting and
parent-text rendering go through _render_message_text, markers appear on
the cold-start hydrate, the explicit-mention delta refresh, restart
rehydration, and reply_to_text.

Reapplied from #32315 onto the current adapter (original patched the
pre-plugin gateway/platforms/slack.py, moved in the plugin migration;
annotation labels reworked to per-file typed markers, download side
handled separately).
When the bot is mentioned mid-thread for the first time, the thread root
is very often the artifact the mention is about ("@bot what's in this
chart?" posted as a reply under an image) — but the root's image never
reached the agent, so it answered blind.

On the cold-start hydrate path (and only there), _collect_thread_root_images
reads the root message from the thread-context cache the immediately
preceding _fetch_thread_context call just populated (zero extra Slack API
calls in the normal case), downloads its image/* attachments through the
existing authenticated _download_slack_file helper, and delivers them as
media_urls/media_types on the same MessageEvent — upgrading the message
type to PHOTO so vision routing engages.

Scope and safety:
- One-time delivery by construction: the cold-start path is guarded by
  _has_active_session_for_thread, so later turns in the same session can
  never re-download or re-deliver. No new gateway/session plumbing needed.
- Bounded by _THREAD_ROOT_IMAGE_MAX (4); non-image root attachments stay
  text-only markers.
- Slack Connect stubs (file_access=check_file_info) resolve via files.info.
- Best-effort: a failed download degrades to the [image: ...] marker from
  the thread context — never an error turn.
- Also hardens the video mimetype fallback (mimetype can be empty) so
  media_types entries are always non-None strings.

Adapted from #69185 by @KCAYAAI — the original plumbed MessageEvent media
through gateway/base.py, run.py and session.py with durable one-time
delivery markers (2,441 lines); this lands the user-visible behavior
adapter-locally by reusing the session guard already on the hydrate path.
…lity

Covers cluster C1-images (#69185, #32315, #66136):
- _slack_file_marker unit tests: typed markers per mimetype family,
  hostile-filename sanitization (newlines/brackets can't fake context
  structure).
- _render_message_text appends markers; a caption-less image post no
  longer vanishes from thread context.
- Cold-start hydrate integration: prior-message images surface as
  markers in channel_context; the thread root's image is downloaded,
  delivered as media_urls, and upgrades message_type to PHOTO.
- Failure path: root-image download failure degrades to the marker,
  never blocks the turn.
- Bounds: root delivery capped at _THREAD_ROOT_IMAGE_MAX; non-image
  root attachments stay marker-only (no download).
- One-time delivery: active thread session skips the hydrate → no
  re-download/re-delivery on later turns.
- Composition: the trigger's own event files still ride alongside a
  delivered root image; Slack Connect stubs resolve via files.info;
  the collector never issues its own conversations.replies call.
- Delta refresh (#23918 path): images in new replies past the watermark
  surface as markers, with no root re-download.

A/B: 14 of 15 tests fail with the adapter fix reverted, all pass with
it applied.
…almarket

Contributor-email mapping for the #32315 salvage (thread image/file
markers reapplied onto the plugin adapter).
@teknium1 teknium1 added the platform/slack Slack app adapter label Jul 23, 2026
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 5d0d402

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 visual diff.

inline evidence upload failed.

Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso)

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages needs-decision Awaiting maintainer decision before any implementation labels Jul 23, 2026
@teknium1
teknium1 merged commit 93a47dd into main Jul 23, 2026
40 checks passed
@teknium1
teknium1 deleted the slack/c1i-image-context branch July 23, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have platform/slack Slack app adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants