Artifacts and sandboxed code execution: tools, workflows, and document I/O#2568
Merged
Conversation
Introduce artifacts and artifact_versions tables: an append-only, pass-by-reference store with a stable identity row and immutable versions that atomically bump current_version. Access is parent-derived (conversation or workflow run); a CHECK enforces at least one parent and a nullable team_id is reserved for future team scoping. Adds the 0025_artifacts migration, ArtifactsRepository with a parent-scoped safe getter alongside the unscoped one, and repo tests.
Serve artifact metadata and bytes over HTTP with parent-derived authorization: conversation-parented artifacts inherit conversation access (owner, shared_with, or a public share token whose conversation matches the parent), workflow-run artifacts check run ownership, and access fails closed when the parent is missing or deleted. Adds list/get/versions/download/restore routes, an authenticated storage-agnostic download (sanitized Content-Disposition, 302 to a short-lived private S3 presigned URL when that strategy is configured), a generate_presigned_url primitive on the storage base and S3 backend, and generalizes the tools artifact endpoint for documents/files. Shared authorization helpers live in a dedicated module used by both surfaces.
…nner) Introduce a pluggable CodeSandbox abstraction with a SandboxManager and a Jupyter Kernel Gateway backend: the app is a client of a single always-on runner that executes code in stateful in-process kernels (no child-container spawning, no docker socket). Sessions bind to a conversation or workflow run with an agent-selectable TTL clamped by a global cap; execution enforces a wall-clock deadline with interrupt-on-timeout and capped output, and file transfer is workspace-contained with size and integrity checks. Adds a docsgpt-sandbox docker-compose service (resource-capped, read-only, internal network) plus settings, with a real local-gateway integration test.
Extend the artifact sidebar with a document/file view: a version selector, authenticated download honoring Content-Disposition (following the backend redirect to a presigned URL when configured), and version restore. Preview is chosen by kind - html, svg, and mermaid render inside a sandboxed iframe with a restrictive CSP (mermaid via its own sandboxed render so untrusted diagram text never reaches the app DOM), office documents and PDFs show a download card, and code or data render as text. Wires the new artifact endpoints into the API service and leaves the existing notes/todo path unchanged.
Add a pluggable Daytona backend (SANDBOX_BACKEND=daytona) that runs code on Daytona Cloud via the Apache-2.0 SDK and DAYTONA_API_KEY, conforming to the CodeSandbox interface. Sessions reattach to a labelled cloud sandbox by id (waking a stopped one) and are created crash-safe so a failed setup never orphans a paid sandbox; a configurable ceiling caps concurrent sandboxes and auto-delete is clamped on so orphans always self-reap. File transfer is workspace-contained with a size guard. Includes mocked unit tests and an opt-in live smoke test.
Add a code_executor agent tool with a run_code action that runs agent-provided code in the per-conversation sandbox session and captures produced files as artifacts. Inputs are materialized only from artifacts the caller can access (parent-scoped); produced files are stored under the user's namespace with server-computed size and sha256, and the storage write is ordered last in the transaction so a failure cannot orphan bytes. Output is a compact payload with no raw bytes, and the produced artifact lights up the existing tool artifact rail. Execution honors a wall-clock timeout and an agent-selectable session TTL clamped by the global cap, and the action can be gated behind approval. Tool-call argument logging is redacted so code bodies are not written to logs.
Add a code workflow node that runs code in the run-scoped sandbox session and writes produced files as artifact references into workflow state, passing them by reference (only id and metadata, never bytes) so downstream nodes and CEL conditions can branch on them. Add an artifacts.* templating namespace that resolves those references to metadata via a run-scoped lookup, available to both the workflow engine and the prompt renderer. Extract the sandbox-to- artifact persistence into a shared helper reused by the code node and the code_executor tool.
Add an artifact generator tool with create, edit, and rewrite actions that render presentations, documents, spreadsheets, and PDFs from a validated JSON spec by running a fixed program in the sandbox (the spec travels as data, so its contents can never execute). The spec is stored as the source of truth on each artifact version; edits apply a JSON merge-patch and re-render, appending a new version while earlier versions stay intact.
Add a document extractor tool with an extract_document action that converts an input artifact (PDF, docx, pptx, ...) into structured JSON by running a fixed Docling program in the sandbox (the document and parameters travel as data, so nothing in them can execute). Output is a compact payload bounded by an input size cap, a head-and-tail markdown window, and per-table caps, can be validated against a JSON schema, and is persisted as a data artifact by reference. Docling is heavy, so it stays out of the base image and ships in an opt-in sandbox image variant.
Add runtime governance for the sandbox and artifact store: a per-process concurrent-session cap with least-recently-used eviction of idle sessions and a periodic idle reaper (Celery beat), so sandbox kernels do not accumulate. The session manager performs all backend start/stop outside its lock and tears down the captured handle, so eviction never closes a concurrently re-opened session. Add per-user artifact quotas (count, total bytes, and per-file size) enforced at persistence time as a soft cap, and best-effort cleanup of per-render scratch directories in the sandbox workspace.
Expose a user's artifacts through the MCP server as readable resources: each is listed under an artifact:// URI with its mime type and read on demand as inline text or a base64 blob, bounded by a size cap and scoped strictly to the owning principal resolved from the request's API key, so no artifact is served across tenants. Ship the network-level egress controls the sandbox runner needs but cannot self-apply: a Kubernetes NetworkPolicy that allows public egress while denying RFC1918, link-local, and cloud-metadata ranges, an optional docker-compose egress overlay, and runner network-hardening docs.
The output-cap test flooded ~100MB through a 20s wall-clock timeout, so under heavy parallel load the timeout could fire before enough output accumulated to trip the cap, intermittently failing. Emit a bounded ~200 KiB (4x the cap) under a generous timeout so the truncation path triggers deterministically.
Render produced-artifact bytes inline when a version has no embedded spec: HTML and SVG in the existing sandboxed iframe (with the restrictive CSP), images inline via a blob URL, and Markdown/CSV/JSON/plain text as escaped text; office documents and PDFs keep the download card. Bytes are fetched through the existing authenticated download path, version-aware, with object URLs revoked on change. Untrusted HTML/SVG renders only inside the scriptless sandboxed iframe and Markdown is rendered without raw HTML, so artifact content cannot script the app.
Give each produced artifact a short virtual handle - A1, A2, ... - the n-th artifact in the conversation or workflow run (case-insensitive, not stored). The tools return it in their results, and the edit, rewrite, and input parameters accept either the handle or the full id. A handle resolves only within the caller's own conversation or run, and the resolved id is still checked against that parent before any read, so it cannot reach another tenant's artifact. This fixes the model creating a duplicate instead of a new version when asked to edit an artifact.
Expose the workflow code node in the builder UI: a draggable palette entry, a canvas node, and a config panel for the code, inputs, output variable, timeout, and an optional JSON schema (validated before save). The panel serializes to the same config the engine reads, so a code node built in the UI runs and produces an artifact reference; workflows without code nodes load unchanged.
Add an html kind to the artifact generator: it renders a styled HTML report from a stored JSON spec (title + heading/paragraph/list/table/code blocks), producing text/html bytes the sidebar already previews inline in the sandboxed iframe, while keeping the spec as the source of truth so edit and rewrite make real versions. So a single artifact both renders inline and versions conversationally. The renderer is a fixed program fed the spec as data, and every spec text value is HTML-escaped into the output.
Stamp the engine's workflow_run_id into a workflow agent node's tool config, so artifact_generator, code_executor, and document_extractor address artifacts by the workflow run. An artifact (and its short ref like A1) created by one agent node is then visible to a later code node and editable by a later agent node in the same run - enabling an 'agent generates, a code node transforms, a second agent edits' flow. Workflow agent nodes carry no conversation id, so only the run scope applies.
Let workflow runs consume and produce documents end to end: bridge uploaded attachments into run-scoped artifacts so nodes receive the input documents (with a per-run cap and server-computed size/sha256, and the run row pre-created so produced artifacts are authorized during the run); emit the run id to the client and add a builder panel that lists, previews, and downloads a run's artifacts; and allow attaching documents to a Preview run via the existing upload flow. Also fixes issues a compliance workflow surfaced: attachment ownership now keys on the raw identity instead of a sanitized one (the sanitized form could not be read back and could collide across users); workflow code nodes read prior state from a state.json data file instead of templating it into the program, so untrusted document content can never be interpolated into executed code; structured node output wrapped in code fences is recovered; and the live speech-to-text ownership check compares the raw identity.
When an executed tool call raises, append the assistant message carrying the tool_calls before the tool error result, mirroring the success path. Previously only the tool result was appended, leaving a tool message with no preceding tool_calls parent, which made the next provider completion reject the request. Every tool_call id in a batch is now answered even when some calls fail.
Run each kernel under a scrubbed environment so untrusted code can never read the host's secrets. A custom 'docsgpt-python' kernelspec launches ipykernel through a wrapper that keeps only what the kernel needs (PATH, HOME, LANG, and the Jupyter runtime/data dirs), dropping API keys, tokens, the database URL, and the gateway token. The app selects this kernel by name via SANDBOX_KERNEL_NAME, so the distinct name is never shadowed by the stock python3 spec. Per-session workspaces are created mode 0700 (defense in depth under the shared uid). The README documents the runner as a single trust domain and points to the Daytona backend for per-tenant isolation.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Add the run input-documents variable to the workflow builder's variable dropdown (workflow input) and the artifact-by-id lookup to both the workflow builder and the agent prompt builder, so the variables exposed by the shared template namespaces are discoverable. agent.input_documents is workflow-only (injected from run state) and stays out of the agent builder, which would render it empty.
…ecutors # Conflicts: # frontend/src/agents/workflow/WorkflowBuilder.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an artifacts + code-execution system to DocsGPT. Agents and workflows can run sandboxed Python, generate and version documents, extract content from uploaded files, and pass results between steps by reference. Artifacts are first-class, versioned, access-controlled objects; code runs in a pluggable sandbox (a self-hosted Jupyter Kernel Gateway runner or Daytona Cloud).
What's added
Artifacts
artifacts/artifact_versionstables (migration0025_artifacts) + repository; append-only versioning; parent-derived authorization (a conversation or a workflow run); shared-link access inherits.Code execution
CodeSandboxabstraction with two backends: a self-hosted Jupyter Kernel Gateway runner (always-on container underdeployment/sandbox/) and Daytona Cloud (per-session VM).SandboxManagerenforces a per-process session cap, LRU-idle eviction, and a Celery-beat reaper.code_executor(run code → persisted artifacts, approval-gated, agent-selectable TTL),artifact_generator(create / edit / rewrite presentations, documents, spreadsheets, pdf, html from an injection-safe spec), anddocument_extractor(Docling).Workflows
artifacts.*templating namespace (pass-by-reference between steps).agent.input_documentsand short refsA1,A2; code nodes read prior state from astate.jsondata file. The builder surfaces a run's outputs in an "Artifacts" panel, and the Preview can attach input documents.Security
0700workspaces, and kernels run under a scrubbed environment so no API keys, tokens, database URL, or gateway token reach kernel code.state.json), never templated into the program, so untrusted document content cannot become executed code.deployment/sandbox/README.md.Config & deployment
SANDBOX_BACKEND(jupyter|daytona),SANDBOX_GATEWAY_URL,SANDBOX_KERNEL_NAME(set todocsgpt-pythonto select the env-scrubbing kernel),DAYTONA_API_KEY, plus artifact quota / TTL caps.docsgpt-sandboxservice indeployment/docker-compose.yaml, the k8s manifests, and the egress NetworkPolicy. Docling install is gated behind anINSTALL_DOCLINGbuild arg.alembic upgrade headfor migration0025_artifacts.Testing
ruff clean repo-wide; backend unit + integration suites pass (including a live Jupyter-gateway integration suite and the sandbox / workflow / artifact tests); frontend lint, build, and vitest pass. Each slice was reviewed for code, security, and end-to-end behavior before being committed.
Demo
A mock compliance workflow runs end-to-end (classify → extract → compute → decide → emit a rule-table
.docx). A demo GIF will be attached to this PR.