Skip to content

Commit 474259f

Browse files
committed
feat(agent-workspace): add query trace capability lane
1 parent 451de65 commit 474259f

14 files changed

Lines changed: 288 additions & 1 deletion

docs/brainstorms/2026-04-14-mainline-reality-reconciliation-and-next-direction-requirements.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,18 @@ npm run verify:agent-workspace:tauri
231231
- frontend payload-builder misconception endpoint/body assertions,
232232
- runtime endpoint/payload/message assertions for misconception inspection execution.
233233

234+
#### M6.9 Progress Note (2026-04-14)
235+
236+
- [Done] typed conversation capability surface expanded with `inspect_query_trace` action, `query_knowledge` operation, and `query_trace_card` presentation.
237+
- [Done] conversation capability emission now includes scoped query-trace inspection execution through `/api/knowledge/query`.
238+
- [Done] contract/runtime planners preserve bounded query payload shaping:
239+
- non-empty `query` enforcement,
240+
- bounded `topK`.
241+
- [Done] regression coverage expanded for:
242+
- query-trace capability wiring in conversation output,
243+
- frontend payload-builder query endpoint/body assertions,
244+
- runtime endpoint/payload/message assertions for query-trace execution.
245+
234246
### M7 (Future): Foundation Lane Re-entry (Evidence-First)
235247

236248
Entry criteria:

docs/diataxis/en/explanation/development-progress-dashboard.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,21 @@ Execution anchor:
203203
- frontend misconception payload-builder endpoint/body assertions,
204204
- runtime endpoint/payload/message assertions for misconception execution and rendering.
205205

206+
## Latest Mainline Increment (2026-04-14 M6.9 Query Trace Capability Lane)
207+
208+
- Added `inspect_query_trace` to the typed conversation action union on mainline.
209+
- Extended capability operation/result unions with:
210+
- `query_knowledge`,
211+
- `query_trace_card`.
212+
- Extended `runAgentConversation(...)` capability emission to include query-trace inspection through `/api/knowledge/query`.
213+
- Kept contract/runtime planner behavior aligned for query payload shaping:
214+
- non-empty `query`,
215+
- bounded `topK`.
216+
- Expanded regression coverage for:
217+
- query-trace capability wiring in conversation output,
218+
- frontend query payload-builder endpoint/body assertions,
219+
- runtime endpoint/payload/message assertions for query-trace execution and rendering.
220+
206221
## Mainline vs Working-Branch Snapshot (2026-04-14)
207222

208223
| Capability Slice | Working Branch (`feat/learning-multi-tutor-adapter`) | Mainline (`origin/main`) | Integration Status |

docs/diataxis/zh/explanation/development-progress-dashboard.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,21 @@
203203
- 前端 misconception payload-builder 的 endpoint/body 断言,
204204
- runtime misconception 路径 endpoint/payload/消息断言。
205205

206+
## 主线最新增量(2026-04-14 M6.9 检索追踪能力链路)
207+
208+
- 主线 typed conversation action union 新增 `inspect_query_trace`
209+
- capability operation/result union 同步扩展:
210+
- `query_knowledge`
211+
- `query_trace_card`
212+
- `runAgentConversation(...)` capability 输出新增检索追踪动作,执行路径对接 `/api/knowledge/query`
213+
- contract/runtime payload 规划保持对齐:
214+
- `query` 非空约束,
215+
- `topK` 有界收敛。
216+
- 回归覆盖同步扩展:
217+
- conversation 输出中的 query-trace capability 连线断言,
218+
- 前端 query payload-builder 的 endpoint/body 断言,
219+
- runtime query-trace 路径 endpoint/payload/消息断言。
220+
206221
## 主线 vs 工作分支快照(2026-04-14)
207222

208223
| 能力切片 | 工作分支(`feat/learning-multi-tutor-adapter`| 主线(`origin/main`| 集成状态 |

src/agent_workspace.contract.parity.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('agent_workspace contract parity', () => {
1313
'build_learning_path',
1414
'execute_tutor_action',
1515
'build_study_session',
16+
'query_knowledge',
1617
'query_session_history',
1718
'query_mastery_misconceptions',
1819
'capture_learning_quality_snapshot',
@@ -29,6 +30,7 @@ describe('agent_workspace contract parity', () => {
2930
'learning_path_card',
3031
'tutor_action_card',
3132
'study_session_card',
33+
'query_trace_card',
3234
'session_history_card',
3335
'mastery_misconceptions_card',
3436
'learning_quality_snapshot_card',

src/agent_workspace.frontend.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,31 @@ describe('agent_workspace frontend operation contract', () => {
187187
});
188188
});
189189

190+
test('query_knowledge builds bounded query trace payload', () => {
191+
const requestPlan = buildKnowledgeOperationRequestPayload({
192+
request: {
193+
userId: 'learner-a',
194+
atomId: 'atom-2',
195+
query: 'retrieval stabilization',
196+
topK: 6,
197+
},
198+
execution: {
199+
operationId: 'query_knowledge',
200+
},
201+
});
202+
203+
expect(requestPlan).toEqual({
204+
operationId: 'query_knowledge',
205+
endpoint: '/api/knowledge/query',
206+
method: 'POST',
207+
resultPresentation: 'query_trace_card',
208+
body: {
209+
query: 'retrieval stabilization',
210+
topK: 6,
211+
},
212+
});
213+
});
214+
190215
test('query_session_history builds bounded history payload', () => {
191216
const requestPlan = buildKnowledgeOperationRequestPayload({
192217
request: {
@@ -369,6 +394,7 @@ describe('agent_workspace frontend operation contract', () => {
369394
build_learning_path: 'learning_path_card',
370395
execute_tutor_action: 'assistant_message',
371396
build_study_session: 'study_session_card',
397+
query_knowledge: 'query_trace_card',
372398
query_session_history: 'session_history_card',
373399
query_mastery_misconceptions: 'mastery_misconceptions_card',
374400
capture_learning_quality_snapshot: 'learning_quality_snapshot_card',
@@ -380,6 +406,9 @@ describe('agent_workspace frontend operation contract', () => {
380406
expect(diagnostics.operationAllowedResultPresentations.build_study_session).toEqual(
381407
expect.arrayContaining(['study_session_card'])
382408
);
409+
expect(diagnostics.operationAllowedResultPresentations.query_knowledge).toEqual(
410+
expect.arrayContaining(['query_trace_card'])
411+
);
383412
expect(diagnostics.operationAllowedResultPresentations.query_session_history).toEqual(
384413
expect.arrayContaining(['session_history_card'])
385414
);

src/agent_workspace.runtime.behavior.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ describe('agent workspace runtime behavior', () => {
8282
? '/api/knowledge/path'
8383
: operationId === 'build_study_session'
8484
? '/api/knowledge/session/plan'
85+
: operationId === 'query_knowledge'
86+
? '/api/knowledge/query'
8587
: operationId === 'query_session_history'
8688
? '/api/knowledge/session/history'
8789
: operationId === 'query_mastery_misconceptions'
@@ -111,6 +113,18 @@ describe('agent workspace runtime behavior', () => {
111113
},
112114
};
113115
}
116+
if (operationId === 'query_knowledge') {
117+
return {
118+
operationId,
119+
endpoint,
120+
method: 'POST',
121+
resultPresentation: capability.execution.resultPresentation,
122+
body: {
123+
query: request.query || request.message || '',
124+
topK: Number.isFinite(Number(request.topK)) ? Number(request.topK) : 4,
125+
},
126+
};
127+
}
114128
if (operationId === 'query_mastery_misconceptions') {
115129
return {
116130
operationId,
@@ -373,6 +387,92 @@ describe('agent workspace runtime behavior', () => {
373387
expect(messages.textContent || '').toContain('Study session built');
374388
});
375389

390+
test('executes query trace capability and renders retrieval summary', async () => {
391+
const fetchMock = jest
392+
.fn()
393+
.mockResolvedValueOnce({
394+
ok: true,
395+
status: 200,
396+
json: async () => ({
397+
success: true,
398+
result: {
399+
userId: 'agent_user_default',
400+
message: 'Found 1 local knowledge point(s).',
401+
knowledgePoints: [
402+
{
403+
atomId: 'atom-query-1',
404+
title: 'Query Candidate',
405+
snippet: 'Inspect retrieval trace.',
406+
score: 0.79,
407+
capabilities: [
408+
{
409+
actionId: 'inspect_query_trace',
410+
label: 'Query Trace',
411+
request: {
412+
userId: 'agent_user_default',
413+
atomId: 'atom-query-1',
414+
query: 'retrieval loop',
415+
topK: 5,
416+
},
417+
execution: {
418+
kind: 'knowledge_operation',
419+
operationId: 'query_knowledge',
420+
resultPresentation: 'query_trace_card',
421+
},
422+
},
423+
],
424+
},
425+
],
426+
},
427+
}),
428+
})
429+
.mockResolvedValueOnce({
430+
ok: true,
431+
status: 200,
432+
json: async () => ({
433+
success: true,
434+
result: {
435+
items: [{ atom: { id: 'a1' } }, { atom: { id: 'a2' } }],
436+
trace: {
437+
retrievalModes: ['keyword', 'graph_traversal'],
438+
latencyMs: 17,
439+
evidenceCoverageRatio: 0.75,
440+
},
441+
},
442+
}),
443+
});
444+
(global as unknown as Record<string, unknown>).fetch = fetchMock;
445+
446+
const runtime = runtimeModule.createAgentWorkspaceRuntime({ defaultUserId: 'agent_user_default' });
447+
runtime.init();
448+
449+
const input = document.getElementById('agent-workspace-input') as HTMLTextAreaElement;
450+
const form = document.getElementById('agent-workspace-form') as HTMLFormElement;
451+
input.value = 'inspect query trace';
452+
form.dispatchEvent(new dom!.window.Event('submit', { bubbles: true, cancelable: true }));
453+
await flushAsync();
454+
455+
const actionButton = document.querySelector('.agent-workspace-action-button') as HTMLButtonElement;
456+
expect(actionButton).not.toBeNull();
457+
actionButton.click();
458+
await flushAsync();
459+
460+
expect(fetchMock).toHaveBeenNthCalledWith(
461+
2,
462+
'/api/knowledge/query',
463+
expect.objectContaining({ method: 'POST' })
464+
);
465+
const payload = JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body || '{}')) as {
466+
query?: string;
467+
topK?: number;
468+
};
469+
expect(payload.query).toBe('retrieval loop');
470+
expect(payload.topK).toBe(5);
471+
472+
const messages = document.getElementById('agent-workspace-messages') as HTMLElement;
473+
expect(messages.textContent || '').toContain('Query trace loaded');
474+
});
475+
376476
test('executes mastery misconceptions capability and renders summary message', async () => {
377477
const fetchMock = jest
378478
.fn()

src/agent_workspace.runtime.integration.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('agent workspace runtime integration baseline', () => {
2525
const runtimeSource = fs.readFileSync(runtimePath, 'utf8');
2626
expect(runtimeSource).toContain('/api/knowledge/conversation');
2727
expect(runtimeSource).toContain('/api/knowledge/session/plan');
28+
expect(runtimeSource).toContain('/api/knowledge/query');
2829
expect(runtimeSource).toContain('/api/knowledge/session/history');
2930
expect(runtimeSource).toContain('/api/knowledge/mastery/misconceptions');
3031
expect(runtimeSource).toContain('/api/knowledge/quality/snapshot');

src/frontend/agent_workspace.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
defaultResultPresentation: 'study_session_card',
3333
allowedResultPresentations: Object.freeze(['study_session_card']),
3434
}),
35+
query_knowledge: Object.freeze({
36+
endpoint: '/api/knowledge/query',
37+
method: 'POST',
38+
defaultResultPresentation: 'query_trace_card',
39+
allowedResultPresentations: Object.freeze(['query_trace_card']),
40+
}),
3541
query_session_history: Object.freeze({
3642
endpoint: '/api/knowledge/session/history',
3743
method: 'POST',
@@ -135,6 +141,13 @@
135141
return Math.max(1, Math.min(20, Math.floor(base)));
136142
}
137143

144+
function resolveQueryTopK(rawTopK, fallback) {
145+
const numericTopK = Number(rawTopK);
146+
const numericFallback = Number(fallback);
147+
const base = Number.isFinite(numericTopK) ? numericTopK : (Number.isFinite(numericFallback) ? numericFallback : 4);
148+
return Math.max(1, Math.min(8, Math.floor(base)));
149+
}
150+
138151
function createAgentConversationPayload(options) {
139152
const source = options || {};
140153
const userId = typeof source.userId === 'string' ? source.userId.trim() : '';
@@ -259,6 +272,23 @@
259272
};
260273
}
261274

275+
if (operationId === 'query_knowledge') {
276+
const query = resolveCapabilityMessage(request.query || request.message);
277+
if (!query) {
278+
throw new Error('query_knowledge requires request.query or request.message.');
279+
}
280+
return {
281+
operationId,
282+
endpoint: config.endpoint,
283+
method: config.method,
284+
resultPresentation,
285+
body: {
286+
query,
287+
topK: resolveQueryTopK(request.topK, 4),
288+
},
289+
};
290+
}
291+
262292
if (operationId === 'query_session_history') {
263293
const userId = String(request.userId || '').trim();
264294
if (!userId) {

src/frontend/agent_workspace_runtime.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@
8787
return Math.max(1, Math.min(20, Math.floor(base)));
8888
}
8989

90+
function resolveQueryTopK(rawTopK, fallback) {
91+
const numericTopK = Number(rawTopK);
92+
const numericFallback = Number(fallback);
93+
const base = Number.isFinite(numericTopK) ? numericTopK : (Number.isFinite(numericFallback) ? numericFallback : 4);
94+
return Math.max(1, Math.min(8, Math.floor(base)));
95+
}
96+
9097
function getI18nText(key, fallback, params) {
9198
if (
9299
globalScope
@@ -212,6 +219,22 @@
212219
},
213220
};
214221
}
222+
if (operationId === 'query_knowledge') {
223+
const query = trimString(request.query || request.message);
224+
if (!query) {
225+
throw new Error('query_knowledge requires request.query or request.message.');
226+
}
227+
return {
228+
operationId,
229+
endpoint: '/api/knowledge/query',
230+
method: 'POST',
231+
resultPresentation: trimString(execution.resultPresentation) || 'query_trace_card',
232+
body: {
233+
query,
234+
topK: resolveQueryTopK(request.topK, 4),
235+
},
236+
};
237+
}
215238
if (operationId === 'query_session_history') {
216239
return {
217240
operationId,
@@ -587,6 +610,30 @@
587610
return;
588611
}
589612

613+
if (presentation === 'query_trace_card') {
614+
const trace = result && result.trace && typeof result.trace === 'object'
615+
? result.trace
616+
: {};
617+
const itemsCount = Array.isArray(result && result.items) ? result.items.length : 0;
618+
const retrievalModes = Array.isArray(trace.retrievalModes) ? trace.retrievalModes.join(', ') : '';
619+
const latencyMs = Number(trace.latencyMs);
620+
const evidenceCoverageRatio = Number(trace.evidenceCoverageRatio);
621+
appendMessage(
622+
'assistant',
623+
getI18nText(
624+
'agentWorkspace.messages.queryTraceLoaded',
625+
`Query trace loaded (${itemsCount} hits, modes ${retrievalModes || 'n/a'}, latency ${Number.isFinite(latencyMs) ? latencyMs : 0} ms, evidence ${Number.isFinite(evidenceCoverageRatio) ? evidenceCoverageRatio.toFixed(2) : '0.00'}).`,
626+
{
627+
itemsCount,
628+
retrievalModes: retrievalModes || 'n/a',
629+
latencyMs: Number.isFinite(latencyMs) ? latencyMs : 0,
630+
evidenceCoverageRatio: Number.isFinite(evidenceCoverageRatio) ? evidenceCoverageRatio.toFixed(2) : '0.00',
631+
}
632+
)
633+
);
634+
return;
635+
}
636+
590637
if (presentation === 'session_history_card') {
591638
const summary = result && result.summary && typeof result.summary === 'object'
592639
? result.summary

src/frontend/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@
240240
"generateTransfer": "Generate Transfer",
241241
"generateCounterexample": "Generate Counterexample",
242242
"buildStudySession": "Build Session",
243+
"inspectQueryTrace": "Query Trace",
243244
"inspectSessionHistory": "Session History",
244245
"inspectMasteryMisconceptions": "Mastery Misconceptions",
245246
"inspectLearningQualitySnapshot": "Learning Quality",
@@ -267,6 +268,8 @@
267268
"analyzeAnswerRequired": "Analyze answer requires a non-empty learner answer.",
268269
"studySessionBuildFailed": "Study session build failed.",
269270
"studySessionBuilt": "Study session built ({totalActions} actions, {totalEstimatedMinutes} min).",
271+
"queryTraceFailed": "Query trace request failed.",
272+
"queryTraceLoaded": "Query trace loaded ({itemsCount} hits, modes {retrievalModes}, latency {latencyMs} ms, evidence {evidenceCoverageRatio}).",
270273
"sessionHistoryFailed": "Session history request failed.",
271274
"sessionHistoryLoaded": "Session history loaded ({totalRecords} sessions, {totalExecutedActions} actions).",
272275
"masteryMisconceptionsFailed": "Mastery misconceptions request failed.",

0 commit comments

Comments
 (0)