@stigmer/runner 3.20.0 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/execution-record-fixture.d.ts +7 -0
  3. package/dist/__test-utils__/execution-record-fixture.js +10 -1
  4. package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
  5. package/dist/__test-utils__/hermetic-activity.d.ts +7 -4
  6. package/dist/__test-utils__/hermetic-activity.js +12 -2
  7. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  8. package/dist/__test-utils__/turn-input-fixture.js +0 -1
  9. package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
  10. package/dist/activities/execute-cursor/prompt-builder.js +8 -3
  11. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  12. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +1 -3
  13. package/dist/activities/execute-deep-agent/prompt-builder.js +2 -3
  14. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  15. package/dist/activities/generate-session-subject.d.ts +2 -1
  16. package/dist/activities/generate-session-subject.js +18 -10
  17. package/dist/activities/generate-session-subject.js.map +1 -1
  18. package/dist/harness/turn-context.js +3 -1
  19. package/dist/harness/turn-context.js.map +1 -1
  20. package/dist/shared/blueprint-resolver.d.ts +14 -4
  21. package/dist/shared/blueprint-resolver.js +23 -10
  22. package/dist/shared/blueprint-resolver.js.map +1 -1
  23. package/dist/shared/builtin-assistant-prompt.d.ts +30 -0
  24. package/dist/shared/builtin-assistant-prompt.js +36 -0
  25. package/dist/shared/builtin-assistant-prompt.js.map +1 -0
  26. package/package.json +5 -5
  27. package/src/__test-utils__/execution-record-fixture.ts +17 -1
  28. package/src/__test-utils__/hermetic-activity.ts +19 -6
  29. package/src/__test-utils__/turn-input-fixture.ts +0 -1
  30. package/src/activities/__tests__/generate-session-subject.test.ts +14 -0
  31. package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.no-instructions.prompt.md +142 -0
  32. package/src/activities/execute-cursor/__tests__/hermetic/built-in-assistant.test.ts +127 -0
  33. package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +14 -1
  34. package/src/activities/execute-cursor/prompt-builder.ts +8 -3
  35. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +2 -1
  36. package/src/activities/execute-deep-agent/__tests__/hermetic/built-in-assistant.test.ts +101 -0
  37. package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +1 -1
  38. package/src/activities/execute-deep-agent/prompt-builder.ts +3 -5
  39. package/src/activities/generate-session-subject.ts +20 -13
  40. package/src/harness/turn-context.ts +3 -1
  41. package/src/shared/__tests__/blueprint-resolver.test.ts +78 -8
  42. package/src/shared/blueprint-resolver.ts +25 -12
  43. package/src/shared/builtin-assistant-prompt.ts +38 -0
@@ -48,6 +48,7 @@ import {
48
48
  type StandingSectionKind,
49
49
  type VisionPromptInfo,
50
50
  } from "../../shared/prompt-sections.js";
51
+ import { effectiveInstructions } from "../../shared/builtin-assistant-prompt.js";
51
52
  import { formatTurnRecoveryText } from "./turn-recovery.js";
52
53
  import type { SkillMetadata } from "../../shared/skill-resolver.js";
53
54
  import type { AgentResolution, AgentResolutionReason } from "./session-lifecycle.js";
@@ -201,9 +202,10 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
201
202
  sections.push(implementPlan);
202
203
  }
203
204
 
204
- if (options.instructions) {
205
- sections.push(formatInstructions(options.instructions));
206
- }
205
+ // The blueprint's instructions, or the built-in assistant's when it
206
+ // carries none (a session with no agent, or an agent that left them
207
+ // empty) — the one text both harnesses read on this arm.
208
+ sections.push(formatInstructions(effectiveInstructions(options.instructions)));
207
209
 
208
210
  if (options.skills.length > 0) {
209
211
  sections.push(formatSkillsSection(options.skills));
@@ -241,6 +243,9 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
241
243
  sections.push(formatReferencedFiles(options.workspaceFileRefs));
242
244
  }
243
245
 
246
+ // The engine-facing house rules ride along when the blueprint itself
247
+ // said nothing about how to respond; an author's instructions are taken
248
+ // to have covered them.
244
249
  if (!options.instructions) {
245
250
  sections.push(formatResponseRules());
246
251
  }
