diff --git a/packages/cloud-agents/src/server/__tests__/resolveStandardTaskSurface.test.ts b/packages/cloud-agents/src/server/__tests__/resolveStandardTaskSurface.test.ts
index 4e8086e46..aaeee5a96 100644
--- a/packages/cloud-agents/src/server/__tests__/resolveStandardTaskSurface.test.ts
+++ b/packages/cloud-agents/src/server/__tests__/resolveStandardTaskSurface.test.ts
@@ -1,4 +1,8 @@
-import { resolveStandardTaskSurface } from '../cloud-agent-workflow';
+import {
+ isResultOnlyAutomationChatDelivery,
+ resolveStandardTaskSurface,
+ shouldAttachChatLifecycleInstructions,
+} from '../cloud-agent-workflow';
describe('resolveStandardTaskSurface', () => {
it('prefers Slack channel payload bindings', () => {
@@ -59,3 +63,52 @@ describe('resolveStandardTaskSurface', () => {
).toBe('web');
});
});
+
+describe('isResultOnlyAutomationChatDelivery', () => {
+ it('uses result-only delivery for an initial automation report', () => {
+ expect(
+ isResultOnlyAutomationChatDelivery({ initiatorKind: 'automation' }),
+ ).toBe(true);
+ });
+
+ it('keeps directed automation follow-ups conversational', () => {
+ expect(
+ isResultOnlyAutomationChatDelivery({
+ initiatorKind: 'automation',
+ slackThreadTs: '123.456',
+ }),
+ ).toBe(false);
+ expect(
+ isResultOnlyAutomationChatDelivery({
+ initiatorKind: 'automation',
+ communicationMessageId: 'message-123',
+ }),
+ ).toBe(false);
+ });
+
+ it('does not change user-initiated chat delivery', () => {
+ expect(isResultOnlyAutomationChatDelivery({ initiatorKind: 'user' })).toBe(
+ false,
+ );
+ });
+});
+
+describe('shouldAttachChatLifecycleInstructions', () => {
+ it('omits generic lifecycle instructions from initial automation reports', () => {
+ expect(
+ shouldAttachChatLifecycleInstructions({
+ inheritedCommunicationContext: false,
+ resultOnlyChatDelivery: true,
+ }),
+ ).toBe(false);
+ });
+
+ it('keeps lifecycle instructions for directed follow-ups', () => {
+ expect(
+ shouldAttachChatLifecycleInstructions({
+ inheritedCommunicationContext: false,
+ resultOnlyChatDelivery: false,
+ }),
+ ).toBe(true);
+ });
+});
diff --git a/packages/cloud-agents/src/server/cloud-agent-workflow.ts b/packages/cloud-agents/src/server/cloud-agent-workflow.ts
index 8a9c9f219..be5e46570 100644
--- a/packages/cloud-agents/src/server/cloud-agent-workflow.ts
+++ b/packages/cloud-agents/src/server/cloud-agent-workflow.ts
@@ -105,6 +105,30 @@ export function resolveStandardTaskSurface({
}
}
+export function isResultOnlyAutomationChatDelivery({
+ initiatorKind,
+ slackThreadTs,
+ communicationMessageId,
+}: {
+ initiatorKind?: string | null;
+ slackThreadTs?: string | null;
+ communicationMessageId?: string | null;
+}): boolean {
+ return (
+ initiatorKind === 'automation' && !slackThreadTs && !communicationMessageId
+ );
+}
+
+export function shouldAttachChatLifecycleInstructions({
+ inheritedCommunicationContext,
+ resultOnlyChatDelivery,
+}: {
+ inheritedCommunicationContext: boolean;
+ resultOnlyChatDelivery: boolean;
+}): boolean {
+ return !inheritedCommunicationContext && !resultOnlyChatDelivery;
+}
+
export async function generatePrompt({
taskRun,
taskSpec,
@@ -147,6 +171,7 @@ export async function generatePrompt({
actorDisplayName: true,
slackThreadTs: true,
surface: true,
+ initiatorKind: true,
},
});
const commitAuthor = taskRow
@@ -316,6 +341,16 @@ export async function generatePrompt({
const communicationMessageId = getCommunicationMessageIdFromTaskPayload(
taskSpec.payload,
);
+ const resultOnlyChatDelivery = isResultOnlyAutomationChatDelivery({
+ initiatorKind: taskRow?.initiatorKind,
+ slackThreadTs,
+ communicationMessageId,
+ });
+ const attachChatLifecycleInstructions =
+ shouldAttachChatLifecycleInstructions({
+ inheritedCommunicationContext,
+ resultOnlyChatDelivery,
+ });
const teamsTenantId = getCommunicationTenantIdFromTaskPayload(
taskSpec.payload,
);
@@ -386,6 +421,7 @@ export async function generatePrompt({
sourceChannelId: communicationChannelId ?? undefined,
sourceThreadId: communicationThreadId ?? undefined,
sourceMessageId: communicationMessageId ?? undefined,
+ resultOnlyChatDelivery,
interactiveMode: taskSpec.payload.bootstrap?.interactiveMode,
requestFormat,
linkedWorkItems: taskSpec.payload.linkedWorkItems,
@@ -398,7 +434,7 @@ export async function generatePrompt({
prAction,
});
- if (!inheritedCommunicationContext && slackChannel && slackThreadTs) {
+ if (attachChatLifecycleInstructions && slackChannel && slackThreadTs) {
const slackInstructions = buildSlackMessageInstructions({
includeRequestUserInputGuidance: true,
});
@@ -407,7 +443,7 @@ export async function generatePrompt({
: slackInstructions;
}
- if (!inheritedCommunicationContext && nonSlackChatProvider) {
+ if (attachChatLifecycleInstructions && nonSlackChatProvider) {
const chatInstructions =
nonSlackChatProvider === 'teams'
? buildTeamsMessageInstructions()
diff --git a/packages/cloud-agents/src/server/workflows/__tests__/standardTaskSurfaceContext.test.ts b/packages/cloud-agents/src/server/workflows/__tests__/standardTaskSurfaceContext.test.ts
index 0f4b4192c..1b787c5e1 100644
--- a/packages/cloud-agents/src/server/workflows/__tests__/standardTaskSurfaceContext.test.ts
+++ b/packages/cloud-agents/src/server/workflows/__tests__/standardTaskSurfaceContext.test.ts
@@ -68,6 +68,31 @@ describe('Standard Task surface context', () => {
);
});
+ it('keeps initial automation chat delivery silent until the result', () => {
+ const { harnessInstructions } = standardTask({
+ description: 'Scan repositories and report suggestions',
+ repo: 'Roomote/example-app',
+ taskSurface: 'slack',
+ resultOnlyChatDelivery: true,
+ });
+
+ expect(harnessInstructions).toContain(
+ 'This run was launched by an automation with slack as its report destination. It was not launched by a directed chat turn.',
+ );
+ expect(harnessInstructions).toContain(
+ "The automation's chat message must always be its result, never an in-progress message.",
+ );
+ expect(harnessInstructions).toContain(
+ 'The automation-specific prompt remains authoritative for whether to report, which chat tools to use, and the number and shape of final messages.',
+ );
+ expect(harnessInstructions).not.toContain(
+ 'The first and only chat-visible reply for this automation turn must use `send_chat_reply`',
+ );
+ expect(harnessInstructions).not.toContain(
+ 'This run was launched from a Slack conversation surface',
+ );
+ });
+
it('labels Gitea-started review tasks as Gitea pull request runs', () => {
const { harnessInstructions } = standardTask({
description: 'Review pull request',
diff --git a/packages/cloud-agents/src/server/workflows/standardTask.ts b/packages/cloud-agents/src/server/workflows/standardTask.ts
index d04066b3f..213d26049 100644
--- a/packages/cloud-agents/src/server/workflows/standardTask.ts
+++ b/packages/cloud-agents/src/server/workflows/standardTask.ts
@@ -73,6 +73,7 @@ export function standardTask({
sourceChannelId,
sourceThreadId,
sourceMessageId,
+ resultOnlyChatDelivery = false,
linkedWorkItems,
interactiveMode = false,
requestFormat = 'plain',
@@ -122,6 +123,8 @@ export function standardTask({
sourceChannelId?: string;
sourceThreadId?: string;
sourceMessageId?: string;
+ /** Initial automation report with no directed chat turn to acknowledge. */
+ resultOnlyChatDelivery?: boolean;
linkedWorkItems?: LinkedWorkItem[];
interactiveMode?: boolean;
requestFormat?: 'plain' | 'structured';
@@ -258,6 +261,11 @@ export function standardTask({
`
: '';
const defaultMode = interactiveMode ? 'interactive' : 'autonomous';
+ const isChatTaskSurface =
+ taskSurface === 'slack' ||
+ taskSurface === 'teams' ||
+ taskSurface === 'telegram' ||
+ taskSurface === 'discord';
const taskSurfaceContext =
taskSurface === 'slack'
? `
@@ -330,6 +338,15 @@ ${buildGitHubMessageInstructions()}`
If a workflow or packaged skill distinguishes web dashboard tasks from other surfaces, treat this run as a web dashboard task.
When a secure web-task flow exists for the current step, prefer that flow over asking the user to paste secrets into chat or make local-only task edits.
`;
+ const effectiveTaskSurfaceContext =
+ resultOnlyChatDelivery && isChatTaskSurface
+ ? `
+
+ This run was launched by an automation with ${taskSurface} as its report destination. It was not launched by a directed chat turn.
+ The automation's chat message must always be its result, never an in-progress message. Keep acknowledgements, reactions, progress updates, partial findings, and routine status in the web task only.
+ The automation-specific prompt remains authoritative for whether to report, which chat tools to use, and the number and shape of final messages. This context only suppresses acknowledgements and in-progress updates before that delivery contract's result.
+ `
+ : taskSurfaceContext;
const sourceContext =
sourceProvider && (sourceChannelId || sourceThreadId || sourceMessageId)
? `
@@ -404,7 +421,7 @@ ${buildGitHubMessageInstructions()}`
Screencast auto-classification is disabled for this task.
- ${taskSurfaceContext}
+ ${effectiveTaskSurfaceContext}
${sourceContext}
${sourceControlContext}
${codeReviewSelfReviewCloseoutContext}