Skip to content

ux/security(agent): Room-native ACP approval for resident Harnesses #286

Closed

Description

@madawei2699

Parent

Sub-issue of #246 — minimize time-to-collaboration for a temporary Agent LAN.

Status

COMPLETED — SHIPPED IN AGENT v0.5.24 AND PRODUCTION-VERIFIED WITH THE BUILT-IN CLAUDE HARNESS.

The generic Room-native ACP approval feature is implemented and deployed through #290, #291, #293, and the #296 production Worker routing fix; the public Runtime bootstrap was activated in #294.

Production verification on 2026-09-08 used a real Chrome Room, a real resident Claude Agent, and the shipped v0.5.24 Runtime:

Claude canUseTool
→ ACP session/request_permission
→ POST /api/room/permissions/request
→ production Room approval card
→ Human Allow Once / Deny
→ targeted resident WebSocket decision
→ exact native option returned
→ same Claude ACP turn continues or is denied

Verified behavior:

  • bounded Human-facing command presentation was visible in the Room;
  • one approved command resumed the same Claude turn;
  • a second tool call in that same turn produced a second approval card;
  • explicit Deny returned a native denial and the tool did not bypass it;
  • production Cloudflare logs showed successful permission-route requests after fix: dispatch Room permission requests #296, with no new 404s.

This satisfies #286's real built-in-Harness acceptance target. The feature does not require every built-in Harness to use interactive ACP approval: a Harness may instead execute under its own native/default policy. Pi's current behavior is an acceptable example of that model.

Built-in compatibility at closure

Remaining per-Harness compatibility/documentation work is tracked separately and must not reopen the generic Room approval architecture.

Historical design record

The remainder of this issue preserves the design/spike history that led to the shipped implementation.

Free4Chat's positioning is intentionally simple: a temporary, small, mutually trusted collaboration Room — a collaboration Swiss Army knife, not an enterprise workspace/RBAC/approval product.

The first implementation should therefore optimize for one simple, uniform interaction:

Harness emits session/request_permission
→ Room shows a structured approval message/card
→ any Human currently in the Room may explicitly choose one Harness-offered option
→ Runtime returns that exact optionId to the Harness
→ the same turn continues

No approver graph, owner/admin role, policy database, approval routing, delegation chain, or enterprise governance layer is required.

A resident Free4Chat Agent is intentionally headless after setup: the Go Runtime keeps one ACP session alive and wakes it from Room events without a local interactive Harness terminal.

Before v0.5.24, that created this failure mode:

setup Agent successfully installs / verifies / joins a Harness
→ Room collaboration starts
→ Harness reaches a native tool action that normally asks the Human
→ ACP sends session/request_permission
→ pre-v0.5.24 Free4Chat has no Room approval surface
→ ACP client fails closed / cancels the request
→ useful work stops even though the Human would have approved it

v0.5.24 resolves this generic failure mode whenever the Harness actually emits ACP session/request_permission. The remaining compatibility question is whether each built-in Harness exposes a native policy/mode that reaches that ACP request path in real use.

This is not Room authorization and not the #276 environment-inheritance problem. It is the local Harness authorization policy for unattended resident execution.

Before the v0.5.24 implementation, the gap was deterministic:

  • the pre-feat(agent): add ACP permission responder foundation #290 ACPAdapter answered every session/request_permission with cancelled;
  • the built-in Codex launcher selected INITIAL_AGENT_MODE=read-only; later production evidence showed that codex-acp's advertised read-only preset still resolves to an on-request + workspaceWrite turn policy, matching upstream codex-acp limitations;
  • built-in Harnesses do not share one permission model;
  • some Harnesses expose session modes / permission rules, while others have weak or no interactive approval mechanism at all.

The initial product implication was:

A setup Agent should establish an explicit Human-approved non-interactive Harness policy before the participant joins the Room.

That remains useful, but preflight alone is insufficient. It creates a privilege-pressure failure mode:

Human chooses conservative policy
→ collaboration starts successfully
→ one legitimate later action needs approval
→ no mid-turn approval path exists
→ task fails
→ Human learns to choose the broadest mode next time

That would make "least privilege" operationally worse than unrestricted access.

Design correction — baseline policy + bounded escalation

ACP already defines session/request_permission as a normal Agent→Client Human-approval request. The ACP Client is expected to present the exact options offered by the Harness and return the selected option. ACP session modes can also be changed during an active session where the Harness supports them.

So #286 should evaluate a two-layer model:

setup-time baseline policy
→ narrowest useful unattended default

PLUS, when a Human is present in the Room:

