Skip to content
Merged
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
40 changes: 24 additions & 16 deletions packages/junior/src/chat/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ function formatConfigurationLines(
}

const HEADER =
"You are a Slack-based helper assistant. Follow the personality block for voice and tone in every reply. The behavior and output blocks define platform mechanics and override personality only when those mechanics conflict.";
"You are a Slack-based helper assistant. Follow the personality section for voice and tone in every reply. Platform mechanics and output rules override personality and world context when they conflict.";

const TURN_CONTEXT_HEADER =
"Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
Expand Down Expand Up @@ -425,21 +425,31 @@ function buildOutputSection(): string {
}

function buildIdentitySection(): string {
return renderTagBlock(
"identity",
`Your Slack username is \`${escapeXml(botConfig.userName)}\`.`,
);
return [
"# Identity",
`Your Slack username is \`${botConfig.userName}\`.`,
].join("\n");
}

function buildPersonalitySection(): string {
return ["# Personality", JUNIOR_PERSONALITY.trim()].join("\n");
}

function buildWorldSection(): string | null {
if (!JUNIOR_WORLD) {
return null;
}

return ["# World", JUNIOR_WORLD.trim()].join("\n");
}

function buildRuntimeSection(params: {
conversationId?: string;
traceId?: string;
}): string | null {
const lines = [
params.conversationId
? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}`
: "",
params.traceId ? `- trace_id: ${escapeXml(params.traceId)}` : "",
].filter(Boolean);

if (lines.length === 0) {
Expand All @@ -457,10 +467,6 @@ function buildContextSection(params: {
}): string | null {
const blocks: string[][] = [];

if (JUNIOR_WORLD) {
blocks.push(renderTag("world", [JUNIOR_WORLD.trim()]));
}

const referenceLines = formatReferenceFilesLines();
if (referenceLines) {
blocks.push(
Expand Down Expand Up @@ -543,7 +549,7 @@ function buildCapabilitiesSection(params: {
return null;
}

return renderTagBlock("capabilities", blocks.join("\n\n"));
return blocks.join("\n\n");
}

type TurnContextPromptInput = {
Expand All @@ -553,7 +559,6 @@ type TurnContextPromptInput = {
toolGuidance?: ToolPromptContext[];
runtime?: {
conversationId?: string;
traceId?: string;
};
invocation: SkillInvocation | null;
requester?: {
Expand All @@ -568,10 +573,13 @@ type TurnContextPromptInput = {
const STATIC_SYSTEM_PROMPT = [
HEADER,
buildIdentitySection(),
renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
renderTagBlock("behavior", buildBehaviorSection()),
buildPersonalitySection(),
buildWorldSection(),
buildBehaviorSection(),
buildOutputSection(),
].join("\n\n");
]
.filter((section): section is string => Boolean(section))
.join("\n\n");

/** Return byte-stable platform instructions shared by every conversation and turn. */
export function buildSystemPrompt(): string {
Expand Down
2 changes: 0 additions & 2 deletions packages/junior/src/chat/respond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { botConfig } from "@/chat/config";
import {
extractGenAiUsageAttributes,
extractGenAiUsageSummary,
getActiveTraceId,
logException,
logInfo,
logWarn,
Expand Down Expand Up @@ -971,7 +970,6 @@ export async function generateAssistantReply(
toolGuidance,
runtime: {
conversationId: spanContext.conversationId,
traceId: getActiveTraceId(),
},
invocation: skillInvocation,
requester: context.requester,
Expand Down
1 change: 0 additions & 1 deletion packages/junior/tests/unit/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ describe("prompt builders", () => {
},
runtime: {
conversationId: "conversation-alpha",
traceId: "trace-alpha",
},
toolGuidance: [
{
Expand Down
22 changes: 14 additions & 8 deletions specs/agent-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Metadata

- Created: 2026-04-28
- Last Edited: 2026-05-30
- Last Edited: 2026-06-01

## Purpose

Expand All @@ -29,14 +29,15 @@ Define the canonical contract for Junior's platform-owned agent prompt so prompt

- The core prompt owns platform behavior: tool-use policy, execution bias, context boundaries, Slack output shape, and failure reporting expectations.
- `SOUL.md` and other deployment-authored personality files are voice-only. Platform behavior must still work if those files are empty or heavily customized.
- `WORLD.md` is trusted deployment-stable world context. It belongs in the static system prompt, not per-turn context, and must not carry load-bearing platform mechanics.
- Skill files own domain-specific workflow mechanics. They must not duplicate generic harness behavior such as "use tools before answering" or "ask only when blocked."
- The core prompt must not name or describe specific installed plugins, plugin providers, plugin-owned config keys, plugin-owned default targets, plugin-owned tools, or plugin-specific workflows. That knowledge belongs to dynamic capabilities.

### Section boundaries

`buildSystemPrompt()` must be static: no parameters, no requester/thread/session/runtime/model/provider/catalog data, and no content that can vary between conversations or turns. Deployment-stable assistant identity, such as the bot Slack username, belongs here. This is required for provider prompt-prefix caching and for consistent multi-turn behavior.
`buildSystemPrompt()` must be static: no parameters, no requester/thread/session/runtime/model/provider/catalog data, and no content that can vary between conversations or turns. Deployment-stable assistant identity, such as the bot Slack username, and deployment-stable world context from `WORLD.md` belong here. This is required for provider prompt-prefix caching and for consistent multi-turn behavior.

`buildTurnContextPrompt(...)` owns session bootstrap prompt context. It is attached to the first model-visible user message in a Pi session projection, including requester identity, available capabilities, configuration, artifacts, runtime identifiers, and resumed-turn context. Completed turns may store that bootstrap context as part of durable Pi history so later follow-up messages can avoid duplicating it. Compaction replacement history must omit stale bootstrap context; the next user turn after a projection reset receives fresh bootstrap context exactly once.
`buildTurnContextPrompt(...)` owns session bootstrap prompt context. It is attached to the first model-visible user message in a Pi session projection, including requester identity, available capabilities, configuration, artifacts, model-actionable runtime identifiers, and resumed-turn context. Completed turns may store that bootstrap context as part of durable Pi history so later follow-up messages can avoid duplicating it. Compaction replacement history must omit stale bootstrap context; the next user turn after a projection reset receives fresh bootstrap context exactly once.

Turn context may disclose dynamic capability surfaces that the model can act on, such as available skill names/descriptions, active MCP catalog summaries, and tool guidance attached to the current native tool set. It must not separately disclose plugin ownership or installed plugin/provider catalogs as prompt knowledge. If the model needs plugin-specific behavior, that behavior must arrive through the loaded skill body, tool description, tool schema, `promptSnippet`, or `promptGuidelines`.

Expand All @@ -45,15 +46,18 @@ Turn context is not a session-state cache. If prior tool use, loaded skills, MCP
The combined prompt surface must keep these concerns distinct:

1. Identity/personality.
2. Core operating rules.
3. Slack output contract.
4. Available and loaded capabilities.
5. Runtime and thread context.
2. Deployment-stable world context.
3. Core operating rules.
4. Slack output contract.
5. Available and loaded capabilities.
6. Runtime and thread context.

Context blocks describe facts. Behavior and output blocks carry instructions.
Context blocks describe facts. Operating-rule and output sections carry instructions.

Prompt order is part of the contract. Stable, high-priority operating rules live in the system prompt. Volatile requester, artifacts, active catalogs, configuration defaults, runtime metadata, and resume state must stay out of the system prompt and live in session bootstrap context.

Trusted deployment-authored Markdown files, such as `SOUL.md` and `WORLD.md`, should render as Markdown sections rather than raw XML payloads. XML-style tags are appropriate for generated runtime blocks whose dynamic values are escaped. Do not add generic wrapper markers that only repeat child-section meaning, such as wrapping all operating rule sections in an additional behavior tag or wrapping all capability blocks in an additional capabilities tag.

Session bootstrap context is injected on the first model-visible user message in each Pi session. Ordinary follow-up messages in the same session must not duplicate that bootstrap context. When compaction creates a replacement projection, the replacement history must omit the old bootstrap context; the next user turn starts a new Pi session projection and receives fresh bootstrap context exactly once.

The agent session log contract is defined in `./agent-session-resumability.md`. Prompt code must treat that log as the source of durable model history and must not introduce an alternate prompt-side history, provider catalog, loaded-skill list, or resume-state channel.
Expand Down Expand Up @@ -115,6 +119,8 @@ The tool policy must make sandbox workspace ownership explicit: sandbox-backed f

Runtime facts should live in a compact runtime block inside session bootstrap context. Include only facts that help the model choose valid behavior, such as runtime version, model ids, selected thinking level, channel capabilities, and sandbox workspace root. Do not mix requester, artifacts, or configuration defaults into that runtime block.

Runtime tracing and logging correlation identifiers, such as `trace_id`, are observability data, not model-actionable context, and must not be included in prompt runtime context.

The safety section must stay generic and runtime-level: remain within the user's request, respect stop/pause/audit/approval boundaries, avoid access expansion, and avoid administrative prompt/tool/security/config changes unless explicitly requested and supported by an available tool.

### Bloat controls
Expand Down
Loading