Skip to content

Expose AgentRequestContext (host/session/principal) to RSC operation handlers #223

Description

@ScriptedAlchemy

Summary

Wave 1 (#113 / parent #95) installs a full AgentRequestContext in runAgentRequest and exposes await agent() from the same ALS store, but RSC operation handlers still receive only { signal } on operation.execute. MCP wiring forwards native sessionId into the request store yet never supplies host identity; CLI marks host unavailable('unsupported-surface'). Plugin authors therefore cannot rely on transport-observed host/session in handler code or in structured audit fields without re-declaring identity in tool JSON.

This is a focused wiring/API slice on top of #95 (typed request context). It should be proven by the generated harness in #103, consumed by Workbench/event surfaces in #105, and guarded against source→installed drift in #218.

Evidence (current main-line behavior)

RscOperationContext exposes only signal:

// packages/rsc-runtime/src/operation.ts:6-8
export interface RscOperationContext {
  readonly signal: AbortSignal;
}

AgentRequestContext already carries observed host/session/actor/workspace:

// packages/rsc-runtime/src/agent-request.ts:128-137
export interface AgentRequestContext {
  readonly invocation: AgentInvocation;
  readonly host: Observed<AgentHostIdentity>;
  readonly session: Observed<AgentSessionIdentity>;
  readonly actor: Observed<AgentActorIdentity>;
  readonly workspace: Observed<AgentWorkspaceIdentity>;
  readonly capabilities: AgentRequestCapabilities;
  readonly progress: AgentProgressReporter;
  readonly signal: AbortSignal;
  readonly services: AgentServiceRegistry;
  readonly providers: AgentProviderValues;
  // ...
}

MCP: session from native context, but execute gets { signal } only — no host:

// packages/rsc-runtime/src/mcp-server.ts (tool handler)
async (input, context) => runAgentRequest({
  ...(context.http?.authInfo?.clientId === undefined
    ? {}
    : { actor: available({ id: context.http.authInfo.clientId }, 'native') }),
  invocation: { kind: 'tool', operationId: operation.id, surface: mcp.name },
  ...(typeof context.sessionId === 'string' && context.sessionId.trim() !== ''
    ? { session: available({ sessionId: context.sessionId }, 'native') }
    : {}),
  signal: context.mcpReq.signal,
}, async () => {
  const result = await operation.execute(input, { signal: context.mcpReq.signal });
  // ...
});

CLI: host explicitly unavailable:

// packages/rsc-runtime/src/cli.ts:44-45 (representative)
host: unavailable('unsupported-surface'),
// ...
async () => operation.execute(cli.parse(commandArguments), { signal })

Real-world impact (cargo-conductor)

In cargo-conductor, ledger rows cc-800 and cc-803 recorded session plan40-stage3-sol but host NULL, rendering UI as ? · plan40-stage3-sol. The conductor_request tool input had to ask agents to manually supply host/session in JSON. Transport already knew session (MCP native); host should be observed from adapter/plugin/client metadata where knowable. Plugins should not force callers to re-declare transport identity in business input.

Requested design

  1. Extend RscOperationContext with the request-bound AgentRequestContext, or at minimum the observed host / session / actor / workspace / invocation fields, sourced from the same ALS store used by agent() during runAgentRequest (no second, divergent snapshot).

  2. Populate MCP observed host from host adapter / plugin manifest / MCP client metadata where knowable (e.g. Cursor, Claude Code, Codex, Workbench dev proxy). Preserve unavailable with typed reason when not knowable — never fabricate placeholders.

  3. CLI, Workbench, and semantic event routes should receive honest observed identities (derived or native as appropriate), not blanket unsupported-surface where the framework can observe more.

  4. Generated test harness acceptance (#103):

    • Native MCP session (and host when adapter provides it) reaches operation.execute context (or await agent() parity).
    • Explicit user/tool input cannot spoof or override transport-observed identity unless route policy explicitly allows override.
    • unavailable remains typed with provenance/reason in assertions.
  5. API shape: prefer a principal/context argument separate from operation business input (second parameter to execute, or documented await agent()) rather than auto-injecting host/session fields into the Zod input schema — keeps validation boundaries clear and prevents agents from forging transport identity via JSON.

Non-goals

  • Replacing #95 provider discovery/typegen work.
  • Inferring parent/subagent topology without host contract evidence (#95 non-goals).

Acceptance criteria

  • operation.execute(input, ctx) (or equivalent) exposes the same observed identities available via await agent() for that invocation.
  • MCP tool calls populate session and host (when knowable) without requiring duplicate fields in tool input.
  • CLI/Workbench/event entrypoints document and test their host/session provenance.
  • Harness tests cover spoof resistance and typed unavailable.
  • cargo-conductor-style ledger/audit can use handler context instead of manual host/session args.

Stack links

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions