You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by "xxhZs (via GitHub)" <gi...@apache.org> on 2026/10/22 14:55:53 UTC
[PR] feat(assistant): split proactive discovery and judgement into persistent sessions [maka]
xxhZs opened a new pull request, #6244:
URL: https://github.com/apache/maka/pull/6244
## Summary
Split proactive discovery into two persistent background sessions. Discovery saves candidate evidence to a private report file; judgement reads the report, investigates further, and passes worthwhile findings to the assistant Bot through `DiscoveryReport`. Neither background session sends its final text directly to the user conversation.
Preserve both session bindings and pending reports across checks and restarts. A failed judgement can resume its saved report without rerunning discovery. Keep the existing scheduler cadence, feature controls, and source/index data.
This PR submits the existing two-stage branch after merging the latest `main`. It does not include the later shared-notebook prototype or private experiment scripts/data.
## Validation
- `git diff --check`: passed.
- Build and focused assistant regressions: running in an isolated copy of the PR head; results will be updated before handoff.
- No production restart, deployment, or new live-model experiment performed for this synchronization.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(assistant): share append-only context between discovery and judgement [maka]
Posted by "xxhZs (via GitHub)" <gi...@apache.org>.
xxhZs merged PR #6244:
URL: https://github.com/apache/maka/pull/6244
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(assistant): share append-only context between discovery and judgement [maka]
Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #6244:
URL: https://github.com/apache/maka/pull/6244#discussion_r4357456982
##########
packages/runtime-host/src/assistant/proactive-matters/tools.ts:
##########
@@ -161,6 +175,7 @@ export function buildMatterTools(deps: MatterToolsDeps): MakaTool[] {
.describe(
'Optional working note to retain for future activations. Omit to keep the previous note; no draft file is needed.',
),
+ progressSummary,
Review Comment:
> **Automated review notice:** This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
P2 — Update the existing settlement-context test fixture with this required field. `packages/runtime-host/src/__tests__/matter-settlement-context.test.ts` still parses submissions without `progressSummary`, so eight tests now throw `ZodError` before reaching the settlement behavior. This reproduces locally after a clean build and in CI run 39909989174 attempt 2. Adding a valid progress summary only to the fixture changes the focused result from 3/11 to 11/11 without altering production code.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(assistant): share append-only context between discovery and judgement [maka]
Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #6244:
URL: https://github.com/apache/maka/pull/6244#discussion_r4350676133
##########
packages/runtime-host/src/assistant/index-initiative/controller.ts:
##########
@@ -115,58 +164,116 @@ export class InitiativeController {
async dispatch() {
const s = this.store.get();
if ((!s?.enabled && !s?.checkRequested) || s.active || s.nextAt > Date.now()) return;
- const agent = await this.asOwner(s, async () => {
- let sessionId = s.discoverySession;
+ const agent = await this.worker(
+ s,
+ s.pendingJudgement || s.candidateReport ? 'judgement' : 'discovery',
+ );
+ const snapshot = await agent.snapshot();
+ if (snapshot?.agent?.status === 'running' || awaitingUser(snapshot)) return;
+ this.ready();
+ const current = this.store.claim();
+ if (!current?.active) return;
+ this.running = this.run(agent, current).finally(() => {
+ this.running = undefined;
+ });
+ }
+ private async worker(s: any, phase: 'discovery' | 'judgement') {
+ return this.asOwner(s, async () => {
+ const field = phase === 'discovery' ? 'discoverySession' : 'judgementSession';
+ let sessionId = s[field];
if (!sessionId) {
- const label = `assistant-discovery:${s.sessionId}`;
+ const label = `assistant-${phase}:${s.sessionId}`;
const matches = (await this.ctx.sessionQuery.list()).filter((item: any) =>
item.labels?.includes(label),
);
- if (matches.length > 1) throw Error('Multiple exploration workers found');
+ if (matches.length > 1) throw Error(`Multiple ${phase} workers found`);
sessionId =
matches[0]?.id ??
(
await this.ctx.agents.create({
background: true,
- name: '个人助手 · 主动发现',
+ name: phase === 'discovery' ? '个人助手 · 主动发现' : '个人助手 · 发现判断',
labels: [label, ASSISTANT_BACKGROUND_SESSION_LABEL],
})
).sessionId;
this.store.update((current) => {
- current.discoverySession = sessionId;
+ current[field] = sessionId;
});
}
return this.ctx.agents.resume({ sessionId });
});
+ }
+ private async execute(agent: any, state: any) {
+ this.ready();
+ this.activeAgent = agent;
+ await this.refreshFeedback(state);
+ const role = state.active.phase === 'discovery' ? 'exploration' : 'judgement';
+ const directory = join(this.store.directory, 'notebook-wakes', state.active.id, role);
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
+ const viewPath = join(directory, 'notebook.md');
+ const outputPath = join(directory, 'operations.json');
+ const revision = this.notebook.revision();
+ const version = createHash('sha256')
+ .update((role === 'exploration' ? DISCOVERY_TASK : JUDGEMENT_TASK) + '\n' + NOTEBOOK_RULES)
+ .digest('hex');
+ writeFileSync(viewPath, this.notebook.render(), { mode: 0o600 });
+ let input = wakeInput({
+ role,
+ now: new Date().toISOString(),
+ viewPath,
+ outputPath,
+ first: state.promptVersions?.[role] !== version,
+ });
+ if (role === 'judgement' && state.candidateReport?.path)
+ input += `\n升级前保留的历史报告:${state.candidateReport.path}。它是参考材料,不是指定候选或当前指令。`;
+ const admitted = await agent.followup(
+ `Runtime heartbeat, not a new human request.\nHeartbeat ID: ${state.active.id}\nPhase: ${state.active.phase}\n${input}`,
+ );
+ if (!['turn_started', 'followup'].includes(admitted?.disposition))
+ throw Error('Host rejected proactive wake');
+ while (
+ admitted.disposition === 'followup' &&
+ !JSON.stringify(await agent.transcript()).includes(`Heartbeat ID: ${state.active.id}`)
+ )
+ await delay(25, undefined, { signal: this.abort.signal });
+ await agent.whenIdle(this.abort.signal);
const snapshot = await agent.snapshot();
- if (snapshot?.agent?.status === 'running' || awaitingUser(snapshot)) return;
+ const status = snapshot?.agent?.status;
+ if (['aborted', 'blocked', 'waiting_for_user'].includes(status) || awaitingUser(snapshot))
+ throw Error(`Heartbeat ended with ${status}; inspect before resuming`);
this.ready();
- const current = this.store.claim();
- if (!current?.active) return;
- this.running = this.run(agent, current).finally(() => {
- this.running = undefined;
+ this.store.update((s) => {
+ (s.promptVersions ??= {})[role] = version;
});
+ await this.refreshFeedback(state);
+ const draft = JSON.parse(readFileSync(outputPath, 'utf8'));
+ this.notebook.commitDraft({
+ role,
+ operationId: `${state.active.id}:${role}`,
+ expectedFeedbackRevision: revision,
+ draft,
+ });
+ // Only the shared notebook survives. Do not expose the other worker's private output files.
+ rmSync(directory, { recursive: true, force: true });
+ if (role === 'judgement') this.forwardPrepared(state);
}
async run(agent: any, state: any) {
const id = state.active.id;
this.activeAgent = agent;
try {
- const admitted = await agent.followup(wake(state));
- if (!['turn_started', 'followup'].includes(admitted?.disposition))
- throw Error('Host rejected proactive wake');
- const completion = async () => {
- // Queued admission is not completion; wait for its input to enter Session history.
- while (
- admitted.disposition === 'followup' &&
- !JSON.stringify(await agent.transcript()).includes(`Heartbeat ID: ${id}`)
- )
- await delay(25, undefined, { signal: this.abort.signal });
- await agent.whenIdle(this.abort.signal);
- };
- await completion();
- const status = (await agent.snapshot())?.agent?.status;
- if (['aborted', 'blocked', 'waiting_for_user'].includes(status))
- throw Error(`Heartbeat ended with ${status}; inspect before resuming`);
+ await this.execute(agent, state);
Review Comment:
[P2] Replay a committed Bot handoff before rerunning judgement
If `forwardPrepared()` throws after `commitDraft()` has persisted a recommendation (for example, a temporarily unavailable Bot or failed mailbox write), `finish(id, error)` retains `pendingJudgement` but clears the active wake. The next claim gets a new wake ID and reaches this `execute()` again, rather than replaying the committed handoff first. The notebook view does not include unreported prepared messages. When judgement returns the same recommendation on that retry, it is saved under a second ID, and `forwardPrepared()` posts both entries; BotStore deduplicates by ID, so it accepts both.
I reproduced two inbox rows after one mailbox failure and one retry using the actual controller/notebook/bridge/BotStore, both with and without reopening the stores. A control that forwards the existing prepared message before generating another draft leaves one row. Keep a durable handoff/replay phase (with the existing message identity), or explicitly reconcile/supersede pending recommendations before asking judgement to generate replacements.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(assistant): share append-only context between discovery and judgement [maka]
Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #6244:
URL: https://github.com/apache/maka/pull/6244#discussion_r4356776067
##########
packages/runtime-host/src/__tests__/assistant-bot.test.ts:
##########
@@ -1341,3 +1366,181 @@ test('connector input retains sender provenance through protocol, mailbox and vi
/external connector source/,
);
});
+
+test('only explicitly linked replies to delivered discoveries reach the notebook, across restart', async (t) => {
+ const f = await fixture();
+ t.after(f.close);
+ let round = 0;
+ let deliveries = 0;
+ let handled = 0;
+ let visibleId = '';
+ let feedbackId = '';
+ const discoveryContext = () => {
+ const store = new BotStore(join(f.directory, 'native', 'index-initiative'));
+ try {
+ return {
+ deliveries: store.discoveryDeliveries(),
+ feedback: store.discoveryFeedback(),
+ };
+ } finally {
+ store.close();
+ }
+ };
+ const installRunner = () =>
+ f.runner(async ({ prompt, invoke }) => {
+ if (prompt.includes('Runtime heartbeat')) {
+ const notebook = await readFile(/记事本:([^\n]+)/.exec(prompt)![1], 'utf8');
+ assert.doesNotMatch(notebook, /普通聊天只问天气|普通聊天的回答/);
+ let draft: any = {};
+ if (prompt.includes('Phase: discovery')) {
+ round++;
+ if (round === 1) {
+ assert.match(notebook, /暂无用户反馈/);
+ draft = {
+ notes: [
+ {
+ topicId: 'tea',
+ title: '茶',
+ text: '附近有茶展',
+ sources: ['公告'],
+ },
+ ],
+ };
+ } else {
+ assert.match(notebook, /我不喝茶了/);
+ assert.match(notebook, /主题:tea/);
+ feedbackId = /|(feedback:[^|\n]+)|待理解/.exec(notebook)![1];
+ }
+ } else if (round === 1) {
+ draft = {
+ messages: [{ text: '附近的茶展本周开放。', topicIds: ['tea'] }],
+ };
+ } else {
+ draft = {
+ feedbackDecisions: [
+ {
+ feedbackId,
+ understanding: '用户不喝茶了',
+ rules: [
+ {
+ quote: '我不喝茶了',
+ status: 'closed',
+ reason: '用户不喝茶了',
+ topicIds: ['tea'],
+ tags: [],
+ },
+ ],
+ },
+ ],
+ };
+ }
+ await writeFile(
+ /完成后把增量操作写入 (.+),格式:/.exec(prompt)![1],
+ JSON.stringify(draft),
+ );
+ return;
+ }
+ const batch = await invoke('InboxCheck');
+ for (const item of batch.items) {
+ if (item.type === 'discovery') {
+ const result = await invoke('SendUser', {
+ messageId: 'tea-delivery',
+ messages: [{ text: '附近的茶展本周开放。', discoveryIds: [item.id] }],
+ });
+ visibleId = result.messageIds[0];
+ deliveries++;
+ } else if (item.id === 'reply') {
+ const args = { userMessageId: 'reply', messageId: visibleId };
+ await invoke('DiscoveryFeedback', args);
+ await invoke('DiscoveryFeedback', args);
+ } else {
+ await invoke('SendUser', {
+ messageId: 'ordinary',
+ messages: [{ text: '普通聊天的回答' }],
+ });
+ }
+ handled++;
+ }
+ await invoke('Sleep');
+ });
+ installRunner();
+ await f.send('ordinary', '普通聊天只问天气');
+ await until(() => handled === 1);
+ assert.deepEqual(discoveryContext(), { deliveries: [], feedback: [] });
+ await f.service.call('assistant.control', { action: 'check' });
+ await until(() => deliveries === 1);
+ assert.equal(discoveryContext().feedback.length, 0);
+ await f.send('reply', '我不喝茶了');
+ await until(() => handled === 3);
+ assert.equal(discoveryContext().feedback.length, 1);
+ await f.restart();
+ installRunner();
+ assert.equal(discoveryContext().deliveries.length, 1);
+ assert.equal(discoveryContext().feedback.length, 1);
+ await f.service.call('assistant.control', { action: 'check' });
+ await until(async () => {
+ const status: any = await f.service.call('assistant.status', {});
+ if (status.lastError) throw Error(status.lastError);
+ return round === 2 && !status.active;
Review Comment:
> **Automated review notice:** This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
P3 — Wait for the nested initiative state before inspecting notebook results. `assistant.status` returns `{ state, bot, ... }`, so `status.active` and `status.lastError` here are always absent. Once the second discovery runner increments `round`, this wait can finish before the judgement phase commits its feedback resolution, producing `0 !== 1` at the final assertion. I reproduced this with the original test and a delayed second draft; reading `status.state.active` / `status.state.lastError` fixes the same sequence without changing product code.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org