Skip to content

Commit bdf1519

Browse files
authored
Merge pull request #4987 from loopx-project/codex/live-team-l1-evidence-20260924
feat(team): trace a correction through exact versioned work
2 parents 90f21a5 + e429950 commit bdf1519

7 files changed

Lines changed: 186 additions & 3 deletions

File tree

‎apps/presentation/dashboard/src/features/personal-workspace/goal-loopx-mode.css‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@
5353
.goal-team-lineage ul { list-style: none; padding: 0; margin: 12px 0; }
5454
.goal-team-lineage li { display: grid; gap: 8px; padding: 0 0 16px 16px; border-left: 2px solid var(--pw-line, #ebebeb); overflow-wrap: anywhere; }
5555
.goal-team-lineage button { justify-self: start; max-width: 100%; overflow-wrap: anywhere; text-align: start; }
56+
.goal-team-episode { margin-block: 20px; border: 1px solid var(--pw-line, #ebebeb); border-radius: 10px; padding: 16px; background: var(--pw-bg, #fafafa); }
57+
.goal-team-episode-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
58+
.goal-team-episode > p { color: var(--pw-muted, #666); line-height: 1.5; }
59+
.goal-team-episode ol { list-style: none; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; padding: 0; margin: 16px 0 0; background: var(--pw-line, #ebebeb); border: 1px solid var(--pw-line, #ebebeb); border-radius: 6px; overflow: hidden; }
60+
.goal-team-episode li { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-width: 0; padding: 12px; background: var(--pw-surface, #fff); overflow-wrap: anywhere; }
61+
.goal-team-episode li > span:first-child { font-size: 11px; color: var(--pw-muted, #666); }
62+
.goal-team-episode li > strong { font-size: 13px; }
63+
.goal-team-episode li > button { margin-top: auto; }
64+
@media (max-width: 780px) { .goal-team-episode ol { grid-template-columns: 1fr; } }
5665
.goal-loopx-dialog:has(.goal-team-comparison) { width: min(1040px, calc(100vw - 32px)); }
5766
.goal-team-comparison { margin-block: 24px; }
5867
.goal-team-comparison-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import {useEffect, useRef, useState} from "react";
2+
import {readLoopXTeamWork, type DelegationDependency, type DelegationReadback} from "../../data/chat";
3+
4+
type VerifiedLink = {link: DelegationDependency; source: DelegationReadback};
5+
type Episode = {original: VerifiedLink; response: VerifiedLink; downstream: DelegationReadback | null};
6+
7+
/** Resolve only an explicitly requested correction path. Reads are on demand and never start work. */
8+
export function GoalTeamEpisode({sessionId, result, zh, onInspect}: {
9+
sessionId: string; result: DelegationReadback; zh: boolean; onInspect: (operationId: string) => void;
10+
}) {
11+
const original = result.dependencies?.find(link => link.relation === "revises");
12+
const response = result.dependencies?.find(link => link.relation === "responds_to" && link.operation_id !== original?.operation_id);
13+
const adoption = result.adoptions?.find(row => row.state === "current") ?? result.adoptions?.[0];
14+
const [episode, setEpisode] = useState<Episode | null>(null);
15+
const [busy, setBusy] = useState(false);
16+
const [error, setError] = useState("");
17+
const generation = useRef(0);
18+
useEffect(() => {
19+
generation.current++; setEpisode(null); setError(""); setBusy(false);
20+
return () => {generation.current++;};
21+
}, [result, sessionId]);
22+
if (!original || !response) return null;
23+
async function trace() {
24+
const current = ++generation.current;
25+
setEpisode(null); setError(""); setBusy(true);
26+
try {
27+
const [first, challenge, revised, downstream] = await Promise.all([
28+
readLoopXTeamWork(sessionId, original!.operation_id),
29+
readLoopXTeamWork(sessionId, response!.operation_id),
30+
readLoopXTeamWork(sessionId, result.operation_id),
31+
adoption?.state === "current" ? readLoopXTeamWork(sessionId, adoption.consumer_operation_id) : Promise.resolve(null),
32+
]);
33+
const matches = (link: DelegationDependency, source: DelegationReadback) =>
34+
link.state === "current" && source.status === "accepted" && !source.recovery_required && !source.error
35+
&& source.artifacts?.some(artifact => artifact.ref === link.ref && artifact.sha256 === link.sha256);
36+
const responseBindsOriginal = challenge.dependencies?.some(link =>
37+
link.relation === "responds_to" && link.operation_id === original!.operation_id && matches(link, first));
38+
const revisionMatchesObservation = revised.status === "accepted" && revised.agent_id === result.agent_id
39+
&& !revised.recovery_required && !revised.error
40+
&& revised.artifacts?.length === result.artifacts?.length
41+
&& result.artifacts?.every(expected => revised.artifacts?.some(
42+
artifact => artifact.ref === expected.ref && artifact.sha256 === expected.sha256))
43+
&& revised.dependencies?.some(link => link.relation === "revises" && link.operation_id === original!.operation_id && matches(link, first))
44+
&& revised.dependencies?.some(link => link.relation === "responds_to" && link.operation_id === response!.operation_id && matches(link, challenge));
45+
const refreshedAdoption = adoption?.state === "current" ? revised.adoptions?.find(row =>
46+
row.consumer_operation_id === adoption.consumer_operation_id && row.requester_agent_id === adoption.requester_agent_id
47+
&& row.consumer_agent_id === adoption.consumer_agent_id && row.state === "current") : null;
48+
const downstreamBindsRevision = downstream?.dependencies?.some(link =>
49+
link.relation === "uses" && link.operation_id === revised.operation_id && matches(link, revised));
50+
const downstreamMatchesReceipt = downstream?.status === "accepted" && !downstream.recovery_required && !downstream.error
51+
&& refreshedAdoption?.consumer_artifacts.every(expected => downstream.artifacts?.some(
52+
artifact => artifact.ref === expected.ref && artifact.sha256 === expected.sha256));
53+
const sourceMatchesReceipt = refreshedAdoption?.source_artifacts.every(expected => revised.artifacts?.some(
54+
artifact => artifact.ref === expected.ref && artifact.sha256 === expected.sha256));
55+
if (result.status !== "accepted" || result.error || result.recovery_required
56+
|| !matches(original!, first) || !matches(response!, challenge)
57+
|| !responseBindsOriginal || !revisionMatchesObservation
58+
|| (adoption?.state === "current" && (!sourceMatchesReceipt || !downstreamBindsRevision || !downstreamMatchesReceipt))) {
59+
throw new Error("linked evidence unavailable");
60+
}
61+
if (current === generation.current) setEpisode({original: {link: original!, source: first},
62+
response: {link: response!, source: challenge}, downstream});
63+
} catch {
64+
if (current === generation.current) setError(zh ? "关联执行或版本已变化;请重新读取证据。" : "A linked execution or version changed; recheck the evidence.");
65+
} finally {if (current === generation.current) setBusy(false);}
66+
}
67+
return <section className="goal-team-episode" aria-label={zh ? "纠偏证据路径" : "Correction evidence path"} aria-busy={busy}>
68+
<div className="goal-team-episode-heading"><h4>{zh ? "追踪一次纠偏" : "Trace a correction"}</h4>
69+
<button type="button" disabled={busy} onClick={() => void trace()}>{zh ? "核验关联执行" : "Verify linked work"}</button></div>
70+
<p>{zh ? "按需核验原始版本、复核回应、修订及后续采用。关系和验收不能替代对异议内容的判断。"
71+
: "Verify the original version, review response, revision and downstream use on demand. Relationships and acceptance do not judge the objection's content."}</p>
72+
{busy ? <p role="status">{zh ? "正在核验关联执行与指定版本…" : "Checking linked executions and exact versions…"}</p> : null}
73+
{error ? <p role="alert">{error}</p> : null}
74+
{episode ? <ol>
75+
<li><span>01 · {zh ? "原始版本" : "Original version"}</span><strong>{episode.original.source.agent_id}</strong>
76+
<button type="button" onClick={() => onInspect(episode.original.link.operation_id)}>{zh ? "阅读原始产物" : "Read original"}</button></li>
77+
<li><span>02 · {zh ? "复核回应" : "Review response"}</span><strong>{episode.response.source.agent_id}</strong>
78+
<button type="button" onClick={() => onInspect(episode.response.link.operation_id)}>{zh ? "阅读回应与证据" : "Read response and evidence"}</button></li>
79+
<li><span>03 · {zh ? "修订产物 · 当前验收有效" : "Revised output · currently accepted"}</span><strong>{result.agent_id}</strong>
80+
<span>{zh ? "与原始版本的正文对照见下方" : "Compare with the original below"}</span></li>
81+
<li><span>04 · {episode.downstream ? (zh ? "后续结果 · 当前验收与采用记录有效" : "Downstream result · acceptance and adoption current")
82+
: adoption ? (zh ? "采用证据无法核验" : "Adoption evidence unavailable") : (zh ? "尚无请求方采用" : "No requester adoption")}</span>
83+
{episode.downstream && adoption ? <><strong>{episode.downstream.agent_id} · {adoption.requester_agent_id}</strong>
84+
<button type="button" onClick={() => onInspect(adoption.consumer_operation_id)}>{zh ? "阅读后续结果" : "Read downstream result"}</button></> : null}</li>
85+
</ol> : null}
86+
</section>;
87+
}

‎apps/presentation/dashboard/src/features/personal-workspace/goal-team-evidence.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {GoalTeamLineage} from "./goal-team-lineage";
2+
import {GoalTeamEpisode} from "./goal-team-episode";
23
import {GoalTeamComparison} from "./goal-team-comparison";
34
import {TeamArtifactReport} from "./team-artifact-content";
45
import {useEffect, useRef, useState} from "react";
@@ -55,6 +56,8 @@ export function GoalTeamEvidence({sessionId, operationId, zh, canMessage, ingres
5556
const receiptLabel = status === "pending" ? (zh ? "已进入协调员收件箱,等待读取" : "In the coordinator inbox; awaiting read")
5657
: status === "delivered" ? (zh ? "已交给协调员;尚无应用回执" : "Delivered to coordinator; application not confirmed")
5758
: (zh ? "投递状态待核实" : "Delivery requires reconciliation");
59+
const hasCorrectionPath = Boolean(result?.dependencies?.some(link => link.relation === "revises")
60+
&& result?.dependencies?.some(link => link.relation === "responds_to"));
5861
return <section className="goal-team-evidence" aria-label={zh ? "执行证据" : "Execution evidence"} aria-busy={busy}>
5962
<div className="goal-team-work-actions"><h3>{zh ? "执行证据" : "Execution evidence"}</h3>
6063
<button type="button" disabled={busy} onClick={() => void read()}>{zh ? "重新读取证据" : "Recheck evidence"}</button></div>
@@ -63,8 +66,11 @@ export function GoalTeamEvidence({sessionId, operationId, zh, canMessage, ingres
6366
{result ? <>
6467
<p role="status"><strong>{result.agent_id} · {delegationStateLabel(result, zh)}</strong>{" · "}{observedAt}</p>
6568
<p>{zh ? "按需读取的当前观察,不是持续在线状态;验收不代表协调员已采用。" : "An on-demand observation, not continuous liveness; acceptance does not establish coordinator adoption."}</p>
69+
<GoalTeamEpisode sessionId={sessionId} result={result} zh={zh} onInspect={onInspect}/>
6670
<GoalTeamComparison sessionId={sessionId} result={result} zh={zh}/>
67-
<GoalTeamLineage result={result} zh={zh} onInspect={onInspect}/>
71+
{hasCorrectionPath ? <details className="goal-team-lineage-detail"><summary>{zh ? "版本与采用关系详情" : "Version and adoption details"}</summary>
72+
<GoalTeamLineage result={result} zh={zh} onInspect={onInspect}/></details>
73+
: <GoalTeamLineage result={result} zh={zh} onInspect={onInspect}/>}
6874
{result.error ? <p role="alert">{result.error}</p> : null}
6975
{result.status === "accepted" && !result.error && !result.recovery_required && result.artifacts?.length ? result.artifacts.map(artifact => <div key={`${artifact.ref}:${artifact.sha256}`}>
7076
<TeamArtifactReport artifact={artifact} zh={zh}/>

‎docs/architecture/rfcs/live-team-workspace-v0.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,17 @@ second assignment. This strengthens the optional request/intervention entry;
388388
real dual-card click-through remains post-install acceptance. It does not prove
389389
execution, result return or whole-team stopping.
390390
`consume_return` alone still means consumption, not version-bound adoption.
391+
Implementation checkpoint (2026-09-24): an on-demand correction path can
392+
cross-check the original, review response, revised output and downstream
393+
adoption against current version-bound delegation reads. It exposes actor
394+
identities and opens each accepted artifact; an unavailable adoption or rejected
395+
review clears the previously verified path. This is a presentation and
396+
readback slice, not L1 completion: the relation `responds_to` does not certify
397+
that the response is an objection, and current acceptance lacks an explicit
398+
verifier identity in this read model. The packaged browser scenario is
399+
synthetic; the existing real correction run must still be exercised through
400+
the packaged UI and independently read back, with the missing and lost-observation
401+
cases in Section 9.
391402
Motion is retained
392403
only when it clarifies these transitions;
393404
remove effects that obscure absent execution, absent acceptance or source loss.

‎docs/architecture/rfcs/live-team-workspace-v0.zh-CN.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,12 @@ Chat 会话推断报告。复用现有 authority 与产物读取边界,不另
299299
点击重复分配。这加强可选请求/干预入口;真实双卡点击仍需安装后验收,不能证明
300300
执行、结果回报或整队停止。
301301
`consume_return` 单独仍只代表消费,不能替代版本绑定的采用。
302+
实施检查点(2026-09-24):按需打开的纠偏路径可用当前版本绑定的 delegation
303+
回读,交叉核验原始产物、复核回应、修订产物和后续采用,显示成员身份并打开每份已
304+
验收产物;采用不可用或复核被拒绝时清除旧路径。这是展示与回读切片,尚非 L1
305+
完成:`responds_to` 关系不能证明回应确实提出异议,当前回读中的验收也缺少明确
306+
的 verifier 身份。打包前端的浏览器场景仍是合成数据;已有的真实纠偏运行还须
307+
在打包界面和独立 CLI 上核验同一过程,并覆盖第 9 节的缺失与失联情况。
302308
动态只有让上述变化更清楚才保留;掩盖未执行、未验收
303309
或来源失联的效果应移除。更广的语义缩放、成员扩张和 renderer 探索在此后推进。
304310

‎examples/personal-workspace-browser/fixture.mjs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1508,7 +1508,17 @@ export async function installApi(page, { goalSubagentConfigurationEnabled = true
15081508
}
15091509
if (body.operation === "read") {
15101510
if (current.fixtureTeamReadDelayMs) await new Promise(resolveWait => setTimeout(resolveWait, current.fixtureTeamReadDelayMs));
1511-
if (body.operation_id === "accepted-synthesis") {
1511+
if (current.fixtureCorrectionEpisode && body.operation_id === "original-analysis") {
1512+
await route.fulfill({json: {ok: true, operation_id: body.operation_id, request_id: "request-original",
1513+
agent_id: "local-analyst", todo_id: "todo_original", status: "accepted", worker_active: false,
1514+
recovery_required: false, artifacts: [{ref: "report.json", sha256: "a".repeat(64), text: '{"cash_flow":90}'}]}});
1515+
} else if (current.fixtureCorrectionEpisode && body.operation_id === "review-objection") {
1516+
await route.fulfill({json: {ok: true, operation_id: body.operation_id, request_id: "request-review",
1517+
agent_id: "independent-reviewer", todo_id: "todo_review", status: "accepted", worker_active: false,
1518+
recovery_required: false, artifacts: [{ref: "objection.json", sha256: "b".repeat(64), text: '{"objection":"The source was superseded"}'}],
1519+
dependencies: [{operation_id: "original-analysis", ref: "report.json", sha256: "a".repeat(64),
1520+
input_ref: "original.json", relation: "responds_to", state: "current"}]}});
1521+
} else if (body.operation_id === "accepted-synthesis") {
15121522
await route.fulfill({json: {ok: true, operation_id: body.operation_id, request_id: "request-synthesis",
15131523
agent_id: "synthesizer", todo_id: "todo_synthesis", status: "accepted", worker_active: false,
15141524
recovery_required: false, artifacts: [{ref: "synthesis.json", sha256: "e".repeat(64), text: '{"accepted_cash_flow":75}'},
@@ -1522,6 +1532,11 @@ export async function installApi(page, { goalSubagentConfigurationEnabled = true
15221532
await route.fulfill({json: {ok: true, operation_id: body.operation_id, request_id: "request-analysis",
15231533
agent_id: "local-analyst", todo_id: current.fixturePlanTodoId ?? "todo_analysis", status: "accepted", worker_active: false,
15241534
recovery_required: false,
1535+
...(current.fixtureCorrectionEpisode ? {dependencies: [
1536+
{operation_id: "original-analysis", ref: "report.json", sha256: "a".repeat(64),
1537+
input_ref: "original.json", relation: "revises", state: "current"},
1538+
{operation_id: "review-objection", ref: "objection.json", sha256: "b".repeat(64),
1539+
input_ref: "objection.json", relation: "responds_to", state: "current"}]} : {}),
15251540
...(current.fixtureAdoptionState ? {adoptions: [{requester_agent_id: "lead", consumer_operation_id: "accepted-synthesis",
15261541
consumer_request_id: "request-synthesis", consumer_agent_id: "synthesizer", consumer_todo_id: "todo_synthesis",
15271542
source_artifacts: [{ref: "report.json", sha256: "d".repeat(64)}],

0 commit comments

Comments
 (0)