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
28 changes: 17 additions & 11 deletions docs/architecture/rfcs/capable-manager-semantic-handoff-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,23 @@ grant for linked Core details. The shipped managed-Goal context grant below is
a bounded step; full planning/effect inheritance still needs its typed grant
chain, installed receiver adoption and original-route acceptance.

Use managed Goal scope for an authenticated owner's context-delegation grant:
all current and future registered Agents within those Goals inherit it. Avoid
requiring separate enrollment every time a worker joins. Retain exact-recipient
grants for restricted sources, and explicit recipient revocations that override
the Goal grant. New Goals, evidence-read scope and execution permissions are
separate authority; registration or a quoted request cannot expand them.
The shared `collaboration/source_grants.ts` owner resolves the same current
policy for the catalog, direct handoff and peer forwarding. The existing local
operator command can configure a Goal target by omitting `--agent-id`, with
preview, locked apply and readback. This bounded configuration slice does not
qualify settings-UI editing, native receiver adoption or the full M1–M3 journey.
**Local context-delivery default.** An operator-configured source with a verified authorized
sender defaults to all active registered recipients on its selected local registry,
across Goals and later registrations. `local_delivery_scope=selected` deliberately
retains an enrollment boundary; an old enrollment list alone no longer restricts
the default. Explicit Agent and Goal exclusions survive broad restoration and
apply at direct delivery, replay and each parent-forwarding hop. Missing or
malformed source provenance cannot activate the default. Context delivery grants
no evidence-read expansion, remote delivery, execution, claim/lease or protected
operation. Shared TypeScript `collaboration/source_grants.ts` owns the decision;
Python observes registration/provenance and persists operator changes. Qualify
cross-Goal delivery, future registration, revoked replay and original-route return
through the existing App/Lark conversation, without claiming worker adoption from
catalog access. The existing local operator commands preview, apply and read back exceptions.
Restoring a Goal retains its individually revoked Agents; selected scope can
enroll a whole Goal by omitting `--agent-id`. Editing source policy in packaged
settings remains an unqualified configuration journey. Native receiver adoption
and full M1–M3 execution are separate acceptance gates.

LoopX state mutations always use the existing typed command boundary, even if initiated through shell. The manager does not edit registry/authority files behind the control plane. Repository modifications use the project's normal worktree/review practice. Scoped merge/deploy authorization may be reused; unrelated payment or trading authority cannot be inferred from it.

