Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Peer directory projection and inherited source evidence

## Outcome and ownership

This bounded T3 projection migration removes Python's second directory decision
owner. The public `agent-directory` caller keeps its command, handler and
`build_peer_agent_directory()` signature. One `agent.directory.project` request
owns membership, work fields, route publication caps, counts, cursor,
limitations and page-local rollup, and invokes `sealProjectionEnvelope` directly
in the same TypeScript process.

Python retains source collection, public-output safety checks and request/result
compatibility. Its `_work_block()`, `_rollup()` and replaced directory assembly
branches are retired, with no semantic fallback on bridge failure. The existing
management projection remains the owner of Todo selection and management state;
the existing binding owner remains the route interpreter. Neither is copied
into the directory projector.

## Compatibility and authority

Membership uses the complete registered set, including Agents outside the
24-row display page. Unregistered historical claimants remain diagnostic rows,
not members. `omitted_row_count` keeps its whole-directory definition on later
and empty pages. Rollup category counts cover the returned page before its
eight-item display cap. Route counts include withheld candidates even though
at most three publishable candidates are shown. Unavailable, empty and partial
execution facts retain their distinct meanings, as do false, null and absent
fields.

The only intended public additions are `projection_envelope` and necessary
source-warning presentation. The envelope inherits upstream source observations
rather than replacing them with directory assembly time. Missing evidence stays
unknown; source incompleteness is distinct from display pagination. This does
not create a presence provider, resolve an exact host route, confer execution
permission or alter claims, leases, persistence or provider defaults. See the
[directory contract](../../../../reference/protocols/peer-agent-directory-and-observation-v0.md#directory-projection-ownership-and-source-evidence).

## Cost and acceptance boundary

The fixed implementation baseline is `faab5f5c291c5359216a0f944fe63fcfbaacdedc`.
The previous directory assembly was local Python work; this change adds one
coarse-grained runtime request to that stage. It does **not** make the entire
CLI a one-request operation. Rule ownership and retained source evidence are
the intended benefit; moving code alone is not evidence of a speedup or better
whole-Goal outcomes.

## Measured candidate qualification

Candidate `9d91c5bc178a0acddbbab3276b404b9d0ceada3f` was checked against the
fixed baseline above. The results below qualify this bounded synthetic fixture,
not a universal performance guarantee or completion of T4:

- Native TypeScript tests: 6,129 passed, 77 skipped; focused Python tests:
33 passed. Randomized comparison covered 160 parity cases. Both independent
mutations (page-only membership and refreshing old observation timestamps)
were caught.
- Eight actual CLI baseline/head pairs covered File and SQLite first page,
last page, cursor past the end and unregistered caller, including a registered
caller beyond the first page. Existing fields, order, diagnostics and exit
status matched; the new envelope was checked separately. Registry, Goal,
canonical authority and fence data stayed unchanged. Initial SQLite reads
created empty WAL/SHM sidecars, so this is not a zero-filesystem-write claim.
- Fresh wheel and sdist environments passed 16 installed console commands
across both providers and the four cases, outside the checkout with installed
import provenance and matching runtime-source hashes. Minimum-Node and
Windows qualification were not performed.
- Premerge exited 0 with an inherited advisory. Skips and the advisory remain
limitations, not additional passed checks.

### Whole-command cost

Linux, Python 3.12.14 and Node 24.22.1 measurements used identical synthetic
28-Agent canonical File/SQLite fixtures with an empty work set, explicit runtime
roots and usage telemetry disabled. The final alternating baseline/head run
contained 120 command executions: 20 warm and 10 cold samples per provider and
version. Warm means a new Python process using a running TS server; cold means
a new Python process and isolated runtime directory without a TS server. OS
filesystem caches were not cleared. Times include the complete subprocess;
p95 uses nearest rank on these small samples.

| Provider/runtime | Baseline p50 / p95 (ms) | Candidate p50 / p95 (ms) |
| --- | --- | --- |
| File warm | 348.85 / 397.95 | 348.14 / 380.69 |
| File cold | 949.17 / 986.70 | 950.08 / 1026.45 |
| SQLite warm | 342.77 / 396.21 | 338.94 / 373.52 |
| SQLite cold | 947.77 / 1020.42 | 953.57 / 1078.73 |

Every measured command went from five to six runtime requests. First-page
stdout grew from 9,330 to 11,384 UTF-8 bytes (+2,054; 22.0%). Cold p95 rose
39.75 ms (4.0%) for File and 58.31 ms (5.7%) for SQLite; lower warm samples
do not establish a speedup or statistical significance. This records the
additional crossing and envelope cost, not a latency or release qualification.
Earlier CLI optimization results do not qualify this candidate.

The first paired attempt reused long-idle runtime directories and hit a SQLite
transport failure in the unchanged `explore.configuration.goal` call before
directory execution (exit 1). The underlying cause is not established. That
attempt's results remain separate negative evidence. A complete rerun with fresh
isolated runtime directories passed all 120 measurements with unchanged fixture
hashes; it was not a selective retry of the failing sample. Long-idle runtime
reliability remains unqualified. This checkpoint does not close T4 or change
release qualification.

## Bridge exit and rollback

Delete the remaining Python adapter when actual directory callers and necessary
fact providers can call the native owner directly. Until then it only supplies
facts, public safety and compatibility transport. No new store, dual-write or
persisted migration is introduced; rollback restores the previous code package.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Peer Agent 目录投影与上游来源依据

## 目标与所有权

本次有界 T3 投影迁移删除 Python 中第二份目录决策实现。公开 `agent-directory`
保持原命令、handler 和 `build_peer_agent_directory()` 签名。一次
`agent.directory.project` 请求拥有成员检查、工作字段、路由展示上限、计数、
cursor、limitations 和当前页 rollup,并在同一 TypeScript 进程内直接调用
`sealProjectionEnvelope`。

Python 保留来源收集、公共输出安全检查及请求/结果兼容校验;删除 `_work_block()`、
`_rollup()` 和被替换的目录组装分支,bridge 失败时不回退到 Python 语义实现。
既有管理投影继续拥有 Todo 选择和管理状态,既有绑定 owner 继续解释路由;
目录投影不复制这两者的算法。

## 兼容与权限

成员资格依据完整注册集,包括不在当前 24 行展示页的 Agent。历史未注册 claimant
仍是诊断行,不取得成员资格。后续页和空页的 `omitted_row_count` 保留整份目录的
既有定义。rollup 分类计数覆盖当前返回页,不受八项展示上限影响;路由计数包含
被隐去的候选,即使只展示最多三个可公开候选。execution facts 未取得、已取得空集
与部分覆盖保持区别,false、null 和字段缺失也保留各自语义。

唯一计划内的公开增量是 `projection_envelope` 及必要的来源告警展示。envelope
继承上游来源观察时间,不以目录组装时间替换;缺失依据保持未知,来源不完整与
展示分页分别记录。本次不增加 presence provider,不解析精确宿主路由,不授予
执行权限,不改变 claim、lease、持久化格式或 provider 默认值。见
[目录契约](../../../../reference/protocols/peer-agent-directory-and-observation-v0.md#directory-projection-ownership-and-source-evidence)。

## 成本与验收边界

固定实现基线为 `faab5f5c291c5359216a0f944fe63fcfbaacdedc`。此前目录组装为
Python 本地工作,本次为该阶段增加一次粗粒度运行时请求;这**不代表整个 CLI
只有一次请求**。收益是目录规则收口及保留来源依据,代码迁移本身不能证明加速
或整 Goal 成果改善。

## 候选实测资格

候选 `9d91c5bc178a0acddbbab3276b404b9d0ceada3f` 与上述固定基线比较。
以下结果只覆盖本次有界合成 fixture,不代表通用性能保证或 T4 完成:

- 原生 TypeScript 测试 6,129 项通过、77 项跳过;focused Python 测试 33 项
通过。随机差分覆盖 160 个等价案例;仅以当前页判断成员、以当前时间刷新旧
观察时间这两种独立变异均被检出。
- 八组实际 CLI baseline/head 配对覆盖 File/SQLite 首页、末页、越界 cursor
和未注册调用方,包括位于首页之外的注册调用方。旧字段、顺序、诊断及退出状态
一致,新增 envelope 单独核对。registry、Goal、canonical authority 和 fence
数据不变;SQLite 初次读取创建了空 WAL/SHM sidecar,因此不宣称零文件系统写入。
- 独立 wheel/sdist 环境在 checkout 外通过 16 条安装态 console 命令,覆盖
两个 provider 和四种案例;确认导入自安装目录且运行时源码 hash 一致。
未执行最低 Node 版本或 Windows 资格验证。
- Premerge 退出码为 0,并保留一项继承的 advisory。跳过项与 advisory 仍是
验收限制,不能算成额外通过的检查。

### 完整命令成本

测量环境为 Linux、Python 3.12.14、Node 24.22.1,使用相同的合成 28-Agent
canonical File/SQLite fixture,工作集为空,显式指定 runtime root 并关闭
usage telemetry。最终交替 baseline/head 的测量共 120 次命令执行:每个
provider、每个版本各 20 次暖态和 10 次冷态。暖态为新 Python 进程复用运行中
TS server;冷态为新 Python 进程及无 TS server 的隔离 runtime 目录;未清除
操作系统文件缓存。计时包含整个子进程,小样本 p95 使用 nearest-rank 方法。

| Provider/runtime | 基线 p50/p95(ms) | 候选 p50/p95(ms) |
| --- | --- | --- |
| File 暖态 | 348.85 / 397.95 | 348.14 / 380.69 |
| File 冷态 | 949.17 / 986.70 | 950.08 / 1026.45 |
| SQLite 暖态 | 342.77 / 396.21 | 338.94 / 373.52 |
| SQLite 冷态 | 947.77 / 1020.42 | 953.57 / 1078.73 |

每次测量的运行时请求数均从五次增至六次。首页 stdout 从 9,330 增至 11,384
UTF-8 字节(增加 2,054,22.0%)。File 冷态 p95 增加 39.75 ms(4.0%),
SQLite 增加 58.31 ms(5.7%);较低的暖态样本不能证明加速或统计显著性。
本记录量化新增 crossing 和 envelope 的成本,不授予延迟或发布资格;此前的
CLI 优化结果不能资格化本候选。

首轮配对尝试复用了长时间空闲的 runtime 目录,在目录执行前、未改动的
`explore.configuration.goal` 调用中遇到一次 SQLite transport 失败,退出码为 1;
确切根因尚未确认。该轮结果作为独立负面证据保留。随后使用全新隔离 runtime
目录完整重跑,120 次测量全部通过且 fixture hash 不变,并非仅重试失败样本。
长时间空闲 runtime 的可靠性仍未获资格确认。本检查点不关闭 T4,也不改变发布资格。

## Bridge 退出与回滚

当真实目录调用方及必要事实来源可直接调用原生 owner 时,删除剩余 Python
适配器;此前它只负责事实、公共安全与兼容传输。本次不新增存储、双写或持久化
迁移,回滚仅恢复上一版代码包。
Original file line number Diff line number Diff line change
Expand Up @@ -2856,3 +2856,4 @@ with shared write/read status-phase checks. See the [2026-10-02 checkpoint](ledg
- Progress-review receipt qualification: [2026-11-02 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-02-progress-review-qualification.md).
- Global Todo readiness complete owner cutover (both consumers): [2026-11-02 ledger](ledger/typescript-control-plane-migration-v0/2026-11-02-global-todo-readiness.md).
- Selected Todo source read inside the context transaction: [2026-11-04](ledger/typescript-control-plane-migration-v0/2026-11-04-selected-context-source-read.md).
- Peer directory projection and inherited source evidence: [2026-11-06 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-06-peer-directory.md).
Original file line number Diff line number Diff line change
Expand Up @@ -2118,3 +2118,4 @@ SQLite 中位数略升,收益不能外推为全面加速。这只资格化本
- 进度评审 receipt 资格判定:[2026-11-02 检查点](ledger/typescript-control-plane-migration-v0/2026-11-02-progress-review-qualification.zh-CN.md)。
- Global Todo 就绪分类完整 owner 切换(两个消费者):[2026-11-02 记录](ledger/typescript-control-plane-migration-v0/2026-11-02-global-todo-readiness.zh-CN.md)。
- 当前 Todo 来源读取并入上下文事务:[2026-11-04](ledger/typescript-control-plane-migration-v0/2026-11-04-selected-context-source-read.zh-CN.md).
- Peer Agent 目录投影与上游来源依据:[2026-11-06 检查点](ledger/typescript-control-plane-migration-v0/2026-11-06-peer-directory.zh-CN.md)。
1 change: 1 addition & 0 deletions docs/reference/contracts/projection-envelope-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Python-owned projections do so through `loopx/control_plane/projection_envelope_

| Projection | `projection` | Coverage scope |
| --- | --- | --- |
| `loopx agent-directory` | `peer_agent_directory` | `goal_registered_agents` |
| `loopx status` | `status` | `registry`, `goal` with `--goal-id`, or `activation.<state>` |
| `loopx status --use-projection-cache` hit | `status`, `served_from_cache: true` | as stored |
| `loopx global-summary` | `global_summary` | `global` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,11 @@ is omitted, and delivery remains available through the durable hand-off path.

The first producer is `loopx agent-directory --goal-id <goal> [--agent-id
<caller>] [--cursor <agent-id>]` (`loopx/cli_commands/agent_directory.py` over
`loopx/control_plane/agents/directory.py`). It reads the Goal's existing agent
management projection, so identity, work, claims and staleness keep their
current owners and the packet adds no second read of the registry or a lease
store. It is the same surface for both audiences: a peer Agent inside the Goal
`loopx/control_plane/agents/directory.py`, which supplies facts to the single
`agent.directory.project` TypeScript request). It consumes the Goal's existing
agent management projection and complete registration facts. Management work
selection and binding-route interpretation keep their existing owners; the
directory does not introduce a second lease store or routing resolver. It is the same surface for both audiences: a peer Agent inside the Goal
and the steward channel call one command.

What it emits, and what it refuses to imply:
Expand Down Expand Up @@ -438,6 +439,42 @@ What it emits, and what it refuses to imply:
It writes nothing. Reading it grants no claim, no lease, no priority and no work
edit.

### Directory projection ownership and source evidence

The internal request uses `peer_agent_directory_project_request_v0`; the public
packet remains `peer_agent_directory_v0`.
`loopx/control_plane/agents/directory.ts` owns directory membership checks,
work-field projection, route display limits, counts, continuation, limitations,
and the attention rollup. The rollup covers the returned page only; its category
counts include all matching page rows even when displayed items are capped at
eight. The Python adapter retains fact collection, the existing public-output
safety checks, transport and compatibility validation. A failed typed request
must not fall back to a Python implementation of directory decisions.

The packet also carries the shared
[`projection_envelope`](../contracts/projection-envelope-contract.md). This is
a `loopx_projection_envelope_v0` envelope with projection `peer_agent_directory`,
an intentional additive field, not a change to `peer_agent_directory_v0`'s
existing row, scope, cursor or count semantics. The directory calls the existing
`sealProjectionEnvelope` owner in-process during its one projection request;
it does not add a separate sealing RPC or freshness algorithm.

Upstream source rows and observation times survive composition. `collected_at`
is the directory assembly time, not proof that its sources were just read.
Serving or replaying a projection cannot refresh those source observations.
Stale, unreadable and incomplete upstream evidence remains visible; absent
upstream evidence is unknown, never freshly complete. A display page limit is
recorded separately from missing source coverage: undisplayed registered Agents
are not by themselves an unreadable source. Unavailable execution facts, an
observed empty fact set and partial Agent coverage remain distinct, and an
unknown lease must not become an absent lease.

This read adds no presence, delivery, claim, lease or execution authority. No
persisted format or provider default changes. Retire the Python adapter when
the real directory callers and necessary fact providers can invoke the typed
owner directly; until then it is a compatibility boundary, not a second policy
owner. Rollback restores the prior code package without migrating business state.

## Reference Implementation: Herdr

[Herdr](https://github.com/herdrdev/herdr) is a terminal-space provider whose
Expand Down
6 changes: 4 additions & 2 deletions loopx/cli_commands/agent_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

from ..agent_registry import load_goal_from_registry
from ..control_plane.agents.directory import build_peer_agent_directory
from ..control_plane.effect_runtime import EffectRuntimeRemoteError
from ..control_plane.effect_runtime import EffectRuntimeRemoteError, EffectRuntimeStartupError
from ..control_plane.projection_envelope_facts import render_projection_envelope_markdown
from ..status import AUTONOMOUS_REPLAN_PERIODIC_LOOKBACK, collect_status


Expand Down Expand Up @@ -68,7 +69,7 @@ def handle_agent_directory(args, registry_path, runtime_root, print_payload, out
caller_agent_id=args.agent_id,
cursor=args.cursor,
)
except (ValueError, TypeError, OSError, KeyError, EffectRuntimeRemoteError) as exc:
except (ValueError, TypeError, OSError, KeyError, EffectRuntimeRemoteError, EffectRuntimeStartupError) as exc:
payload = {
"ok": False,
"schema_version": "peer_agent_directory_v0",
Expand Down Expand Up @@ -131,6 +132,7 @@ def render_agent_directory(payload):
presence = payload.get("presence_coverage") or {}
lines.append(f"presence: {presence.get('state')} — {presence.get('note')}")
lines.append("limitations: " + ", ".join(payload.get("limitations") or []))
lines.extend(render_projection_envelope_markdown(payload.get("projection_envelope")))
lines.append(
"Observation and delivery grant no claim, lease, priority or work edit."
)
Expand Down
Loading
Loading