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
32 changes: 32 additions & 0 deletions src/agent-harness/answer-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,38 @@ describe("composeAnswerContext", () => {
})
})

it("puts profile and lessons before knowledge and memory", async () => {
const message = await composeAnswerContext({
ledger: {
...makeLedger(),
retrievalCount: 0,
chunks: [],
evidence: [],
},
memoryItems: [],
profile: { occupation: "教师" },
agentFeedbackLessons: [
{
itemId: "fb_1",
text: "查毛利率 引用错了",
reflection: "下次先核对引用",
},
],
userText: "再查一次",
})

const text = userContentParts(message)
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n")
expect(text.indexOf("## User Profile")).toBeLessThan(
text.indexOf("## Lessons from Past Mistakes"),
)
expect(text).toContain("occupation: 教师")
expect(text).toContain("下次先核对引用")
expect(text).toContain("## User's Question")
})

it("omits knowledge and memory sections when neither search ran", async () => {
const message = await composeAnswerContext({
ledger: {
Expand Down
41 changes: 41 additions & 0 deletions src/agent-harness/answer-context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { ModelMessage } from "ai"

import type {
AgentFeedbackLesson,
EvidenceLedgerSnapshot,
EvidencePart,
MemorySearchItem,
WorkspaceProfile,
} from "./types"

type UserContentParts = Exclude<
Expand All @@ -16,9 +18,26 @@ export async function composeAnswerContext(input: {
readonly memoryItems: readonly MemorySearchItem[]
readonly memorySearchAttempted?: boolean
readonly userText: string
readonly profile?: WorkspaceProfile | null
readonly agentFeedbackLessons?: readonly AgentFeedbackLesson[]
}): Promise<ModelMessage> {
const content: UserContentParts = []

const profileText = formatProfile(input.profile)
if (profileText) {
appendText(content, `## User Profile\n${profileText}`)
}

if (input.agentFeedbackLessons && input.agentFeedbackLessons.length > 0) {
appendText(
content,
[
"## Lessons from Past Mistakes",
...input.agentFeedbackLessons.map(formatLesson),
].join("\n\n"),
)
}

if (input.ledger.evidence.length === 0 && input.ledger.retrievalCount > 0) {
appendText(
content,
Expand Down Expand Up @@ -93,6 +112,28 @@ function appendRawText(content: UserContentParts, text: string): void {
content.push({ type: "text", text })
}

function formatProfile(profile: WorkspaceProfile | null | undefined): string | null {
if (!profile) return null
const lines = (
[
["name", profile.name],
["occupation", profile.occupation],
["ageStage", profile.ageStage],
["communicationHabit", profile.communicationHabit],
["workHabit", profile.workHabit],
] as const
).flatMap(([key, value]) =>
value && value.length > 0 ? [`${key}: ${value}`] : [],
)
return lines.length > 0 ? lines.join("\n") : null
}

function formatLesson(lesson: AgentFeedbackLesson): string {
return [lesson.text.trim(), lesson.reflection.trim()]
.filter((part) => part.length > 0)
.join("\n")
}

function formatMemoryItem(item: MemorySearchItem): string {
return [
`[memory ref="${item.ref}" kind="${item.kind}"]`,
Expand Down
7 changes: 7 additions & 0 deletions src/agent-harness/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { createEvidenceLedger } from "./ledger"
import { knowhereToolText } from "./knowhere-text"
import { memoryToolText } from "./memory-text"
import type {
AgentFeedbackLesson,
AgentTurn,
AgentTurnInput,
ContextPolicy,
Expand All @@ -32,6 +33,7 @@ import type {
OutputCitation,
OutputManifest,
ResolveConnectedAssets,
WorkspaceProfile,
} from "./types"
import { memorySearchKinds } from "./types"

Expand All @@ -49,6 +51,8 @@ export type RunAgentHarnessInput = {
readonly memoryTools: MemoryToolRuntime
readonly resolveConnectedAssets?: ResolveConnectedAssets
readonly maxSteps?: number
readonly profile?: WorkspaceProfile | null
readonly agentFeedbackLessons?: readonly AgentFeedbackLesson[]
}

type HarnessToolState = {
Expand Down Expand Up @@ -220,6 +224,8 @@ export async function runAgentHarness(
memoryItems: state.memoryItems ?? [],
memorySearchAttempted: state.memorySearchAttempted === true,
userText: input.turn.userText,
profile: input.profile,
agentFeedbackLessons: input.agentFeedbackLessons,
})
await ledger.resolveConnectedAssets(input.resolveConnectedAssets)
}
Expand Down Expand Up @@ -250,6 +256,7 @@ export async function runAgentHarness(
const ledgerSnapshot = ledger.snapshot()
return {
manifest,
memoryItems: state.memoryItems ?? [],
trace: {
intent: state.intent,
contextPolicy: state.contextPolicy,
Expand Down
15 changes: 15 additions & 0 deletions src/agent-harness/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ export type MemoryToolRuntime = {
readonly search: (input: MemorySearchRequest) => Promise<MemorySearchResponse>
}

export type WorkspaceProfile = {
readonly name?: string
readonly occupation?: string
readonly ageStage?: string
readonly communicationHabit?: string
readonly workHabit?: string
}

export type AgentFeedbackLesson = {
readonly itemId: string
readonly text: string
readonly reflection: string
}

export type MemoryCitation = {
readonly ref: string
readonly itemId: string
Expand Down Expand Up @@ -316,4 +330,5 @@ export type HarnessTrace = {
export type HarnessRunResult = {
readonly manifest: OutputManifest
readonly trace: HarnessTrace
readonly memoryItems: readonly MemorySearchItem[]
}
Loading
Loading