openclaw - ✅(Solved) Fix Add per-session envelope to sessions.create / sessions.patch [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#74632Fetched 2026-04-30 06:21:55
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Timeline (top)
cross-referenced ×1

sessions.create accepts session metadata (key, agentId, label, model, parentSessionKey, task, message) but rejects runtime-policy fields. There is no first-class gateway-RPC field for an external client (any application built on the gateway protocol) to declare "this session can use Read+Grep+Bash, in src/** paths only, with localhost network only" at session-creation time and have openclaw enforce that envelope on every tool call inside the session's runs.

This gap matters for any client that wants to build dynamic per-session policy enforcement on top of openclaw's runtime. It's adjacent to the existing subagents.allowAgents config + tools.allow / tools.deny policy machinery and the plugin before_tool_call hook. Those surfaces can enforce policy, but they do not give a gateway client a declarative, session-owned envelope through sessions.create / sessions.patch.

Error Message

If envelope is provided, openclaw stores it on the session record. Every subsequent tool call from any run in that session is checked against the envelope before execution. Rejected calls emit a structured lifecycle event, e.g. a new tool_call_rejected event or the existing tool-call error event with a stable rejection reason, with { toolName, input, envelopeRule, matchedPattern } so client applications can render the rejection in their own audit logs.

Root Cause

sessions.create accepts session metadata (key, agentId, label, model, parentSessionKey, task, message) but rejects runtime-policy fields. There is no first-class gateway-RPC field for an external client (any application built on the gateway protocol) to declare "this session can use Read+Grep+Bash, in src/** paths only, with localhost network only" at session-creation time and have openclaw enforce that envelope on every tool call inside the session's runs.

This gap matters for any client that wants to build dynamic per-session policy enforcement on top of openclaw's runtime. It's adjacent to the existing subagents.allowAgents config + tools.allow / tools.deny policy machinery and the plugin before_tool_call hook. Those surfaces can enforce policy, but they do not give a gateway client a declarative, session-owned envelope through sessions.create / sessions.patch.

Fix Action

Fix / Workaround

Issue: Add per-session envelope to sessions.create / sessions.patch

This gap matters for any client that wants to build dynamic per-session policy enforcement on top of openclaw's runtime. It's adjacent to the existing subagents.allowAgents config + tools.allow / tools.deny policy machinery and the plugin before_tool_call hook. Those surfaces can enforce policy, but they do not give a gateway client a declarative, session-owned envelope through sessions.create / sessions.patch.

  • sessions.create gateway RPC — creates a session before any message, accepts: key, agentId, label, model, parentSessionKey, task, message. Unknown fields are rejected.
  • sessions.patch gateway RPC — can update session metadata / runtime settings, but does not accept an envelope.
  • sessions_spawn agent tool — for sub-agent spawning with cwd, model, runtime, thinking, runTimeoutSeconds, etc. This is an agent tool, not a first-class gateway RPC for external clients to create sessions with policy.
  • Tool policy at agent-profile / config level — tools.allow, tools.deny, subagents.allowAgents, sandbox config.
  • sandbox config — limits filesystem/process scope for sandboxed sub-agents.
  • Plugin before_tool_call hook — can block tool calls, rewrite params, require approval, and fail closed. It receives session/run context and is an authorization surface, but policy has to live in plugin/config state rather than on the session record created through sessions.create.

PR fix notes

PR #74799: Add per-session runtime envelopes

Description (problem / solution / changelog)

Refs #74632

Summary

  • add an optional envelope object to sessions.create and sessions.patch
  • persist the envelope on SessionEntry, with sessions.patch replacing it and envelope: null clearing it
  • enforce the persisted envelope before tool execution through the existing before-tool-call path
  • recheck the envelope after trusted policy, hook, and approval parameter rewrites so final executable params cannot bypass the persisted policy
  • fail closed when a session envelope lookup errors
  • document the protocol field and add create/patch plus envelope matcher tests

The enforcement is intentionally declarative and additive:

  • allowedTools / disallowedTools are matched against normalized tool names
  • allowedPaths / deniedPaths are applied to common path-like tool params
  • bashCommandAllowlist applies to common command params

This PR intentionally does not expose networkAccess / host allowlists. Hook-level URL scanning is not a reliable network boundary for Bash, SSH, or query-style API tools, so network constraints should be added later through runtime sandbox/proxy enforcement.

Tests

  • pnpm test src/agents/session-runtime-envelope.test.ts
  • pnpm test src/agents/session-runtime-envelope.test.ts src/agents/pi-tools.before-tool-call.session-envelope.test.ts
  • pnpm test src/gateway/server.sessions.create.test.ts src/gateway/server.sessions.store-rpc.test.ts
  • pnpm tsgo:test:src
  • pnpm check:changed
  • pnpm exec oxfmt --check docs/gateway/protocol.md src/agents/pi-tools.before-tool-call.ts src/agents/pi-tools.before-tool-call.session-envelope.test.ts src/agents/session-runtime-envelope.ts src/agents/session-runtime-envelope.test.ts src/config/sessions/types.ts src/gateway/protocol/index.ts src/gateway/protocol/schema/protocol-schemas.ts src/gateway/protocol/schema/sessions.ts src/gateway/protocol/schema/types.ts src/gateway/server-methods/sessions.ts src/gateway/server.sessions.create.test.ts src/gateway/server.sessions.store-rpc.test.ts src/gateway/sessions-patch.ts

Changed files

  • docs/gateway/protocol.md (modified, +6/-2)
  • src/agents/pi-tools.before-tool-call.session-envelope.test.ts (added, +77/-0)
  • src/agents/pi-tools.before-tool-call.ts (modified, +65/-4)
  • src/agents/session-runtime-envelope.test.ts (added, +195/-0)
  • src/agents/session-runtime-envelope.ts (added, +238/-0)
  • src/config/sessions/types.ts (modified, +10/-0)
  • src/gateway/protocol/index.ts (modified, +4/-0)
  • src/gateway/protocol/schema/protocol-schemas.ts (modified, +2/-0)
  • src/gateway/protocol/schema/sessions.ts (modified, +13/-0)
  • src/gateway/protocol/schema/types.ts (modified, +1/-0)
  • src/gateway/server-methods/sessions.ts (modified, +1/-0)
  • src/gateway/server.sessions.create.test.ts (modified, +37/-0)
  • src/gateway/server.sessions.store-rpc.test.ts (modified, +31/-0)
  • src/gateway/sessions-patch.ts (modified, +26/-0)

Code Example

sessions.create({
  key?: string,
  agentId?: string,
  label?: string,
  model?: string,
  parentSessionKey?: string,
  task?: string,
  message?: string,

  // NEW — optional envelope, applies to every tool call inside this
  // session's runs until the session is deleted or the envelope updated
  // via sessions.patch.
  envelope?: {
    allowedTools?: string[];      // exact match on tool names; if present, deny everything else
    disallowedTools?: string[];   // exact match; if present, deny these specifically
    allowedPaths?: string[];      // glob patterns (minimatch) for filesystem operations
    deniedPaths?: string[];       // glob deny — overrides allowedPaths on conflict
    networkAccess?: 'none' | 'localhost' | 'full';
    bashCommandAllowlist?: string[]; // substring or regex, for bash exec recursion guard
  }
}){ sessionKey: string }

---

// New gateway-side configuration option
gateway.toolAuthorization?: {
  // URL to POST every tool call to; openclaw forwards the response
  callbackUrl?: string;
  // OR a registered plugin that handles the check in-process
  pluginId?: string;
}
RAW_BUFFERClick to expand / collapse

Issue: Add per-session envelope to sessions.create / sessions.patch

Summary

sessions.create accepts session metadata (key, agentId, label, model, parentSessionKey, task, message) but rejects runtime-policy fields. There is no first-class gateway-RPC field for an external client (any application built on the gateway protocol) to declare "this session can use Read+Grep+Bash, in src/** paths only, with localhost network only" at session-creation time and have openclaw enforce that envelope on every tool call inside the session's runs.

This gap matters for any client that wants to build dynamic per-session policy enforcement on top of openclaw's runtime. It's adjacent to the existing subagents.allowAgents config + tools.allow / tools.deny policy machinery and the plugin before_tool_call hook. Those surfaces can enforce policy, but they do not give a gateway client a declarative, session-owned envelope through sessions.create / sessions.patch.

What exists today (verified against 2026.4.26)

  • sessions.create gateway RPC — creates a session before any message, accepts: key, agentId, label, model, parentSessionKey, task, message. Unknown fields are rejected.
  • sessions.patch gateway RPC — can update session metadata / runtime settings, but does not accept an envelope.
  • sessions_spawn agent tool — for sub-agent spawning with cwd, model, runtime, thinking, runTimeoutSeconds, etc. This is an agent tool, not a first-class gateway RPC for external clients to create sessions with policy.
  • Tool policy at agent-profile / config level — tools.allow, tools.deny, subagents.allowAgents, sandbox config.
  • sandbox config — limits filesystem/process scope for sandboxed sub-agents.
  • Plugin before_tool_call hook — can block tool calls, rewrite params, require approval, and fail closed. It receives session/run context and is an authorization surface, but policy has to live in plugin/config state rather than on the session record created through sessions.create.

What's missing

  • sessions.create does not accept runtime-policy fields. External gateway-RPC clients cannot declare a per-session envelope at the moment the session is created.
  • sessions.patch cannot update a session envelope. There is no way to tighten or relax a session-owned policy after creation.
  • No first-class session-owned tool / path / network scope. A plugin can approximate per-session policy by consulting ctx.sessionKey, but the gateway client cannot attach that policy to the session through the gateway protocol.
  • No gateway-managed external authorization callback. before_tool_call supports in-process plugin authorization. There is not an equivalent gateway option for posting each tool call to an external policy service owned by the client application.

Concrete proposal

Part 1 — extend sessions.create schema with optional envelope

sessions.create({
  key?: string,
  agentId?: string,
  label?: string,
  model?: string,
  parentSessionKey?: string,
  task?: string,
  message?: string,

  // NEW — optional envelope, applies to every tool call inside this
  // session's runs until the session is deleted or the envelope updated
  // via sessions.patch.
  envelope?: {
    allowedTools?: string[];      // exact match on tool names; if present, deny everything else
    disallowedTools?: string[];   // exact match; if present, deny these specifically
    allowedPaths?: string[];      // glob patterns (minimatch) for filesystem operations
    deniedPaths?: string[];       // glob deny — overrides allowedPaths on conflict
    networkAccess?: 'none' | 'localhost' | 'full';
    bashCommandAllowlist?: string[]; // substring or regex, for bash exec recursion guard
  }
}){ sessionKey: string }

If envelope is provided, openclaw stores it on the session record. Every subsequent tool call from any run in that session is checked against the envelope before execution. Rejected calls emit a structured lifecycle event, e.g. a new tool_call_rejected event or the existing tool-call error event with a stable rejection reason, with { toolName, input, envelopeRule, matchedPattern } so client applications can render the rejection in their own audit logs.

Enforcement should happen in the narrowest available layer: tool-input checks for filesystem tools, sandbox/process controls for Bash and network constraints where available, and fail-closed behavior where openclaw cannot enforce a declared envelope exactly.

Part 2 — sessions.patch accepts envelope updates

Allow sessions.patch to update an existing session's envelope without recreating the session. This is needed for any flow where an agent's envelope tightens or relaxes over time based on observed behavior.

This is part of the core ask: sessions.create defines the initial envelope, and sessions.patch updates it.

Part 3 — optional follow-up: gateway-managed external authorization callback

Openclaw already has an in-process plugin authorization hook via before_tool_call. For consumers that need out-of-process logic owned by the client application (e.g. checking against a remote policy service, rate-limiting tool calls, or implementing custom denial reasons without writing an openclaw plugin), expose an optional gateway-side callback:

// New gateway-side configuration option
gateway.toolAuthorization?: {
  // URL to POST every tool call to; openclaw forwards the response
  callbackUrl?: string;
  // OR a registered plugin that handles the check in-process
  pluginId?: string;
}

The callback receives { sessionKey, runId, toolName, input, envelope } and returns { permission: 'allow' } | { permission: 'deny', reason: string }. Openclaw enforces the response. Timeouts default to deny (fail-closed).

This would be the gateway/client-facing equivalent of the Anthropic Claude Code SDK's canUseTool option. Without a session envelope or external callback, consumers that want dynamic policy have to either:

  • Rebuild agent profiles on every policy change (slow + restart-required), or
  • Write an openclaw plugin that maintains its own external session-policy map keyed by sessionKey, or
  • Run their own client-side check after openclaw streams the tool call (race condition: the side effect may already have happened).

Why this matters generically

Any client application built on top of the openclaw gateway that wants to enforce per-session policy hits the same wall. Today's options for those clients are:

  • Block agents at hire time (pre-dispatch) — works via the static agent-profile tool policy.
  • Enforce with an in-process openclaw plugin — works via before_tool_call, but requires plugin installation and separate policy state keyed by session.
  • Watch agents do things and react after the fact — works via event stream, but the side effect may already have happened.

But they can't:

  • Spawn an agent with policy "this session can read but not write, on these paths only" and have openclaw enforce it.
  • Dynamically tighten an agent's scope based on runtime signals using sessions.patch.
  • Show end users a "rejected by envelope" event with deterministic semantics.

Net: without a first-class session envelope, gateway clients either have to install a custom plugin or trust agents to honor declared scope by convention.

Backward compatibility

All proposed changes are additive optional fields. Existing sessions.create callers without envelope continue to work unchanged. Existing agent-profile tool policies continue to apply (envelope is more restrictive — applied as AND, not as override).

Where this fits relative to existing surfaces

  • This complements existing agent-profile tool policy, sandboxing, and the plugin before_tool_call authorization hook.
  • The declarative envelope is the gateway/session-record equivalent of Anthropic SDK's canUseTool callback (Claude Code path) for common static constraints.
  • The optional external callback is the client-owned out-of-process equivalent for custom dynamic constraints.
  • It's compatible with the sessions_spawn agent-tool path: that path can inherit envelope from parent or specify its own.

Test cases that would exist

  • Given a session created with allowedTools: ['Read', 'Grep'], when an agent calls Write, the call is not executed and a tool_call_rejected event fires.
  • Given deniedPaths: ['/etc/**', '**/secrets/**'], attempts to read /etc/passwd or ~/secrets/keys.txt are blocked.
  • Given networkAccess: 'localhost', Bash("curl https://example.com") is rejected; Bash("curl http://127.0.0.1:3100/api") is allowed.
  • sessions.patch updating envelope mid-session immediately applies to subsequent tool calls.
  • gateway.toolAuthorization.callbackUrl is invoked per tool call; deny response blocks; timeout fails closed.
  • Existing before_tool_call plugin behavior still applies. If both plugin policy and session envelope exist, the more restrictive result wins.

Verified scope

This was investigated against openclaw 2026.4.26 (homebrew install). The grep verifying the gap covered:

  • dist/server-methods-b3jaTRE_.js (sessions.create handler — confirmed accepts only key / agentId / label / model / parentSessionKey / task / message)
  • dist/protocol-Hjar_s3V.js (sessions.create / sessions.patch schemas — no envelope field)
  • dist/runtime-schema-TpYHXgGk.js (no session envelope schema present)
  • dist/plugin-sdk/src/plugins/hook-types.d.ts and docs/plugins/hooks.md (confirmed before_tool_call is an authorization hook, not just diagnostic)
  • docs/tools/subagents.md (sessions_spawn docs — agent-tool only, not gateway RPC)
  • Changelog 2026.4.26 (no envelope work mentioned)

If a parallel design exists or has been considered before, link it on the issue.

extent analysis

TL;DR

To address the issue, extend the sessions.create schema with an optional envelope field to allow declarative per-session policy enforcement.

Guidance

  1. Extend sessions.create schema: Add an optional envelope field to the sessions.create schema to allow clients to declare per-session policy at creation time.
  2. Implement envelope enforcement: Enforce the declared envelope on every tool call inside the session's runs, using the narrowest available layer (e.g., tool-input checks, sandbox/process controls).
  3. Update sessions.patch to accept envelope updates: Allow sessions.patch to update an existing session's envelope without recreating the session.
  4. Consider adding an optional external authorization callback: Expose an optional gateway-side callback to allow clients to implement custom dynamic constraints.

Example

sessions.create({
  // ...
  envelope?: {
    allowedTools?: string[];
    disallowedTools?: string[];
    allowedPaths?: string[];
    deniedPaths?: string[];
    networkAccess?: 'none' | 'localhost' | 'full';
    bashCommandAllowlist?: string[];
  }
})

Notes

The proposed changes are additive and backward compatible, with existing sessions.create callers continuing to work unchanged. The declarative envelope complements existing agent-profile tool policy, sandboxing, and plugin authorization hooks.

Recommendation

Apply the proposed workaround by extending the sessions.create schema with an optional envelope field, as it provides a declarative way for clients to enforce per-session policy at creation time.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Add per-session envelope to sessions.create / sessions.patch [1 pull requests, 1 participants]