Justin Blumencranz 273ca06ddb fix(files): serve rendered documents instead of source code (#6139)
* fix(files): serve rendered documents to attachments and workflow reads

Generated documents store their generation source under a .pdf/.docx name and
keep the compiled binary in a separate content-addressed artifact store, so any
consumer doing a raw read handed out source text under a document name.

- Route attachments and readUserFileContent through the servable resolver so
  they get the compiled artifact, and stop the internal generation-source MIME
  marker reaching providers as a content type.
- Render on read when the artifact is missing. The artifact key is (workspace,
  source hash), so forking a workspace, moving a file, or editing the source
  outside a recompiling writer orphaned it permanently and reported "still
  being generated" forever. Rendering self-heals those and stores the result.
- Fall back to serving stored bytes as application/octet-stream when a render
  fails, instead of failing forever, and remember not to retry those bytes.
- Only compile without a workspace context when the file's type positively
  says it is generation source, so unrelated stored bytes are never executed.
- Make the doc-not-ready error opt-in per caller and give it a 409 via
  HttpError, so output decoration degrades instead of failing completed work.

* fix(files): keep render failures retryable and never relabel unrendered bytes

Addresses the first review round.

- Only memoize a render failure when it is deterministic. A DocCompileUserError
  means the source will never render, so remembering it is safe; sandbox
  outages, timeouts, and cancellations are transient and were stranding valid
  documents for the life of the process. Infra failures now propagate, which
  also restores DocCompileUserError reaching callers that map it to 409.
- Refuse to hand back bytes the resolver could not render. readUserFileContent
  returns a string, so the resolver's honest application/octet-stream could not
  travel with it and attachment builders re-inferred a document MIME from the
  filename — shipping generation source to a provider as a PDF. The file-serve
  route keeps the graceful passthrough, where a human downloading the bytes is
  useful.
- Normalize the declared type once so a padded or upper-cased source marker
  cannot pass the resolver gate on one code path and fail it on the other.
- Import the doc-not-ready guard lazily. The static import pulled the
  doc-compile module graph (remote sandbox, task runner, execution limits) into
  every hydration consumer and broke an unrelated test's module mock in CI.

* perf(files): coalesce concurrent renders of the same missing artifact

An artifact miss is identical for every concurrent reader — a freshly forked
workspace whose document several viewers open at once, or one request whose
blocks read the same file — and each was paying for its own compile of the same
bytes. Share one in-flight render per (workspace, source, ext) key and drop the
entry as soon as it settles, so a later read still re-renders normally.

* fix(files): refuse unrendered bytes at the download boundary

Addresses the second review round.

- Throw UnrenderableDocumentError from downloadServableFileFromStorage instead
  of returning bytes with an `unrendered` flag. Around 45 call sites (email
  attachments, cloud uploads, zip entries, provider attachments) receive only a
  Buffer and re-infer the type from the filename, so a flag they must remember
  to check is a flag they will not check. Those callers already handled the
  previous not-ready throw, so failing is the shape they expect. The file-serve
  route is unaffected — it resolves bytes directly and keeps the graceful
  passthrough, where a human downloading the file has a use for it.
- Surface that failure through hydration: with throwOnDocNotReady set, the
  caller cannot use a file with no content, so an unrenderable document now
  reaches it verbatim instead of degrading to null and reporting a misleading
  "may exceed size limit or no longer accessible".
- Stop a shared render inheriting one caller's cancellation. The coalesced run
  no longer carries any caller's signal; each caller races its own instead, so
  an aborting reader gives up promptly while the render finishes for the others
  and still lands in the cache.

* fix(files): move the unrenderable error out of the 'use server' module

file-utils.server.ts carries 'use server', whose exports must all be async
functions, so exporting an error class from it failed the production build with
67 cascading errors. The class now lives in the plain file-utils.ts beside the
other shared file helpers, which also lets the hydration path import it directly
instead of through a dynamic import.

Also bounds how long a failed render is remembered. The isolated-vm engine
cannot tell a bad source from a sandbox outage, so a permanent entry let one
transient failure block re-rendering that source for the life of the process.
Entries now expire after five minutes: long enough to stop a read loop spending
a sandbox run per read, short enough that an outage self-heals without a deploy.

* fix(files): finish the render cancellation and failure-surfacing edges

- Race the E2B render against the caller's signal too. Only the isolated-vm
  branch did, so an aborted request on the E2B path waited for the sandbox to
  finish and could return a success the caller no longer wanted.
- Attach a terminal handler to the shared render. Every caller races it against
  its own signal, so all of them can walk away; a later rejection with no waiters
  left would otherwise surface as an unhandled rejection.
- Stop narrowing what throwOnDocNotReady rethrows. readUserFileContent now runs
  document compiles and can fail in ways this module has no business
  enumerating; narrowing produced three consecutive review rounds of "this
  particular failure is still swallowed". The flag means "do not degrade".
- Do not mark an unrendered response immutable. The serve route caches versioned
  responses for a year, which would pin a one-off render failure to that URL long
  after a later compile succeeds on the same version.

* revert(files): drop the concurrent-render coalescing

The coalescing was an optional efficiency win — rendering is content-addressed
and idempotent, so duplicate concurrent renders produced the same artifact and
cost only extra sandbox time on an artifact miss. It bought that at the price of
the most intricate code in the change set, and produced three concurrency
findings across two review rounds: a shared render inheriting one caller's
cancellation, an E2B/isolated-vm asymmetry in how the signal was raced, and
orphaned rejections once every caller could race away.

Removing it also restores true cancellation on the isolated-vm path: the caller's
signal now reaches runSandboxTask again, so an abort cancels the sandbox work
rather than only abandoning the wait for it.

* fix(review): simplify generated document attachments

* fix(files): mock servable downloads in hydration tests

* fix(files): preserve rendered attachment semantics

* fix(files): preserve cached artifact size

* fix(files): refuse unresolved xlsx source

* fix(files): resolve execution artifact workspace
2026-08-01 10:56:02 -07:00
2026-07-28 19:25:59 -07:00

Sim.ai Documentation Slack X

Ask DeepWiki Set Up with Cursor

Sim — Integrate, Context, Build, and Monitor AI agents

A workspace to build, deploy and manage AI agents and workflows.

Quickstart

Cloud-hosted: sim.ai

Open sim.ai

Self-hosted

git clone https://github.com/simstudioai/sim.git && cd sim
bun run setup

Open http://localhost:3000

The Sim platform — chat on the left, the visual workflow builder on the right

Capabilities

  • Connect 1,000+ integrations and every major LLM
  • Add Slack, Notion, HubSpot, Salesforce, databases, and more
  • Build agents visually, conversationally, or with code
  • Ingest files, knowledge bases, and structured table data
  • Monitor runs, logs, schedules, and workflow activity

One workspace, every surface

Chat and workflows are just the start — tables, files, knowledge, and scheduled tasks all live in the same workspace.

Tables in Sim — structured data your agents can query

Tables — a database, built in

Files in Sim — documents for your team and every agent

Files — one store for your team and every agent

Knowledge bases in Sim — synced docs your agents can search

Knowledge — your agents' memory

Scheduled tasks in Sim — recurring agent runs on a calendar

Scheduled tasks — runs on your schedule

Self-hosting

Requirements: Bun and Docker.

bun run setup is an interactive wizard: it provisions the database, generates secrets, writes your .env files, connects a Chat API key, and starts Sim the way you choose:

  • Local dev — run from source to contribute or hack on Sim
  • Docker Compose — a self-contained instance for testing self-hosting
  • Kubernetes (Helm) — deploy to a local cluster

When it finishes, open http://localhost:3000.

Manage your install with bun run sim:

bun run sim start | stop | restart   # bring your install up / down / cycle
bun run sim status                    # what's installed and healthy
bun run sim logs                      # follow logs
bun run sim doctor                    # diagnose configuration problems
bun run sim down                      # remove containers (data kept)
bun run sim reset                     # archive .env and wipe managed data

sim detects how you're running (Docker Compose, local dev, or Kubernetes) and acts accordingly.

Prefer a bare sim? Run bun link once — but note sim lands in ~/.bun/bin, which Homebrew's bun doesn't add to your PATH, so you may need export PATH="$HOME/.bun/bin:$PATH" in your shell profile.

Sim also supports local models via Ollama and vLLM. See the self-hosting docs for details.

Chat API Keys

Chat is a Sim-managed service. bun run setup connects a Chat API key for you — sign in when it opens your browser and the key is stored automatically. To view, create, or revoke keys later, go to sim.ai/selfhost/settings/chat-keys.

Environment Variables

See the environment variables reference for the full list, or apps/sim/.env.example for defaults.

Tech Stack

Next.js · Bun · PostgreSQL · Drizzle · Better Auth · Tailwind — and the rest of the stack

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Built by the Sim team in San Francisco

Languages
TypeScript 77%
MDX 20.8%
JavaScript 1.9%
CSS 0.1%