Skip to content

docs(research): plan attachment fixes without a new primitive - #4625

Closed
AndrewBarba wants to merge 3 commits into
mainfrom
barba/attachments-research
Closed

AndrewBarba wants to merge 3 commits into
mainfrom
barba/attachments-research

Conversation

@AndrewBarba

@AndrewBarba AndrewBarba commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Attachment bugs keep breaking whole sessions. When eve can't resolve a file, or a provider rejects it, the file stays in history and every later model call fails. #4223 proposed a pluggable attachment store, which is more than these bugs need. This plan keeps today's storage and fixes the pipeline around one rule: every attachment becomes a staged file or a note before it enters history. It adds three small authoring surfaces, fetchFile on eveChannel(), attachmentError, and FetchFileContext.session, and no new definition type. It replaces #4223, and the PRs above this one in the stack implement it.

Related to #3419, #855, #705, #497, #4194, #4304, and #543.

Validation

  • pnpm docs:check passes. oxfmt formatted the plan.
  • I checked the plan's claims against the current source. Tests and a changeset don't apply, because this PR adds only a research document.

Checklist

  • I added tests and documentation where relevant
  • I added a changeset if this touches the published eve package
  • DCO sign-off passes for every commit (git commit --signoff)

@AndrewBarba
AndrewBarba requested a review from a team as a code owner October 10, 2026 16:15
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
eve-docs Ready Ready Preview, v0 Oct 10, 2026 4:57pm UTC
eve-pkg Ready Ready Preview, v0 Oct 10, 2026 4:57pm UTC

Comment thread research/attachments.md Outdated
Comment on lines +65 to +66
When `fetchFile` returns `null`, or the channel has none, eve fetches public
`https:` links itself. The fetch rejects private and reserved addresses, stops

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetcher needs a tighter spec than "rejects private and reserved addresses". If eve only checks the hostname or the first resolved IP, a public https: URL can still 30x to 169.254.169.254 or rebind DNS between the check and the connect. Today's resolvers call plain fetch with default redirect handling (packages/eve/src/public/channels/slack/attachments.ts:183-193), so there's nothing to copy from.

Suggest saying: resolve and pin the IP, reject private/reserved v4 and v6 (including mapped forms), follow redirects manually with a hop limit and recheck each target, and stream with a byte cap and timeout. Then add the redirect-to-private and rebinding tests to Verification.

Comment thread research/attachments.md
Comment on lines +52 to +53
- Channels don't filter attachments by media type. `uploadPolicy` decides what
eve accepts, and the inline decision decides inline or label.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uploadPolicy runs before staging, against the declared media type, and it skips the size check when the length isn't known. A URL's length never is (packages/eve/src/public/channels/upload-policy.ts:148-153, packages/eve/src/internal/attachments/data.ts:73-80). Staging then writes whatever the resolver returned and never checks the policy again (packages/eve/src/harness/attachment-staging.ts:485-497).

