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
52 changes: 38 additions & 14 deletions docs/integrations/codex-subagent-orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,23 @@ loopx quota should-run \
--available-capability peer_agent_activation
```

The contract includes only peer lanes that are currently actionable. Dormant
registered agents and closed, blocked, or deferred todos are not coordinator
candidates. A dormant or non-resumable lane is projected under
`blocked_peer_lanes`; if no peer lane can run, the bundle has
The peer contract is scoped to `execution_scope=peer_agent_activation`.
Its `task_selection=canonical_claimed_candidates` identifies open claimed
Todo candidates, not the task pinned to a running session. Canonical inventory
outranks display rows; all eligible tasks for the same peer remain visible.
Large inventories stay in the full decision. The thin TurnEnvelope preserves
scoped gates, counts and a signed content hash plus a required detail read;
counts alone never authorize a task selection. Native child lanes are preserved
when only their nested peer diagnostic needs compaction.
Only currently actionable candidates appear under `eligible_peer_lanes`.
Closed, blocked, or deferred Todos are excluded. An open candidate whose peer
is dormant or whose dependency is not ready appears under `blocked_peer_lanes`;
if no peer lane can run, the bundle has
`execution_state=blocked`,
`terminal_outcome=blocked`, and `retry_policy=material_peer_state_change_only`.
When native child admission succeeds, its adaptive contract remains actionable
and retains the blocked peer contract as `peer_activation_diagnostic`. Neither
path grants permission to use a different entrypoint.
That blocked diagnostic does not replace the coordinator's own runnable lane or
re-arm an activation obligation on every heartbeat. If the coordinator also
has no in-scope runnable fallback, the final interaction mode is
Expand Down Expand Up @@ -467,23 +478,36 @@ At `before_plan`, `loopx agent-context` considers at most six bindings authorize
for the current requester and projects as many as fit the existing context byte
budget; `authorized_count` and `routes_truncated` make omissions explicit. Each
route carries only binding/Agent/Todo/runtime
identity, `ready|blocked|unknown`, an optional public-safe execution profile and
identity, separate `runtime_readiness` and `readiness` observations, an optional public-safe execution profile and
one stable `loopx delegation` entrypoint. A separate, explicit
`agent-context --phase after_delegate_result` read may include bounded
operation-status and recovery-required counts. Automatic planning and managed
return paths do not enumerate the operation journal. These reads do not launch,
resume or accept a worker, and they never expose raw host
arguments, workspaces, output references, credentials or child results.

Runtime availability and business adoption are separate. `ready` says that the
existing runtime owner did not find a launch blocker; it does not select the
route, establish task fit or prove execution. `blocked` or `unknown` never
prevents useful native work. Before dispatch, the coordinator must use the
authorized binding, recheck its runtime/model/budget and record a stable
operation id; it must not silently substitute another runtime or model. No
heartbeat is required to use every route. When Lark or another managed surface
exposes these facts, it must consume the same signed capability context rather
than own another route configuration.
Runtime availability, entrypoint admission and business adoption are separate.
`runtime_readiness=ready` only reports the existing runtime probe. Planning does
not run the execution preflight: `execution_scope=bound_delegation` and
`preflight=required` accompany `readiness=unknown` (or `blocked` for a known
runtime failure). Use `loopx delegation inspect` on the selected binding to
check canonical authority, task validation and Turn admission before dispatch.
A peer-activation blocker does not assess this route or native children; neither
does a successful runtime probe bypass authority promotion or another gate.
Before dispatch, recheck runtime/model/budget and record a stable operation id;
never silently substitute a runtime or model. User preferences guide independent
batch selection; no heartbeat must relaunch every route. Lark and other managed
surfaces consume this same capability context, not another route configuration.

中文:peer 合约的阻塞范围仅为 `peer_agent_activation`,候选来自完整权威任务源,
不再以显示列表第一条任务冒充绑定;同一 Agent 的多条候选保留。
候选过多时,全量决策保留全部条目,简版携带状态、数量、哈希和必读详情引用;
不能凭数量选择任务。若 native 子 Agent
通过独立准入,使用 adaptive 合约并保留 `peer_activation_diagnostic`,不因另一个
入口受阻而提前返回。运行库/凭据可用只记为 `runtime_readiness`;委派入口尚未预检时
`readiness=unknown`、`preflight=required`,通过现有 `loopx delegation inspect`
核验权威状态、任务验证与 Turn 准入。未知不等于禁用,ready 运行库也不等于可执行。
用户的异构偏好影响批次选择,不要求每次心跳重启所有路线,不绕过任一真实门禁。

中文:可在现有 `multi_subagent` 能力中配置
`.loopx/config/delegations.json` 指针,让当前请求 Agent 在规划前看到自己已获授权的
Expand Down
4 changes: 3 additions & 1 deletion examples/codex-subagent-orchestration-contract-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
'"agent_model": "peer_v1"',
"independent worktrees",
"Review remains `action_kind=review`",
"Dormant registered agents and closed, blocked, or deferred todos are not coordinator candidates.",
"Only currently actionable candidates appear under `eligible_peer_lanes`.",
"Closed, blocked, or deferred Todos are excluded.",
"appears under `blocked_peer_lanes`",
)

FORBIDDEN_PHRASES = (
Expand Down
5 changes: 4 additions & 1 deletion loopx/control_plane/collaboration/delegation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def _route(binding: dict[str, Any]) -> dict[str, Any]:
"todo_id": binding["todo_id"],
"runtime_id": executor.get("executor") or "unknown",
"executor_kind": executor.get("executor_kind") or "generic",
"readiness": readiness,
"runtime_readiness": readiness,
"readiness": "blocked" if available is False else "unknown",
}
profile = str(executor.get("execution_profile") or "").strip()
if profile:
Expand Down Expand Up @@ -116,6 +117,8 @@ def project_delegation_context(
result = {
"schema_version": "loopx_delegation_context_v0",
"configuration_state": "ready",
"execution_scope": "bound_delegation",
"preflight": "required",
"observed_at": observed_at,
"authorized_count": len(bindings),
"projected_count": len(routes),
Expand Down
5 changes: 5 additions & 0 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {selectPeriodicReportProgress, selectPeriodicReportApprovalRetry} from "./capabilities/periodic_report_progress.ts";
import {planIssueFixMonitorReconciliation} from "./capabilities/issue_fix_monitor_reconciliation.ts";
import {projectPeerOrchestration} from "./quota/peer_orchestration.ts";
import {inspectTaskLease} from "./work_items/task_lease_inspection.ts";
import {evaluateTodoPriority} from "./todos/priority.ts";
import {evaluateUserCompletion} from "./todos/user_completion.ts";
Expand Down Expand Up @@ -623,6 +624,10 @@ export function createEffectRuntimeHandlers(
"governed_capability.settlement_status",
(params) => governedCapabilitySettlementStatus(params.failure),
],
[
"quota.peer_orchestration.project",
(params) => projectPeerOrchestration(params),
],
[
"capability_hook.agent_context.describe",
() => describeSubagentContext(),
Expand Down
62 changes: 62 additions & 0 deletions loopx/control_plane/quota/peer_orchestration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/** Peer activation admission only; native children and bound delegation have separate gates. */
import type { JsonObject } from "../effect_program.ts";
import { jsonObject, requireJsonObject } from "../runtime_decode.ts";

type ActivationState = "ready" | "blocked";
const activeStates = new Set(["running", "monitoring", "executing", "bound", "launchable"]);
const rows = (value: unknown): JsonObject[] => Array.isArray(value)
? value.flatMap(item => { const row = jsonObject(item); return row ? [row] : []; }) : [];

export function projectPeerOrchestration(value: unknown): JsonObject | null {
const input = requireJsonObject(value, "peer orchestration");
const coordinator = String(input.agent_id ?? "");
const registered = new Set(Array.isArray(input.registered_agents) ? input.registered_agents : []);
const activation = Array.isArray(input.available_capabilities)
&& input.available_capabilities.includes("peer_agent_activation");
const runtime = new Map(rows(input.agents).map(row => [row.agent_id, row]));
// A peer can own several open tasks. Never let the first display row hide
// another canonical task, or describe a claimed candidate as a session binding.
const candidates = rows(input.items).filter(row => row.done !== true
&& ["", "open"].includes(String(row.status ?? "").trim().toLowerCase())
&& row.task_class === "advancement_task" && row.claimed_by
&& row.claimed_by !== coordinator && registered.has(row.claimed_by)
&& typeof row.todo_id === "string" && row.todo_id.length > 0);
const unique = new Map(candidates.map(row => [JSON.stringify([row.claimed_by, row.todo_id]), row]));
const eligible: JsonObject[] = [], blocked: JsonObject[] = [];
for (const row of [...unique.values()].sort((a, b) =>
String(a.claimed_by).localeCompare(String(b.claimed_by))
|| String(a.todo_id).localeCompare(String(b.todo_id)))) {
const lane: JsonObject = {
agent_id: row.claimed_by, todo_id: row.todo_id,
priority: row.priority ?? null, task_class: row.task_class,
action_kind: row.action_kind ?? null,
title: String(row.title ?? row.text ?? "").trim(),
resume_when: row.resume_when ?? null, resume_ready: row.resume_ready ?? null,
};
const reasons: string[] = [];
if (!activation) reasons.push("peer_agent_activation_unavailable");
const peer = runtime.get(row.claimed_by);
if (!peer) reasons.push("peer_liveness_unavailable");
else if (peer.stale_claim_hint) reasons.push("peer_runtime_stale");
else if (!activeStates.has(String(peer.state))) reasons.push("peer_runtime_not_active");
if (lane.resume_when && lane.resume_ready !== true) reasons.push("peer_lane_not_resume_ready");
if (reasons.length) blocked.push({ ...lane, reason_codes: reasons });
else eligible.push(lane);
}
if (!eligible.length && !blocked.length) return null;
const state: ActivationState = eligible.length ? "ready" : "blocked";
return {
schema_version: "task_orchestration_contract_v1", mode: "task_scoped_peer",
coordinator_agent_id: coordinator,
execution_scope: "peer_agent_activation", task_selection: "canonical_claimed_candidates",
execution_state: state, activation_required: eligible.length > 0,
activation_allowed: activation, required_capability: "peer_agent_activation",
eligible_peer_lanes: eligible, blocked_peer_lanes: blocked,
retry_policy: "material_peer_state_change_only",
terminal_outcome: state === "blocked" ? "blocked" : null,
writeback_owner: "task_coordinator",
coordinator_obligation: eligible.length
? "Activate or resume eligible peer tasks; multiple tasks may share a peer. Review returned evidence before writeback."
: "Peer activation is blocked for these tasks; retry this entrypoint only after its gates change. This does not assess native children or bound delegation; inspect their own admission before use.",
};
}
Loading
Loading