runtime receives session/request_permission
→ preserve exact tool-call context + Harness-offered options
→ publish a structured Room approval message/card
→ any current Human explicitly selects one offered option
→ Runtime returns that exact optionId to the Harness
→ turn continues

If no Human responds before the bounded request/turn lifetime ends, the request is cancelled and remains fail-closed.

Room membership alone never authorizes a tool action. Authorization happens only when a Human explicitly acts on the exact pending structured approval request. Ordinary chat text, Agent messages, collab_request acceptance, and participant presence must not be interpreted as approval.

Do not infer permission lifetime or scope from labels such as allow_always; the Harness/adapter owns the actual effect. Preserve the exact offered option id and presentation, and verify scope per Harness.

The implementation therefore has two simple layers:

  1. use a sensible Harness-native baseline mode where one is available; and
  2. relay explicit ACP permission requests into the Room as structured Human actions.

Do not keep #286 open for further enterprise-style authorization design.

Phase 0 — completed compatibility evidence

Phase 0 is complete. See the spike result in the issue comments.

Do not run another broad authorization spike. Any remaining probes should be narrowly tied to implementation or a specific Harness integration bug.

Questions to answer

For each built-in launcher — Hermes, OpenCode, Codex, Claude, Pi, and DeepSeek Harness preview — determine:

  1. what unattended/native permission modes actually exist;
  2. how the current effective mode is obtained;
  3. whether the mode can be selected through ACP session/set_mode or session config options;
  4. whether launch-time environment/arguments/local config are required instead;
  5. whether the choice is session-scoped, process/resident-scoped, or global/persistent;
  6. whether session/request_permission can still occur under each useful unattended mode;
  7. what a safe Free4Chat setup Agent can truthfully tell the Human before join;
  8. whether the Harness has no safe unattended mode and must remain trusted-room / explicitly limited;
  9. what exact session/request_permission payload/options are emitted for representative harmless gated actions;
  10. whether the pending permission request can remain open long enough for an external Human response without corrupting/cancelling the retained ACP turn;
  11. whether selecting an offered allow_always/durable option is session-scoped, workspace/local-config scoped, or otherwise Harness-defined;
  12. whether session/set_mode during an active session is useful as an explicit policy escalation mechanism or should remain separate from per-tool approval.

Required evidence

Do not rely only on upstream README claims. Use the pinned adapter versions and a deterministic local probe/fake Room-independent harness test where practical.

At minimum capture:

session/new response
→ advertised modes/config options

selected mode/config
→ exact ACP request or launcher mechanism

one harmless operation that would normally cross the policy boundary
→ executes / asks / denies as expected

residual session/request_permission behavior
→ understood and bounded

pending request
→ delayed explicit client response is proven or disproven

selected option
→ exact resulting scope/effect is recorded where observable

No destructive operation is required.

Spike deliverable

Update the compatibility matrix in this issue with verified values and record the chosen implementation seam for each built-in launcher.

A separate research document is unnecessary unless the evidence becomes too large for this issue.

Phase 0 evidence notes — no longer an implementation gate

  • Codex pinned version is verified for read-only / agent / agent-full-access and the preferred session-vs-launch control is chosen;
  • Claude pinned version's advertised modes/config and non-interactive semantics are verified;
  • OpenCode ACP permission behavior and its allow/ask/deny relationship to ACP are verified;
  • Pi and Hermes are explicitly classified rather than assumed to support a common permission model;
  • DeepSeek preview is either verified or explicitly excluded from first implementation;
  • one common Runtime representation is chosen that stores policy intent/capability metadata, not a fake universal permission guarantee;
  • the Invite/setup UX can be specified without vendor-specific guesses;
  • residual ACP permission requests are proven to support or not support a bounded delayed Human response for each relevant Harness;
  • approval model chosen: any current Room Human may explicitly act on the exact structured pending approval request; ordinary Room presence/messages remain insufficient;
  • when no authorized approver is reachable, residual requests remain fail-closed with a clear diagnostic.

The unchecked Harness-specific items are follow-up implementation/integration evidence, not blockers for the generic Room-native approval path. Do not schedule another broad authorization spike. OpenCode launcher correctness is #287.

Product goal

Extend the fresh Invite/setup journey conceptually from:

install
→ verify Runtime
→ verify Harness
→ join

to:

install
→ verify Runtime
→ inspect selected Harness policy capabilities
→ explain the effective unattended policy in Human language
→ Human chooses / confirms the local policy
→ setup Agent applies it locally
→ verify that the resident can run under that policy without hidden interactive approval
→ join

The Human should make this decision in the original setup conversation / local operator context, where interaction still exists.

Once the resident is participating in the Room, normal work should not depend on a permission dialog that nobody can see.

Security boundary

