From a Tool Call to Recoverable Execution: How Open Nua Governs Agents
Open Nua Engineering
Agent governance is often introduced as a set of rules: which prompts are allowed, which tools require approval, and which outputs need review.
Those rules matter, but they are not the runtime. The difficult cases happen while work is already in motion. The same service exposes both reads and writes. A user takes over a page while the Agent is operating it. A remote mutation times out after its side effect may have happened. A process crashes and the task must resume without repeating completed work. An operations page says “failed” without showing whether the model, provider, tool, or runtime was responsible.
Open Nua does not assume that an Agent will always be correct. It makes every autonomous execution answer six questions: who initiated it, which identity it runs under, which capabilities it may use, where they execute, when control must return to a person, and which facts support recovery and accountability.
This article describes the governance mechanisms already present across Desktop, Plugins, Gateway, and the quality evidence chain. It is not a declaration that “AI safety is complete.” A comprehensive Agent threat model, a unified policy interface, and the organization-level Managed Agent Runtime still have explicit future boundaries.
The smallest governable object is an Execution, not an Agent
“This Agent has permission” is too coarse. The same Agent may be triggered by chat, automation, a Feishu message, or system recovery. One conversation may contain an initial attempt, a HITL resume, and a failure recovery. Collapsing them into one run_id causes cancellation, retries, usage, and accountability to bleed into one another.
Desktop separates runtime identity into three layers:
threadIdholds the conversation and work context a user can return to;interactionIdrepresents one stable user intent;executionIdidentifies one actual execution, with distinct identities for initial work, HITL resume, and recovery.
Lifecycle events carry the interaction, execution, source, and a monotonic sequence. A run moves through running, waiting, cancelling, recovering, and terminal. After a process restart, an abandoned running state is not presented as still active; it becomes an explicit system-interrupted fact.
These identities are more than logging metadata. They determine which execution is cancelled, which intent a recovery continues, where usage is attributed, and whether a late event is still allowed to affect the terminal outcome.
Separate product availability, turn authorization, and execution capability
Permission systems usually fail because one Boolean carries too many meanings, not because they are missing one more flag.
Open Nua once had shared feature flags that mixed server entitlements, local UI settings, integration switches, and per-session authorization. After crossing Renderer, Main, and the Python Bridge, such fields produced duplicated allowlists, silently dropped values, and the dangerous implication that renderer input could grant authority.
Those concerns are now separated:
AppEntitlementsexpresses only server-granted product eligibility;LocalUiSettingscontrols local presentation behavior;- integrations keep their own enablement state;
AgentTurnRuntimeContextcarries only the minimum context the Python execution needs for this turn;- Plugin Capabilities independently declare what an Agent or App may invoke.
The Main process derives runtime context again from trusted entitlements for every turn. A Renderer-provided value cannot enable a server capability. A Skill cannot grant authority either: it can teach the Agent when to invoke something, which evidence to preserve, and when to stop, but execution authority comes from a Capability verified by the Host.
A Capability is an executable contract, not a tool name
A bare tool name says that a function exists. It does not answer the questions governance needs. An Open Nua Capability declaration includes at least:
audience: Agent, App, or both;operation: query or mutation;risk: low, medium, or high;requiresConfirmation/requiresHitl: whether the user or a human must intervene;idempotency: how duplicate execution is handled;providerandtarget: which governed boundary maps the capability to a real operation.
Desktop does not accept a gateway route and tool name supplied directly by the Renderer or a Plugin App. It resolves the Capability again from the installed, enabled, verified package, then checks audience, context, risk, and the target allowlist. Mutations, high-risk operations, and explicitly confirmed operations stop at a Host-owned prompt rather than trusting the UI to claim that it already asked.
Agents, declarative workbenches, and full Plugin Apps therefore share the same capability governance. A button does not bypass policy because a model did not click it, and an Agent does not gain authority by guessing an underlying MCP tool name.
Short-lived credentials carry only the necessary authority
When a capability requires remote execution, Desktop does not hand the primary session, a long-lived provider secret, or a generic network primitive to the Agent runtime.
The Main process exchanges the login session for a short-lived scoped tool token. The token binds audience, scopes, a user projection, and an expiry; Gateway checks the required scope again at each data-plane route. The user projection used for ordinary tool execution excludes the refresh token, and a Plugin call receives a narrow scope such as plugin:vibe-trading.
A remote Plugin can declare only a Gateway-known governed path, a tool allowlist, and a timeout. It cannot place an arbitrary upstream URL into the client. Real service locations, network egress, account isolation, and credentials remain behind Gateway.
The responsibility chain for one call becomes:
user intent
→ interaction / execution identity
→ Host resolves an enabled Capability
→ audience / risk / context check
→ short-lived scoped token
→ Gateway verifies scope and proxies
→ result and lifecycle evidence return to the same execution
Authority is not a permanent property. It is context that must be proved again whenever a request crosses a boundary.
The browser is the hardest stress test of this model
Browser automation concentrates the difficult parts of Agent governance. Page state changes continuously. Users and Agents compete for control. Websites request sensitive permissions. Uploads and downloads touch local files. A failed write may leave an unknown side effect.
Open Nua does not let the Python Agent connect directly to Electron's raw CDP endpoint. The bundled browser client enters an Electron Main restricted CDP broker over a Unix domain socket or Windows named pipe. The broker creates a capability token at startup and binds each connection to a thread, page, and automation lease.
The Host enforces several hard constraints at this boundary:
- a CDP method allowlist decides which reads and writes may pass;
- user takeover revokes Agent write control, and returning control requires a fresh snapshot;
- uploads are limited to the workspace, user attachments, and managed downloads;
- camera, microphone, location, clipboard, screen capture, and file-system permissions are denied by default;
- downloads land in a per-thread managed directory;
- recording is explicit, and passwords, verification codes, payment input, cookies, and authorization data do not enter artifacts.
The most important rule is that a read-only CDP command may recover and retry after a renderer crash, while a write may not. If a write fails after dispatch and its side effect cannot be disproved, the broker returns unknown-outcome. The caller must observe again or return control to the user instead of replaying the operation automatically.
This is why a prompt instruction to “be careful before acting” is insufficient. Governance must turn control ownership, page freshness, file provenance, permission scope, and uncertain outcomes into a Host-enforced protocol.
HITL is a recoverable state machine, not a dialog
If human-in-the-loop is only a confirmation dialog before a tool call, it cannot handle background work, login, CAPTCHA, permission requests, structural drift, window closure, and process restart.
In Open Nua, an execution that needs human input enters waiting; it is not marked failed. Resume keeps the original interactionId but creates a new executionId, carrying an interrupt id and the user's decisions or responses. The system preserves the continuity of one intent without pretending that two actual executions were one.
Failure recovery is stricter. The runtime must first produce a structured proposal with a checkpoint, safeToResume: true, and safety preconditions. The Host verifies that it belongs to the current execution, that cancellation has not begun, and that the recovery budget remains. Only then does it create a recovery execution. The recovery directive explicitly continues from the checkpoint without repeating completed side effects.
Cancellation is also not a UI-local switch to a cancelled label. Main, the Python runtime, tool calls, and the lifecycle must complete the cancellation together. If the terminal state cannot be confirmed safely, the system retains interrupted or unknown semantics.
Governance must admit when it does not know what happened
One of the most dangerous messages in distributed execution is “failed, safe to retry” when the system cannot tell whether the remote side already acted.
Open Nua makes idempotency and uncertainty part of Capability and lifecycle contracts:
- a mutation may require a stable idempotency key;
- the same client command cannot enter the Host concurrently twice;
- a confirmed terminal outcome cannot be overwritten by a late event;
- uncertain browser or network writes are not replayed blindly;
- recovery carries checkpoints, preconditions, and an attempt budget;
- unconfirmed results remain interrupted or unknown instead of being guessed into success or failure.
The same principle drives the Managed Agent Runtime draft and its Task → Execution → Run → Attempt model. Transport retries should reuse the existing Run, while an explicit resume creates a new Execution. If provider state cannot be confirmed, the platform should enter unknown and reconciliation instead of silently switching providers and running again.
That managed path is still a design direction, not a deployed hosted runtime. What it extends is not a new philosophy, but the governance principles already exercised by the Desktop local path.
An evidence chain does not mean recording everything
A governable Agent does not imply that every administrator can read everything.
Open Nua records interaction and execution identities, model and tool calls, status, timing, usage, allowlisted diagnostic fields, and artifact references for recovery, quality analysis, and operational projections. Gateway owns the usage ledger, Desktop keeps a bounded event journal, and the quality system separates immutable observation facts from managed error classification.
At the same time, ordinary diagnostics do not automatically gain prompts, response bodies, credentials, passwords, verification codes, or complete sensitive tool arguments in the name of observability. Quality aggregation, diagnostic drill-down, and governance mutation use distinct permissions. Accepting a risk does not rewrite the underlying quality fact.
The purpose of audit is to prove which controls and outcomes an execution passed through, not to create a second privacy system of record.
What this system is not yet
Open Nua has several enforceable governance boundaries today, but it is not yet a complete general-purpose Agent security platform.
A threat model covering prompt injection, model input and output guards, workspace policy, credential brokering, cross-Agent handoff, and a security evaluation suite remains in the backlog. Today's controls are owned by Desktop Host, Plugin contracts, Gateway scopes, the browser broker, interaction lifecycle, and the quality system. There is no central policy service that claims to decide every risk.
The Managed Agent Runtime is also still a protocol draft. Task / Execution / Run / Attempt, tenant isolation, dispatch idempotency, provider conformance, and unknown reconciliation have a defined direction, but hosted workers, a general Provider Router, a Web managed client, and organization-level policy operations are not shipped capabilities.
Without these limits, a governance article would turn “several hard local boundaries” into “the complete platform is finished.” Credible engineering narratives need scope governance too.
Six lessons from governing Agents
1. Govern executions, not abstract Agents. Stable interaction, execution, and terminal identities keep cancellation, recovery, usage, and audit from bleeding together.
2. Knowledge cannot grant authority. A Skill teaches the Agent how to work; Capability, Host, and scoped tokens decide whether it may act.
3. Authority must be proved at every boundary. Renderer, Agent, Plugin App, and remote services never inherit one universal key.
4. HITL is a lifecycle, not a dialog. Waiting, resume, cancel, recovery, and checkpoint belong to one state machine.
5. Uncertainty must be a first-class result. Automatically retrying an unknown side effect is more dangerous than an explicit failure.
6. Audit should be minimally sufficient. Preserve identity, controls, status, and evidence without copying sensitive bodies and credentials into another system of record.
An Agent creates value through autonomy. Governance does not eliminate that autonomy; it gives autonomous execution explicit boundaries, interruptible control, recoverable state, and an accountable chain of responsibility.
When a system can say who ran, what it was allowed to do, where it executed, why it stopped, whether retry is safe, and where the evidence lives, an Agent becomes more than a model call. It becomes a product capability that can operate over time.