From 4a7685bd1d6017d39a94bf6891ba50a99b08a8f3 Mon Sep 17 00:00:00 2001 From: Shy Alter Date: Fri, 31 Jul 2026 10:46:10 +0200 Subject: [PATCH 1/4] feat(agent): tell cloud agents to upload deliverables as artifacts Add guidance to the cloud task system prompt instructing the agent to call the `upload_artifact` tool for non-code deliverables (reports, images, rendered canvases, data files) so they appear in the user's artifact panel as downloads, rather than being left in the workspace where the user can't see them. The instruction is appended to every cloud system-prompt branch and skipped for Slack-origin sessions, which get a runtime "no artifact delivery" constraint. Exposes UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME from the tool module, mirroring the signed-commit tool-name constants, so the prompt references the qualified tool id from a single source. Generated-By: PostHog Code Task-Id: 887b21b0-78b0-4741-be7c-1f0e456f926a --- .../local-tools/tools/upload-artifact.ts | 15 +++++++++-- packages/agent/src/server/agent-server.ts | 26 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts b/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts index 78fbb500f5..701540088b 100644 --- a/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts +++ b/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts @@ -2,12 +2,23 @@ import { readFile, realpath, stat } from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { createSandboxPosthogClient } from "../../../signed-commit-artefacts"; -import { defineLocalTool, type LocalToolResult } from "../registry"; +import { + defineLocalTool, + type LocalToolResult, + qualifiedLocalToolName, +} from "../registry"; const MAX_ARTIFACT_UPLOAD_BYTES = 30 * 1024 * 1024; +export const UPLOAD_ARTIFACT_TOOL_NAME = "upload_artifact"; + +/** The qualified tool id as the model and system prompt reference it. */ +export const UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME = qualifiedLocalToolName( + UPLOAD_ARTIFACT_TOOL_NAME, +); + export const uploadArtifactTool = defineLocalTool({ - name: "upload_artifact", + name: UPLOAD_ARTIFACT_TOOL_NAME, description: "Deliver a file you created to the user as a downloadable task artifact. " + "Call this for every non-code deliverable (reports, images, archives, data files, and similar output) " + diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index f53594a274..db6b8070fc 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -48,6 +48,7 @@ import { isPromptTooLongError, } from "../adapters/error-classification"; import { isSupportedReasoningEffort } from "../adapters/reasoning-effort"; +import { UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME } from "../adapters/local-tools/tools/upload-artifact"; import { appendRtkGuidanceForCodex } from "../adapters/rtk-guidance"; import { SIGNED_COMMIT_QUALIFIED_TOOL_NAME, @@ -3625,6 +3626,21 @@ Optimize for the fewest shell round trips. - Read multiple files at once. - Never rerun a command solely to reproduce output you already have.`; + // Slack sessions cannot deliver artifacts (the Slack host injects a runtime + // constraint saying so), so only surface this guidance elsewhere. The + // `upload_artifact` tool itself is gated to cloud sessions with a live task + // run, which every branch below already is. + const artifactInstructions = isSlack + ? "" + : ` +## Delivering non-code files (artifacts) +When you produce a non-code deliverable — a report, chart, image, CSV, archive, rendered +canvas, or any other file the user should be able to download — call the \`upload_artifact\` +tool (full name \`${UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME}\`) with the file's path BEFORE your +final reply, then mention it in that reply. Uploaded files show up in the user's artifact +panel as downloads; a file merely left in the workspace does not. Do this for every such +deliverable. Do NOT upload source code or repository changes — those belong in a commit or PR.`; + const whyContextInstruction = ` - Add a brief **Why** to the body — one or two sentences capturing the reason the user asked for this change (the motivation, not a restatement of the diff). Keep it short.`; const publicRepoSafetyInstruction = ` - **Public-repo safety.** Treat the target repository as public-readable unless you have verified otherwise. The PR title, description, and commit messages must not contain private operational scale (exact event counts, internal row volumes, customer-usage percentages), customer names / emails / companies, references to internal tickets or incidents, the contents of Slack threads (do not quote or paraphrase what was said), or unreleased roadmap details. Linking to the originating Slack thread is fine and encouraged — Slack links are auth-gated and useful as context — as are channel references like "raised in #team-foo". Describe findings qualitatively ("present on nearly all X events, absent from Y") rather than with quantitative figures pulled from analytics queries — the reasoning that uses those numbers can stay in the thread; the PR copy cannot.`; const prMentionSafetyInstruction = ` - **Never guess a GitHub identity.** Do NOT \`@\`-mention, tag, assign, request review from, or attribute the PR to a person (in the title, description, commit message, or reviewers) using a name or handle taken from Slack or this thread. A Slack display name or handle is NOT a GitHub username. Finding a similar-looking handle in the repo's git history, CODEOWNERS, or existing PRs/issues does NOT confirm it belongs to this person: repository presence proves the handle exists, not that it is the person you mean, so treating it as a match still \`@\`-tags an unrelated account (e.g. Slack "Ross" is not necessarily GitHub \`@ross\`, even if some \`@ross\` has committed to the repo). Only \`@\`-mention a GitHub \`@handle\` the user gave you explicitly in this thread. Otherwise refer to people by plain-text name, or omit the mention entirely.`; @@ -3652,7 +3668,7 @@ Do the requested work, but stop with local changes ready for review. Important: - Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks. - Do NOT create a new branch or a new pull request unless the user explicitly asks. -${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions} +${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}${artifactInstructions} `; } @@ -3673,7 +3689,7 @@ After completing the requested changes: Important: - Do NOT create a new branch or a new pull request unless the user explicitly asks. - Do NOT push fixes for review comments without replying to and resolving each related thread. -${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions} +${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}${artifactInstructions} `; } @@ -3725,7 +3741,7 @@ ${publishInstructions} Important: - Prefer using MCP tools to answer questions with real data over giving generic advice. -${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions} +${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}${artifactInstructions} `; } @@ -3743,7 +3759,7 @@ ${publicRepoSafetyInstruction.trimStart()} ${prMentionSafetyInstruction.trimStart()} - End the PR description with a horizontal rule followed by this footer line: ${prFooter} - Always create the PR as a draft. -${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions} +${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}${artifactInstructions} `; } @@ -3771,7 +3787,7 @@ ${prFooter} Important: - Always create the PR as a draft. Do not ask for confirmation. -${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions} +${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}${artifactInstructions} `; } From 1078880d7f9a7ba7c7eefd7a4b1604ee500e0849 Mon Sep 17 00:00:00 2001 From: Shy Alter Date: Fri, 31 Jul 2026 10:46:12 +0200 Subject: [PATCH 2/4] refactor(agent): simplify artifact upload prompt per review Address review feedback from @k11kirky: drop the exported UPLOAD_ARTIFACT tool-name constants (revert upload-artifact.ts to referencing the literal name), remove the verbose explanatory code comment, and tighten the artifact-delivery prompt prose. The instruction now references the `upload_artifact` tool by its short name. Generated-By: PostHog Code Task-Id: 887b21b0-78b0-4741-be7c-1f0e456f926a --- .../adapters/local-tools/tools/upload-artifact.ts | 15 ++------------- packages/agent/src/server/agent-server.ts | 12 +----------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts b/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts index 701540088b..78fbb500f5 100644 --- a/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts +++ b/packages/agent/src/adapters/local-tools/tools/upload-artifact.ts @@ -2,23 +2,12 @@ import { readFile, realpath, stat } from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { createSandboxPosthogClient } from "../../../signed-commit-artefacts"; -import { - defineLocalTool, - type LocalToolResult, - qualifiedLocalToolName, -} from "../registry"; +import { defineLocalTool, type LocalToolResult } from "../registry"; const MAX_ARTIFACT_UPLOAD_BYTES = 30 * 1024 * 1024; -export const UPLOAD_ARTIFACT_TOOL_NAME = "upload_artifact"; - -/** The qualified tool id as the model and system prompt reference it. */ -export const UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME = qualifiedLocalToolName( - UPLOAD_ARTIFACT_TOOL_NAME, -); - export const uploadArtifactTool = defineLocalTool({ - name: UPLOAD_ARTIFACT_TOOL_NAME, + name: "upload_artifact", description: "Deliver a file you created to the user as a downloadable task artifact. " + "Call this for every non-code deliverable (reports, images, archives, data files, and similar output) " + diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index db6b8070fc..ea220e0d07 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -48,7 +48,6 @@ import { isPromptTooLongError, } from "../adapters/error-classification"; import { isSupportedReasoningEffort } from "../adapters/reasoning-effort"; -import { UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME } from "../adapters/local-tools/tools/upload-artifact"; import { appendRtkGuidanceForCodex } from "../adapters/rtk-guidance"; import { SIGNED_COMMIT_QUALIFIED_TOOL_NAME, @@ -3626,20 +3625,11 @@ Optimize for the fewest shell round trips. - Read multiple files at once. - Never rerun a command solely to reproduce output you already have.`; - // Slack sessions cannot deliver artifacts (the Slack host injects a runtime - // constraint saying so), so only surface this guidance elsewhere. The - // `upload_artifact` tool itself is gated to cloud sessions with a live task - // run, which every branch below already is. const artifactInstructions = isSlack ? "" : ` ## Delivering non-code files (artifacts) -When you produce a non-code deliverable — a report, chart, image, CSV, archive, rendered -canvas, or any other file the user should be able to download — call the \`upload_artifact\` -tool (full name \`${UPLOAD_ARTIFACT_QUALIFIED_TOOL_NAME}\`) with the file's path BEFORE your -final reply, then mention it in that reply. Uploaded files show up in the user's artifact -panel as downloads; a file merely left in the workspace does not. Do this for every such -deliverable. Do NOT upload source code or repository changes — those belong in a commit or PR.`; +When you create a non-code deliverable the user should be able to download (a report, chart, image, data file, rendered canvas), call the \`upload_artifact\` tool with its path before your final reply, then mention it. Files left in the workspace don't reach the user; uploaded ones show up in their artifact panel. Don't upload source code or repository changes — those belong in a commit or PR.`; const whyContextInstruction = ` - Add a brief **Why** to the body — one or two sentences capturing the reason the user asked for this change (the motivation, not a restatement of the diff). Keep it short.`; const publicRepoSafetyInstruction = ` - **Public-repo safety.** Treat the target repository as public-readable unless you have verified otherwise. The PR title, description, and commit messages must not contain private operational scale (exact event counts, internal row volumes, customer-usage percentages), customer names / emails / companies, references to internal tickets or incidents, the contents of Slack threads (do not quote or paraphrase what was said), or unreleased roadmap details. Linking to the originating Slack thread is fine and encouraged — Slack links are auth-gated and useful as context — as are channel references like "raised in #team-foo". Describe findings qualitatively ("present on nearly all X events, absent from Y") rather than with quantitative figures pulled from analytics queries — the reasoning that uses those numbers can stay in the thread; the PR copy cannot.`; From 71fd26f01642d369b2b67ba741790bd4c4419e37 Mon Sep 17 00:00:00 2001 From: Shy Alter Date: Fri, 31 Jul 2026 10:46:14 +0200 Subject: [PATCH 3/4] refactor(agent): always include artifact upload guidance Address review feedback from @puemos: drop the Slack gating so the artifact-delivery instruction appears in every cloud session. Sessions that genuinely can't deliver artifacts already get a runtime constraint that overrides the prompt. Generated-By: PostHog Code Task-Id: 887b21b0-78b0-4741-be7c-1f0e456f926a --- packages/agent/src/server/agent-server.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index ea220e0d07..42cb27817f 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -3625,9 +3625,7 @@ Optimize for the fewest shell round trips. - Read multiple files at once. - Never rerun a command solely to reproduce output you already have.`; - const artifactInstructions = isSlack - ? "" - : ` + const artifactInstructions = ` ## Delivering non-code files (artifacts) When you create a non-code deliverable the user should be able to download (a report, chart, image, data file, rendered canvas), call the \`upload_artifact\` tool with its path before your final reply, then mention it. Files left in the workspace don't reach the user; uploaded ones show up in their artifact panel. Don't upload source code or repository changes — those belong in a commit or PR.`; From ecc0aba0d24cb1192b8926c75d3bb8fa8d66bcad Mon Sep 17 00:00:00 2001 From: Shy Alter Date: Fri, 31 Jul 2026 14:36:51 +0200 Subject: [PATCH 4/4] fix(agent): link uploaded artifacts in final responses Generated-By: PostHog Code Task-Id: 842d598b-48cc-4059-8fed-18a9965a71c4 --- packages/agent/src/server/agent-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index 42cb27817f..990fd388db 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -3627,7 +3627,7 @@ Optimize for the fewest shell round trips. const artifactInstructions = ` ## Delivering non-code files (artifacts) -When you create a non-code deliverable the user should be able to download (a report, chart, image, data file, rendered canvas), call the \`upload_artifact\` tool with its path before your final reply, then mention it. Files left in the workspace don't reach the user; uploaded ones show up in their artifact panel. Don't upload source code or repository changes — those belong in a commit or PR.`; +When you create a non-code file the user should be able to download (such as a report, chart, image, archive, or data file), call the \`upload_artifact\` tool with its path before your final reply. In your final reply, link to the download URL returned by the tool—never link to the file's local workspace path. Files left in the workspace don't reach the user. Don't upload source code or repository changes—those belong in a commit or PR.`; const whyContextInstruction = ` - Add a brief **Why** to the body — one or two sentences capturing the reason the user asked for this change (the motivation, not a restatement of the diff). Keep it short.`; const publicRepoSafetyInstruction = ` - **Public-repo safety.** Treat the target repository as public-readable unless you have verified otherwise. The PR title, description, and commit messages must not contain private operational scale (exact event counts, internal row volumes, customer-usage percentages), customer names / emails / companies, references to internal tickets or incidents, the contents of Slack threads (do not quote or paraphrase what was said), or unreleased roadmap details. Linking to the originating Slack thread is fine and encouraged — Slack links are auth-gated and useful as context — as are channel references like "raised in #team-foo". Describe findings qualitatively ("present on nearly all X events, absent from Y") rather than with quantitative figures pulled from analytics queries — the reasoning that uses those numbers can stay in the thread; the PR copy cannot.`;