refactor(agent): rename agent/request-messages to agent/request-advice
tianyicui's review: the seam name did not say what the event or its types do. 'advice' reads both ways — advisory content for the model, and AOP before/after advice woven around a join point (here the derived history) without modifying it — so RequestAdvice.before/after are self-describing. Types follow: RequestAdvice / RequestAdviceContext; the logged EpochHeader fields keep their positional names (messagePrefix/messageSuffix). Also sharpens the core.md wording the review flagged as ambiguous: before-advice sits in front of the ENTIRE derived history, directly after the system slot (the conventional home for session-stable openers — an AGENTS.md digest, a skills catalog), after-advice follows the history's last message. Catalogs and doc graphs regenerated.
This commit is contained in:
@@ -59,7 +59,7 @@ forever:
|
||||
boundary = session.deriveMessages() ⟵ reconstruction boundary: same sync frame,
|
||||
session('step/start') strictly before step/start
|
||||
config = waterfall agent/request ⟵ frozen seed; return a replacement to switch
|
||||
reqMsgs = waterfall agent/request-messages ⟵ request-only before/after messages; recorded
|
||||
reqMsgs = waterfall agent/request-advice ⟵ request-only before/after messages; recorded
|
||||
on the header, never session history
|
||||
session('request/header'[-delta]) ⟵ the header event this request owes the log
|
||||
stream llm.stream(freeze({header..., messages: before+boundary+after})) → session('assistant/chunk')
|
||||
@@ -86,7 +86,7 @@ Cancellation: `agent.cancel()` is the single public stop primitive — it clears
|
||||
### What is NOT here
|
||||
|
||||
Everything that goes beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy:
|
||||
- Hooks: `agent/session-start`, `agent/prompt-submit`, `agent/pre-step`, `agent/request`, `agent/request-messages`, `agent/step-result`, `tools/pre-execute`, `tools/post-execute`, `agent/turn-continuation`
|
||||
- Hooks: `agent/session-start`, `agent/prompt-submit`, `agent/pre-step`, `agent/request`, `agent/request-advice`, `agent/step-result`, `tools/pre-execute`, `tools/post-execute`, `agent/turn-continuation`
|
||||
- Compaction: `agent/pre-step`
|
||||
- Sandbox, permission, plan mode: `tools/pre-execute` (deny/ask gate), `tools/post-execute`
|
||||
- Sub-agents: implemented outside the loop as `ctx.subagents` providers; in-process providers use `ctx.agents.create()` and owned `AgentHandle` teardown, while child streaming/progress and background/poll collection remain deferred.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import type { Context } from 'cordis'
|
||||
import type { FinishReason, GenerateOptions, LlmCallConfig, Message } from '@deepseek-ai/dsh-llm'
|
||||
import { BlockAssembler, HarnessError, deepFreeze } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContinuationDecision, HookContext, PromptDecision, RequestMessages } from '@deepseek-ai/dsh-agent'
|
||||
import type { ContinuationDecision, HookContext, PromptDecision, RequestAdvice } from '@deepseek-ai/dsh-agent'
|
||||
import { canonicalHeader } from '@deepseek-ai/dsh-session'
|
||||
import type { Session, TurnEndReason, TurnTrigger } from '@deepseek-ai/dsh-session'
|
||||
import { createTransmissionLog, recordRequestHeader } from './request-log.ts'
|
||||
@@ -161,7 +161,7 @@ export interface LoopHandle {
|
||||
* boundary = session.deriveMessages() ⟵ the reconstruction boundary: snapshot in the
|
||||
* session('step/start') same sync frame, strictly before step/start
|
||||
* config = waterfall agent/request(config) ⟵ frozen seed; a returned replacement switches
|
||||
* reqMsgs = waterfall agent/request-messages ⟵ request-only before/after messages; logged on
|
||||
* advice = waterfall agent/request-advice ⟵ request-only before/after advice; logged on
|
||||
* the header, never session history
|
||||
* session('request/header'|'request/header-delta') ⟵ the header event this request owes the
|
||||
* log (initial/resume anchor, delta, fallback)
|
||||
@@ -720,32 +720,35 @@ async function runStep(
|
||||
throw new Error(`agent "${agent.id}" has no model: set AgentOptions.model or supply one via the agent/request waterfall`)
|
||||
}
|
||||
|
||||
// Collect request-ONLY messages: `before` contributions precede the boundary
|
||||
// snapshot in the request, `after` contributions follow it. They are not
|
||||
// session history — the header event below is their only durable record
|
||||
// (EpochHeader.messagePrefix/messageSuffix), which keeps the request a pure
|
||||
// function of the log. The frozen empty seed serves both the listener chain
|
||||
// and the no-listener fallback: a contribution is a RETURNED extension of
|
||||
// `await next()`, never an in-place push. Fired AFTER the boundary snapshot,
|
||||
// so a listener's session append lands past the boundary and joins the NEXT
|
||||
// Collect the request-ONLY advice: `before` messages go in front of the
|
||||
// entire boundary snapshot, `after` messages follow its last message. Advice
|
||||
// is not session history — the header event below is its only durable
|
||||
// record (EpochHeader.messagePrefix/messageSuffix), which keeps the request
|
||||
// a pure function of the log. The frozen empty seed serves both the
|
||||
// listener chain and the no-listener fallback: a contribution is a RETURNED
|
||||
// extension of `await next()`, never an in-place push. The context gets a
|
||||
// frozen COPY of the boundary (the request is built from the internal
|
||||
// snapshot), so a listener cannot smuggle unlogged content into the request
|
||||
// by mutating what it was shown. Fired AFTER the boundary snapshot, so a
|
||||
// listener's session append lands past the boundary and joins the NEXT
|
||||
// request — the same window rule as the `agent/request` waterfall.
|
||||
const emptyRequestMessages: RequestMessages = deepFreeze({ before: [], after: [] })
|
||||
const requestMessagesBoundary = deepFreeze([...boundaryMessages])
|
||||
const requestMessages = await ctx.waterfall(
|
||||
'agent/request-messages', agent, turn, step, emptyRequestMessages,
|
||||
{ system, assembly, boundaryMessages: requestMessagesBoundary, signal },
|
||||
() => Promise.resolve(emptyRequestMessages),
|
||||
const emptyRequestAdvice: RequestAdvice = deepFreeze({ before: [], after: [] })
|
||||
const requestAdviceBoundary = deepFreeze([...boundaryMessages])
|
||||
const requestAdvice = await ctx.waterfall(
|
||||
'agent/request-advice', agent, turn, step, emptyRequestAdvice,
|
||||
{ system, assembly, boundaryMessages: requestAdviceBoundary, signal },
|
||||
() => Promise.resolve(emptyRequestAdvice),
|
||||
)
|
||||
|
||||
// The request header (the log's request/header* vocabulary): canonical form,
|
||||
// recorded before dispatch so the log always explains the request —
|
||||
// including the request-only messages, which no other event carries.
|
||||
// including the request-only advice, which no other event carries.
|
||||
const header = canonicalHeader({
|
||||
config,
|
||||
...system ? { system } : {},
|
||||
...assembly.tools.length > 0 ? { tools: assembly.tools } : {},
|
||||
...requestMessages.before.length > 0 ? { messagePrefix: requestMessages.before } : {},
|
||||
...requestMessages.after.length > 0 ? { messageSuffix: requestMessages.after } : {},
|
||||
...requestAdvice.before.length > 0 ? { messagePrefix: requestAdvice.before } : {},
|
||||
...requestAdvice.after.length > 0 ? { messageSuffix: requestAdvice.after } : {},
|
||||
})
|
||||
recordRequestHeader(session, transmission, header)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import AgentRegistry, {
|
||||
AgentId,
|
||||
type ContinuationDecision,
|
||||
type PromptDecision,
|
||||
type RequestMessages,
|
||||
type RequestAdvice,
|
||||
type SessionStartSource,
|
||||
} from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
@@ -311,7 +311,7 @@ describe('agent/session-start', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('agent/request-messages (RequestMessages)', () => {
|
||||
describe('agent/request-advice (RequestAdvice)', () => {
|
||||
it('frames the derived history: before precedes it, after follows it, and the header records both', async () => {
|
||||
const adapter = new MockAdapter([textResponse('ok')])
|
||||
const ctx = await harness(adapter)
|
||||
@@ -319,7 +319,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
|
||||
const reminder: Message = { role: 'user', content: [{ type: 'text', text: '<system-reminder>catalog</system-reminder>' }] }
|
||||
const trailer: Message = { role: 'user', content: [{ type: 'text', text: 'trailing note' }] }
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, _messages, _context, next): Promise<RequestMessages> => {
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, _messages, _context, next): Promise<RequestAdvice> => {
|
||||
const result = await next()
|
||||
return { before: [...result.before, reminder], after: [...result.after, trailer] }
|
||||
})
|
||||
@@ -351,7 +351,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
const agent = ctx.agentLoop.create(AgentId('a1'), { model: 'mock' })
|
||||
|
||||
const seen: { system: string; boundaryRoles: string[]; sectionCount: number }[] = []
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, _messages, context, next): Promise<RequestMessages> => {
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, _messages, context, next): Promise<RequestAdvice> => {
|
||||
const result = await next()
|
||||
seen.push({
|
||||
system: context.system,
|
||||
@@ -360,7 +360,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
})
|
||||
return { before: [{ role: 'user', content: [{ type: 'text', text: 'first' }] }, ...result.before], after: result.after }
|
||||
})
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, _messages, _context, next): Promise<RequestMessages> => {
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, _messages, _context, next): Promise<RequestAdvice> => {
|
||||
const result = await next()
|
||||
return { before: [...result.before, { role: 'user', content: [{ type: 'text', text: 'second' }] }], after: result.after }
|
||||
})
|
||||
@@ -385,7 +385,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
const agent = ctx.agentLoop.create(AgentId('a1'), { model: 'mock' })
|
||||
|
||||
// A listener that delegates without contributing — the canonical no-op.
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, _messages, _context, next) => next())
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, _messages, _context, next) => next())
|
||||
|
||||
send(agent, 'hi')
|
||||
await waitForIdle(ctx, agent)
|
||||
@@ -402,7 +402,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
const agent = ctx.agentLoop.create(AgentId('a1'), { model: 'mock' })
|
||||
|
||||
let mutationError: unknown
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, messages, _context, next): Promise<RequestMessages> => {
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, messages, _context, next): Promise<RequestAdvice> => {
|
||||
try {
|
||||
messages.before.push({ role: 'user', content: [{ type: 'text', text: 'smuggled' }] })
|
||||
} catch (error: unknown) {
|
||||
@@ -424,7 +424,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
const agent = ctx.agentLoop.create(AgentId('a1'), { model: 'mock' })
|
||||
|
||||
let mutationError: unknown
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, _messages, context, next): Promise<RequestMessages> => {
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, _messages, context, next): Promise<RequestAdvice> => {
|
||||
try {
|
||||
const mutableBoundary = context.boundaryMessages as Message[]
|
||||
mutableBoundary.push({ role: 'user', content: [{ type: 'text', text: 'smuggled' }] })
|
||||
@@ -454,7 +454,7 @@ describe('agent/request-messages (RequestMessages)', () => {
|
||||
const agent = ctx.agentLoop.create(AgentId('a1'), { model: 'mock' })
|
||||
|
||||
let step = 0
|
||||
ctx.on('agent/request-messages', async (_agent, _turn, _step, _messages, _context, next): Promise<RequestMessages> => {
|
||||
ctx.on('agent/request-advice', async (_agent, _turn, _step, _messages, _context, next): Promise<RequestAdvice> => {
|
||||
const result = await next()
|
||||
step += 1
|
||||
return { before: [...result.before, { role: 'user', content: [{ type: 'text', text: `reminder v${step}` }] }], after: result.after }
|
||||
|
||||
Reference in New Issue
Block a user