Skip to content
Merged
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
20 changes: 20 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 @@ -1846,6 +1846,26 @@ 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.

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 @@ -1205,6 +1205,20 @@ provider 在这次复核与已完成工作的读回中复用同一组被动原

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

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

provider 每次核对至多 64 条未核验原消息,形成完整的来源控制清单;数量更大、清单
不完整或存在无法读取的原消息时继续保留等待,原实时接收优先。含有此类预留的存储
降级到不支持仅补答案协议的旧 reader,不在本恢复契约内。
若重启发生在取得队列 claim 后、原生派发前,worker 沿正常准备与执行屏障采用同一
未启动 Turn,不空等 queued 自己变成 running;尚未发布的受管接收和已启动工作仍由
各自原 owner 处理。
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