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
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ Audited at `6c3da75ca`. These are current facts, not proposed behavior.
| Attached broker | [`loopx/attached_session.py`](../../../loopx/attached_session.py) implements bind, claim, and complete under `loopx_attached_agent_session_broker_v0`, adapter kind `attached_host_session`, upstream mode `host_broker`, with a bounded claim wait of 1800 seconds, duplicate-safe claim and completion receipts, and per-binding file locks. |
| Runtime fencing | [`loopx/chat_runtime.py`](../../../loopx/chat_runtime.py) never starts a managed adapter for an attached session and fails closed with typed errors such as `attached_session_live_steering_unavailable`, `live_steering_requires_active_turn`, and `live_steering_session_not_attached`. |
| CLI surface | `loopx worker-bridge attached-session-bind`, `-list`, `-claim`, and `-complete` exist in [`loopx/cli_commands/worker_bridge.py`](../../../loopx/cli_commands/worker_bridge.py), documented in the [broker guide](../../integrations/attached-agent-session-broker.md) and the [worker-bridge install contract](../../integrations/worker-bridge-install-contract.md). |
| Existing-session delegation | [`loopx delegation`](../../reference/local-delegation.md#use-an-existing-agent-conversation-through-its-shell) exposes the same explicitly bound work as MCP to an existing shell-capable Agent. It retains the caller conversation and original operation on reconnect; it does not provision an Agent, migrate a host or install an automatic wake policy. |
| Focused tests | [`tests/test_attached_session_cli.py`](../../../tests/test_attached_session_cli.py) and `tests/test_chat_codex_home.py::test_attached_session_uses_existing_host_not_managed_adapter` cover bind/claim/complete and the no-managed-adapter fence. |
| Product-level proposal | The [Desktop execution frontends RFC](desktop-execution-frontends-v0.md) owns the Mode A/Mode B product comparison, the connector and event-source orthogonality, and the Desktop non-goals. |
| Host-side loop guidance | [Codex CLI TUI loop](../../product/runtimes/codex-cli/codex-cli-tui-loop.md) documents session-attached automation and resume options for one visible host. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ LoopX 启动,另一种已经属于其他宿主。当绑定没有说明自己
| 挂接 broker | [`loopx/attached_session.py`](../../../loopx/attached_session.py) 在 `loopx_attached_agent_session_broker_v0` 下实现 bind/claim/complete,适配器类型 `attached_host_session`,上游模式 `host_broker`,claim 等待上限 1800 秒,claim 与完成回执去重,并按绑定加文件锁。 |
| 运行时围栏 | [`loopx/chat_runtime.py`](../../../loopx/chat_runtime.py) 绝不为挂接会话启动托管适配器,并以类型化错误失败关闭,例如 `attached_session_live_steering_unavailable`、`live_steering_requires_active_turn`、`live_steering_session_not_attached`。 |
| CLI 面 | `loopx worker-bridge attached-session-bind`、`-list`、`-claim`、`-complete` 存在于 [`loopx/cli_commands/worker_bridge.py`](../../../loopx/cli_commands/worker_bridge.py),并在 [broker 指南](../../integrations/attached-agent-session-broker.md) 与 [worker-bridge 安装契约](../../integrations/worker-bridge-install-contract.md) 中记录。 |
| 原会话委派 | [`loopx delegation`](../../reference/local-delegation.md#use-an-existing-agent-conversation-through-its-shell) 让有 shell 能力的原 Agent 使用与 MCP 相同的显式执行绑定;重连保留原对话和操作身份,不创建 Agent、不迁移宿主,也不安装自动唤醒策略。 |
| 聚焦测试 | [`tests/test_attached_session_cli.py`](../../../tests/test_attached_session_cli.py) 与 `tests/test_chat_codex_home.py::test_attached_session_uses_existing_host_not_managed_adapter` 覆盖 bind/claim/complete 与"不启动托管适配器"的围栏。 |
| 产品级提案 | [桌面执行前端 RFC](desktop-execution-frontends-v0.zh-CN.md) 拥有 Mode A/Mode B 的产品对比、连接器与事件源正交性,以及桌面端非目标。 |
| 宿主侧循环指引 | [Codex CLI TUI loop](../../product/runtimes/codex-cli/codex-cli-tui-loop.md) 记录了一个可见宿主的会话挂接自动化与恢复选项。 |
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ sessions, generic Agent creation, dynamic governed work derivation, complete
inbox/queue/steer, authenticated remote authority and packaged frontend/Lark
companion work remain R2/R3/R4/R6 boundaries. Existing Goals are not promoted.

An existing shell-capable coordinator can now use `delegation list/start/read/wait/resume` without replacing its session or loading new MCP tools. The synthetic example's `prepare` path creates only isolated operator bindings; the existing Agent chooses and starts the work. This completes the attached-caller entrypoint over the existing execution owner. Dynamic identity/profile provisioning, unattended lead wakeup and full inbox/queue/steer remain separate R2/R3 requirements; fixed binding readback is not fleet readiness.

### R3: Semantic Requests and Automatic Return

- **Owner:** manager RFC M2/M3; migrate existing `manager_context` request/tracking/return into one typed collaboration transaction, incorporating the #4094 adapter.
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ Todo 完成入口分别执行当前 pinned 检查,accepted 返回读 canonical
完成。长期 attached 会话、通用 Agent 创建、动态受治理工作派生、完整 inbox/queue/steer、
认证远端权威与 packaged frontend/Lark 配套仍归 R2/R3/R4/R6;不晋升已有 Goal。

有 shell 能力的原 coordinator 现在可通过 `delegation list/start/read/wait/resume` 调用已有执行 owner,无需替换会话或重新加载 MCP 工具。合成示例的 `prepare` 只准备隔离绑定,由原 Agent 自行选择并启动工作。这闭合原会话调用入口;动态身份/profile 创建、无人值守唤醒和完整 inbox/queue/steer 仍按 R2/R3 推进,固定绑定读回不等于团队全部就绪。

### R3:语义请求与自动回报

- **Owner:** 管家 RFC M2/M3;从已有 `manager_context` request/tracking/return 迁移到单一 typed collaboration 事务,纳入 #4094 adapter。
Expand Down
66 changes: 66 additions & 0 deletions docs/reference/local-delegation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,72 @@ select `generic-cli`, `fresh`, and the optional adapter's `--config` invocation.
Profiles, executables, workspace isolation and credential custody remain the
operator's responsibility. No model tool accepts those values.

## Use an existing Agent conversation through its shell

An attached Codex or other shell-capable Agent can use the same execution
bindings without opening a replacement conversation or adding MCP tools to a
running session. Use its registered requester identity and the exact registry,
runtime and operator configuration; this trusted local CLI is not a remote
authentication boundary.

```bash
delegate() {
loopx --registry "$REGISTRY" --runtime-root "$RUNTIME_ROOT" --format json \
delegation "$@" --goal-id "$GOAL_ID" --agent-id "$AGENT_ID" \
--execution-config "$DELEGATION_CONFIG"
}

delegate list
delegate start --binding-id independent-review --operation-id review-round-1 \
--brief-file request.json --execute
delegate read --operation-id review-round-1
delegate wait --operation-id review-round-1
```

`request.json` contains the same `collaboration_brief_v0` used by MCP:

```json
{
"schema_version": "collaboration_brief_v0",
"purpose": "Independently check the current analysis",
"context": "Reconcile the corrected source with the earlier conclusion.",
"constraints": ["Use only the supplied material; no external actions"],
"inputs": [],
"acceptance": ["Satisfy the task's pinned independent acceptance"],
"return_requirement": "Return evidence, uncertainty and the checked artifact"
}
```

The Agent chooses questions, sequencing and synthesis. After `start` returns,
it can continue its own investigation; closing that CLI process does not stop
the worker. Another invocation reads the original operation. `wait` observes
for a bounded interval and does not start, resume or accept work. `ok: true`
means the command succeeded; inspect `status`, `recovery_required`, `error` and
the independently checked artifacts to determine the work result. Neither a
`running` result nor a saved peer opinion means accepted completion.

After a lost start response, repeat the same start with the same operation id
and brief. If readback reports `recovery_required`, use:

```bash
delegate resume --operation-id review-round-1 --execute
```

Resume keeps the original operation and Turn; it cannot silently retarget
work. A new scope or repair round requires a new operation, still subject to
the configured task, quota and acceptance owners. A member coordinating its
own authorized peers supplies `--parent-request-id` on start. CLI and MCP
share grant validation, detached execution, wait/readback and recovery rather
than maintaining separate rules.

This entrypoint does not create Agents, grant bindings or wake an idle Codex
conversation. The existing host/LoopX continuation policy owns the next lead
turn. The conversation remains persistent independently of whether autonomous
LoopX mode is enabled. Current Dashboard/Lark setup is unchanged; those surfaces
keep their existing conversation and runtime owners.

## Use the same bindings through MCP

Start the existing stdio server with the explicit opt-in:

```bash
Expand Down
34 changes: 34 additions & 0 deletions examples/managed-research-team/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,40 @@ uv run --no-sync --extra test python -m loopx.cli \

## Collaboration path

### Keep an existing Codex or other local lead

Use `prepare` instead of `run` to provision only the disposable fixture and
operator bindings. It makes no model call and does not start another lead
session. Keep the provider setup above, including the existing Environment:

```bash
uv run --no-sync --extra test python examples/managed-research-team/research_team.py \
prepare "$DEMO_ROOT" --model "$ARK_MODEL_ID" --environment-id "$ARK_ENVIRONMENT_ID"
export LOOPX_RESEARCH_DEMO_ROOT="$DEMO_ROOT"

uv run --no-sync --extra test loopx --registry "$DEMO_ROOT/registry.json" \
--runtime-root "$DEMO_ROOT/runtime" --format json delegation list \
--goal-id synthetic-managed-research --agent-id lead \
--execution-config "$DEMO_ROOT/delegation-config.json"
```

The existing Agent then uses [delegation start/read/wait/resume](../../docs/reference/local-delegation.md#use-an-existing-agent-conversation-through-its-shell)
for the listed bindings, writing its own briefs. It reads the synthetic
`input.json` files and returned artifacts, chooses the work order and continues
its own analysis while members run. The nested cloud analyst still requests
its local reviewer through the same service. No business phase argument is
introduced.

After reading all four canonical completions and exact artifact hashes, the
lead writes `lead/report.json` with the fields described by `scenario.py` and
the acceptance table below. Run `validate-report`, then complete the report
through ordinary `todo complete --todo-id todo_lead-report --agent-id lead
--no-follow-up` against this disposable registry/runtime. That command reruns
the bound validator. Retain the original conversation; preparation does not
attach, resume, migrate or impersonate any existing production Agent.

### Member relationships

The primary `local-led` profile has four independently accepted member tasks:

- The local lead delegates initial-filing analysis to local DSH `local-analyst`.
Expand Down
33 changes: 25 additions & 8 deletions examples/managed-research-team/research_team.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,15 @@ def git(*args: str) -> None:
for actor, revision in pairs:
identity = todo_id(actor, revision)
text = (
"Use the research_team MCP tools. Read the assignment with read_assignment. Organize the registered "
"Use the research_team MCP tools or the same delegation CLI from an existing local session. "
"Read the assignment with read_assignment, or inspect the synthetic team/input files. Organize the registered "
"members with list_execution_bindings/start_delegation/wait_delegation to analyze their authorized revisions. "
"Use stable operation ids and collaboration_brief_v0 (purpose, context, constraints, inputs, acceptance, return_requirement). "
"Complete local-analyst before requesting cloud-reviewer, who must adopt its exact artifact. "
"Cloud-analyst is responsible for delegating its local-reviewer prerequisite through the same tools. "
"You can start independent branches concurrently. A running operation is not failure; wait for its original result. "
"Read all final artifacts with read_accepted_evidence. Decide questions and order yourself. Review their "
"accepted results, resolve differences, then write_report with all four evidence hashes. "
"Read all final artifacts with read_accepted_evidence or canonical CLI readback. Decide questions and order yourself. Review their "
"accepted results, resolve differences, then write_report or lead/report.json with all four evidence hashes. "
"Only return validated_progress after write_report confirms independent checks."
if actor == "lead" else
"Read TASK.md and DELEGATION.json, or use read_input/write_output. Read context and assess_request before working. "
Expand Down Expand Up @@ -153,9 +154,7 @@ def launch(root: Path, model: str, environment_id: str, dsh_model: str, topology
raise ValueError("install_loopx_deepseek_harness_extra_in_this_interpreter")
if not os.environ.get("ARK_API_KEY") or not os.environ.get("DEEPSEEK_API_KEY"):
raise ValueError("ARK_API_KEY_and_DEEPSEEK_API_KEY_required")
prepare(root, topology=topology)
write(root / "settings.json", {"dsh_model": dsh_model, "ark_model": model, "environment_id": environment_id})
configure_delegations(root)
prepare_execution(root, model, environment_id, dsh_model, topology)
os.environ["LOOPX_RESEARCH_DEMO_ROOT"] = str(root)
result = turn(root, "lead", "report", root / "lead", [sys.executable, str(HERE / "research_team.py"), "validate-report", str(root)],
host_arguments(root, "lead", "report", host="dsh" if topology == "local-led" else "ark"), 1200)
Expand All @@ -170,19 +169,37 @@ def launch(root: Path, model: str, environment_id: str, dsh_model: str, topology
return summary


def prepare_execution(root: Path, model: str, environment_id: str, dsh_model: str,
topology: str = "local-led") -> dict:
"""Prepare a fresh operator fixture without starting a replacement lead."""
prepare(root, topology=topology)
write(root / "settings.json", {"dsh_model": dsh_model, "ark_model": model, "environment_id": environment_id})
config = configure_delegations(root)
return {"goal_id": GOAL, "agent_id": "lead", "registry": str(root / "registry.json"),
"runtime_root": str(root / "runtime"), "execution_config": str(config),
"workspace": str(root / "lead"), "execution_started": False,
"next_action": "Use delegation list/start/read/wait from the existing Agent session. "
"Supply LOOPX_RESEARCH_DEMO_ROOT and the configured credentials when starting work. "
"Independent task acceptance remains bound; prepare does not complete any task."}


def main() -> None:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("command", choices=["run", "validate-worker", "validate-report"])
p.add_argument("command", choices=["prepare", "run", "validate-worker", "validate-report"])
p.add_argument("root", type=Path)
p.add_argument("--revision", choices=REVISIONS)
p.add_argument("--model", default=os.environ.get("ARK_MODEL_ID"))
p.add_argument("--environment-id", default=os.environ.get("ARK_ENVIRONMENT_ID"))
p.add_argument("--dsh-model", default="deepseek-v4-flash")
p.add_argument("--topology", choices=["local-led", "cloud-led"], default="local-led")
args = p.parse_args()
if args.command == "run":
if args.command in {"prepare", "run"}:
if not args.model or not args.environment_id:
p.error("explicit model and existing environment required")
if args.command == "prepare":
print(json.dumps(prepare_execution(args.root.resolve(), args.model, args.environment_id,
args.dsh_model, args.topology)))
return
result = launch(args.root.resolve(), args.model, args.environment_id, args.dsh_model, args.topology)
print(json.dumps(result))
if result.get("status") != "committed" or result.get("result_kind") != "validated_progress":
Expand Down
3 changes: 3 additions & 0 deletions examples/managed-research-team/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def task(revision: str, question: str) -> str:
"source_id values from issuer, prior and repost; cite the prior filing for the period-comparability check), "
"reason (short). Count independent_source_families only for corroboration of CURRENT-period "
"figures; prior-period comparison material is not current-period corroboration. "
"If read_input returns an upstream artifact, independently check it and include adopted_dependencies "
"in output.json: an object mapping upstream.identity to its full upstream.artifact_sha256. "
"Matching its numbers or mentioning a hash in prose does not record adoption. "
"Do not use network, read another worker, modify Goal state, commit, or trade. "
"Write only output.json. Return the normal Turn candidate after writing the artifact."
)
Expand Down
6 changes: 5 additions & 1 deletion examples/managed-research-team/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ def read_input() -> dict:

@worker_server.tool()
def write_output(output: dict) -> dict:
"""Write only this assignment's output.json; return independent domain-check feedback."""
"""Submit output.json, including adopted_dependencies for any read_input upstream.

That object maps upstream.identity to the full upstream.artifact_sha256.
Return the normal Turn JSON candidate only after artifact_checks_passed.
"""
workspace, revision = worker_workspace()
if len(json.dumps(output)) > 16_000:
raise ValueError("output_too_large")
Expand Down
4 changes: 4 additions & 0 deletions loopx/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .cli_commands.agent_context import register_agent_context, handle_agent_context
from .cli_commands.todo_continuation import register_todo_continuation, handle_todo_continuation
from .cli_commands.manager_inbox import register_manager_inbox, handle_manager_inbox
from .cli_commands.delegation import register_delegation, handle_delegation
from .capabilities.content_ops.cli import (
handle_content_ops_command,
register_content_ops_commands,
Expand Down Expand Up @@ -332,6 +333,7 @@ def build_parser() -> LoopXArgumentParser:
register_project_lifecycle_commands(sub, add_subcommand_format)
register_goal_channel_commands(sub, add_subcommand_format)
register_manager_inbox(sub, add_subcommand_format)
register_delegation(sub, add_subcommand_format)
register_agent_capabilities(sub, add_subcommand_format)
register_agent_context(sub, add_subcommand_format)
register_agent_directory(sub, add_subcommand_format)
Expand Down Expand Up @@ -783,6 +785,8 @@ def main(argv: list[str] | None = None) -> int:

if args.command == "manager-inbox":
return handle_manager_inbox(args, registry_path, effective_runtime_root(registry_path, args.runtime_root))
if args.command == "delegation":
return handle_delegation(args, registry_path, effective_runtime_root(registry_path, args.runtime_root))

lark_inbox_result = handle_lark_inbox_command(
args,
Expand Down
Loading
Loading