@@ -1,4 +1,5 @@
1
- You are a helpful AI assistant.
1
+ You are Stigmer's assistant. Help with whatever the person brings: answer, reason, write and edit, and use the tools and skills attached to this conversation when they help.
2
+ Be direct and concrete. Say what you do not know. Ask when a request is ambiguous. Prefer changing what exists over creating anew.
2
3
 
3
4
  ## Response rules
4
5
 
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Hermetic pin: the BUILT-IN ASSISTANT through the whole `ExecuteDeepAgent`
3
+ * activity — a session that names no agent instance, so the blueprint chain
4
+ * stops at the session (`shared/blueprint-resolver.ts`), the control plane
5
+ * is never asked for an instance or an agent, and the system prompt opens
6
+ * with the one built-in text (`shared/builtin-assistant-prompt.ts`).
7
+ *
8
+ * Invariant pinned: the agent-less record is a first-class turn, not an
9
+ * error arm — IN_PROGRESS then COMPLETED with the scripted answer, the same
10
+ * shape as `plain-turn.test.ts`; and the words the model saw are the shared
11
+ * ones, read off the scripted model's `systemPrompt` context. The status
12
+ * itself is not goldened here: nothing agent-shaped reaches the status, so
13
+ * the plain-turn golden already photographs it.
14
+ */
15
+
16
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
17
+ import { ExecutionPhase, MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
18
+
19
+ vi.mock("../../../../shared/model-client.js", async () =>
20
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
21
+ );
22
+ vi.mock("../../../../client/stigmer-client.js", async () =>
23
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
24
+ );
25
+
26
+ import {
27
+ ScriptedClock,
28
+ createHermeticEnvironment,
29
+ type HermeticEnvironment,
30
+ } from "../../../../__test-utils__/hermetic-activity.js";
31
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
32
+ import { BUILT_IN_ASSISTANT_INSTRUCTIONS } from "../../../../shared/builtin-assistant-prompt.js";
33
+ import {
34
+ beginDeepAgentScenario,
35
+ deepAgentExecutionRecord,
36
+ runDeepAgentTurn,
37
+ } from "../../__test-utils__/hermetic-deep-agent.js";
38
+
39
+ const USER_MESSAGE = "Say hello.";
40
+ const ASSISTANT_TEXT = "Hello from the built-in assistant.";
41
+
42
+ describe("ExecuteDeepAgent hermetic — the built-in assistant", () => {
43
+ let env: HermeticEnvironment;
44
+ let registry: ReturnType<typeof stubRegistryFetch>;
45
+ const clock = new ScriptedClock();
46
+
47
+ beforeAll(() => {
48
+ env = createHermeticEnvironment();
49
+ registry = stubRegistryFetch();
50
+ clock.install();
51
+ });
52
+
53
+ afterAll(() => {
54
+ clock.uninstall();
55
+ registry.restore();
56
+ env.dispose();
57
+ });
58
+
59
+ it("runs a session with no agent to COMPLETED on the built-in prompt, never reading an instance or an agent", async () => {
60
+ clock.reset();
61
+ const record = deepAgentExecutionRecord({ message: USER_MESSAGE, builtInAssistant: true });
62
+ const systemPrompts: string[] = [];
63
+ const instanceRead = vi.fn(async () => {
64
+ throw new Error("must not be reached");
65
+ });
66
+ const agentRead = vi.fn(async () => {
67
+ throw new Error("must not be reached");
68
+ });
69
+ const scenario = beginDeepAgentScenario({
70
+ env,
71
+ clock,
72
+ record,
73
+ clientOverrides: { getAgentInstance: instanceRead, getAgent: agentRead },
74
+ script: (_tools, context) => {
75
+ systemPrompts.push(context.systemPrompt);
76
+ return { turns: [{ text: ASSISTANT_TEXT, usage: { inputTokens: 900, outputTokens: 20 } }] };
77
+ },
78
+ });
79
+
80
+ const invocation = await runDeepAgentTurn(scenario);
81
+
82
+ expect(invocation.outcome.kind).toBe("returned");
83
+ expect(record.persistedPhases).toEqual([
84
+ ExecutionPhase.EXECUTION_IN_PROGRESS,
85
+ ExecutionPhase.EXECUTION_COMPLETED,
86
+ ]);
87
+ const final = record.lastFullStatus!;
88
+ const ai = final.messages.filter((m) => m.type === MessageType.MESSAGE_AI);
89
+ expect(ai).toHaveLength(1);
90
+ expect(ai[0].content).toBe(ASSISTANT_TEXT);
91
+
92
+ // The chain stopped at the session: the refusing reads were never
93
+ // reached (they would have failed the resolve phase).
94
+ expect(instanceRead).not.toHaveBeenCalled();
95
+ expect(agentRead).not.toHaveBeenCalled();
96
+
97
+ // The model met the shared words, first.
98
+ expect(systemPrompts).toHaveLength(1);
99
+ expect(systemPrompts[0].startsWith(BUILT_IN_ASSISTANT_INSTRUCTIONS)).toBe(true);
100
+ });
101
+ });
@@ -20,7 +20,7 @@
20
20
  * the filter fires, channel templates, referenced files, three input files
21
21
  * with a rename and a download URL, the vision disclosure, all five standing
22
22
  * sections); the below-threshold skills branch; plan mode; build-from-plan;
