diff --git a/docs/reference/operator-model-credential.md b/docs/reference/operator-model-credential.md index 176a341b9c..d9ba56d5eb 100644 --- a/docs/reference/operator-model-credential.md +++ b/docs/reference/operator-model-credential.md @@ -101,6 +101,79 @@ loopx machine-config credential clear The Dashboard's machine capability settings expose the same read and write through `/api/chat/operator-credential`. +## Choosing What Answers, And Where + +Two surfaces answer on this machine, and they are selected separately. + +- **The steward channel** is the conversation a person talks to: the Dashboard + manager channel and the bound Lark/Feishu manager group. Its executor is one + machine-level choice, `steward_executor.executor_endpoint`, holding a shipped + channel endpoint (`codex`, or `dsh` for the managed host). +- **A managed Turn or managed agent** is bounded work that runs without a person + in the loop. Its host resolves from the operator credential: with one stored, + the shipped default is the managed host `dsh`, and without one it is the + interactive CLI endpoint. + +Choose the steward on the managed host when the machine should answer from an +API-backed host instead of an individual CLI login; keep the interactive CLI +endpoint when the steward must run as the operator's own logged-in session. +These are independent: setting the steward to `dsh` does not move any managed +Turn, and storing a credential does not switch the steward. + +### Selecting and reading it back + +```bash +# List the registered machine-configuration namespaces. +loopx machine-config describe + +# Read the stored document and the effective steward resolution. +loopx machine-config inspect + +# Preview an exact change, then apply it with the plan revision it returned. +loopx machine-config preview +loopx machine-config apply +``` + +The service environment remains the bootstrap and escape hatch, and it is +lower precedence than the machine document: `LOOPX_MANAGER_ENDPOINT` selects the +steward endpoint, `LOOPX_MANAGER_MODEL` and `LOOPX_MANAGER_REASONING_EFFORT` +select its model and effort. A managed Turn resolves its profile from +`LOOPX_TURN_PROVIDER`, `LOOPX_TURN_MODEL` and `LOOPX_TURN_REASONING_EFFORT`, +then from the shipped managed profile. + +Every entry point publishes the same readback, so a reader never has to infer +the host from the name it resolved: `/api/chat/capabilities` reports the +steward's `executor_endpoint`, its `executor_endpoint_source` +(`machine_configuration`, `explicit_config` or `product_default`), the +`execution_profile` (`deepseek-v4-flash@high` on the shipped managed profile), +`available`, and the bound Session's `session_mode` and `session_status`. +A connection record stores the resolved endpoint as an observation, so it cannot +outrank the machine setting. + +### Disabling or rolling back + +```bash +# Preview removing the namespace, then apply the returned plan revision. +loopx machine-config remove + +# Preview a rollback to the previous revision, then apply it. +loopx machine-config rollback +``` + +Unsetting the environment variables restores the same lower layers. With no +machine document and no environment override, the steward resolves to the +shipped default (`codex`) and a credential-less machine keeps the interactive +CLI endpoint, exactly as a machine that never configured anything. + +### What this does not authorize + +The credential authenticates the selected endpoint; it never selects one. The +steward channel still only proposes: it may describe work, and it may hand an +authorized intent to a worker, but Todos, agent registration, quota and goal +policy change only through their canonical owners and only after the owner's +confirmation. Selecting the managed host grants no new filesystem, provider or +audience permission, and it does not let a conversation change hosts mid-thread. + ## Authority Boundary Storing a credential grants no authority. It does not select an executor, a diff --git a/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md b/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md index 800b81bf7c..2b911ec641 100644 --- a/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md +++ b/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md @@ -94,6 +94,25 @@ or an explicit reason for deferral/rejection. A plan is not the execution result of an implementation request. Only use `handoffs` for troubleshooting or an explicit follow-up; the original exchange must not depend on a second question. +When one owner sentence asks for a team rather than a single task, answer with +one plan preview before anything is created. The preview names, in this order: +the lanes and the Agent each one runs on; the first bounded Todo per lane with +its declared priority; the quota or cadence envelope that bounds them; the +acceptance signal that ends each lane; and the stop condition that ends the +team. Build every lane from Agents and Todos Core already knows, and from the +capabilities the current profile actually grants. Name a requested lane you +cannot staff as a gap, with the missing registration or grant, instead of +inventing a lane, an Agent, or a capability. + +A team preview is a proposal, never an effect. Do not create Todos, register +Agents, set quota, or start work until the owner confirms that exact preview; +state what will be created and which canonical owner creates it — Agent +registration, Todo creation, quota or goal policy — so the owner sees where +each effect lands. After confirmation, apply through those owners only, reuse +the Agent and Todo identities the preview named, and report one readback of +what now exists. Never widen the confirmed scope while applying it, and never +charge quota for the preview itself. + Core owns truth and permissions. This skill supplies reasoning guidance, not new authority. Keep front-end and group answers within their respective scopes; give concise, concrete answers with source and coverage notes where they matter. diff --git a/tests/test_manager_team_plan_guidance.py b/tests/test_manager_team_plan_guidance.py new file mode 100644 index 0000000000..9dd6c01e3b --- /dev/null +++ b/tests/test_manager_team_plan_guidance.py @@ -0,0 +1,30 @@ +"""The steward's shipped guidance owns the one-sentence team plan contract.""" + +from __future__ import annotations + +from loopx.chat_manager import manager_skill_text + + +def test_manager_guidance_orders_one_team_preview_before_any_effect() -> None: + """A team request is answered with one preview, never with silent creates.""" + + text = manager_skill_text() + + ordered = [ + "the lanes and the Agent each one runs on", + "the first bounded Todo per lane", + "quota or cadence envelope", + "acceptance signal", + "stop condition", + ] + positions = [text.index(marker) for marker in ordered] + assert positions == sorted(positions), ordered + + # The preview gates every effect, and the effects keep their canonical owners. + assert "proposal, never an effect" in text + assert "until the owner confirms that exact preview" in text + assert "Agent\nregistration, Todo creation, quota or goal policy" in text + assert "charge quota for the preview itself" in text + # An unstaffable lane is named as a gap rather than invented. + assert "as a gap, with the missing registration or grant" in text + assert "inventing a lane, an Agent, or a capability" in text