Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8302913
feat(workflow): add autonomous NOTE 21-2 execution guidance
duc15052006-dotcom Sep 19, 2026
18110c1
test(workflow): pin NOTE 21-2 autonomous scene contract
duc15052006-dotcom Sep 19, 2026
0b5ba1e
feat(workflow): give framework Bots NOTE 21-2 guidance
duc15052006-dotcom Sep 19, 2026
4c47420
feat(workflow): give framework Bots NOTE 21-2 guidance
duc15052006-dotcom Sep 19, 2026
0a1013f
test(workflow): verify NOTE 21-2 reaches framework Bots
duc15052006-dotcom Sep 19, 2026
e5758b5
test(workflow): verify NOTE 21-2 reaches framework Bots
duc15052006-dotcom Sep 19, 2026
53dd734
feat(workflow): apply NOTE 21-2 to runtime coworkers
duc15052006-dotcom Sep 19, 2026
4c605d4
test(workflow): verify NOTE 21-2 reaches runtime coworkers
duc15052006-dotcom Sep 19, 2026
6ee328a
style(workflow): apply repository formatting
duc15052006-dotcom Sep 19, 2026
c94ddca
style(workflow): apply repository formatting
duc15052006-dotcom Sep 19, 2026
3eec085
style(workflow): apply repository formatting
duc15052006-dotcom Sep 19, 2026
7053ce9
style(workflow): apply repository formatting
duc15052006-dotcom Sep 19, 2026
b77914d
style(workflow): apply repository formatting
duc15052006-dotcom Sep 19, 2026
808e374
perf(workflow): avoid duplicating NOTE 21-2 guidance
duc15052006-dotcom Sep 19, 2026
cbb7a04
perf(workflow): avoid duplicating NOTE 21-2 guidance
duc15052006-dotcom Sep 19, 2026
cecd302
test(workflow): pin NOTE 21-2 prompt de-duplication
duc15052006-dotcom Sep 19, 2026
6a864f6
test(workflow): pin NOTE 21-2 prompt de-duplication
duc15052006-dotcom Sep 19, 2026
3ac9488
feat(workflow): pin NOTE 21-2 token budgets
duc15052006-dotcom Sep 19, 2026
ee8a17e
test(workflow): cover NOTE 21-2 token budgets
duc15052006-dotcom Sep 19, 2026
b84d4a4
test(workflow): count NOTE 21-2 guidance without JSON escaping
duc15052006-dotcom Sep 19, 2026
55ea221
test(workflow): count NOTE 21-2 guidance without JSON escaping
duc15052006-dotcom Sep 19, 2026
ec1fa5a
test(workflow): update standing-role baseline for NOTE 21-2
duc15052006-dotcom Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion agent-bot/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type { RunAgentInput } from "@ag-ui/core";
import type OpenAI from "openai";
import {
AUTONOMOUS_WORKFLOW_GUIDANCE,
COMPUTER_GUIDANCE,
NO_ANSWER_CAME,
ROUTINE_GUIDANCE,
Expand All @@ -20,8 +21,18 @@ export { NO_ANSWER_CAME };
export function toProviderMessages(
input: RunAgentInput,
): OpenAI.Chat.ChatCompletionMessageParam[] {
const workflowAlreadySupplied = input.messages.some(
(message) =>
(message.role === "system" || message.role === "developer") &&
String(message.content ?? "").includes(AUTONOMOUS_WORKFLOW_GUIDANCE),
);
const systemGuidance = [
COMPUTER_GUIDANCE,
ROUTINE_GUIDANCE,
...(workflowAlreadySupplied ? [] : [AUTONOMOUS_WORKFLOW_GUIDANCE]),
].join("\n\n");
const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: `${COMPUTER_GUIDANCE}\n\n${ROUTINE_GUIDANCE}` },
{ role: "system", content: systemGuidance },
// AG-UI application context is separate from history. The A2UI catalog and tool instructions
// arrive here; omitting them leaves the model guessing component names and action schemas.
...(input.context ?? []).map(({ description, value }) => ({
Expand Down
29 changes: 29 additions & 0 deletions agent-bot/tests/history.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import type { RunAgentInput } from "@ag-ui/core";
import { AUTONOMOUS_WORKFLOW_GUIDANCE } from "../../shared/bot-prompt";
import { NO_ANSWER_CAME, toProviderMessages } from "../src/history";

test("includes durable Routine behavior in the built-in system guidance", () => {
Expand All @@ -12,6 +13,34 @@ test("includes durable Routine behavior in the built-in system guidance", () =>
);
});

test("includes NOTE 21-2 autonomous workflow boundaries in the built-in adapter", () => {
const messages = toProviderMessages(input([]));
const system = messages[0];
expect(String(system?.content)).toContain("projectId + sceneId");
expect(String(system?.content)).toContain(
"An approved prompt is an execution input, not something to grade",
);
expect(String(system?.content)).toContain(
"Never claim you will automatically wake",
);
});

test("does not duplicate NOTE 21-2 when OpenBot already supplied it in the standing role", () => {
const messages = toProviderMessages(
input([
{
id: "standing-workflow",
role: "system",
content: `Creative role.\n\n${AUTONOMOUS_WORKFLOW_GUIDANCE}`,
} as never,
]),
);
const occurrences = messages.filter((message) =>
String(message.content ?? "").includes(AUTONOMOUS_WORKFLOW_GUIDANCE),
);
expect(occurrences).toHaveLength(1);
});

/**
* The Bot that ships in the box, and the conversation a declined handover used to end.
*
Expand Down
13 changes: 12 additions & 1 deletion agent-langgraph/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ToolMessage,
} from "@langchain/core/messages";
import {
AUTONOMOUS_WORKFLOW_GUIDANCE,
COMPUTER_GUIDANCE,
NO_ANSWER_CAME,
ROUTINE_GUIDANCE,
Expand All @@ -28,8 +29,18 @@ export { NO_ANSWER_CAME };

/** Translate the conversation AG-UI carries into LangChain's message classes. */
export function toLangChainMessages(input: RunAgentInput): BaseMessage[] {
const workflowAlreadySupplied = input.messages.some(
(message) =>
(message.role === "system" || message.role === "developer") &&
String(message.content ?? "").includes(AUTONOMOUS_WORKFLOW_GUIDANCE),
);
const systemGuidance = [
COMPUTER_GUIDANCE,
ROUTINE_GUIDANCE,
...(workflowAlreadySupplied ? [] : [AUTONOMOUS_WORKFLOW_GUIDANCE]),
].join("\n\n");
const messages: BaseMessage[] = [
new SystemMessage(`${COMPUTER_GUIDANCE}\n\n${ROUTINE_GUIDANCE}`),
new SystemMessage(systemGuidance),
// AG-UI carries application context separately from conversation history. CopilotKit puts
// the A2UI catalog and tool instructions here; dropping it leaves the model guessing the
// component schema and can strand the renderer on an invalid, never-painted surface.
Expand Down
29 changes: 29 additions & 0 deletions agent-langgraph/tests/history.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import type { RunAgentInput } from "@ag-ui/core";
import { AUTONOMOUS_WORKFLOW_GUIDANCE } from "../../shared/bot-prompt";
import {
AIMessage,
SystemMessage,
Expand All @@ -16,6 +17,34 @@ test("includes durable Routine behavior in the framework Bot system guidance", (
);
});

test("includes NOTE 21-2 autonomous workflow boundaries in the LangGraph adapter", () => {
const messages = toLangChainMessages(input([]));
const system = messages.find((message) => message instanceof SystemMessage);
expect(String(system?.content)).toContain("projectId + sceneId");
expect(String(system?.content)).toContain(
"An approved prompt is an execution input, not something to grade",
);
expect(String(system?.content)).toContain(
"Never claim you will automatically wake",
);
});

test("does not duplicate NOTE 21-2 when OpenBot already supplied it in the standing role", () => {
const messages = toLangChainMessages(
input([
{
id: "standing-workflow",
role: "system",
content: `Creative role.\n\n${AUTONOMOUS_WORKFLOW_GUIDANCE}`,
} as never,
]),
);
const occurrences = messages.filter((message) =>
String(message.content ?? "").includes(AUTONOMOUS_WORKFLOW_GUIDANCE),
);
expect(occurrences).toHaveLength(1);
});

/**
* A tool call nobody answered does not end the conversation.
*
Expand Down
8 changes: 8 additions & 0 deletions server/src/copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "rxjs";
import { z } from "zod";
import {
AUTONOMOUS_WORKFLOW_GUIDANCE,
COMPUTER_GUIDANCE,
PROVENANCE_GUIDANCE,
} from "../../shared/bot-prompt";
Expand Down Expand Up @@ -179,6 +180,7 @@ export function standingRoleMessage(
* "Investigate policies, transaction monitoring, and control evidence."
*/
PROVENANCE_GUIDANCE,
AUTONOMOUS_WORKFLOW_GUIDANCE,
].join("\n\n"),
};
}
Expand Down Expand Up @@ -416,6 +418,12 @@ export function builtInAgentConfiguration(
* it says comes from its own knowledge, and saying so is the only honest move available.
*/
PROVENANCE_GUIDANCE,
/*
* Shared execution discipline for long autonomous work. It is conditional in its own wording:
* ordinary tasks remain ordinary tasks, while multi-scene workflows get stable state and exact
* prompt execution regardless of which tools happen to be granted on this turn.
*/
AUTONOMOUS_WORKFLOW_GUIDANCE,
...(grantedToolGuidance(tools, connectedVendors)
? [grantedToolGuidance(tools, connectedVendors)]
: []),
Expand Down
52 changes: 47 additions & 5 deletions server/tests/copilot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { HttpAgent } from "@ag-ui/client";
import { LLMock } from "@copilotkit/aimock";
import { BuiltInAgent } from "@copilotkit/runtime/v2";
import { EMPTY } from "rxjs";
import { PROVENANCE_GUIDANCE } from "../../shared/bot-prompt";
import {
AUTONOMOUS_WORKFLOW_GUIDANCE,
PROVENANCE_GUIDANCE,
} from "../../shared/bot-prompt";
import { MAX_INLINED_BYTES_PER_RUN } from "../src/channels/attachment-parts";
import { loadConfig } from "../src/config";
import type { LoadAttachment } from "../src/copilot";
Expand Down Expand Up @@ -244,7 +247,7 @@ describe("registered Copilot agents", () => {
model: "openai/gpt-5.6-terra",
// The provenance rule is unconditional, so even a Bot with no tools and no computer carries
// it. That Bot needs it most: nothing it says was read anywhere.
prompt: `Be helpful.\n\n${PROVENANCE_GUIDANCE}`,
prompt: `Be helpful.\n\n${PROVENANCE_GUIDANCE}\n\n${AUTONOMOUS_WORKFLOW_GUIDANCE}`,
apiKey: "openai-secret",
});
});
Expand Down Expand Up @@ -631,6 +634,7 @@ describe("standing agent roles", () => {
// travel in it or the Bot never hears it. Referenced rather than restated, so the assertion
// stays exact without pinning the wording twice.
PROVENANCE_GUIDANCE,
AUTONOMOUS_WORKFLOW_GUIDANCE,
].join("\n\n"),
});
});
Expand Down Expand Up @@ -832,6 +836,7 @@ describe("standing agent roles", () => {
"Reconcile corporate card statements.",
"This standing role applies in every channel. Treat channel messages as task-specific instructions within it.",
PROVENANCE_GUIDANCE,
AUTONOMOUS_WORKFLOW_GUIDANCE,
].join("\n\n"),
);
});
Expand Down Expand Up @@ -1092,6 +1097,41 @@ describe("what a Bot is told it holds", () => {
* So both paths are asserted, because they are built by different functions and a fix to one is not
* a fix to the other.
*/
describe("NOTE 21-2 autonomous workflow guidance reaches every coworker path", () => {
test("a built-in coworker receives the scene isolation and exact-prompt contract", () => {
const prompt = builtInAgentConfiguration(
{
id: "creator",
name: "Creator",
type: "built_in",
systemPrompt: "Produce campaign assets.",
},
{ provider: "openai", defaultModel: "gpt-5.6-terra" },
"openai-secret",
).prompt as string;

expect(prompt).toContain(AUTONOMOUS_WORKFLOW_GUIDANCE);
expect(prompt).toContain("projectId + sceneId");
expect(prompt).toContain(
"An approved prompt is an execution input, not something to grade",
);
});

test("a remote coworker receives the same contract in its standing role", () => {
const content = standingRoleMessage({
id: "creator",
name: "Creator",
title: "Creative Production",
roleDescription: "Produce campaign assets using the selected services.",
}).content;

expect(content).toContain(AUTONOMOUS_WORKFLOW_GUIDANCE);
expect(content).toContain(
"Never claim you will automatically wake, monitor in the background or receive a completion event",
);
});
});

describe("where a Bot says its answer came from", () => {
test("a built-in Bot carries the rule even holding nothing at all", () => {
// The Bot that needs it most. No tools and no computer means nothing it says was read anywhere.
Expand Down Expand Up @@ -1226,9 +1266,11 @@ describe("a person's standing instructions", () => {
const prompt = promptWith(instructions as string | null);

expect(prompt).not.toContain("standing instructions");
// Byte for byte what a deployment had before any of this existed, which is what most people
// on most days get.
expect(prompt).toBe(`Be helpful.\n\n${PROVENANCE_GUIDANCE}`);
// NOTE 21-2 is deployment-wide guidance, independent of whether this person has written
// standing instructions of their own.
expect(prompt).toBe(
`Be helpful.\n\n${PROVENANCE_GUIDANCE}\n\n${AUTONOMOUS_WORKFLOW_GUIDANCE}`,
);
},
);

Expand Down
77 changes: 76 additions & 1 deletion shared/bot-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { describe, expect, test } from "bun:test";
import { COMPUTER_GUIDANCE, ROUTINE_GUIDANCE } from "./bot-prompt";
import {
AUTONOMOUS_WORKFLOW_GUIDANCE,
COMPUTER_GUIDANCE,
ROUTINE_GUIDANCE,
} from "./bot-prompt";

describe("COMPUTER_GUIDANCE", () => {
test("keeps paragraph breaks as blank lines instead of collapsing them into spaces", () => {
Expand Down Expand Up @@ -66,3 +70,74 @@ describe("ROUTINE_GUIDANCE", () => {
);
});
});

describe("AUTONOMOUS_WORKFLOW_GUIDANCE", () => {
test("keeps the Agent goal-driven instead of hard-coding every task into one pipeline", () => {
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"operate from the person's goal",
);
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"Do not turn this guidance into a fixed pipeline for unrelated tasks",
);
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"Do not silently substitute a different service, account, custom chatbot, model, or order of operations",
);
});

test("keeps every scene's prompt references and output isolated by project and scene", () => {
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain("projectId + sceneId");
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"Never mix, borrow, recycle or overwrite another scene's prompt, reference image or output",
);
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"never put passwords, session cookies, API keys or other secrets into the checkpoint",
);
});

test("executes approved prompts exactly instead of spending turns grading them", () => {
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"An approved prompt is an execution input, not something to grade",
);
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"Submit the exact approved prompt and reference assets for that scene",
);
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"Do not turn execution verification into another prompt-review loop",
);
});

test("pins the NOTE 21-2 service handoff without mixing scene assets", () => {
for (const evidence of [
"script-writing chatbot",
"prompt-writing chatbot",
"ChatGPT/custom GPT",
"send only that scene's reference image and exact scene prompt to Flow",
]) {
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(evidence);
}
});

test("does not fake background wakeups or busy-loop while generation is pending", () => {
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"not a reason to resubmit the scene or burn model turns in a refresh-and-reason loop",
);
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"Never claim you will automatically wake, monitor in the background or receive a completion event",
);
});

test("pins the NOTE 21-2 planning budget without pretending usage metering exists", () => {
for (const evidence of [
"1,000–5,000",
"15,000–40,000",
"50,000–100,000",
"1,000,000",
"when usage accounting is available",
]) {
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(evidence);
}
expect(AUTONOMOUS_WORKFLOW_GUIDANCE).toContain(
"stop and ask how they want to proceed rather than silently overspending",
);
});
});
42 changes: 42 additions & 0 deletions shared/bot-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,48 @@ export const ROUTINE_GUIDANCE = ROUTINE_GUIDANCE_LINES.reduce<string[]>(
[""],
).join("\n\n");

/**
* How a Bot carries out a long, multi-step creative workflow without turning it into a hard-coded
* pipeline.
*
* NOTE 21-2 is deliberately guidance rather than a new workflow engine. The person gives a goal and
* the Bot remains responsible for planning, choosing its available browser/tools and adapting to
* what actually happens. When the person supplies an explicit service sequence, however, that
* sequence is part of the task and must not be silently replaced by a different one.
*/
const AUTONOMOUS_WORKFLOW_GUIDANCE_LINES = [
"For substantial multi-step work, operate from the person's goal rather than waiting for them to prescribe every click. Make a concise plan, use the tools and Computer you actually have, observe real results, and adapt the next step when the service state changes. Do not turn this guidance into a fixed pipeline for unrelated tasks.",
"When the person gives an explicit workflow or names the services to use, preserve that workflow unless a real blocker makes a change necessary. Do not silently substitute a different service, account, custom chatbot, model, or order of operations.",
"",
"For multi-scene image or video work, each project and scene is an isolated unit of state. Keep a stable projectId and sceneId and bind that scene's script slice, prompt, source/reference inputs, generated reference image and generated output to the same projectId + sceneId. Never mix, borrow, recycle or overwrite another scene's prompt, reference image or output merely because the files look similar.",
"Checkpoint long work in your persistent workspace when it may span turns. Use paths or records that name the projectId and sceneId, record the current stage and the artifact identities you actually observed, and never put passwords, session cookies, API keys or other secrets into the checkpoint.",
"",
"An approved prompt is an execution input, not something to grade. Do not spend model turns scoring, critiquing, rewriting or repeatedly 'improving' prompt quality. Submit the exact approved prompt and reference assets for that scene unless the service refuses them, the inputs are technically unusable, or the person explicitly changes them. If a real blocker forces a change, say what blocked the exact input before changing it.",
"Verify execution state, not creative taste: confirm the intended service/account is open, the correct projectId + sceneId inputs were submitted, generation really started, it completed or failed, and the resulting artifact belongs to that same scene. Do not turn execution verification into another prompt-review loop.",
"",
"When the person's selected NOTE 21-2 workflow uses their linked custom chatbots, keep the roles separate: use the script-writing chatbot for the script; give the script plus the person's project images/product context to the prompt-writing chatbot; for each scene use that scene's prompt and references with the selected ChatGPT/custom GPT to produce that scene's reference image; then send only that scene's reference image and exact scene prompt to Flow for generation. Advance scene by scene from observed results, never by assuming the previous website action succeeded.",
"Use the exact custom-chatbot links/accounts the person supplied when they are available through the Computer. Never ask for passwords, MFA codes or session tokens in chat; sign-in, MFA, security keys, device approval and CAPTCHA remain human-handoff steps under the Computer rules.",
"",
"A website showing 'generating', 'queued' or an equivalent in-progress state is not a reason to resubmit the scene or burn model turns in a refresh-and-reason loop. Preserve the scene state and leave the browser session intact. Continue only after a real later trigger lets you observe progress again: a person's next turn, a durable scheduled/event mechanism that was actually created, or another genuine run. Never claim you will automatically wake, monitor in the background or receive a completion event when no such durable mechanism exists.",
"Treat model/provider budgets as finite. Avoid duplicate generations, repeated speculative reasoning and unnecessary re-reading of unchanged state. Plan an ordinary reasoning/wake cycle around roughly 1,000–5,000 model tokens and an ordinary 3–5-scene video around roughly 15,000–40,000 total model tokens. Retries or blocked services can raise a difficult job toward roughly 50,000–100,000 tokens, but that is a warning range, not a target. Treat 1,000,000 model tokens per Bot per day as a hard planning ceiling: when usage accounting is available, do not intentionally plan work beyond it; if the remaining work would exceed the person's stated or known limit, stop and ask how they want to proceed rather than silently overspending.",
];

export const AUTONOMOUS_WORKFLOW_GUIDANCE =
AUTONOMOUS_WORKFLOW_GUIDANCE_LINES.reduce<string[]>(
(paragraphs, line) => {
if (line === "") {
paragraphs.push("");
return paragraphs;
}
const last = paragraphs.length - 1;
paragraphs[last] = paragraphs[last]
? `${paragraphs[last]} ${line}`
: line;
return paragraphs;
},
[""],
).join("\n\n");

/**
* What a tool call is given when its answer never came.
*
Expand Down
Loading