23
- * and the minimal prompt (no instructions, so `DEFAULT_INSTRUCTIONS`). Plan
23
+ * and the minimal prompt (no instructions, so the built-in assistant's). Plan
24
24
  * mode and build-from-plan are mutually exclusive in production, so each has
25
25
  * its own golden.
26
26
  *
@@ -40,6 +40,7 @@ import {
40
40
  type StandingSectionKind,
41
41
  type VisionPromptInfo,
42
42
  } from "../../shared/prompt-sections.js";
43
+ import { effectiveInstructions } from "../../shared/builtin-assistant-prompt.js";
43
44
  import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
44
45
  import {
45
46
  buildImplementPlanDirective,
@@ -115,11 +116,8 @@ directly over delegating. Only delegate when context isolation \
115
116
  or parallelism genuinely helps the user.
116
117
  `;
117
118
 
118
- /** The agent's instructions when the blueprint carries none; the prompt is never empty (since #1096: was `setup.ts`'s alone). */
119
- export const DEFAULT_INSTRUCTIONS = "You are a helpful AI assistant.";
120
-
121
119
  export interface PromptBuilderInput {
122
- /** The blueprint's instructions, raw; empty falls back to {@link DEFAULT_INSTRUCTIONS}. */
120
+ /** The blueprint's instructions, raw; empty reads the built-in assistant's (shared/builtin-assistant-prompt.ts). */
123
121
  instructions: string;
124
122
  provisionResults: ProvisionResult[];
125
123
  containerRoot: string;
@@ -233,7 +231,7 @@ const STANDING_SECTION_HEADINGS: Record<StandingSectionKind, string> = {
233
231
  * sections. Pure function with no I/O.
234
232
  */
235
233
  export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
236
- let prompt = input.instructions || DEFAULT_INSTRUCTIONS;
234
+ let prompt = effectiveInstructions(input.instructions);
237
235
 
238
236
  const workspaceSection = buildWorkspacePromptSection(
239
237
  input.provisionResults,
@@ -137,22 +137,26 @@ export async function generateSessionSubject(
137
137
  return;
138
138
  }
139
139
 
140
+ // The agent, when the conversation has one, lends the title its purpose;
141
+ // the built-in assistant (no agent on the session) is titled from the
142
+ // message alone rather than left "Untitled".
143
+ let agentName = "";
144
+ let agentDescription = "";
140
145
  const agentId = await resolveAgentId(execution, session, client);
141
- if (agentId === "") {
142
- log(`cannot resolve agent_id for execution ${executionId}, skipping`);
143
- return;
146
+ if (agentId !== "") {
147
+ const agent = await getOrSkip(
148
+ () => client.getAgent(agentId),
149
+ `agent not found: ${agentId}`,
150
+ );
151
+ if (agent === undefined) return;
152
+ agentName = agent.metadata?.name ?? "";
153
+ agentDescription = agent.spec?.description ?? "";
144
154
  }
145
155
 
146
- const agent = await getOrSkip(
147
- () => client.getAgent(agentId),
148
- `agent not found: ${agentId}`,
149
- );
150
- if (agent === undefined) return;
151
-
152
156
  const subject = await generateTitle({
153
157
  userMessage,
154
- agentName: agent.metadata?.name ?? "",
155
- agentDescription: agent.spec?.description ?? "",
158
+ agentName,
159
+ agentDescription,
156
160
  executionId,
157
161
  options,
158
162
  });
@@ -175,7 +179,8 @@ export async function generateSessionSubject(
175
179
  /**
176
180
  * The agent behind the execution: the direct agent_id when the execution
177
181
  * carries one, else resolved through the session's agent-instance chain.
178
- * Empty string when unresolvable. Mirrors the cloud activity's resolveAgentId.
182
+ * Empty string when there is none (the built-in assistant) or when the
183
+ * instance cannot be read.
179
184
  */
180
185
  export async function resolveAgentId(
181
186
  execution: AgentExecution,
@@ -264,7 +269,9 @@ export function buildUserPrompt(
264
269
  agentDescription: string,
265
270
  ): string {
266
271
  let prompt = `User's first message:\n"${userMessage}"\n\n`;
267
- prompt += `Agent: ${agentName}\n`;
272
+ if (agentName !== "") {
273
+ prompt += `Agent: ${agentName}\n`;
274
+ }
268
275
  if (agentDescription !== "") {
269
276
  prompt += `Agent purpose: ${agentDescription}\n`;
270
277
  }
@@ -759,7 +759,9 @@ export async function resolveMcpServersAndPolicies(
759
759
  execution.spec?.recalledMemories,
760
760
  {
761
761
  org: session.metadata?.org ?? "",
762
- agentId: blueprint.agent.metadata?.id ?? "",
762
+ // Provenance, not scope: empty for the built-in assistant, and the
763
+ // carrier omits an empty field (memory-attachment.ts).
764
+ agentId: blueprint.agent?.metadata?.id ?? "",
763
765
  sessionId,
764
766
  agentExecutionId: executionId,
765
767
  },
@@ -1,17 +1,87 @@
1
1
  /**
2
- * `shared/blueprint-resolver.ts` `mergeSkillRefs`: the one merge of the
2
+ * `shared/blueprint-resolver.ts`: `mergeSkillRefs`, the one merge of the
3
3
  * agent's and the session's skill refs the turn runtime resolves from
4
- * (`resolveAgentBlueprint`). Union, deduplicated by slug, the session's ref
5
- * winning a collision (it may pin a different version).
4
+ * (`resolveBlueprint`) — union, deduplicated by slug, the session's ref
5
+ * winning a collision (it may pin a different version); and
6
+ * `resolveBlueprint`'s two arms: the agent chain walked when the session
7
+ * names an instance, and the built-in assistant when it does not — no
8
+ * instance or agent read, empty instructions, no sub-agents, and the
9
+ * session's own usages and refs as the whole tool set.
6
10
  *
7
- * Carried from `skill-writer.test.ts` in #1096, when the native
8
- * orchestrator's byte-twin of this function was deleted with its module;
9
- * until then the runtime's copy was tested only through the twin.
11
+ * `mergeSkillRefs` carried from `skill-writer.test.ts` in #1096, when the
12
+ * native orchestrator's byte-twin of this function was deleted with its
13
+ * module; until then the runtime's copy was tested only through the twin.
10
14
  */
11
15
 
12
- import { describe, it, expect } from "vitest";
16
+ import { describe, it, expect, vi } from "vitest";
17
+ import { create } from "@bufbuild/protobuf";
18
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
19
+ import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
20
+ import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
13
21
  import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
14
- import { mergeSkillRefs } from "../blueprint-resolver.js";
22
+ import { mockStigmerClient } from "../../__test-utils__/mock-client.js";
23
+ import { mergeSkillRefs, resolveBlueprint } from "../blueprint-resolver.js";
24
+
25
+ describe("resolveBlueprint", () => {
26
+ it("walks session -> instance -> agent and merges with the session's tools", async () => {
27
+ const client = mockStigmerClient({
28
+ getAgentInstance: vi.fn().mockResolvedValue(
29
+ create(AgentInstanceSchema, { metadata: { id: "agi_1" }, spec: { agentId: "agt_1" } }),
30
+ ),
31
+ getAgent: vi.fn().mockResolvedValue(
32
+ create(AgentSchema, {
33
+ metadata: { id: "agt_1", name: "builder" },
34
+ spec: {
35
+ instructions: "You build things.",
36
+ mcpServerUsages: [{ mcpServerRef: { org: "acme", slug: "github" } }],
37
+ skillRefs: [{ org: "acme", slug: "release" }],
38
+ },
39
+ }),
40
+ ),
41
+ });
42
+ const session = create(SessionSchema, {
43
+ metadata: { id: "ses_1", org: "acme" },
44
+ spec: {
45
+ agentInstanceId: "agi_1",
46
+ mcpServerUsages: [{ mcpServerRef: { org: "acme", slug: "notes" } }],
47
+ },
48
+ });
49
+
50
+ const blueprint = await resolveBlueprint(client, session);
51
+
52
+ expect(blueprint.agent?.metadata?.id).toBe("agt_1");
53
+ expect(blueprint.instructions).toBe("You build things.");
54
+ expect(blueprint.mergedMcpServerUsages.map((u) => u.mcpServerRef?.slug).sort()).toEqual(["github", "notes"]);
55
+ expect(blueprint.mergedSkillRefs.map((r) => r.slug)).toEqual(["release"]);
56
+ expect(client.getAgentInstance).toHaveBeenCalledWith("agi_1");
57
+ expect(client.getAgent).toHaveBeenCalledWith("agt_1");
58
+ });
59
+
60
+ it("answers the built-in assistant for a session with no instance: no agent read, the session's tools alone", async () => {
61
+ const client = mockStigmerClient({
62
+ getAgentInstance: vi.fn().mockRejectedValue(new Error("must not be reached")),
63
+ getAgent: vi.fn().mockRejectedValue(new Error("must not be reached")),
64
+ });
65
+ const session = create(SessionSchema, {
66
+ metadata: { id: "ses_2", org: "acme" },
67
+ spec: {
68
+ agentInstanceId: "",
69
+ mcpServerUsages: [{ mcpServerRef: { org: "acme", slug: "notes" } }],
70
+ skillRefs: [{ org: "acme", slug: "writing" }],
71
+ },
72
+ });
73
+
74
+ const blueprint = await resolveBlueprint(client, session);
75
+
76
+ expect(blueprint.agent).toBeUndefined();
77
+ expect(blueprint.instructions).toBe("");
78
+ expect(blueprint.subAgents).toEqual([]);
79
+ expect(blueprint.mergedMcpServerUsages.map((u) => u.mcpServerRef?.slug)).toEqual(["notes"]);
80
+ expect(blueprint.mergedSkillRefs.map((r) => r.slug)).toEqual(["writing"]);
81
+ expect(client.getAgentInstance).not.toHaveBeenCalled();
82
+ expect(client.getAgent).not.toHaveBeenCalled();
83
+ });
84
+ });
15
85
 
16
86
  function makeRef(slug: string, org = "test-org"): ApiResourceReference {
17
87
  return { slug, org, $typeName: "ai.stigmer.commons.apiresource.ApiResourceReference" } as ApiResourceReference;
@@ -6,6 +6,14 @@
6
6
  * mcp_server_usages. Also merges agent-level and session-level MCP usages
7
7
  * and skill refs following the same merge semantics.
8
8
  *
9
+ * A session with an EMPTY agent_instance_id is the built-in assistant
10
+ * (session/v1/spec.proto): the chain stops at the session, `agent` is
11
+ * undefined, the instructions are empty (each harness's prompt builder
12
+ * substitutes the one built-in prompt, shared/builtin-assistant-prompt.ts),
13
+ * there are no sub-agents, and the session's own MCP usages and skill refs
14
+ * are the whole tool set — the same merge with an empty agent side, so no
15
+ * consumer downstream needs an agent-less arm of its own.
16
+ *
9
17
  * Workspace isolation: resolved workspace directories are validated to
10
18
  * ensure they never point at the runner's own app directory. Paths
11
19
  * containing runner-internal markers are rejected with a warning.
@@ -32,8 +40,8 @@ export interface CloudRepo {
32
40
  }
33
41
 
34
42
  export interface ResolvedBlueprint {
35
- agent: Agent;
36
- agentSpec: AgentSpec;
43
+ /** The agent the session is bound to; undefined for the built-in assistant. */
44
+ agent: Agent | undefined;
37
45
  session: Session;
38
46
  sessionSpec: SessionSpec;
39
47
  instructions: string;
@@ -47,7 +55,9 @@ export interface ResolvedBlueprint {
47
55
  * Resolve the full agent blueprint from the execution chain.
48
56
  *
49
57
  * Chain: execution.spec.sessionId -> session.spec.agentInstanceId ->
50
- * agentInstance.spec.agentId -> agent (with full spec)
58
+ * agentInstance.spec.agentId -> agent (with full spec); an empty
59
+ * agent_instance_id ends the chain at the session (the built-in
60
+ * assistant).
51
61
  */
52
62
  export async function resolveBlueprint(
53
63
  client: StigmerClient,
@@ -55,18 +65,22 @@ export async function resolveBlueprint(
55
65
  ): Promise<ResolvedBlueprint> {
56
66
  const sessionSpec = session.spec!;
57
67
 
58
- const agentInstance = await client.getAgentInstance(sessionSpec.agentInstanceId);
59
- const agentId = agentInstance.spec!.agentId;
60
- const agent = await client.getAgent(agentId);
61
- const agentSpec = agent.spec!;
68
+ let agent: Agent | undefined;
69
+ let agentSpec: AgentSpec | undefined;
70
+ if (sessionSpec.agentInstanceId !== "") {
71
+ const agentInstance = await client.getAgentInstance(sessionSpec.agentInstanceId);
72
+ const agentId = agentInstance.spec!.agentId;
73
+ agent = await client.getAgent(agentId);
74
+ agentSpec = agent.spec!;
75
+ }
62
76
 
63
77
  const mergedMcpServerUsages = mergeMcpServerUsages(
64
- agentSpec.mcpServerUsages,
78
+ agentSpec?.mcpServerUsages ?? [],
65
79
  sessionSpec.mcpServerUsages,
66
80
  );
67
81
 
68
82
  const mergedSkillRefs = mergeSkillRefs(
69
- agentSpec.skillRefs,
83
+ agentSpec?.skillRefs ?? [],
70
84
  sessionSpec.skillRefs,
71
85
  );
72
86
 
@@ -74,11 +88,10 @@ export async function resolveBlueprint(
74
88
 
75
89
  return {
76
90
  agent,
77
- agentSpec,
78
91
  session,
79
92
  sessionSpec,
80
- instructions: agentSpec.instructions,
81
- subAgents: agentSpec.subAgents,
93
+ instructions: agentSpec?.instructions ?? "",
94
+ subAgents: agentSpec?.subAgents ?? [],
82
95
  mergedMcpServerUsages,
83
96
  mergedSkillRefs,
84
97
  cloudRepos,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The built-in assistant's instructions: the ONE text a run reads when its
3
+ * blueprint carries no instructions — because the session names no agent
4
+ * (session/v1/spec.proto, an empty agent_instance_id) or because the agent
5
+ * it names left its instructions empty. Both harnesses' prompt builders
6
+ * substitute these words on that arm and frame them their own way (the
7
+ * `prompt-sections.ts` doctrine: the framing is house style, the words are
8
+ * shared), so a person who picks no agent meets the same assistant on the
9
+ * native and the Cursor harness, and a maintainer changes the assistant in
10
+ * one place.
11
+ *
12
+ * A prompt is code: versioned here, pinned by the prompt goldens of both
13
+ * harnesses (`execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md`,
14
+ * `execute-cursor/__tests__/goldens/*no-instructions*`). There is no stored
15
+ * "default agent" row anywhere whose instructions could drift from these.
16
+ *
17
+ * What the words leave to the builders on purpose: the response rules, the
18
+ * sub-agent rules, the skills protocol and every other section quote an
19
+ * engine's tools and stay per harness. These words say who the assistant
20
+ * is and how it works with a person; nothing here names a tool.
21
+ */
22
+
23
+ /** The display name every surface uses for the built-in assistant. */
24
+ export const BUILT_IN_ASSISTANT_NAME = "Assistant";
25
+
26
+ export const BUILT_IN_ASSISTANT_INSTRUCTIONS = [
27
+ "You are Stigmer's assistant. Help with whatever the person brings: answer, reason, write and edit, and use the tools and skills attached to this conversation when they help.",
28
+ "Be direct and concrete. Say what you do not know. Ask when a request is ambiguous. Prefer changing what exists over creating anew.",
29
+ ].join("\n");
30
+
31
+ /**
32
+ * The instructions a run reads: the blueprint's own when it has any, the
33
+ * built-in assistant's otherwise. Both prompt builders call this on their
34
+ * instructions arm so neither carries a second copy of the fallback.
35
+ */
36
+ export function effectiveInstructions(instructions: string): string {
37
+ return instructions === "" ? BUILT_IN_ASSISTANT_INSTRUCTIONS : instructions;
38
+ }