Skip to content

Add stable guardrails check protocol for external extensions #36

@aliou

Description

@aliou

Note

This comment has been written by Pi (openai-code/gpt-5.5).

Context

This should be done after PR #34 is merged.

PR #34 splits pi-guardrails into a cleaner architecture with src/core, src/shared, and separate extensions for policies, path access, and permission gate. Once that lands, we should add a small stable integration API so other Pi extensions can voluntarily ask guardrails for an allow/block decision before performing sensitive actions.

This is related to:

Goal

Provide a stable, versioned, low-maintenance API that external extensions can use without pi-guardrails maintaining tool-specific adapters.

The API should let a cooperating extension say:

I am about to perform this action on this resource. Should I allow it?

Then pi-guardrails can answer:

allow, block, ask, or error.

Non-goals

  • Do not maintain per-tool adapters for MCP tools or other third-party extensions.
  • Do not monkey-patch filesystem or process APIs.
  • Do not require external extensions to import or depend on @aliou/pi-guardrails.
  • Do not expose internal src/core types as public API.
  • Do not bake zones concepts into the protocol.
  • Do not change existing telemetry events like guardrails:blocked and guardrails:dangerous as part of v1.

Proposed protocol

Use the Pi extension event bus with a request/response pattern.

Request event:

guardrails:check

Response event:

guardrails:decision

The caller emits guardrails:check, waits for a matching guardrails:decision by requestId, then enforces the decision before doing the work.

Proposed request shape

type GuardrailsCheckV1 = {
  protocolVersion: "1";
  requestId: string;

  caller: {
    extensionId: string;
    extensionVersion?: string;
  };

  action: {
    resource: {
      kind: "file" | "process" | "network" | "custom";
      type?: string;
      uri?: string;
    };

    operation: string;

    targets?: Array<{
      uri: string;
      metadata?: Record<string, unknown>;
    }>;

    attributes?: Record<string, unknown>;
  };

  context?: {
    cwd?: string;
    workspaceRoot?: string;
    reason?: string;
  };

  options?: {
    interactive?: boolean;
    timeoutMs?: number;
  };
};

Proposed decision shape

type GuardrailsDecisionV1 = {
  protocolVersion: "1";
  requestId: string;

  decision: "allow" | "block" | "ask" | "error";

  reasonCode?: string;
  message?: string;

  provider?: {
    id: string;
    version?: string;
  };
};

Canonical v1 actions

Start small.

Resource kinds:

  • file
  • process

Canonical file operations:

  • read
  • write
  • delete
  • list
  • stat

Canonical process operations:

  • exec
  • spawn

Keep network and custom as escape hatches or future expansion points, but implementation can initially ignore or error on unsupported kinds.

Example: MCP tool writing a file

{
  "protocolVersion": "1",
  "requestId": "abc-123",
  "caller": {
    "extensionId": "pi-mcp-adapter"
  },
  "action": {
    "resource": {
      "kind": "file",
      "type": "mcp-tool"
    },
    "operation": "write",
    "targets": [
      { "uri": "file:///repo/package.json" }
    ],
    "attributes": {
      "toolName": "write_file"
    }
  },
  "context": {
    "cwd": "/repo"
  },
  "options": {
    "interactive": true,
    "timeoutMs": 1000
  }
}

Timeout behavior

The caller owns timeout behavior.

Recommended caller behavior:

  • Emit guardrails:check.
  • Wait for a matching requestId until timeoutMs.
  • If no provider answers, use caller policy:
    • fail closed for destructive writes/deletes/process execution.
    • fail open only for low-risk reads or non-sensitive actions.

This keeps pi-guardrails optional and lets integrations choose the right safety posture.

How this addresses #27

For non-bash tools, pi-guardrails does not need to understand each tool.

The tool author knows the intended action and can send a generic check request before doing it. Guardrails only evaluates the normalized action.

How this relates to #29

Zones should be a policy implementation detail behind the provider.

The protocol should not mention zones. It should only describe the attempted action. A zones implementation can then use path prefixes, CWD priority, and operation-specific policy internally to answer the same guardrails:check request.

Implementation sketch after #34

  1. Add public protocol types and event constants.
  2. Add a guardrails:check listener in pi-guardrails.
  3. Normalize public requests into internal core actions.
  4. Reuse existing policy/path-access/permission-gate checks where applicable.
  5. Emit guardrails:decision with a matching requestId.
  6. Document the protocol for external extension authors.
  7. Optionally add a tiny helper function for callers to wait for decisions with timeout.

The helper should be optional. Plain JSON events should remain the actual API.

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