Preserve the existing architecture:

Room
= collaboration / addressing / shared context

Runtime
= resident lifecycle / ACP client

Harness
= intelligence / native tools / local authorization policy

Human/operator
= authority to choose the Harness policy

Approval must remain an explicit structured Human action

Never infer local tool authorization from ordinary collaboration activity:

  • Room join;
  • Room id;
  • participant identity/name;
  • capability advertisement;
  • targeted text;
  • collab_request acceptance;
  • Human presence by itself;
  • an ordinary Room message saying "yes";
  • another Agent's request.

The first simple model intentionally lets any current Human in the Room approve an exact pending ACP permission request, but only through the structured approval action/card for that request.

Do not default to "approve everything"

The setup Agent may explain an available unrestricted/native bypass mode, but it must not silently choose it merely because the user wants Agent collaboration.

A policy change that materially broadens local tool authority requires explicit Human/operator approval.

Prefer per-resident / per-session policy

If the Harness supports session-scoped or launch-scoped policy, prefer:

one Free4Chat resident
→ one explicitly selected local policy
→ policy disappears with that resident/session

over editing a permanent global Harness configuration.

If a Harness can only express the required unattended policy through persistent local configuration, treat that as a stronger setup action:

  • explain the scope;
  • require explicit Human approval;
  • keep the change local;
  • make it reversible;
  • do not mutate unrelated Harness settings.

Do not create a Free4Chat-hosted policy database or credential/config service.

Harnesses are heterogeneous

Do not invent a universal permission claim that Free4Chat cannot enforce.

Different Harnesses may expose different native controls, for example:

  • a launch mode such as read-only / workspace agent / full access;
  • ACP session modes;
  • ACP session config options;
  • local allow/ask/deny rules;
  • an external sandbox;
  • no granular approval mechanism at all.

The first implementation should discover and adapt to truthful native capabilities rather than pretending every Harness implements the same policy language.

A small Human-facing vocabulary may still be useful as intent, not as a security guarantee, for example:

Observe / read-only
Work in this workspace
Trusted unrestricted

Only offer an intent when the selected Harness can map it honestly. Otherwise show the Harness-native choice and its limitation.

Candidate setup flow

1. Preflight before Room join

The setup Agent should be able to inspect the selected built-in/custom Harness locally before declaring it ready.

Candidate information:

Harness
current effective mode/policy
available session modes/config options
whether ACP permission requests may still occur
whether the Harness has native sandboxing
whether the choice is session/launch/global scoped
whether a safe unattended mode exists

Exact CLI/API shape should come from implementation evidence. It may extend doctor / readiness or add a narrow policy-preflight surface.

Do not expose secrets, private tool inventories, paths, account names, or unrelated local configuration.

2. Ask the Human before joining

The Invite prompt should teach the setup Agent:

This resident will run without an interactive permission window.
Before joining, determine the selected Harness's unattended permission behavior.
If normal useful work would require interactive approval, explain the available local policy choices and ask the Human to choose one.
Do not silently broaden permissions.

For a powerful Harness that already executes tools without approval, the setup Agent should make that fact explicit rather than presenting it as "no policy needed".

3. Apply policy locally

Use the narrowest native mechanism supported by that Harness:

  • launch/session mode;
  • session config option;
  • local policy profile;
  • trusted custom wrapper only as an advanced escape hatch.

Do not send policy configuration through Room messages or make it participant-visible collaboration state.

4. Verify the effective unattended behavior

Before claiming readiness, verify enough to know whether ordinary expected work will immediately hit an invisible approval boundary.

This does not mean running destructive tools as a test.

A launcher-specific capability/mode check or deterministic fake/smoke Harness is preferable.

5. Residual permission requests — explicit escalation when possible, fail closed otherwise

Even after preflight, a legitimate later action may still request explicit permission. Treating every such request as immediate cancellation would pressure users toward unrestricted setup modes.

The spike must therefore evaluate a bounded explicit approval path.

Candidate semantics:

session/request_permission
→ Runtime records one pending request for the active ACP turn
→ publish a structured approval message/card to the Room
→ any current Human explicitly selects one exact Harness-offered option
→ first valid Human action resolves the request
→ Runtime returns that exact optionId
→ Harness continues the same turn

If no authorized approver exists, the approver is absent, the request expires, or the ACP turn is cancelled:

→ fail closed / cancelled
→ clear secret-safe diagnostic
→ no indefinite hang

Do not auto-select allow_once, allow_always, a bypass mode, or any other option.

Do not interpret ordinary Room chat text, collab_request acceptance, Agent actions, or mere participant presence as approval.

