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
5 changes: 5 additions & 0 deletions .changeset/simplify-meta-instruction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@perstack/runtime": patch
---

Simplify meta instruction and remove duplicate delegate description from system prompt
65 changes: 7 additions & 58 deletions packages/runtime/src/messages/instruction-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,29 @@ import { dedent } from "ts-dedent"

function getMetaInstruction(startedAt: number): string {
return dedent`
IMPORTANT:
Based on the user's initial message, you must determine what needs to be done.
You must iterate through hypothesis and verification to fulfill the task.
YOU MUST CONTINUE TO CALL TOOLS UNTIL THE TASK IS COMPLETE.
If you do not call tools, the task will be considered complete, and the agent loop will end.
Call tools iteratively to complete the user's task.
When the task is complete, or when you cannot help, call attemptCompletion.
Call attemptCompletion ONLY as a tool call — do not include any text response with it.

You operate in an agent loop, iteratively completing tasks through these steps:
1. Analyze Events: Understand user needs and current state through the event stream, focusing on the latest user messages and execution results
2. Select Tools: Choose the next tool call based on current state, task planning, relevant knowledge, and available data APIs
3. Wait for Execution: The selected tool action will be executed by the sandbox environment with new observations added to the event stream
4. Iterate: Choose only one tool call per iteration, patiently repeat the above steps until task completion
5. Notify Task Completion: Call attemptCompletion ONLY - do NOT include any text response with this tool call
6. Generate Final Results: AFTER attemptCompletion returns, you will be prompted to produce a final result in a SEPARATE response

Conditions for ending the agent loop:
If any of the following apply, **immediately call the attemptCompletion tool**.
When the agent loop must end, calling any tool other than attemptCompletion is highly dangerous.
Under all circumstances, strictly follow this rule.
- When the task is complete
- When the user's request is outside your expertise
- When the user's request is unintelligible

Rules for requests outside your area of expertise:
- Tell your area of expertise to the user in final results

Environment information:
- Current time is ${new Date(startedAt).toISOString()}
- Current working directory is ${process.cwd()}
Environment:
- Current time: ${new Date(startedAt).toISOString()}
- Working directory: ${process.cwd()}
`
}

export function createInstructionMessage(
expert: Expert,
experts: Record<string, Expert>,
startedAt: number,
): InstructionMessage {
export function createInstructionMessage(expert: Expert, startedAt: number): InstructionMessage {
const instruction = dedent`
You are Perstack, an AI expert that tackles tasks requested by users by utilizing all available tools.

(The following information describes your nature and role as an AI, the mechanisms of the AI system, and other meta-cognitive aspects.)

${getMetaInstruction(startedAt)}

---
(The following describes the objective, steps, rules, etc. regarding your expert task.)

${expert.instruction}

---
(The following is an overview of each skill and the rules for calling tools.)

${getSkillRules(expert)}

---
(The following is an overview of each delegate expert and the rules for calling tools.)

You can delegate tasks to the following experts by calling delegate expert name as a tool:

${getDelegateRules(expert, experts)}
`
return {
type: "instructionMessage",
Expand Down Expand Up @@ -91,18 +55,3 @@ function getSkillRules(expert: Expert): string {
`.trim()
}, "" as string)
}

function getDelegateRules(expert: Expert, experts: Record<string, Expert>): string {
return expert.delegates.reduce((acc, delegateExpertName) => {
const delegate = experts[delegateExpertName]
if (!delegate) {
return acc
}
return dedent`
${acc}

About "${delegate.name}":
${delegate.description}
`.trim()
}, "" as string)
}
56 changes: 4 additions & 52 deletions packages/runtime/src/messages/message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ describe("@perstack/messages: instruction-message", () => {
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
}
const result = createInstructionMessage(expert, {}, startedAt)
const result = createInstructionMessage(expert, startedAt)
expect(result.type).toBe("instructionMessage")
expect(result.cache).toBe(true)
expect(result.contents[0].type).toBe("textPart")
Expand Down Expand Up @@ -545,7 +545,7 @@ describe("@perstack/messages: instruction-message", () => {
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
}
const result = createInstructionMessage(expert, {}, startedAt)
const result = createInstructionMessage(expert, startedAt)
expect(result.contents[0].text).toContain("Always use this skill carefully.")
expect(result.contents[0].text).toContain('"test-skill" skill rules:')
})
Expand Down Expand Up @@ -573,58 +573,10 @@ describe("@perstack/messages: instruction-message", () => {
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
}
const result = createInstructionMessage(expert, {}, startedAt)
const result = createInstructionMessage(expert, startedAt)
expect(result.contents[0].text).not.toContain('"test-skill" skill rules:')
})

it("includes delegate rules when delegate exists", () => {
const expert = {
key: "test-expert",
name: "Test Expert",
version: "1.0.0",
instruction: "Test instruction",
skills: {},
delegates: ["delegate-expert"],
tags: [],
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
}
const experts = {
"test-expert": expert,
"delegate-expert": {
key: "delegate-expert",
name: "Delegate Expert",
version: "1.0.0",
description: "A delegate expert for testing",
instruction: "Delegate instruction",
skills: {},
delegates: [],
tags: [],
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
},
}
const result = createInstructionMessage(expert, experts, startedAt)
expect(result.contents[0].text).toContain('About "Delegate Expert":')
expect(result.contents[0].text).toContain("A delegate expert for testing")
})

it("skips delegate rules when delegate not found", () => {
const expert = {
key: "test-expert",
name: "Test Expert",
version: "1.0.0",
instruction: "Test instruction",
skills: {},
delegates: ["nonexistent-delegate"],
tags: [],
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
}
const result = createInstructionMessage(expert, {}, startedAt)
expect(result.contents[0].text).not.toContain('About "')
})

it("uses startedAt for timestamp in instruction", () => {
const expert = {
key: "test-expert",
Expand All @@ -637,7 +589,7 @@ describe("@perstack/messages: instruction-message", () => {
runtime: ["local" as const],
minRuntimeVersion: "v1.0" as const,
}
const result = createInstructionMessage(expert, {}, startedAt)
const result = createInstructionMessage(expert, startedAt)
expect(result.contents[0].text).toContain("2023-11-14T22:13:20.000Z")
})
})
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/state-machine/states/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function initLogic({
return startRun(setting, checkpoint, {
initialCheckpoint: checkpoint,
inputMessages: [
createInstructionMessage(expert, experts, setting.startedAt),
createInstructionMessage(expert, setting.startedAt),
createUserMessage([{ type: "textPart", text: setting.input.text }]),
],
})
Expand Down