Under this plan, a part declared as image/png gets through an image/* allowlist, gets fetched, and gets restaged as application/octet-stream (L102-104). A custom maxBytes below 25 MiB also never applies to fetched links, because L67 caps at the default. The plan should rerun the effective policy after resolution and sniffing, using the real byte count and the verified type, and cap the download at the channel's maxBytes. Small one: the default is 25 * 1024 * 1024 (upload-policy.ts:35-38), so write 25 MiB to match the other limits.

Comment thread research/attachments.md Outdated

| Surface | Change |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `FilePart.data` | Bytes, base64, and `data:` URLs are bytes. Any other string or `URL` with a scheme is a link to resolve. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Any other string or URL with a scheme is a link to resolve" also matches eve-sandbox:, eve-url:, and eve-attachment:. Caller-supplied internal refs are a known privileged-read vector: the HTTP route rejects them for that reason (packages/eve/src/eve-channel/request.ts:481-488, see also packages/eve/src/internal/attachments/url-refs.ts:29-45). Please say explicitly that internal schemes are rejected at every untrusted boundary (channel input and send() payloads), and that only refs minted by staging are trusted. That matters most in step 2, which removes eve-attachment: and touches this same classification.

Comment thread research/attachments.md Outdated
| Surface | Change |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `FilePart.data` | Bytes, base64, and `data:` URLs are bytes. Any other string or `URL` with a scheme is a link to resolve. |
| `fetchFile(url, ctx)` | Also on `eveChannel()`. Returning `null` now means "not mine", not "let the provider fetch it". |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks a documented public contract. Today null means "pass the URL through to the model provider unchanged" (packages/eve/src/shared/channel-definition.ts:97-103, packages/eve/src/channel/adapter.ts:160-166), and staging returns the part as-is (packages/eve/src/harness/attachment-staging.ts:471-472). A custom fetchFile that relies on provider fetching for non-https: or non-public URLs will now get a note. Please add a migration note covering who's affected, what to return instead, and whether it needs a changeset or major bump.

Comment thread research/attachments.md Outdated
Comment on lines +148 to +149
7. `attachmentError` and `FetchFileContext.session` (#4304), and a `deliver`
that returns nothing falls back to the default input.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flips existing void semantics. deliver is typed StepInput | void (packages/eve/src/shared/channel-definition.ts:67), and right now a custom deliver that returns undefined suppresses that payload. The default projection only runs when there's no deliver at all (packages/eve/src/execution/session/turn-step.ts:320-326). A handler that returns nothing on purpose to consume or drop a delivery would start sending the raw payload to the model. The plan needs an explicit drop result, or it should keep void as "drop" and add a separate opt-in. Either way, give it a migration note and a test for current void-returning handlers. This also doesn't fit under the attachments heading, so explain why it ships in this step.

Comment thread research/attachments.md Outdated
Comment on lines +44 to +46
- History holds only `eve-sandbox:` refs or text notes for attachments. It
never holds a raw URL, an `eve-url:` marker, or inline bytes. This covers user
messages, cancelled turns, and tool results.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rollout only enforces this for new input. Staging covers the current delivery (packages/eve/src/harness/step/intake.ts:88-99), and hydrateSandboxAttachments returns history unchanged when it has no sandbox ref (packages/eve/src/harness/attachment-staging.ts:174-180). So sessions that already have raw URLs or inline bytes in history will keep sending them, and keep failing, after the upgrade. Those are exactly the stuck #3419/#855 sessions. Old refs also keep their stored, unverified media type for the inline decision (packages/eve/src/internal/attachments/sandbox-refs.ts:124-127). Please add a model-call normalization step that turns pre-existing raw parts into notes and renders unverified old refs label-only, and test it with pre-upgrade history, not just old-ref hydration.

Comment thread research/attachments.md
holds. It inlines only formats that eve verified from the bytes.
- Every attachment the model sees has a label with its sandbox path.
- Channels don't filter attachments by media type. `uploadPolicy` decides what
eve accepts, and the inline decision decides inline or label.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slack isn't the only channel that filters by media type. Telegram only builds attachments from photo and document, and drops audio, voice, and video (packages/eve/src/public/channels/telegram/inbound.ts:221-230). The rollout only fixes Slack (step 8). Either narrow the invariant or add a step for the other built-in channels.

Comment thread research/attachments.md
import { eveChannel } from "eve/channels/eve";
import { attachmentError } from "eve/channels";

export default eveChannel({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example won't type-check: auth is required on EveChannelInput (packages/eve/src/eve-channel/types.ts:103), and TOKEN isn't defined. Add an auth entry and read the token from process.env so authors can copy it as-is.

Signed-off-by: Andrew Barba <barba@hey.com>
…ction

Signed-off-by: Andrew Barba <barba@hey.com>
…d deliver change

Signed-off-by: Andrew Barba <barba@hey.com>
@AndrewBarba

Copy link
Copy Markdown
Collaborator Author

Collapsed into #4635.

This branch was successfully deployed

2 active deployments
Preview – eve-pkg — 54246c6e Deployed Oct 10, 2026 by vercel[bot]
Preview – eve-docs — 54246c6e Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant