Skip to content

Actor principal for generated event scopes: typed unavailable reasons and the one derivable host field (parked from #233/#223) #391

Description

@ScriptedAlchemy

Summary

Generated event request scopes never mount an actor principal, so every event route observes (await agent()).actor as unavailable with the default not-provided reason. This was parked on the closed #233 (state-kernel remainder) and #223 (request-context exposure) and has had no tracker since the G1 closed-issue audit (#366) confirmed the gap is still open. This issue is the tracker. It proposes no fabricated principal; it asks for a typed, evidence-backed unavailable surface and names the one host field that could make actor derivable today.

Current behavior

  • docs/entry-conventions.md (Handler request context, L166-173): "Bare stdio supplies neither a session id nor HTTP actor authentication, so those axes remain honestly unavailable. Generated event scopes currently mount no actor principal, so event routes observe actor as unavailable rather than receiving a fabricated value." packages/agent-bundle/src/routes/public.ts:62 repeats the claim on AgentEventRouteProps.
  • Shared event runtime, packages/agent-bundle/src/mcp-server-runtime.ts (createGeneratedRouteMcpServer event dispatch, ~L499-510): runAgentRequest({ host: available({ name: target }, 'native'), invocation, session?, signal, workspace? }) — no actor key, so the runtime's default unavailable('not-provided') applies.
  • Standalone event wrapper, packages/agent-bundle/src/adapters/hook-contract.ts (~L632-637): derives session from session_id/conversation_id and workspace from cwd; mounts no actor.
  • Contrast: generated MCP tool scopes do mount an actor when the transport supplies one — requestIdentity in mcp-server-runtime.ts (~L104-119) maps http.authInfo.clientId to available({ id }, 'native'); bare stdio yields unavailable('not-provided').
  • The runtime already distinguishes four reasons (packages/rsc-runtime/src/agent-request.ts:51-55): not-provided | unsupported-surface | host-omitted | unauthenticated. Event scopes use none of them deliberately; the observed reason is the default.

Host contracts checked (pinned files)

No pinned host hook contract exposes a human/account principal in its event envelope, with one exception (Cursor workspaceOpen).

Host (pinned revision) Pinned evidence Envelope identity fields Principal?
Claude Code 2.1.250 (src/adapters/schemas/claude/PROVENANCE.json, observedCliVersion: 2.1.250) schemas/claude/hooks.schema.json (hooks.json configuration document — command/matcher/timeout only); live-capture evidence in adapters/capabilities/claude-2.1.250.json (hooks evidence list) and scrubbed envelopes tests/fixtures/events/claude-*.json session_id, transcript_path, cwd, hook_event_name, permission_mode, prompt_id; Subagent* add agent_id, agent_type No. agent_id/agent_type name a subagent, not a user.
Codex 0.147.0 (schemas/codex/PROVENANCE.json, byte-pinned from rust-v0.147.0) schemas/codex/generated/*.command.input.schema.json (e.g. user-prompt-submit: agent_id, agent_type, cwd, hook_event_name, model, permission_mode, prompt, session_id, transcript_path, turn_id; session-end: cwd, session_id, transcript_path, …) as listed No.
Cursor 2026-08-28 (schemas/cursor/PROVENANCE.json) schemas/cursor/hooks.schema.json (configuration document, vendored); envelope evidence in adapters/capabilities/cursor-2026-08-28.json (workspaceOpen entries) and src/events/projection.ts:96-118 common: conversation_id, generation_id, model, session_id, transcript_path, hook_event_name, cursor_version; workspaceOpen (sessionless): hook_event_name, cursor_version, workspace_roots, user_email Only workspaceOpen. projection.ts:109-113 already validates user_email as `string

Also checked: the pinned hooks.schema.json for all three hosts describe the hooks configuration document (which commands run for which matcher), so by construction none of them can carry a principal — the envelope is the only channel, and the envelope evidence above is what rules it out.

What would make actor derivable

  1. Host-supplied principal in the envelope (source: 'native'). Cursor's workspaceOpen.user_email is the only pinned instance. Claude Code and Codex publish none at the pinned revisions; a future common-envelope field would be adopted by re-pinning the schema/capability tables under the normal adapter chore.
  2. Receipt-derived principal (source: 'receipt', already an ObservedSource). A host login receipt (for example the signed-in account the CLI reports) read at event time. Not proposed here: it is a host-install/discovery concern, it crosses the pinned contract, and it must never be read without an explicit capability grant.
  3. Consumer-owned principal via a conventional src/providers/* provider (Mount request context providers for routed CLI and rendered scripts #313/fix(cli): mount request context providers for plain routed CLI commands (#313 audit) #366). Already possible today for projects that know their own identity source; it lands under providers.<key>, not actor, and stays out of the framework's identity axis on purpose.
  4. HTTP authInfo — already derivable for tool scopes over HTTP transports; not applicable to event scopes, which are host-spawned processes.

Proposed typed unavailable surface (not implemented here)

  • Event scopes mount actor explicitly, with the reason that matches the pinned evidence, instead of relying on the runtime default:
    • unavailable('host-omitted') for every Claude Code and Codex event, and for every Cursor event other than workspaceOpen — the pinned envelope has no principal field.
    • Cursor workspaceOpen: available({ id: user_email }, 'native') when user_email is a non-empty string; unavailable('host-omitted') when it is null/absent.
    • Keep not-provided for the harness/module-direct case only (no host in play).
  • Both the shared event runtime (mcp-server-runtime.ts) and the standalone wrapper (hook-contract.ts) must agree; a route-unit test per host fixture pins the reason, and docs/entry-conventions.md L166-173 plus routes/public.ts:62 change from "currently unavailable" to the per-host table above.
  • Non-goals: no synthesized or guessed principal (no USER/os.userInfo() fallback, no transcript parsing), no new AgentContextUnavailableReason unless the pinned contracts require one.

Acceptance

  • Event scopes never observe the default not-provided; the reason is one of the typed reasons above and is pinned per host fixture.
  • Cursor workspaceOpen exposes user_email as actor when present, per the pinned capability table.
  • Docs and public.ts describe the per-host derivation rather than a blanket "unavailable".

Refs #233, #223, #313, #366.

Activity

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

    enhancementNew feature or requesthost-integrationAgent host capabilities, projection, discovery, and lifecyclerscReact Server Components and Agent Document rendering

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions