Skip to content
Closed
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
45 changes: 45 additions & 0 deletions docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,12 @@ receipt → observed work or actionable failure → readable answer in the same
summary through the shared activity contract; `rawThinking`, planner
completion and tool completion remain separate, and final answers still
replace the draft on the original source-bound post.
- Ordinary Codex Chat explicitly requests a concise native reasoning summary
on each Turn, including resumed and restricted answer-review threads whose
inherited summary setting may be `none`. Execution hosts retain their own
summary configuration. Only actual upstream summaries enter the existing
activity projection; a model that emits none keeps coarse activity labels.
This requests no extra Turn and never exposes raw reasoning content.
- Generate the visible answer once, then append a complete hidden review envelope
with `message: ""`. The production parser reuses the preceding Markdown and
applies the same redaction and typed metadata normalization. Legacy nonempty
Expand Down Expand Up @@ -1846,6 +1852,45 @@ This isolated answer review receives its complete original question and verified
visible results at dispatch. It does not import or serially compact the unrelated
conversation before it can answer. Core retains the full transcript, and ordinary
conversation restoration continues to preserve that history.

An original private steward may also have a verified history gap with no Core
request at all. The existing service can reserve one **answer-only review** for
that source on its already-bound Codex Session. Complete history observation,
fresh original-owner/message readback, an idle queue, unchanged binding and no
later source Stop/New are required; a source predating the Session is held.
The provider records `provider_history` provenance, never a received WS event or
a fabricated failed Turn. The review uses the same restricted adapter and normal
account policy, cannot change executor, write, send, delegate or rerun business,
and returns through the original reply worker with independent readback. A lost
queue acknowledgement adopts the same reservation. Captured ordinary requests,
project/attached/group sources, commands, media and unsuccessful reviews retain
their existing boundaries; history cannot turn into ordinary admission or a
business retry. Existing evidence may answer a question directly; unknown past
effects and missing business work remain explicit gaps.

Each historical answer uses an isolated restricted native context rather than
the preceding review's model history. Dispatch includes at most twelve complete
messages from the same Core Session strictly before the original source time,
explicitly labeled as an available Core prefix, not complete provider history.
An input above 64 KiB retains an unavailable reason instead of cutting text.
Later questions, reviews and current work records cannot supply an antecedent
such as “above”. Only a completed, delivery-verified, action-free legacy
read-only history review with an explicit remaining gate qualifies for one
original-service context correction. The old Turn and reply receipts remain
unchanged; the correction returns along the same source, adopting its reserved
identity after a lost queue acknowledgement. Failed or unknown execution,
ordinary business, complete answers, unverified delivery and already-qualified
reviews do not qualify. Stop, revocation, busy queues and native background
work retain their gates; correcting context does not complete the business.
The correction advances only this request's reserved version and retains the
existing read-only client suffix, so earlier answer-capable readers still
recognize its restricted role. Correct original-context delivery requires the
updated runtime; compatibility does not certify answer quality after downgrade.

