Repository navigation
ux/security(agent): Room-native ACP approval for resident Harnesses #286
Description
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
- Claude — PRODUCTION VERIFIED. Real ACP permission requests, Room cards, Allow/Deny, and same-turn continuation work.
- Codex — upstream bridge/policy limitation. Native Codex
untrustedprompts, but currentcodex-acppresets do not expose that behavior cleanly through the tested ACP path. This matchesagentclientprotocol/codex-acpissues agent: expose local ACP session mapping in status #310, fix(room): drop the cosmos stars animation and align the multi-agent story (#449) #450, fix(agent): one production lane-topology decision, driven by the #474 regression #477 and open PRs stabilize: post-#354 safety and correctness hardening #357 / docs(agent): link Shared Room App discovery surfaces #395. Do not patch the Free4Chat approval core for this. - OpenCode — resident ACP transport verified; interactive
ask→ ACP permission behavior remains a Harness-specific compatibility follow-up. - Hermes — current upstream contains ACP permission bridge code, but the exact Free4Chat built-in version/path remains a Harness-specific compatibility follow-up.
- Pi — no Room approval is required for the currently observed native/default behavior; it may execute tools under its own local policy. This is not a blocker for ux/security(agent): Room-native ACP approval for resident Harnesses #286.
- DeepSeek Harness preview — removed from built-ins by [codex] remove DeepSeek Harness preview #292.
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
ACPAdapteranswered everysession/request_permissionwithcancelled; - the built-in Codex launcher selected
INITIAL_AGENT_MODE=read-only; later production evidence showed that codex-acp's advertisedread-onlypreset still resolves to anon-request + workspaceWriteturn 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:
- use a sensible Harness-native baseline mode where one is available; and
- 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:
- what unattended/native permission modes actually exist;
- how the current effective mode is obtained;
- whether the mode can be selected through ACP
session/set_modeor session config options; - whether launch-time environment/arguments/local config are required instead;
- whether the choice is session-scoped, process/resident-scoped, or global/persistent;
- whether
session/request_permissioncan still occur under each useful unattended mode; - what a safe Free4Chat setup Agent can truthfully tell the Human before join;
- whether the Harness has no safe unattended mode and must remain trusted-room / explicitly limited;
- what exact
session/request_permissionpayload/options are emitted for representative harmless gated actions; - whether the pending permission request can remain open long enough for an external Human response without corrupting/cancelling the retained ACP turn;
- whether selecting an offered
allow_always/durable option is session-scoped, workspace/local-config scoped, or otherwise Harness-defined; - whether
session/set_modeduring 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-accessand 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/denyrelationship 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_requestacceptance;- 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_requestacceptance 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_permissioncalls; - 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
- ux(agent): minimize time-to-collaboration for a temporary Agent LAN #246 — parent onboarding / time-to-collaboration evidence.
- Core direction — temporary capability access and collaboration fabric #51 — canonical collaboration-fabric boundary and local-policy ownership.
- ux(agent): let setup Agents safely inherit selected local Harness environment #276 — completed named local environment inheritance; configuration availability is separate from tool authorization.
- stabilization(agent): harden artifact handoff and Harness setup after broader dogfood #281 — completed broader Harness setup/artifact reliability batch.
- experiment: A2A interoperability for Free4Chat Agent participants #160 — A2A interoperability is separate from local Harness authorization.
- bug(agent): verify and fix built-in OpenCode ACP launcher compatibility #287 — OpenCode built-in launcher correctness; separate integration bug and not a blocker for the generic Room approval model.
Activity
madawei2699 commentedon Sep 7, 2026
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.18did not answer the current Runtime's exact stdioinitializerequest 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
@agentclientprotocol/codex-acp@1.6.2read-only,agent,agent-full-accesssession.new.modesandconfigOptions[id=mode]; Runtime currently discards bothINITIAL_AGENT_MODEis an initial launch default only; exact bridge also implementssession/set_modeandsession/set_config_option(mode), per ACP sessionagent-full-accesshasapprovalPolicy=never, but that is not a proof that no later ACP request is possible@agentclientprotocol/claude-agent-acp@0.70.0auto,default,acceptEdits,plan,dontAsk,bypassPermissions(the last one was available in this non-root probe)session/newreturnedmodes.availableModesandconfigOptions[id=mode]with all six valuessession/set_mode(plan)andsession/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 Free4Chatsession/request_permission;bypassPermissionsstill falls through for rule-forced asks.dontAskis documented/implemented as deny when not pre-approved;acceptEditsonly auto-accepts edits;planadvertises no actual tool executionplan/dontAskwrote nothing and issued no permission request before their 50 s model-provider timeout; outbound provider access was deliberately not enabled for the probeopencode 1.18.18allow/ask/denypermission rules and--auto, generally through config/agent profilesopencode acp --hostname 127.0.0.1 --port 0 --mdns=false --pure) did not respond to the Runtime-compatible nd-JSONinitializewithin 30 s--puremeans no external plugins; it is not evidence of a permission profileaskmaps to ACPsession/request_permissionpi-acp@0.0.33modesare thinking levels (off…xhigh), not tool policyinitializewire verified; source showssession/newreports thought level/model configsession/set_modeandthought_levelconfig only change thinking level; tool policy remains Pi-process-owned.session/newstarts Pi RPC and session state, so no stateful behavior test was run against user-local Pi storageHermes Agent 0.21.0, ACP0.21.0hermes acp --helpexposes only setup/check and--accept-hooks(unseen shell-hook auto-approval), not a general tools policy--accept-hooksis not a general permission solution--accept-hooksas a solutionFREE4CHAT_DEEPSEEK_REPOconfiguredSanitized ACP evidence
The Runtime is ACP v1 nd-JSON over stdio. Today it sends
initializewith an emptyclientCapabilities, thensession/new({cwd, mcpServers: []}). Its capability projection retains only image/resume/close; it does not retainmodesorconfigOptions.The current client dispatch is deterministically fail-closed:
The existing isolated fake-ACP regression passed (
TestACPAutoCancelsPermissionAndNegotiatesImages): the parked Harness continuation receivedpermission-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.0actual wire sample (sanitized):The pinned Codex source shows the same two control paths and the exact mode semantics:
INITIAL_AGENT_MODEchooses the initial state only; it is not the only control. Its source implementation applies latersession/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 fromallow_always.Frozen implementation seam (not implemented)
If/when the blockers below are cleared, use a narrow native-control seam:
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
read-only/agent/agent-full-accessbehavior and delayed request lifecycle verified without mutating user state.1.18.18ACPallow/ask/denyrelationship and transport verified.Remaining blockers / next evidence only
1.6.2in 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.1.18.18ACP handshake with the exact launcher arguments, then verify whetheraskemits ACPsession/request_permission, howallowanddenybehave, and whether any nonpersistent per-resident configuration exists.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.18binary.[-]ux/security(agent): preflight non-interactive Harness policy before Room join[/-][+]ux/security(agent): Room-native ACP approval for resident Harnesses[/+]madawei2699 commentedon Sep 7, 2026
#287 transport evidence update — 2026-09-07
The exact local OpenCode
1.18.18binary was re-tested with the Free4Chat ACP v1 initialize frame and strict Harness environment allow-list in a disposable working directory:opencode acpinitialize,session/new, harmless retained-session prompt succeededopencode acp --pureinitialize,session/new, harmless retained-session prompt succeeded--pureinitialize,session/new, harmless retained-session prompt succeededThe installed command's own help confirms loopback, port
0, and mDNS disabled are defaults. #287 therefore simplifies the built-in recipe toopencode 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/denybehavior or implement permission relay; those remain the scope of #286.madawei2699 commentedon Sep 7, 2026
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_permissionfrom 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:
codex-acp@1.6.2, sibling/tmpwrite1.6.2, network requestcodex-acp@1.10.0, same cross-directory writeapproval_policy = "untrusted"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 currentcodex-acppresets 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
canUseTooldecisions through ACPsession/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.
madawei2699 commentedon Sep 7, 2026
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.txtproduced a Room approval card showing the boundedEXECUTEcommand presentation. Selecting Allow Once resumed the same Claude ACP turn. Claude then issued a secondcattool 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-testrequest 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/requestafter #296. The earlier 404 was the missing Worker route dispatch fixed by #296; no new 404s were observed during the successful test.Result
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.