For the first implementation, the Room itself is the approval surface. Keep this a structured explicit Human action, not a separate approval center or policy-management product.

ACP protocol opportunity

Modern ACP supports session modes and session configuration options. Where a Harness advertises these controls, the Runtime can potentially use the protocol-native control surface instead of launcher-specific file mutation.

Evaluate:

  • retaining advertised session mode/config state from session/new;
  • setting an explicitly Human-selected mode/config before Room work begins;
  • keeping launcher-specific environment/bootstrap policy only where the Harness requires it.

Do not expand the Runtime into a second Harness configuration framework.

First compatibility matrix

Before implementation, document the real behavior of every supported built-in launcher:

Harness Current Free4Chat launch policy Can ask over ACP? Native non-interactive policy control Scope Expected setup UX
Hermes trusted-room / powerful native tools investigate investigate investigate explicit trust/policy warning
OpenCode local ACP pure mode yes / investigate exact behavior investigate native permission policy investigate preflight + choose supported unattended policy
Codex currently forced read-only yes launch/session modes launch/session explicit mode choice
Claude ACP bridge yes ACP modes/config + native permission policy session/local explicit mode choice
Pi trusted-room no useful ACP approval path no equivalent granular prompt flow process/local explicit full-trust warning or external restriction
DeepSeek Harness removed from built-ins by #292 n/a n/a n/a use another Harness/custom ACP if desired
custom ACP trusted local unknown implementation-defined implementation-defined require operator knowledge; no invented guarantees

Do not freeze assumptions from this table without verifying the exact versions Free4Chat ships.

Invite prompt requirements

Once the supporting Runtime behavior exists, update the canonical fresh Invite contract in app/public/agent.md and generated docs so the setup Agent can perform the complete loop.

The prompt should teach reasoning and safety boundaries, not hard-code one vendor's mode names.

Required behavior:

Human copies Invite prompt to setup Agent
→ setup Agent resolves exact Runtime + Harness
→ setup Agent checks unattended policy readiness
→ if choice is required, setup Agent explains local consequences
→ Human explicitly selects/confirms policy
→ setup Agent applies the narrow local/session policy
→ setup Agent verifies readiness
→ only then joins

If no acceptable unattended policy exists, the setup Agent should say so and refuse to claim the participant is ready for autonomous collaboration.

Regression / acceptance

Generic

  • fresh Invite setup detects that resident ACP turns have no normal interactive approval window;
  • setup Agent checks the selected Harness's effective unattended policy before Room join;
  • a Human explicitly confirms any meaningful local permission elevation;
  • no Room event or collaboration acceptance is treated as local tool approval;
  • no unrestricted policy is silently selected;
  • session/launch-scoped configuration is preferred over global mutation;
  • any current Room Human can explicitly choose an offered option on a pending ACP permission request;
  • ordinary Room presence/messages can never satisfy a pending ACP permission request;
  • when no authorized approver is reachable, unexpected residual ACP permission requests fail closed and do not hang indefinitely;
  • blocked work gets an actionable, secret-safe diagnostic;
  • policy details/credentials/private tool state do not enter Room state or analytics.

Multi-Harness

  • Codex can truthfully expose/select supported unattended modes rather than being accidentally limited by an invisible read-only default;
  • Claude's advertised session modes/config controls are evaluated through ACP rather than hard-coded from assumptions;
  • OpenCode's native policy behavior is verified in non-interactive ACP use;
  • Pi/Hermes paths explicitly communicate their weaker/no interactive permission boundary instead of implying that ACP provides isolation;
  • custom ACP launchers remain trusted-local and never receive invented Free4Chat security guarantees.

Product

  • the setup journey still feels like one temporary collaboration setup rather than a permanent policy-management product;
  • policy preflight reduces failed mid-collaboration work in real multi-Agent dogfood;
  • no account, workspace, central policy service, or hosted credential system is introduced.

Implementation shape

Keep the implementation small and separable:

A. Runtime / ACP foundation
   - retain native session mode/config metadata where available
   - support native session mode/config changes
   - park session/request_permission instead of always cancelling
   - exact offered option in / exact selected option out
   - default/no responder remains cancelled

B. Room approval surface
   - structured permission request event/card
   - any current Human may explicitly choose one offered option
   - first valid response wins
   - ordinary messages are never approval
   - bounded expiry/cancel

C. Thin integration + Invite/docs activation
   - Runtime publishes pending request into Room
   - Runtime consumes structured Human decision and resumes the same ACP turn
   - sensible simple baseline policy where a Harness exposes one
   - public product docs and Invite contract match shipped behavior

A and B may be developed in parallel behind tests. C should wire the merged contracts together rather than introducing a second authorization design.

