Skip to content
Open
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ P0 blocks correctness or continuity in the current user journey. P1 enables repe
| **S4 Runtime/host/daemon · P0/P1** | Attached/managed, Turn, broker, runtime connectors and Desktop repairs exist; registration does not establish executable capacity | Qualify multi-Turn supervision for one real supported combination; restart/cancel/drain/stop retain work and fence old executors. Then expand host parity, unique service-profile ownership, clean installation and upgrades; show unsupported adapter capabilities |
| **S5 Frontend, Lark and human interaction · P0/P1** | Local chat, settings, proposals and partial Goal Channel verticals exist; Goal Overview, Tasks, the W1 work map, usage, acceptance readback and lifecycle stop ship as separate regions without one page owner; shared audience/session/work readback needs qualification | One journey spans settings, work graph, handoff, blockers, cost, corrections, artifacts and return. **Page:** the [Goal workspace](goal-workspace-surface-v0.md) composes that journey per Goal over shared typed projections: a persistent masthead with scoped Stop, vitals with one meaning per figure (named window and coverage, unknown is not zero), work grouped by owner, results and an all-work rail. M1–M2 come first, after first-screen preview approval. **Cases:** reconnect/repeated-click/stale/original-route. **Realtime IM** reuses Chat/Turn: isolate independent conversations on one listener, then qualify ordinary DM onboarding, explicit role selection, busy-session admission, provisional progress, media and exact permission callbacks through [the shared operational contract](capable-manager-semantic-handoff-v0.md#10-operational-contract). **Team:** [Live team workspace](live-team-workspace-v0.md) makes exchange, revision and original-coordinator continuation visible. Its [Work-scale map track](live-team-workspace-v0.md#11-delivery-order-and-relationship-to-aggressive-r2-progress) draws each Goal's typed Todo relations first (W1, the Goal workspace's Map lens), then live state and outputs on the same nodes. Then intelligent review, keyboard accessibility and a launcher-only command palette, bilingual terminology, actionable errors and offline degradation; interrupt only for actual decisions |
| **S6 Materials, evidence, memory and learning · P1** | Authority registry, material lifecycle/frontier, decision context, reward memory and turn recall exist; explicit project material packets and source-verifier gates are implemented, while private source initialization/Core binding and ordinary-conversation adoption remain unqualified; direction baseline and parts of attribution remain proposed | Connect material revision→same-Agent read→decision reference→artifact/outcome. Expose expiry/revocation/source loss and forgetting policy. Handoff preserves decision-relevant summaries and authorized artifacts; qualify OpenViking/Obelisk as optional providers. Prove causal utility with controls, not relevance alone |
| **S7 Budget, scheduling and fleet scale · P0 observation/P1–P2 expansion** | Quota/scheduler and partial usage aggregates exist; full provider cost, distributed reservations and hundred-Agent concurrency need evidence | Separate configured budget, admission, consumption and estimates; unknown is not zero and replay cannot double-charge. R7 pagination/bounded summaries and [complete-history transport](typescript-control-plane-migration-v0.md), including refresh/replay/single-debit evidence beyond the RPC limit; provider/host limits, fairness, backpressure, event wake and isolation; report registration/activity/throughput and cost per accepted outcome separately. Receipt-backed semantic progress closes its original host Turn without completing an open/waiting Todo; File/SQLite guard replay preserves verified writeback when a later dependency wait changes the frontier and covers automatic quiet writeback during a receipt-owned retry, preserving independent work and monitor clocks. [Full quota and TurnEnvelope](../../reference/protocols/turn-envelope-v0.md) retain the original executable settlement command and single debit. Follow [the quota settlement contract](../../quota-allocation.md#receipt-backed-settlement-progress). External-wait authoring now derives successor readiness from the locked Todo snapshot through the existing resume owner, covering completed dependencies and monitor generation fences without trusting display flags. File/SQLite CLI coverage also rejects an original Turn’s step write into a newly selected Todo, preserves that Todo’s step/CAS and dependencies, and settles the original writeback once. Source-qualified continuation, installed adoption, live Host waiting-state cadence/readback and fleet scale remain separate acceptance; this bounded repair does not qualify fleet scale or host latency. |
| **S7 Budget, scheduling and fleet scale · P0 observation/P1–P2 expansion** | Quota/scheduler and partial usage aggregates exist; full provider cost, distributed reservations and hundred-Agent concurrency need evidence | Separate configured budget, admission, consumption and estimates; unknown is not zero and replay cannot double-charge. R7 pagination/bounded summaries and [complete-history transport](typescript-control-plane-migration-v0.md), including refresh/replay/single-debit evidence beyond the RPC limit; provider/host limits, fairness, backpressure, event wake and isolation; report registration/activity/throughput and cost per accepted outcome separately. Receipt-backed semantic progress closes its original host Turn without completing an open/waiting Todo; File/SQLite guard replay preserves verified writeback when a later dependency wait changes the frontier and covers automatic quiet writeback during a receipt-owned retry, preserving independent work and monitor clocks. The typed full-source batch keeps receipt retry identity independently of the earliest wakeup, reconciles earlier retries with retained caches/hidden clocks, and keeps ordinary notices non-gating across earlier/equal/later monitors; File/SQLite replay and canonical readback qualify this source boundary. [Full quota and TurnEnvelope](../../reference/protocols/turn-envelope-v0.md) retain the original executable settlement command and single debit. Follow [the quota settlement contract](../../quota-allocation.md#receipt-backed-settlement-progress). External-wait authoring now derives successor readiness from the locked Todo snapshot through the existing resume owner, covering completed dependencies and monitor generation fences without trusting display flags. File/SQLite CLI coverage also rejects an original Turn’s step write into a newly selected Todo, preserves that Todo’s step/CAS and dependencies, and settles the original writeback once. Source-qualified continuation, installed adoption, live Host waiting-state cadence/readback and fleet scale remain separate acceptance; this bounded repair does not qualify fleet scale or host latency. |
| **S8 Capabilities, extensions and domain integration · P1/P2** | Capability catalog, extension lifecycle, hooks, engineering/research/content/office capabilities and computer-use contracts exist | First exercise the shared control plane with existing issue-fix/PR-review and material/research callers. Every provider has readiness/version/permissions/default-off/uninstall/rollback/isolation and real-entry evidence. New domain effects start with one simulated operation, not a marketplace or workflow DSL |
| **S9 Identity, authority, privacy and trust · continuous P0/P1–P2 remote** | Public/private scope, capability gates, fencing and confirmation contracts belong to existing owners | R1/R3 cover sender/audience/artifact scope and stale authority; R6 authenticates tenant/Goal/actor/host, rotation/revocation and least privilege. Qualify credential custody, untrusted tool/document inputs, dependency supply chain, audit retention/deletion and vulnerability response through real paths; roles/messages/memory mint no write authority |
| **S10 Reliability, diagnostics and operations · P0/P1** | Recovery/canary, read-only diagnostics prototype and DSH event adapter exist; C0/C1, overhead and full operations qualification are open | Failure classification→observable state→recovery drill→regression prevention; process/storage/network/delivery failures and data growth. Accepted Chat requests must settle even when runtime preparation fails before dispatch; qualify missing runtime assets, stop races and recovery without replay under [the shared conversation operational contract](capable-manager-semantic-handoff-v0.md#10-operational-contract). A context/provider read returning after the stop wait must honor the persisted Turn and exact Session claim: no late provider dispatch or handoff, even when a fresh request has completed. This bounded GQ08 repair does not certify upstream interrupt fidelity or effects already admitted elsewhere. Use [bounded repair lookup and targeted diagnostics](../../../skills/loopx-self-repair/references/targeted-diagnostics.md) to reduce redundant reads above the provider boundary; measure backend-specific cold/warm reads, writes and lock waits separately. Freeze SLO/RPO/RTO/capacity/retention boundaries and measure before qualification. Lark transport backpressure and drain retain the App consumer lease; unpersisted buffered lines are not accepted work or completion receipts. Runbooks include upgrade, restore, stop and human takeover; test counts do not prove recovery |
Expand Down
20 changes: 20 additions & 0 deletions docs/quota-allocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,26 @@ Automatic no-spend stall writeback uses the same receipt-owned retry projection
as live selection, including its post-writeback readback. A future unchanged
monitor therefore stays quiet during that wait without failing the heartbeat
receipt or changing the monitor's due time, hash, or material generation.
The typed deadline owner reconciles a newer, earlier receipt-owned retry with
an existing full-source deadline cache; hidden earlier monitor deadlines and
unchanged caches remain intact. The same typed batch derives `blocked_retry_wait`
from qualified full-source receipts independently of `frontier_deadline`, which
still names the earliest wakeup. A non-blocking `user_action` notice therefore
does not turn the retry into a human gate even when an earlier or equal monitor
owns that wakeup. The retry identity and original due time remain visible; the
monitor clock, genuine gate and independent runnable work keep their own rules.
This corrects the automatic notice explanation without adding configuration or
persisted state. The pending retry is a recheck deadline, not execution authority:
current gates and the configured cadence floor still apply at and after due time.

类型化 deadline owner 会把新叠加的、更早到期的 Turn 重试纳入已有的完整来源
缓存,同时保留显示列表之外的更早监控时钟和未变化的缓存。同一个 typed batch
从完整且合格的 receipt 来源独立投影 `blocked_retry_wait`;`frontier_deadline`
仍表示最早唤醒时间。即使更早或同刻的独立 monitor 拥有下一唤醒,普通
`user_action` 提醒也不会把重试解释为人工门禁;原 retry 身份/到期时间、monitor
时钟、真实 gate 和独立可执行工作各自保持。这是自动通知解释的修正,不新增
配置或持久化状态。重试到期仅要求重新检查,不授予执行权限;当前 gate 和
已配置的调度周期下限仍然有效。

`quota should-run` also separates long-running observation from work that should
advance the selected goal. When the selected goal's current projection is a
Expand Down
5 changes: 3 additions & 2 deletions loopx/control_plane/quota/should_run_packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,8 +1291,9 @@ def _build_active_quota_payload(
# gate explanation. Genuine gates and unknown scope keep their old route.
notice_only = (user_summary.get("user_action_open_count", 0) > 0 and
user_summary.get("all_open_count") == user_summary.get("user_action_open_count"))
retry_wait = (retry_deadline.get("blocked_retry")
if notice_only and retry_deadline.get("source") == "turn_settlement" else None)
retry_wait = ((prepared.agent_todo_summary or {}).get("blocked_retry_wait") or
(retry_deadline.get("blocked_retry")
if retry_deadline.get("source") == "turn_settlement" else None)) if notice_only else None
execution_obligation = _execution_obligation(
should_run=route.should_run,
effective_action=route.effective_action,
Expand Down
13 changes: 10 additions & 3 deletions loopx/control_plane/scheduler/scheduler_hint.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,9 +629,11 @@ def build_codex_app_scheduler_fallback_hint(


def _pending_retry_projection(payload: dict[str, Any]) -> dict[str, Any]:
deadline = _dict_or_empty(_dict_or_empty(payload.get("agent_todo_summary")).get("frontier_deadline"))
retry = deadline.get("blocked_retry")
if deadline.get("source") != "turn_settlement" or not isinstance(retry, dict):
summary = _dict_or_empty(payload.get("agent_todo_summary"))
deadline = _dict_or_empty(summary.get("frontier_deadline"))
retry = summary.get("blocked_retry_wait") or (deadline.get("blocked_retry")
if deadline.get("source") == "turn_settlement" else None)
if not isinstance(retry, dict):
return {}
return {"pending_retry": {**retry, "execution_authority_granted": False,
"recheck": "rerun quota at or after due_at; current gates and owner cadence still apply"}}
Expand All @@ -649,6 +651,11 @@ def _receipt_retry_wait_profile(
or due is None or due <= current_time):
return None
horizon = max(1, math.ceil((due - current_time).total_seconds() / 60))
frontier = build_frontier_recheck_plan(payload, current_time=current_time)
if frontier:
# The retry explains the wait, but an independent earlier clock still
# owns the next wakeup. The owner cadence floor is applied afterward.
horizon = min(horizon, max(1, math.ceil(frontier["frontier_recheck_after_seconds"] / 60)))
return {"interval_minutes": horizon, "retry": retry} if horizon < interval_minutes else None


Expand Down
59 changes: 43 additions & 16 deletions loopx/control_plane/todos/quota_selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,17 @@ function utcIso(micros: bigint): string {
const date = new Date(Number(seconds * 1000n)).toISOString().slice(0, -5);
return `${date}${fraction ? `.${String(fraction).padStart(6, "0")}` : ""}+00:00`;
}
function projectTodoFrontierDeadline(summary: JsonObject, now: bigint, agent: string | null): JsonObject | null {
function projectTodoFrontier(summary: JsonObject, now: bigint, agent: string | null): JsonObject {
const projected = object(summary.frontier_deadline);
const projectedTime = timestamp(projected?.next_due_at);
if (projected && projectedTime !== null && projectedTime > now) return {...projected};
const currentProjection = projected && projectedTime !== null && projectedTime > now ? projected : null;
const seen = new Set<string>();
let selected: JsonObject | null = null, earliest: bigint | null = null;
for (const lane of frontierLanes) {
let selected: JsonObject | null = currentProjection;
let earliest: bigint | null = currentProjection ? projectedTime : null;
let addedRetryCount = 0;
let retryWait: JsonObject | null = null;
let retryDue: bigint | null = null;
for (const lane of currentProjection ? ["resume_blocked_items"] : frontierLanes) {
const items = summary[lane];
if (!Array.isArray(items)) continue;
for (const raw of items) {
Expand All @@ -342,22 +346,38 @@ function projectTodoFrontierDeadline(summary: JsonObject, now: bigint, agent: st
const id = identity(row), key = JSON.stringify([id, String(due)]);
if (seen.has(key)) continue;
seen.add(key);
const retry = object(row.blocked_retry);
const receiptRetry = row.status === "open" && row.task_class === "advancement_task" &&
row.resume_ready === false && retry?.source === "turn_settlement" &&
typeof retry.turn_instance_id === "string" && !!retry.turn_instance_id &&
retry.agent_id === agent && agent !== null && typeof retry.goal_id === "string" &&
(!row.claimed_by || row.claimed_by === retry.agent_id) &&
row.resume_when === retry.resume_when && timestamp(retry.due_at) === due &&
isBoundedBlockedRetry(retry, typeof row.todo_id === "string" ? row.todo_id : null);
// A receipt wait owns one unfinished Todo; the earliest wakeup may
// belong to a different monitor. Keep both facts from this full source.
if (receiptRetry && (retryDue === null || due < retryDue)) {
retryDue = due;
retryWait = retry;
}
if (currentProjection) {
// The full-source cache retains deadlines omitted from visible lanes.
// Only newly overlaid receipt retries can predate that current cache.
if (!receiptRetry || due >= projectedTime!) continue;
if (id !== currentProjection.identity) addedRetryCount += 1;
}
if (earliest === null || due < earliest) {
earliest = due;
const retry = object(row.blocked_retry);
const receiptRetry = row.status === "open" && row.task_class === "advancement_task" &&
row.resume_ready === false && retry?.source === "turn_settlement" &&
typeof retry.turn_instance_id === "string" && !!retry.turn_instance_id &&
retry.agent_id === agent && agent !== null && typeof retry.goal_id === "string" &&
(!row.claimed_by || row.claimed_by === retry.agent_id) &&
row.resume_when === retry.resume_when && timestamp(retry.due_at) === due &&
isBoundedBlockedRetry(retry, typeof row.todo_id === "string" ? row.todo_id : null);
selected = {identity: id, source: receiptRetry ? "turn_settlement" : source(row, lane),
next_due_at: utcIso(due), ...(receiptRetry ? {blocked_retry: retry} : {})};
}
}
}
return selected ? {schema_version: "todo_frontier_deadline_v0", ...selected, candidate_count: seen.size} : null;
const candidateCount = currentProjection
? Math.max(1, Number(currentProjection.candidate_count) || 0) + addedRetryCount : seen.size;
const deadline = currentProjection && selected === currentProjection ? {...currentProjection} :
selected ? {schema_version: "todo_frontier_deadline_v0", ...selected, candidate_count: candidateCount} : null;
return {frontier_deadline: deadline, ...(retryWait ? {blocked_retry_wait: retryWait} : {})};
}

/** One quota read boundary composes scope/claim selection, resume, hint
Expand Down Expand Up @@ -397,10 +417,17 @@ export function projectTodoQuotaPlanning(value: unknown): JsonObject {
const handoff = hasSourceContract ? handoffGateLanes(request.handoff_items, agent, limit) : {};
const route = hasSourceContract ? routeContinuationLanes(request.route_items, agent, limit) : {};
const lanes = requireJsonObject(selected.lanes, "selection lanes");
const frontier = now === null ? null : projectTodoFrontierDeadline({
const resumeBlocked = requireJsonObject(resume.resume_blocked_lanes, "resume blocked lanes");
const frontier = now === null ? null : projectTodoFrontier({
...requireJsonObject(selected.claim_visibility, "claim visibility"),
...requireJsonObject(resume.deferred_lanes, "deferred lanes"),
...requireJsonObject(resume.resume_blocked_lanes, "resume blocked lanes"), ...handoff,
...resumeBlocked, ...handoff,
// Retain the display lane and union the existing full-source successor
// waits before choosing a deadline; visibility limits are not clock limits.
resume_blocked_items: [
...(Array.isArray(resumeBlocked.resume_blocked_items) ? resumeBlocked.resume_blocked_items : []),
...(resume.blocked_successor_items as JsonObject[]),
],
frontier_deadline: request.frontier_deadline ?? null,
monitor_open_items: lanes.monitor_items,
gate_open_items: lanes.gate_items,
Expand All @@ -410,5 +437,5 @@ export function projectTodoQuotaPlanning(value: unknown): JsonObject {
if (now !== null) lanes.gate_items = (lanes.gate_items as JsonObject[]).slice(0, 3);
return {schema_version: "todo_quota_planning_v0", resume_planning: resume, ...selected, ...closure,
...(hasSourceContract ? {handoff_lanes: handoff, route_lanes: route} : {}),
...(now === null ? {} : {frontier_deadline: frontier})};
...(now === null ? {} : frontier)};
}
Loading
Loading