Expand Down
4 changes: 4 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,10 @@ Use scoped discovery and permitted recovery before requesting manual IDs.
Private-owner discovery is broad by default across registered local resources
and authorized connected sources; a delivery allowlist, missing live binding or
bounded first page must not hide an otherwise visible responsible Agent.
Sender-bound local context delivery now defaults to active registered recipients
across Goals, with current/future registration and explicit revocations resolved
by the shared TS source owner. Selected-audience enrollment remains explicit;
this is neither a remote grant nor proof of worker adoption.
Keep discovery, audience evidence access, delegation and execution readiness
separate, as specified by [manager §5.5](capable-manager-semantic-handoff-v0.md#55-responsibility-discovery-and-receiver-owned-planning).

Expand Down
10 changes: 10 additions & 0 deletions docs/product/use-cases/steward/golden-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ an ordinary public issue and a qualified receiver already doing unrelated review
Delivery and read must not pass the test. Require an independent assessment,
reuse an explicitly linked existing task when needed, inspect current facts,
post the authorized checked reply and return its link to the original request.
A configured, sender-bound source delivers to all registered active local
Agents by default, across Goals and later registrations, without recipient
enrollment. Exercise direct delivery and a receiver's subsequent peer handoff
through the same current source rule. Revoke one Agent and one Goal: the next
attempt must be denied, and restoring the Goal must preserve the Agent exception.
Exercise both revocation orders, including an Agent revoked while its Goal is disabled.
An explicit selected scope retains enrollment; an unknown sender, stopped Goal
or remote binding never gains authority from the local default. Delivery remains
separate from evidence access, task acceptance and execution.

A short answer requires no invented Todo. A deferral names its actual condition
and continuation; a monitor responsibility or worker activity is not a task result.

Expand Down
71 changes: 36 additions & 35 deletions loopx/capabilities/manager_context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,56 @@

Built-in capability for original intent delivery and receiver-owned replanning.
The owner's local manager channel uses registered workers automatically.
External channels need an owner-configured grant in
An external conversation requires a trusted operator's sender-bound policy in
`<runtime-root>/.local/manager-context/policy.json`:

```json
{"schema_version":"loopx_manager_context_policy_v1","sources":{
"manager.external.example":{
"sender_ids":["exact-provider-sender"],
"targets":[{"goal_id":"research"}]
"manager.external.0123456789abcdef01234567":{
"sender_ids":["exact-provider-sender"]
}
}}
```

Use the actual connection channel and provider sender identity. Keep this file
private (0600); do not commit it. Missing grants disable external delivery.
For an existing channel with an authorized sender, use the local operator CLI
to preview, grant, or revoke a managed Goal without editing the
policy file by hand:
**Default behavior change:** configured senders can now deliver context to every
active registered Agent in this local registry, across Goals and including future
registrations. Existing enrollment lists do not narrow this default. To retain a
selected audience's previous enrollment boundary, explicitly set
`"local_delivery_scope":"selected"` alongside its `targets` list before upgrading.
Missing policy, missing source, a wrong sender or malformed scope grants nothing.
Keep the policy private (0600); do not commit it. A read grant without a sender
grant is insufficient. This does not grant remote delivery, evidence reads,
worker launch, Todo/lease changes or protected operations.

The existing local operator commands preview, apply and verify exceptions:

```sh
loopx manager-inbox grant-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research
loopx manager-inbox grant-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research --execute
loopx manager-inbox revoke-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research --agent-id worker
loopx manager-inbox revoke-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research --agent-id worker --execute
loopx manager-inbox revoke-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research --execute
loopx manager-inbox grant-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research --execute
loopx manager-inbox grant-delivery-target --channel-id manager.external.0123456789abcdef01234567 --goal-id research --agent-id worker --execute
```

Pass the same `--registry` and `--runtime-root` used by the manager connection.
Without `--execute`, these commands only preview the target and count change.
Omitting `--agent-id` covers all current and future registered Agents in that
Goal. Use this for the owner's managed scope; a newly registered Agent then needs
no separate enrollment. Supplying `--agent-id` retains one-recipient enrollment
or revocation. Individual revocation is stored in `blocked_targets`, overrides
the Goal grant, and survives reapplying that Goal grant. Explicitly grant the
Agent to restore it. Revoking a Goal removes both its broad and individual grants.
Existing exact-recipient policies retain their scope until a trusted operator
promotes them; evidence read scope alone never becomes delegation authority.

Grant requires an active registered Goal (and a registered Agent when specified), an existing sender-bound
channel, and membership in any explicit audience Goal read scope. The command
does not create a sender grant, launch the Agent, or grant protected-operation
authority. Revocation also works when the former Agent is no longer registered.
Remove a source/target grant to revoke future delivery, including replay attempts.
The shared TypeScript source-recipient owner resolves registration and exceptions
for discovery, direct Chat handoff and later peer consultation. Provider adapters
verify ingress and perform locked file IO. Missing, malformed or revoked grants
do not record a request; stopped or unreadable Goals are excluded. This config
slice has a CLI preview/apply/readback; the existing Chat uses its resulting
catalog. Editing source grants in the packaged settings UI remains unqualified.
Provider ingress receipts bind the current message digest, channel and sender;
a model cannot create that provenance through its response.
Pass the manager connection's `--registry` and `--runtime-root`. Without
`--execute`, no policy bytes change. `blocked_targets` retains exact Agent and
whole-Goal revocations, including future members. Restoring a Goal preserves
individual revocations, including those recorded while the Goal is disabled;
restore the Goal first, then an individual Agent when needed. Revocation still
works after unregistration. In `selected` mode, granting
a Goal enrolls its current/future Agents; exact grants enroll only that Agent.
An explicit audience read scope still bounds enrollment in selected mode.

The shared TypeScript source-recipient owner resolves the same current rule for
discovery, direct Chat delivery, replay and later peer consultation. Python verifies
provider ingress and performs locked file IO. Stopped or unreadable Goal activation
is excluded before admission. The existing App and Lark conversation paths consume
this catalog; registration and delivery never prove receiver adoption or execution.
The operator grant editor in packaged settings remains unqualified; this repair
adds no separate configuration page. Remove the sender/source grant to disable
external delivery, including future replay. Existing inbox records are retained.
Provider ingress receipts bind message digest, channel and sender; a model cannot
create or widen that provenance through its response.

The existing worker turn-start hook exposes only a bounded pending count and
required read command, without copying private content into status projections.
Expand Down
9 changes: 6 additions & 3 deletions loopx/control_plane/collaboration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ ingress and policy reader lives in `source_grant_observation.py`; the Chat
capability retains its public `authority` API and supplies its own instruction.
This removes a dependency from shared coordination to a product adapter without
introducing a second policy writer or migrating existing records. The typed
`source_grants.ts` owner resolves exact recipients and managed Goal targets;
Goal grants include future registered Agents while explicit recipient exclusions
remain effective. The existing operator configuration path delegates changes to
`source_grants.ts` owner resolves exact recipients and managed Goal targets.
Authorized sender-bound sources default to all currently registered local
recipients, across Goals and future registrations. Explicit `selected` scope
retains enrollment; exact and whole-Goal exclusions override the default, including
parent forwarding and replay. Provider observations contain only this host's
active registrations; neither scope admits remote execution. The existing operator configuration path delegates changes to
that same owner. Read grants and executor permissions do not imply delegation.

`inbox.py` adapts the existing private file stores; typed request validation stays
Expand Down
57 changes: 39 additions & 18 deletions loopx/control_plane/collaboration/source_grants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts";
import { requireBoolean, requireJsonObject, requireNonEmptyString, requireStringArray } from "../runtime_decode.ts";

type Recipient = { goal_id: string; agent_id?: string };
type LocalDeliveryScope = "all_registered" | "selected";

function recipients(value: unknown, label: string, exact: boolean): Recipient[] {
if (!Array.isArray(value)) throw new EffectRuntimeRequestError(`${label} must be an array`);
Expand All @@ -18,31 +19,42 @@ function recipients(value: unknown, label: string, exact: boolean): Recipient[]
function grants(source: JsonObject) {
return {
targets: recipients(Object.hasOwn(source, "targets") ? source.targets : [], "context delivery targets", false),
blocked: recipients(Object.hasOwn(source, "blocked_targets") ? source.blocked_targets : [], "blocked context recipients", true),
blocked: recipients(Object.hasOwn(source, "blocked_targets") ? source.blocked_targets : [], "blocked context recipients", false),
scope: localScope(source),
};
}

function localScope(source: JsonObject): LocalDeliveryScope {
const scope = Object.hasOwn(source, "local_delivery_scope") ? source.local_delivery_scope : "all_registered";
if (scope !== "all_registered" && scope !== "selected") {
throw new EffectRuntimeRequestError("local delivery scope must be all_registered or selected");
}
return scope;
}

function matches(grant: Recipient, target: Recipient): boolean {
return grant.goal_id === target.goal_id && (grant.agent_id === undefined || grant.agent_id === target.agent_id);
}

function granted(target: Recipient, targets: Recipient[], blocked: Recipient[]): boolean {
return targets.some(row => matches(row, target)) && !blocked.some(row => matches(row, target));
function granted(target: Recipient, targets: Recipient[], blocked: Recipient[], scope: LocalDeliveryScope): boolean {
return (scope === "all_registered" || targets.some(row => matches(row, target))) &&
!blocked.some(row => matches(row, target));
}

/** Source provenance is verified by the provider adapter; registration is observed
* afresh. A Goal target covers its current/future Agents, never other Goals,
* evidence reads, protected operations or executor readiness.
* afresh on this host. Authorized sources default to all registered local
* recipients; selected scope opts into enrollment. Neither scope grants remote
* delivery, evidence reads, protected operations or executor readiness.
*/
export function resolveSourceRecipients(params: JsonObject): JsonObject {
const source = requireJsonObject(params.source, "source policy");
const sender = requireNonEmptyString(params.sender_id, "verified source sender");
if (!requireStringArray(source.sender_ids, "source senders").includes(sender)) {
throw new EffectRuntimeRequestError("source sender is not authorized");
}
const { targets, blocked } = grants(source);
const { targets, blocked, scope } = grants(source);
const available = recipients(params.available, "registered recipients", true);
const selected = available.filter(target => granted(target, targets, blocked));
const selected = available.filter(target => granted(target, targets, blocked, scope));
const unique = new Map(selected.map(row => [JSON.stringify([row.goal_id, row.agent_id]), row]));
return { targets: [...unique.values()].sort((a, b) =>
a.goal_id.localeCompare(b.goal_id) || a.agent_id!.localeCompare(b.agent_id!)) };
Expand All @@ -53,7 +65,7 @@ export function resolveSourceRecipients(params: JsonObject): JsonObject {
*/
export function configureSourceRecipient(params: JsonObject): JsonObject {
const source = requireJsonObject(params.source, "source policy");
const { targets, blocked } = grants(source);
const { targets, blocked, scope } = grants(source);
const goal_id = requireNonEmptyString(params.goal_id, "delivery target Goal");
const agent_id = params.agent_id === null || params.agent_id === undefined
? undefined : requireNonEmptyString(params.agent_id, "delivery target Agent");
Expand All @@ -70,41 +82,50 @@ export function configureSourceRecipient(params: JsonObject): JsonObject {
!available.some(row => matches(target, row)))) {
throw new EffectRuntimeRequestError("delivery target must be a registered Agent or all Agents in an active Goal");
}
if (Object.hasOwn(source, "evidence_goal_ids")) {
if (scope === "selected" && Object.hasOwn(source, "evidence_goal_ids")) {
const readGoals = requireStringArray(source.evidence_goal_ids, "source read Goals");
if (readGoals.some(id => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/.test(id)) || !readGoals.includes(goal_id)) {
throw new EffectRuntimeRequestError("target Goal is outside the channel read scope");
}
}
}
const before = agent_id === undefined
? targets.some(row => row.goal_id === goal_id && row.agent_id === undefined)
: granted(target, targets, blocked);
? (scope === "all_registered" || targets.some(row => row.goal_id === goal_id && row.agent_id === undefined)) &&
!blocked.some(row => row.goal_id === goal_id && row.agent_id === undefined)
: granted(target, targets, blocked, scope);
let updatedTargets = targets;
let updatedBlocked = blocked;
if (grant) {
if (!targets.some(row => matches(row, target))) updatedTargets = [...targets, target];
if (agent_id !== undefined) updatedBlocked = blocked.filter(row => !matches(target, row));
if (agent_id !== undefined && blocked.some(row => row.goal_id === goal_id && row.agent_id === undefined)) {
throw new EffectRuntimeRequestError("restore the Goal delivery grant before restoring an individual Agent");
}
if (scope === "selected" && !targets.some(row => matches(row, target))) updatedTargets = [...targets, target];
// Regrant only this scope; a Goal regrant preserves individual revocations.
updatedBlocked = blocked.filter(row => !(row.goal_id === goal_id && row.agent_id === agent_id));
} else if (agent_id === undefined) {
// Revoking a Goal also revokes individually enrolled members of that Goal.
updatedTargets = targets.filter(row => row.goal_id !== goal_id);
updatedBlocked = blocked.filter(row => row.goal_id !== goal_id);
if (scope === "all_registered" && !blocked.some(row => row.goal_id === goal_id && row.agent_id === undefined)) {
updatedBlocked = [...blocked, target];
}
// Preserve individual exceptions when the Goal is restored later.
} else {
updatedTargets = targets.filter(row => !matches(target, row));
if (updatedTargets.some(row => matches(row, target)) && !blocked.some(row => matches(row, target))) {
// Record the exact revocation even if a Goal block or missing enrollment
// already disables delivery. Restoring that broader scope must not erase it.
if (!blocked.some(row => row.goal_id === goal_id && row.agent_id === agent_id)) {
updatedBlocked = [...blocked, target];
}
}
const changed = JSON.stringify(updatedTargets) !== JSON.stringify(targets) ||
JSON.stringify(updatedBlocked) !== JSON.stringify(blocked);
// Preserve provider metadata when the semantic recipient set is unchanged.
// Preserve provider metadata when the declared grants and exceptions are unchanged.
const updatedSource: JsonObject = { ...source };
if (changed) {
updatedSource.targets = updatedTargets;
if (updatedBlocked.length) updatedSource.blocked_targets = updatedBlocked;
else delete updatedSource.blocked_targets;
}
return { source: updatedSource, target, would_change: changed,
return { source: updatedSource, target, local_delivery_scope: scope, would_change: changed,
granted_before: before, granted_after: grant,
existing_target_count: targets.length, resulting_target_count: updatedTargets.length,
includes_future_agents: agent_id === undefined && grant };
Expand Down
Loading
Loading