#287 is independent and may run in parallel with all of the above.

Do not split further unless implementation evidence forces it.

Public product documentation is part of the feature

This security model must be documented in the public Free4Chat product docs, not only in GitHub issues, source comments, AGENTS.md, or DEVELOPMENT.md.

The implementation PR(s) for #286 must update the user-facing docs site so a Human can understand the model before relying on resident Agents.

Prefer one short, explicit public page such as:

Agent permissions and approvals

and link it from the existing public Agent Room / Runtime & Harness documentation.

The public documentation should explain, in product language:

Free4Chat Room
= temporary collaboration space

Harness
= owns local tools and their permission semantics

ACP permission request
= Harness asks for an explicit Human decision

Free4Chat
= shows that structured request in the Room
  and returns the Human-selected native option

Room membership alone
!= automatic tool permission

Headless / CLI-only behavior

The public docs must also cover the supported headless case explicitly:

Web Room with a Human
→ ACP permission request appears as a Room approval card
→ Human explicitly chooses an offered option

CLI-only / Agent-only / no Human approval surface
→ Harness continues under its current native/default policy
→ if the Harness still requires interactive approval, the request is cancelled
→ the turn fails closed rather than Free4Chat broadening permissions

This is an intentional product trade-off, not a missing enterprise approval subsystem.

Do not require setup as a prerequisite for CLI use. A user may still create/join a Room directly from the CLI with the default Harness behavior. Setup or explicit Harness-native mode selection is only an optional way to prepare an Agent for unattended operation.

The docs should make the operator guidance concise:

  • for ordinary Human + Agent collaboration, use the Web Room approval flow;
  • for unattended/headless collaboration, choose a Harness-native policy that can operate without interactive approval if that Harness supports one;
  • Free4Chat never automatically upgrades a local Harness into a broader/full-access mode;
  • if no Human is available and the Harness asks anyway, the action is cancelled/fails closed.

Prefer a small scenario table over a separate CLI approval subsystem.

Required user-facing points:

  • Free4Chat is intentionally a simple temporary collaboration product, not enterprise RBAC or an approval-management system.
  • Humans and Agents in a Room are assumed to be collaborating in a mutually trusted temporary context.
  • Joining a Room does not automatically grant filesystem, shell, browser, credential, or other local-tool access.
  • A Harness may work without prompting under its own local policy.
  • When a Harness sends ACP session/request_permission, Free4Chat may show a structured approval card/message in the Room.
  • Any current Human in the Room may explicitly choose one of the Harness-provided options in the first simple model.
  • Ordinary chat text such as "yes", Agent messages, or collab_request acceptance are not permission decisions.
  • Free4Chat forwards the exact native Harness option; it does not reinterpret allow once, durable rules, bypass modes, or other vendor semantics.
  • If no Human responds, the request expires/cancels and remains fail-closed.
  • Agent-only/unattended Rooms may therefore require a sufficiently permissive Harness-native policy chosen by the operator before collaboration.
  • ACP is a control protocol, not a sandbox; Harness/operator policy remains the local security boundary.

Keep this documentation concise and user-oriented. Do not expose internal protocol machinery unless it helps the user understand the safety boundary.

The feature is not complete until the public docs match the shipped behavior.

Non-goals

  • Free4Chat-owned universal tool permission DSL;
  • auto-approving arbitrary ACP session/request_permission calls;
  • owner/admin/RBAC/approver-binding or enterprise approval routing;
  • treating ordinary Room messages such as "yes" as ACP permission responses;
  • automatically selecting a Harness-offered permission option;
  • permanent central Harness policy;
  • hosted sandbox/security enforcement;
  • modifying all Harness global configs;
  • pretending ACP itself is a sandbox;
  • solving OS permission dialogs such as Screen Recording/Keychain in this issue;
  • remote shell/control-plane authority;
  • central workflow/DAG/orchestrator.

Relationship to Maka / orchestration

Agent Harnesses increasingly own sophisticated execution internally — scheduling, subagents, DAGs, recovery, append-only execution facts, worktrees, and policy. Free4Chat should remain the collaboration plane between independently owned Harnesses rather than absorbing those mechanisms.

This issue therefore strengthens the existing boundary:

Free4Chat makes independent Harnesses easy to bring together; each Harness/operator still owns how that Agent is allowed to act locally.

Related

