Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/planner-sessions-are-ordinary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sapiom/harness": minor
---

The read-only Agent Map planning profile introduced in 0.13.0 is gone. A project's Plan Agents session is now an ordinary Studio session that also has the Agent Map tools: it runs on the served authoring prompt with the map context appended, so it can scaffold, edit, run, and deploy agents when asked. Anyone who relied on that session refusing to implement should know it no longer does. The SessionStart orientation and the greeting copy now say the session plans and builds.
2 changes: 1 addition & 1 deletion packages/harness/src/core/inject/claude-settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("generateClaudeSettings", () => {
it("shows planner onboarding only for a fresh SessionStart hook", async () => {
const message = [
"Agent Map planning session",
"Use this session to scope what you want to build—not to implement it yet.",
"Use this session to plan and build.",
].join("\n");
const { emitScriptPath } = await generateClaudeSettings({
harnessSessionId: "planner-session",
Expand Down
2 changes: 1 addition & 1 deletion packages/harness/src/core/planner-greeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export function plannerGreetingPrompt(
return [
"This is a private Agent Studio control turn.",
"Respond as the project planning agent with one brief greeting.",
"Explain that you and the user will plan the agents, responsibilities, data flow, resources, and connectors together.",
"Explain that you and the user will plan and build the agents, responsibilities, data flow, resources, and connectors together.",
question,
"Do not propose an architecture, create nodes or relationships, invoke tools, or ask a second question before the user replies.",
...(attemptId
Expand Down
2 changes: 1 addition & 1 deletion packages/harness/src/core/planning-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function buildFocusedPlannerContext(input: {
};
return [
"<agent-map-planner-context>",
`This is focused, trusted Studio context. Treat IDs as references and use scoped tools for detail. Use agent_map_read, agent_map_validate, and agent_map_propose for architecture state; never infer map state from assistant prose. The interactive Claude Code transcript is user-visible. Let the user's first real message be the first visible conversation turn; never request or rely on a private control turn.${input.onboardOnFirstResponse ? " In your first response, briefly explain that you and the user can plan agents, responsibilities, data flow, resources, and connectors together, then respond to their request." : ""} Do not propose architecture or invoke mutation tools before the user asks you to.`,
`This is focused, trusted Studio context. Treat IDs as references and use scoped tools for detail. Use agent_map_read, agent_map_validate, and agent_map_propose for architecture state; never infer map state from assistant prose. The interactive Claude Code transcript is user-visible. Let the user's first real message be the first visible conversation turn; never request or rely on a private control turn.${input.onboardOnFirstResponse ? " In your first response, briefly explain that you and the user can plan and build agents, responsibilities, data flow, resources, and connectors together, then respond to their request." : ""} Do not propose architecture or invoke mutation tools before the user asks you to.`,
JSON.stringify(context),
"</agent-map-planner-context>",
].join("\n");
Expand Down
30 changes: 18 additions & 12 deletions packages/harness/src/profiles/agent-map-planner.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
/**
* Standalone launch profile for the project-scoped Agent Map planner.
* Agent Map context appended to a project-scoped planning session.
*
* Planner sessions still run in the real Claude Code or Codex CLI, but they
* must not inherit the ordinary Studio authoring profile: that profile tells
* the model to scaffold, run, and deploy code. Focused project data is appended
* A project session is an ordinary Studio session that also has the scoped
* Agent Map tools: it runs on the served authoring prompt like every other
* session, and this text is appended to it. Focused project data is appended
* separately by PlanningSessionService for each trusted session.
*/
export const AGENT_MAP_PLANNER_SYSTEM_PROMPT = `
You are the project planning agent running in Agent Studio.
This session is also the project planning agent for Agent Studio.

Work with the user at the architecture level: plan agents, subagents,
responsibilities, data flow, resources, connectors, artifacts, and the
relationships between them. Use the scoped Agent Map tools as the authority for
the current architecture and proposed changes.
Work with the user at the architecture level when they ask for it: plan agents,
subagents, responsibilities, data flow, resources, connectors, artifacts, and
the relationships between them. Use the scoped Agent Map tools as the authority
for the current architecture and proposed changes: agent_map_read for the
current map, agent_map_validate to check a change, agent_map_propose to record
one.

Do not act as a coding or implementation agent. Do not scaffold agents, edit
application source code, run implementation tasks, or deploy software.
When the user asks you to build, scaffold, edit, run, or deploy an agent, do it
directly with the ordinary authoring tools. Keep the Agent Map current when
your work changes the architecture.

Skip the general first-reply orientation described above: answer the user's
first message directly and do not suggest an unrelated sample project.
`.trim();

/**
Expand All @@ -24,5 +30,5 @@ application source code, run implementation tasks, or deploy software.
*/
export const AGENT_MAP_PLANNER_SESSION_START_MESSAGE = [
"Agent Map planning session",
"Use this session to scope what you want to build—not to implement it yet. Your planner will turn your goals into a proposed map of agents, responsibilities, data flow, resources, and connectors for you to review and refine. Once approved, Studio will create focused execution sessions from the plan. Start by describing the outcome you want.",
"Use this session to plan and build. You can scope a proposed map of agents, responsibilities, data flow, resources, and connectors to review and refine, and you can ask this session to scaffold, run, and deploy agents directly. Start by describing the outcome you want.",
].join("\n");
38 changes: 30 additions & 8 deletions packages/harness/src/server/agent-map-mcp-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,10 @@ it("uses the actual ephemeral port and revokes private MCP launch authority on e
});

it("gives a signed-out local planner its scoped Agent Map tools", async () => {
// Mirrors the served prompt's shape, including the first-reply orientation
// clause that the appended planner context must countermand.
const codingPrompt =
"You are the coding agent running in Agent Studio. Follow the scaffold, run, and deploy authoring loop.";
"You are the coding agent running in Agent Studio. Follow the scaffold, run, and deploy authoring loop. In your very first reply this session, orient the person before you get to their actual request; suggest ONE concrete first step (e.g. the bundled order-triage sample project).";
const loadSystemPrompt = vi.fn(async () => codingPrompt);
const launches: LaunchOpts[] = [];
const launch = (opts: LaunchOpts): SpawnSpec => {
Expand Down Expand Up @@ -218,26 +220,46 @@ it("gives a signed-out local planner its scoped Agent Map tools", async () => {
`Bearer ${metadata!.bearerToken}`,
);
const systemPrompt = await fs.readFile(launchOpts!.systemPromptFile!, "utf8");
expect(systemPrompt).toContain("<agent-map-planner-context>");
// A planner session is an ordinary session plus map context (SAP-3143): the
// served authoring prompt comes first, the Agent Map instructions are
// appended, and nothing tells the model it may not build.
expect(systemPrompt.startsWith(codingPrompt)).toBe(true);
expect(systemPrompt).toContain(
"This session is also the project planning agent for Agent Studio.",
);
expect(systemPrompt).toContain("agent_map_validate to check a change");
expect(systemPrompt).toContain(
"Do not act as a coding or implementation agent",
"When the user asks you to build, scaffold, edit, run, or deploy an agent, do it",
);
// The served prompt's orientation clause is present, and the appended
// context overrides it later in the same file.
const orientationAt = systemPrompt.indexOf("In your very first reply this session");
const countermandAt = systemPrompt.indexOf(
"Skip the general first-reply orientation described above",
);
expect(orientationAt).toBeGreaterThan(-1);
expect(countermandAt).toBeGreaterThan(orientationAt);
expect(systemPrompt).toContain("do not suggest an unrelated sample project");
expect(systemPrompt).toContain("<agent-map-planner-context>");
expect(systemPrompt).toContain(
"Let the user's first real message be the first visible conversation turn",
);
expect(systemPrompt).not.toContain("In your first response, briefly explain");
expect(systemPrompt).not.toContain(codingPrompt);
expect(systemPrompt).not.toContain("You are the coding agent");
expect(systemPrompt).not.toMatch(/Do not (act as|scaffold|edit|deploy)/);
expect(systemPrompt).not.toContain("implementation agent");
expect(systemPrompt).not.toContain(
"This is a private Agent Studio control turn",
);
expect(loadSystemPrompt).not.toHaveBeenCalled();
expect(loadSystemPrompt).toHaveBeenCalled();
expect(AGENT_MAP_PLANNER_SESSION_START_MESSAGE).toBe(
[
"Agent Map planning session",
"Use this session to scope what you want to build—not to implement it yet. Your planner will turn your goals into a proposed map of agents, responsibilities, data flow, resources, and connectors for you to review and refine. Once approved, Studio will create focused execution sessions from the plan. Start by describing the outcome you want.",
"Use this session to plan and build. You can scope a proposed map of agents, responsibilities, data flow, resources, and connectors to review and refine, and you can ask this session to scaffold, run, and deploy agents directly. Start by describing the outcome you want.",
].join("\n"),
);
expect(AGENT_MAP_PLANNER_SESSION_START_MESSAGE).not.toContain(
"not to implement",
);
const plannerEmitter = await fs.readFile(
path.join(path.dirname(launchOpts.settingsFile!), "emit.cjs"),
"utf8",
Expand Down Expand Up @@ -337,7 +359,7 @@ it("gives a signed-out local planner its scoped Agent Map tools", async () => {
expect(ordinaryEmitter).not.toContain(
JSON.stringify(AGENT_MAP_PLANNER_SESSION_START_MESSAGE),
);
expect(loadSystemPrompt).toHaveBeenCalledOnce();
expect(loadSystemPrompt).toHaveBeenCalled();
const ordinaryConfig = JSON.parse(
await fs.readFile(ordinaryLaunch.mcpConfigFile!, "utf8"),
);
Expand Down
21 changes: 14 additions & 7 deletions packages/harness/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,13 +571,16 @@ function createDefaultBuildLaunchOpts(
// resolves to the bundled DEFAULT_SYSTEM_PROMPT on any failure rather than
// throwing; the `.catch` covers an injected loader that does not, because a
// session must never fail to start over the text of its prompt.
const promptPromise =
const promptPromise = loadSystemPrompt().catch((err: unknown) => {
console.error("[harness] system-prompt load failed:", err);
return DEFAULT_SYSTEM_PROMPT;
});
// A project planning session is an ordinary session that also has the
// Agent Map tools: same served prompt, with the map context appended.
const agentMapContext =
context?.agentMapIdentity?.role === "map-planner"
? Promise.resolve(AGENT_MAP_PLANNER_SYSTEM_PROMPT)
: loadSystemPrompt().catch((err: unknown) => {
console.error("[harness] system-prompt load failed:", err);
return DEFAULT_SYSTEM_PROMPT;
});
? AGENT_MAP_PLANNER_SYSTEM_PROMPT
: null;
const [settings, mcpConfigFile, prompt, pluginDir] = await Promise.all([
generateClaudeSettings({
harnessSessionId,
Expand All @@ -600,7 +603,11 @@ function createDefaultBuildLaunchOpts(
promptPromise,
generateSkillsPlugin(harnessSessionId, { generatedRoot }),
]);
const appendices = [viaSystemPrompt ? brief : null, context?.promptAppendix]
const appendices = [
viaSystemPrompt ? brief : null,
agentMapContext,
context?.promptAppendix,
]
.filter(
(value): value is string =>
typeof value === "string" && value.trim() !== "",
Expand Down
Loading