The provider inspects at most 64 unresolved originals as one complete source
control inventory. Larger, incomplete or unreadable inventories remain pending;
live captures take priority. Downgrading a store with these reservations to a
reader that lacks the answer-only protocol is outside this recovery contract.
If restart happens after the queue claim but before native dispatch, the worker
adopts that same unstarted Turn through ordinary preparation and execution
fences. It does not wait forever for a queued Turn to become running by itself;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ CLI/SSH 导出使用 `goal-portfolio --manager-view todos --goal-id GOAL --todo-
命令标题。群聊及未经限定的投影仍只显示粗粒度状态。反重力把经过共享脱敏的
`plannerResponse.thinking` 摘要映射进活动契约;`rawThinking`、planner 完成与
工具完成仍是不同事实。最终答案仍在绑定原请求的同一消息中替换草稿。
- 普通 Codex Chat 在每个 Turn 明确请求简短的原生思考摘要,包括继承了
`summary=none` 的恢复会话和受限答复复核会话。执行宿主保留自己的摘要配置。
现有活动投影只展示上游实际返回的摘要;模型没有输出摘要时仍显示粗粒度状态。
该请求不增加 Turn,也不暴露原始推理正文。
- 可见答案只生成一次,随后附加完整隐藏 review envelope,`message: ""` 表示
复用前面的 Markdown,仍经过相同脱敏与 typed 元数据归一化。旧的非空 message
保持优先;缺失或 null 不启用该约定。Codex 采用完成的最终答案 item,排除之前
Expand Down Expand Up @@ -1205,6 +1209,31 @@ provider 在这次复核与已完成工作的读回中复用同一组被动原

这次独立答案复核在派发时收到完整原问题及已核验的可见结果,不在答复前导入、串行
压缩无关的整段对话。Core 完整聊天记录保留;普通对话的历史恢复逻辑继续保留全部历史。

原私聊管家也可能存在已核验的历史缺口,却完全没有 Core 请求。原服务可以在已绑定的
Codex Session 上为该来源预留一次**仅补答案的复核**:须完成历史观察,重新读回原本人
及原消息,队列空闲、binding 不变,且原来源之后没有 Stop/New;早于该 Session 的问题
保留等待。provider 明确记录 `provider_history` 来源,不冒充收到的 WS 事件,也不虚构
失败 Turn。复核复用受限 adapter 与正常账户策略,不切执行器,不写入、发送、委派或
重跑业务;由原回复 worker 沿原消息回传并独立读回。入队确认丢失时采用同一预留。
已捕获的普通请求、项目/attached/group 来源、命令、媒体及失败复核保留原边界;历史
不能转为普通准入或业务重试。现有证据足够时直接答题;过去效果未知或业务未完成的
部分仍明确保留。

历史补答使用独立的受限原生上下文,不复用上一条补答的模型历史。派发输入带原来源
时点之前、同一 Core Session 的至多 12 条完整消息;明确这只是可用 Core 前缀,
不是 provider 全历史证明。超过 64 KiB 时保留不可用原因,不静默截句;不能从后来的
提问、补答或当前工作记录推定“上面”等指代。只有已完成、已核验送达、无动作且
明确保留 gate 的旧版只读历史复核,才可由原服务预留一次上下文纠正;旧 Turn 和
原回复回执保持不变,纠正沿同一来源返回,丢失入队确认仍采用同一预留。失败、
未知执行、普通业务、完整答复、未核实投递及已纠正项不适用,也不解除 Stop、
撤权、忙会话和原生背景工作门禁。上下文纠正不结清原业务。
纠正只推进该请求的预留版本,沿用既有只读 client 后缀,使已支持补答的旧 reader
仍识别受限角色;正确交付原上下文需要更新后的运行版本,兼容不证明降级后的答复质量。

provider 每次核对至多 64 条未核验原消息,形成完整的来源控制清单;数量更大、清单
不完整或存在无法读取的原消息时继续保留等待,原实时接收优先。含有此类预留的存储
降级到不支持仅补答案协议的旧 reader,不在本恢复契约内。
若重启发生在取得队列 claim 后、原生派发前,worker 沿正常准备与执行屏障采用同一
未启动 Turn,不空等 queued 自己变成 running;尚未发布的受管接收和已启动工作仍由
各自原 owner 处理。
Expand Down
17 changes: 17 additions & 0 deletions docs/reference/handoff-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ For an eligible task edit without explicit proof, recovery now offers ordinary
through inspect/acquire/release. Expired or replaced references remain rejected;
recovery advice itself never grants execution or changes state.

If the refused edit supplied `--update-expected-provider-revision`, reclaiming
execution does not validate that old revision, even when the claim reports no
Todo change. Recovery then gives a targeted `todo list --todo-id ...` step after
claiming. Reconcile that Todo with the intended edit before filling the retry's
claim reference and `authority_read.provider_revision` from the read. No full
Goal reread is needed by this recovery contract. The CLI does not replace the
CAS automatically; a later concurrent edit still rejects the retry. Keep the
original operation id and reconciled intent. Calls without an explicit CAS
retain the direct claim-and-retry path, and recovery never overrides a Stop.

An expired hard-lease execution can be reclaimed with ordinary `todo claim`
by an eligible registered Agent. When the expired holder still matches the Todo
claim, the claim owner and a fresh execution generation change in one canonical
Expand Down Expand Up @@ -363,6 +373,13 @@ claim+lease 均不可执行,必须先审核迁移;合并式 claim+lease 仍
提示不包含 execution key、不授予权限,也不修改 mode 或 provider。并发造成 version
变化时重新读取,不能绕过 CAS;dry-run 和拒绝路径不产生 Todo 或租约写入。

如果被拒绝的编辑提供了 `--update-expected-provider-revision`,重新认领不证明旧版本
仍可用于编辑,即使认领回包显示 Todo 没有变化。恢复提示会在认领后给出只读取该 Todo
的 `todo list --todo-id ...`。核对当前内容与原编辑意图后,再把新认领引用和这次读取的
`authority_read.provider_revision` 填入重试命令;此恢复契约不要求重读整个 Goal。
CLI 不会自动替换 CAS,读取后若再有并发修改,重试仍会拒绝。保留原 operation id 和
核对后的意图。不带显式 CAS 的调用保持直接认领并重试,恢复也不会覆盖 Stop。

修改已 deferred Todo 的等待条件若被拒绝,恢复命令先以 `--status open
--clear-resume-when`、审核后的 reason、新 update operation 和当前 provider CAS
显式恢复原任务;读回后,再用另一个 operation 和新的 CAS 提交审核后的延期。
Expand Down
30 changes: 30 additions & 0 deletions loopx/capabilities/native_chat/codex_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ def manager_migration(self, *, refresh: bool) -> bool:
return bool(is_manager_channel(self.session.get("channel_id")) and self.session.get("upstream_mode") != UPSTREAM_MODE
and (self.session.get("manager_context_version") != MANAGER_CONTEXT_VERSION or refresh or self.session.get("answer_review") is True))

@staticmethod
def manager_profile_changed(session: Mapping[str, Any], manager_runtime: Mapping[str, Any] | None) -> bool:
"""Use the same runtime fields when deciding native context restoration."""
from ..manager_runtime import manager_runtime_session_fields
return bool(manager_runtime is not None
and (session.get("manager_runtime_profile") is not None or manager_runtime.get("runtime_profile") != "restricted")
and any(session.get(key) != value for key, value in manager_runtime_session_fields(manager_runtime).items()))

def provider_migration(self, stored: list[dict[str, Any]]) -> bool:
from ...chat_codex_goal import UPSTREAM_MODE
return bool(self.session.get("agent_id") == "codex" and self.session.get("upstream_mode") not in {"chat", UPSTREAM_MODE}
Expand Down Expand Up @@ -449,6 +457,28 @@ def completed_work_observation(self, *, thread_ids: list[str], turn_id: str,
return None
return completed_work_readback(self.session, turn_id, known_turn_ids=known_turn_ids)

def answer_review_replaceable(self) -> bool:
"""Observe whether replacing this live transport would abandon work.

This is not proof of settled business effects and never resumes a
thread. Both observations use the original owning process.
"""
if not self.healthcheck():
return False
deadline = time.monotonic() + 5
try:
def inventory():
return list(self.session._control_items("thread/turns/list",
{"sortDirection": "desc", "itemsView": "summary"}, deadline))
turns = inventory()
ids = [row.get("id") for row in turns]
return (bool(turns) and all(isinstance(ref, str) and ref for ref in ids)
and len(ids) == len(set(ids))
and all(row.get("status") in {"completed", "failed", "interrupted"} for row in turns)
and not self.session._background_terminals(deadline) and inventory() == turns)
except (RuntimeError, ValueError, OSError):
return False

@staticmethod
def failed_request_observation(*, codex_bin: str, codex_home: Path,
thread_ids: list[str], turn_id: str,
Expand Down
Loading
Loading