Activity

  1. madawei2699 commented on Sep 7, 2026

    @madawei2699
    CollaboratorAuthor

    Phase 0 spike result — 2026-09-07

    Status: NOT READY FOR IMPLEMENTATION. No Runtime, Invite, launcher, policy, Room, docs, or production behavior was changed by this spike.

    The negative result is evidence-based: the exact pinned Claude bridge exposes a usable session-native mode surface, but the exact Codex bridge cannot safely be started in this isolated probe without initializing user-local Codex SQLite state, and local native OpenCode 1.18.18 did not answer the current Runtime's exact stdio initialize request within 30 seconds. It would be unsafe to change global Harness configuration, write user state, or silently broaden access merely to turn those cells green.

    Compatibility matrix

    Harness Exact version / local binary Native policy surface How current/available state is learned Select mechanism and scope Residual ACP permission request First seam Confidence
    Codex @agentclientprotocol/codex-acp@1.6.2 read-only, agent, agent-full-access Exact bridge source returns both session.new.modes and configOptions[id=mode]; Runtime currently discards both INITIAL_AGENT_MODE is an initial launch default only; exact bridge also implements session/set_mode and session/set_config_option(mode), per ACP session Yes. Exact bridge has request/await handlers for command, file-change, permission, MCP and plan approvals. agent-full-access has approvalPolicy=never, but that is not a proof that no later ACP request is possible Retain native mode/config metadata; require an explicit pre-join operator choice before either launcher default or session control. Never choose full access from Room input SOURCE-VERIFIED ONLY — exact package inspected; behavior probe safely stopped before initialization because bridge attempted user-local Codex state initialization
    Claude @agentclientprotocol/claude-agent-acp@0.70.0 auto, default, acceptEdits, plan, dontAsk, bypassPermissions (the last one was available in this non-root probe) Wire verified: session/new returned modes.availableModes and configOptions[id=mode] with all six values Wire verified: both session/set_mode(plan) and session/set_config_option({configId: mode, value: plan}) succeeded. The bridge marks this as session permission mode. It also supports separate persistent user/project rules, which must not be mutated by Free4Chat Yes. The bridge awaits session/request_permission; bypassPermissions still falls through for rule-forced asks. dontAsk is documented/implemented as deny when not pre-approved; acceptEdits only auto-accepts edits; plan advertises no actual tool execution Native ACP mode/config control, session-scoped. Offer only truthful native descriptions; do not reduce offered tool permission options to a synthetic Free4Chat policy VERIFIED for protocol discovery/control; SOURCE-VERIFIED ONLY for end-to-end tool boundary. Two isolated write attempts in plan/dontAsk wrote nothing and issued no permission request before their 50 s model-provider timeout; outbound provider access was deliberately not enabled for the probe
    OpenCode native opencode 1.18.18 Official current docs describe granular allow / ask / deny permission rules and --auto, generally through config/agent profiles Not verified for local binary. Exact Free4Chat launcher (opencode acp --hostname 127.0.0.1 --port 0 --mdns=false --pure) did not respond to the Runtime-compatible nd-JSON initialize within 30 s No verified ACP mode/config or safe per-resident mechanism. --pure means no external plugins; it is not evidence of a permission profile Not verified. Do not assume normal CLI ask maps to ACP session/request_permission Preserve current trusted-room behavior; exclude OpenCode from policy selection/approval relay until exact ACP transport + ask/allow/deny behavior is reproduced NOT VERIFIED
    Pi pi-acp@0.0.33 ACP modes are thinking levels (off … xhigh), not tool policy Exact bridge initialize wire verified; source shows session/new reports thought level/model config session/set_mode and thought_level config only change thinking level; tool policy remains Pi-process-owned. session/new starts Pi RPC and session state, so no stateful behavior test was run against user-local Pi storage No generic core-tool permission policy path. The bridge can send ACP permission requests for extension UI confirmations, which must not be confused with a sandbox/tool policy No Free4Chat selectable unattended policy. Keep trusted-local warning; external operator sandbox/profile is the only honest preflight SOURCE-VERIFIED ONLY
    Hermes native Hermes Agent 0.21.0, ACP 0.21.0 No safe no-tools/restricted resident profile found hermes acp --help exposes only setup/check and --accept-hooks (unseen shell-hook auto-approval), not a general tools policy No verified ACP mode/config or per-resident tool restriction. --accept-hooks is not a general permission solution Not verified. Isolated ACP initialize returned an internal error; no unsafe retry was made No policy selection; keep explicit trusted-room/full local-tools warning. Do not present --accept-hooks as a solution SOURCE-VERIFIED ONLY
    DeepSeek Harness preview launcher; no FREE4CHAT_DEEPSEEK_REPO configured Unknown Unavailable Unknown Unknown Explicitly exclude from first implementation; preserve preview/trusted behavior NOT VERIFIED / EXCLUDED

    Sanitized ACP evidence

    The Runtime is ACP v1 nd-JSON over stdio. Today it sends initialize with an empty clientCapabilities, then session/new({cwd, mcpServers: []}). Its capability projection retains only image/resume/close; it does not retain modes or configOptions.

    The current client dispatch is deterministically fail-closed:

    session/request_permission
    → { outcome: { outcome: "cancelled" } }
    

    The existing isolated fake-ACP regression passed (TestACPAutoCancelsPermissionAndNegotiatesImages): the parked Harness continuation received permission-cancelled. The dispatch contains no pending-permission record, timer, explicit approver binding, or selected-option path, so delayed approval is not supported by the current Runtime.

    Claude 0.70.0 actual wire sample (sanitized):

    initialize → protocolVersion: 1; agent: @agentclientprotocol/claude-agent-acp
    session/new → modes.currentModeId: default
                 modes.availableModes: auto, default, acceptEdits, plan, dontAsk, bypassPermissions
                 configOptions.mode.currentValue: default
    session/set_mode(plan) → {}
    session/set_config_option(mode=plan) → configOptions.mode.currentValue: plan
    

    The pinned Codex source shows the same two control paths and the exact mode semantics:

    read-only         approvalPolicy=on-request, sandbox=read-only, network=false
    agent             approvalPolicy=on-request, sandbox=workspace-write, network=false
    agent-full-access approvalPolicy=never, sandbox=danger-full-access
    

    INITIAL_AGENT_MODE chooses the initial state only; it is not the only control. Its source implementation applies later session/set_mode / mode-config updates to the retained session.

    Both exact Claude and Codex bridges use an awaited ACP request when they emit session/request_permission, so a delayed explicit response is adapter-supported in source. This does not make it Runtime-supported today: Free4Chat immediately cancels it. Scope is Harness-defined and option-specific — for example the Codex bridge can offer one-turn or session options — so a Runtime must preserve the exact offered option ID and presentation rather than infer scope from allow_always.

    Frozen implementation seam (not implemented)

    If/when the blockers below are cleared, use a narrow native-control seam:

    session/new
    → retain sanitized native modes/config options in resident-local Runtime state
    → preflight describes only verified choices
    → local Human/operator explicitly selects one native mode/config before join
    → Runtime applies only that native control to the retained session
    → Room work never changes it implicitly
    

    The representation should be metadata, not a universal permission DSL: native control id, display text, source (mode/config/launch), verified scope, whether residual requests are possible, and confidence. Human-facing intents (observe, workspace, trusted unrestricted) may be shown only when a Harness maps them honestly. Codex can map all three provisionally after behavior verification; Claude must show native choices; Pi/Hermes/OpenCode must not be assigned a fictitious common mode.

    Do not add a policy database, write global Harness config, place policy in Room state, auto-select permission options, or treat a Room Human/message/collab acceptance as local tool approval.

    For residual requests, the only safe architecture boundary identified here is a resident-scoped explicitly designated approver capability, created in local setup and bound to one pending ACP request. It must surface the exact Harness-provided tool-call/options and return only the selected offered option. Arbitrary Room presence, chat, or participant identity is not authority. Until that capability/channel is designed and tested, retain immediate cancellation plus a secret-safe diagnostic; do not add a generic Room approval engine.

    Phase 0 exit checklist

    • Codex end-to-end read-only / agent / agent-full-access behavior and delayed request lifecycle verified without mutating user state.
    • Claude harmless tool boundary verified against the provider (wire/config is verified; provider calls were intentionally blocked in the isolated probe).
    • OpenCode 1.18.18 ACP allow / ask / deny relationship and transport verified.
    • Pi and Hermes explicitly classified without inventing common policy semantics.
    • DeepSeek preview explicitly excluded from first implementation.
    • Minimal native-control metadata seam chosen; it is not a universal policy DSL.
    • Invite/setup wording is not ready: it would still require vendor guesses for Codex/OpenCode behavior.
    • Current Runtime delayed approval is disproven: it always cancels immediately.
    • Adapter-level delayed-response behavior remains source-verified, not end-to-end verified, for Codex; OpenCode is unverified.
    • No-authorized-approver behavior remains fail-closed. A secret-safe actionable diagnostic is still an implementation requirement.

    Remaining blockers / next evidence only

    1. Run Codex 1.6.2 in a disposable user-state environment that preserves the exact bridge configuration but does not touch a real user Codex state directory; verify one harmless read/write boundary per mode and a delayed selected/cancelled permission response.
    2. Reproduce the local OpenCode 1.18.18 ACP handshake with the exact launcher arguments, then verify whether ask emits ACP session/request_permission, how allow and deny behave, and whether any nonpersistent per-resident configuration exists.
    3. With explicit test credentials/provider isolation, complete Claude's harmless tool boundary and delayed selected/cancelled request samples. Do not change global Claude settings.

    Only then should an implementation PR start. A likely later sequence is: (1) Runtime native-control metadata + protocol tests, (2) preflight/CLI with explicit Human confirmation and no global mutation, (3) separately scoped designated-approver relay if the evidence still supports it, and (4) Invite/docs activation after dogfood.

    Useful non-pinned upstream context only: OpenCode ACP uses stdio nd-JSON and its documented permission rules do not substitute for verification of the local 1.18.18 binary.

  2. changed the title [-]ux/security(agent): preflight non-interactive Harness policy before Room join[/-] [+]ux/security(agent): Room-native ACP approval for resident Harnesses[/+] on Sep 7, 2026
  3. madawei2699 commented on Sep 7, 2026

    @madawei2699
    CollaboratorAuthor

    #287 transport evidence update — 2026-09-07

    The exact local OpenCode 1.18.18 binary was re-tested with the Free4Chat ACP v1 initialize frame and strict Harness environment allow-list in a disposable working directory:

    Case Result
    opencode acp initialize, session/new, harmless retained-session prompt succeeded
    opencode acp --pure initialize, session/new, harmless retained-session prompt succeeded
    current full recipe with hostname/port/mDNS flags plus --pure initialize, session/new, harmless retained-session prompt succeeded

    The installed command's own help confirms loopback, port 0, and mDNS disabled are defaults. #287 therefore simplifies the built-in recipe to opencode acp --pure, retaining the verified external-plugin boundary while removing redundant historical transport flags.

    This proves the basic ACP transport/session path only. It does not establish OpenCode allow / ask / deny behavior or implement permission relay; those remain the scope of #286.

  4. madawei2699 commented on Sep 7, 2026

    @madawei2699
    CollaboratorAuthor

    v0.5.24 production compatibility update — 2026-09-08

    The generic Room-native ACP approval plane is shipped and deployed in v0.5.24, but the first real built-in-Harness dogfood did not observe a production session/request_permission from Codex, OpenCode, Hermes, or Pi. Claude execution was not available on that machine.

    Codex targeted probe

    A narrow follow-up ruled out a simple pinned-version problem:

    Probe Result
    built-in codex-acp@1.6.2, sibling /tmp write executed, no Room approval card
    built-in 1.6.2, network request executed, no Room approval card
    custom codex-acp@1.10.0, same cross-directory write executed, no Room approval card
    native Codex CLI with temporary approval_policy = "untrusted" native approval prompt observed

    The user config was restored after the probe. No Free4Chat source/global Harness configuration was changed.

    The key distinction is that native Codex can prompt under untrusted, while current codex-acp presets re-assert their own turn policy. This matches existing upstream reports:

    Conclusion: Codex is currently classified as an upstream bridge/policy limitation, not a Free4Chat Room-approval defect. Do not fork/patch codex-acp in Free4Chat based on current evidence.

    Next evidence

    The next highest-value probe is the pinned Claude built-in on a machine with working Claude credentials. Claude's bridge explicitly routes native canUseTool decisions through ACP session/request_permission, so it is the best candidate for the first real built-in production approval proof.

    Keep #286 open until that narrow real-Harness evidence is resolved.

  5. madawei2699 commented on Sep 7, 2026

    @madawei2699
    CollaboratorAuthor

    Production acceptance complete — Claude built-in — 2026-09-08

    After #296 fixed Worker dispatch for /api/room/permissions/request, the built-in Claude Harness was re-tested against production with a real Chrome Room and the shipped v0.5.24 Runtime.

    Allow path

    A harmless Python command writing /tmp/free4chat-permission-test.txt produced a Room approval card showing the bounded EXECUTE command presentation. Selecting Allow Once resumed the same Claude ACP turn. Claude then issued a second cat tool call, which produced a second permission card; after approval Claude completed the original turn and confirmed the marker contents.

    Deny path

    A harmless rm -f /tmp/free4chat-permission-deny-test request produced a Room card. Selecting Deny returned an explicit native denial; Claude reported that it did not attempt to bypass the refusal.

    Production routing evidence

    Cloudflare Observability showed successful requests for /api/room/permissions/request after #296. The earlier 404 was the missing Worker route dispatch fixed by #296; no new 404s were observed during the successful test.

    Result

    Claude tool request
    → ACP session/request_permission
    → Room approval card
    → Human Allow/Deny
    → Runtime receives exact response
    → same ACP turn continues or terminates according to the Harness decision
    

    This is the real built-in-Harness production evidence required to complete #286. Other Harnesses may use different native/default policies and are no longer blockers for the generic feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions