Skip to content

feat(egress-gate): no-fork Pi admission before history writes - #38

Draft
johnnygreco wants to merge 70 commits into
mainfrom
johnny/pi-attested-admission
Draft

feat(egress-gate): no-fork Pi admission before history writes#38
johnnygreco wants to merge 70 commits into
mainfrom
johnny/pi-attested-admission

Conversation

@johnnygreco

@johnnygreco johnnygreco commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

What this adds

A runnable proof of concept for admitting content before it enters an agent's conversation, paired with enforcement before a model request leaves its sandbox.

The main contribution is pi-harness: a launcher using Pi's native TUI and public SDK, with an application-owned agent that checks content before publishing it. Pi still provides the editor, session runtime, tools, resource loading, and JSONL persistence. OpenShell provides the sandbox, network interception, and provider credential delivery. Neither Pi nor OpenShell is patched or forked.

The implementation supports reasoning, real tools and tool continuations, expanded skills, and manual/automatic compaction. Admission can deny content or safely redact it before it reaches live history or storage. Allowed native messages retain their block order and provider metadata; Pi's native compaction computation and retention behavior are used.

Why two checks?

Suppose a tool reads sensitive text. Blocking the next model request stops that text leaving the sandbox, but cannot undo an earlier write to the agent's live conversation or saved transcript. Conversely, a cooperative agent check alone cannot stop a different client from sending a raw provider request.

This example demonstrates two complementary boundaries:

  • Before history: allow content unchanged, replace supported text, or deny it before publication and persistence.
  • Before network egress: require a service-signed approval receipt for the covered context and evaluate the actual request before OpenShell attaches real provider credentials.

This is the narrative for “Gating at the network layer is not enough”: local-history integrity and outbound authorization happen at different times. Neither check replaces the other.

How the pieces fit

flowchart LR
  subgraph Sandbox[OpenShell sandbox]
    UI[Native Pi TUI]
    H[pi-harness: admission-controlled agent]
    S[Pi live history and JSONL session]
    UI --> H
    H -->|Approved events only| S
  end
  subgraph Trusted[Outside the sandbox]
    G[Egress Gate service: policy and receipt signing]
    O[OpenShell network proxy]
  end
  H -->|HTTPS: candidates and context approval| G
  G -->|Allow, replace, deny, or context receipt| H
  H -->|Provider request with receipt header| O
  O -->|Authenticated gRPC: verify actual request| G
  G -->|Allow or deny and remove receipt header| O
  O -->|Only on allow: attach real credential and forward| P[Model provider]
Loading

One Egress Gate service, two interfaces: HTTPS admission for the harness and standard authenticated OpenShell middleware gRPC for the proxy. It is not another model proxy, a loopback bridge, or a custom OpenShell RPC.

Component Responsibility
Unmodified Pi Native TUI, public SDK/session APIs, tools, resource loading, model serialization, and persistence of approved events.
pi-harness Stage candidate content privately, request admission, and publish only approved content. Supply the custom agent through AgentSessionConfig.agent; wire checked compaction through session_before_compact.
Egress Gate Apply policy, return allowed/replacement content, sign context receipts, and verify receipts against intercepted requests.
Unmodified OpenShell Isolate the workload, authenticate middleware calls, supply authoritative sandbox identity, enforce middleware decisions, and attach real provider credentials after approval.
Host-side demo helpers Discover gateway identity, prepare policy/TLS/model configuration, and register, launch, verify, and clean up the example.

A message's journey

sequenceDiagram
  participant H as pi-harness
  participant G as Egress Gate
  participant S as Pi history and JSONL
  participant O as OpenShell proxy
  participant M as Model provider
  H->>G: Check candidate before publication
  alt Denied
    G-->>H: Deny
    Note over H,S: Candidate never enters live history or JSONL
  else Allowed or safely redacted
    G-->>H: Approved content
    H->>S: Publish approved message events
    Note over H,G: Separate check before each model call
    H->>G: Approve ordered user/tool context
    G-->>H: Signed receipt
    H->>O: Provider request + x-egress-admission
    O->>G: Actual request + authenticated sandbox context
    G-->>O: Allow or deny after checking receipt and policy
    opt Allowed
      O->>M: Strip receipt, attach credential, forward
      M-->>H: Response through proxy
      Note over H,S: Response is another candidate: admit before publication
    end
  end
Loading

Candidate checks cover final user text after explicit skill expansion, project context, finalized assistant answer/reasoning and tool calls, tool results/errors, and completed compaction summaries. Manual and automatic compaction share the checked path, using Pi's native summary computation and recent-context retention, including split turns. Real tools and tool continuations remain available.

Pi's native AgentSession remains the persistence owner. The harness gates its events before they can update the transcript; it does not append content and then remove it. Raw response streaming and tool progress are withheld until final content is approved. Temporary candidate buffers and editor drafts are not admitted conversation history.

Redaction happens at insertion, so history and subsequent model context agree. Plain reasoning text can be redacted; signed or structured replay metadata cannot be rewritten independently. Allowed native messages preserve their original content blocks, signatures, and metadata. Executable tool-call fields cannot be rewritten. A replacement at the later context-receipt step is rejected rather than silently changing only the outbound request.

What the attestation actually guarantees

The service signs the ordered user/tool text projection, bound to the sandbox, destination, policy, and expiry. At egress, it extracts that projection from the actual request and verifies the receipt. Missing, invalid, expired, or mismatched receipts fail closed. The intercepted request also passes the configured request policy.

  • The receipt does not sign the complete body, system/assistant history, model settings, or prove that a particular extension ran. It is content approval, not code attestation.
  • Local-history protection covers this harness's supported write paths, not arbitrary same-authority code or a compromised harness rewriting files.
  • Real provider secrets and policy/signing configuration remain outside the sandbox. Proxy credential placeholders are usable capabilities, not proof of process identity.
  • Tool-result admission cannot undo a tool's filesystem or other side effects.
  • Receipts allow identical retries within their five-minute lifetime; service restart invalidates them. This deployment assumes no later middleware rewrites receipt-covered content.
  • Receipt metadata travels in a header removed before forwarding, not in the prompt. Model caching remains under Pi's control; actual redaction or compaction can naturally change cache hits.

Deliberate POC scope

The implementation uses Pi 0.85.1, with OpenShell 0.0.116 as the tested protocol baseline. It supports one selected text-only OpenAI-compatible Chat Completions model, including OpenRouter reasoning requests and replay, fresh sessions, sequential tools, explicit skills, steering/follow-ups, /new, and manual/automatic compaction. The example model configuration uses GLM-5.3-Flash through OpenRouter; the integration is not tied to that provider or model.

The native TUI is real, but this is not full stock Pi behavior or CLI parity. Images, other provider API protocols, arbitrary extensions, resume/import/branching, model switching, resource reload, and direct !/!! execution are outside this POC. The model's bash tool remains available with bounded output. Unchecked tool progress/details are not published. Responses, including reasoning, are buffered until admitted rather than streamed unchecked into the transcript.

Ordinary HTTP-only Egress Gate remains available. The receipt-required service is selected explicitly with --admission-config. Protocol artifacts are managed through omm, using the standard OpenShell contract.

Try it and review it

Start with the example README. Use your existing gateway, your own native Pi models.json, and a provider credential; no NVIDIA-specific endpoint is required. Host service reachability and a shared Docker daemon are prerequisites for this example's image workflow.

The flow is prepare, keep serve running, then register, setup, and launch. verify performs real-model acceptance; cleanup removes demo resources and its registration. Every action supports --print. Registration helpers support local Homebrew and DEB/RPM user services; other deployments need operator-managed registration. Registration restarts the gateway; cleanup deletes sandbox sessions.

The policy's harmless DENY_THIS and REDACT_THIS markers demonstrate denial and replacement with [REDACTED]; they are explicit example rules, not built-in Pi/OpenShell behavior.

Suggested review order:

  1. pi-harness/src: agent.ts owns publication; session.ts wires Pi and compaction; admission.ts maps candidates and obtains receipts.
  2. admission service package: candidate decisions, receipt signing/verification, and the HTTPS boundary.
  3. Architecture guide: trust assumptions and evidence; example scripts cover deployment lifecycle.

Validation and remaining acceptance

Deterministic coverage exercises pending/denied live and durable writes, redaction, real tools and skill expansion, compaction, receipt verification, and gateway JWT authentication. Cross-language integration uses Pi's real serializer and a pseudo-terminal-driven native TUI, with local HTTPS admission/provider endpoints and controlled provider responses. Its authenticated local gRPC channel does not exercise production TLS gRPC startup or a live gateway.

Earlier live-runtime checks demonstrated credential delivery, admission denial, and missing-receipt rejection. A receipt-bearing provider request reached a real endpoint but returned 401 with an intentionally invalid credential. That is transport evidence, not successful model inference.

Keep draft: the current native-TUI workflow still needs a successful ./demo.sh verify run against live OpenShell and a real model. Do not equate deterministic integration or historical CI results with that acceptance gate.

Current local validation passed 384 Python tests, 28 Node tests, formatting/lint/type/import checks, dependency audit, and documentation tests/build. Coverage includes reasoning admission/redaction, immutable replay metadata, native-message preservation, tool continuations, and both compaction paths. These are local results, not a claim about remote CI or live-model acceptance. No Dev Note is published by this PR.

@johnnygreco

Copy link
Copy Markdown
Collaborator Author

Phase 0 Review Record

Frozen range: c7b757d..cc8497e

Scope reviewed: final admission names/contracts, registry-derived bindings, removal of the superseded receipt path, immutable fork/proto pin, runtime/example/docs updates, and provider fixture provenance.

Independent review:

  • Correctness/security/interoperability reviewer found one blocker: JavaScript user-envelope key order did not satisfy Python canonical JSON. No other blocker/high/medium findings.
  • Maintainability/tests/docs/integration reviewer independently found the same blocker. No other blocker/high/medium findings.
  • Minimal fix: cc8497e reorders the two user-envelope object literals and adds one exact-byte assertion covering string and text-block content.
  • Both reviewers re-reviewed 8910d79..cc8497e and returned clean.

Validation:

  • make check: 368 tests passed; format, lint, typing, imports, and audit green.
  • make check-py311: 368 tests passed; all checks green.
  • Focused elevated gRPC integration: 9 passed.
  • Runtime-extension JavaScript test passed, including the exact canonical request bytes.
  • Acceptance grep and git diff --check passed.
  • No protobuf or generated-binding changes.

POC scope remained constrained: 22 files in the main Phase 0 commit with net one-line deletion, followed by the focused two-file interoperability fix. No shims, speculative infrastructure, or unrelated cleanup were added.

@johnnygreco

Copy link
Copy Markdown
Collaborator Author

Phase 1 Review Record

Frozen range: cc8497e..6ddc86e

Scope reviewed: exact seven-origin runtime dispatch, the shared text adapter, distinct assistant/bash adapters, immutable-field validation, Describe bindings, cross-language interoperability, and focused tests.

Independent review found one concrete high-severity interoperability defect: Python byte equality rejected valid JavaScript number spellings in arbitrary assistant tool arguments. The minimal fix in 6ddc86e removes only the assistant envelope's re-encoding byte check while retaining bounded JSON parsing, duplicate-key rejection, strict schema validation, extra-field rejection, and immutable tool-call comparison. A raw JavaScript 1e-7 regression was added. Re-review returned clean.

Scope audit:

  • Production is limited to the seven required origin mappings and three required envelope families.
  • No Phase 2 context binding, provider redesign, protobuf, compatibility, docs expansion, or new infrastructure.
  • Tests are table-driven for shared behavior and focused for assistant/bash differences.

Validation:

  • make check: 377 tests passed; format, lint, typing, imports, and audit green.
  • make check-py311: 377 tests passed; all checks green.
  • Focused runtime-extension JavaScript tests passed.
  • Accepted-fix admission suite: 38 passed.
  • Source-path TypeScript validation reported no runtime-extension errors; the global command remains non-green only for unrelated Pi source errors and missing declarations. No generated output or validation tooling was committed.

@johnnygreco

Copy link
Copy Markdown
Collaborator Author

Phase 2 Review Record

Frozen range: 6ddc86e..3fedae0

Scope reviewed: stateless v2 whole-context hash/count binding, ordered user/tool entry derivation, Chat Completions and Responses extraction, send-time replacements, pre/post gate checks, append-time empty attestations, runtime handle lookup, and shared JS/Python vectors.

Independent findings and disposition:

  • One medium defect was fixed in 01823dd: Chat rejected valid multi-text-block users. Chat, Responses, and JavaScript now join strict text blocks with \n, pinned by the same compact shared vector. Re-review clean.
  • One transport-boundary availability limitation was reproduced: Pi may rewrite tool IDs or synthesize missing tool results after provider-context admission during cross-transport history replay. Eliminating it exactly requires a new provider-preparation contract across Pi AI transports, auth/header ordering, and coding-agent; that architectural expansion was rejected for this POC. 3fedae0 documents the supported workaround: start a fresh session when switching transports and complete tool-call/result sequences. The behavior fails closed with hash/count mismatch before credential delivery; reviewers confirmed no security bypass and accepted the limitation for the supported scope.
  • Security/correctness review otherwise returned clean. Scope/minimality and cross-language re-reviews returned clean at the final head.

Scope audit:

  • Production/runtime: +380/-139 in the main Phase 2 commit; v1 claims/candidate/token logic replaced in place.
  • One compact shared vector; existing test paths converted rather than duplicated.
  • No ledger/state, Phase 2b, assistant/system hashing, caller identity, provider redesign, protobuf change, compatibility shim, or canonicalization framework.

Validation:

  • make check: 376 passed; format, Ruff, ty, imports, and audit green.
  • make check-py311: 376 passed; all checks green.
  • Focused admission tests after fixes: 36 passed.
  • Runtime JavaScript tests/shared vector: 8 passed.
  • Pi focused admission tests: 25 passed; Pi worktree unchanged.
  • Pi npm run check remains non-green only for existing generated model-catalog drift outside this phase.
  • git diff --check: passed.

@johnnygreco

Copy link
Copy Markdown
Collaborator Author

Phase 3 Review Record

Frozen range: 3fedae0..b03c8ea

Scope reviewed: real self-verifying demo cases, printable commands, content-safe reason-code logging, workload readiness removal, base64 runtime bodies, architecture/example docs, and generated mirror workflow.

Independent findings and fixes:

  • The best-effort tool check could false-pass by finding replacement text outside the toolResult record. b03c8ea scopes expected/forbidden checks to actual tool-result JSONL records and preserves the model-dependent SKIP. Re-review clean.
  • Two example README passages still named only user/tool appends. b03c8ea now accurately describes every supported history origin and the generic Pi append boundary. Re-review clean.
  • Demo/privacy, docs/minimality, and cross-repo base64 reviewers found no other blocker/high/medium issue at final head.

Scope audit:

  • Main Phase 3 commit: 15 handwritten files; only 46 net production/runtime lines, the real verifier, focused tests, and one 76-line canonical architecture page.
  • No mock fallback, caller-token work, deployment automation, logging framework, legacy number-array compatibility, or broad docs cleanup.
  • The ignored generated documentation mirror was produced only through the documented staging workflow and byte-matched the canonical page.

Validation:

  • make check: 377 passed.
  • make check-py311: 377 passed.
  • Documentation renderer and strict site build passed; built admission page served locally with HTTP 200 and expected v2 content.
  • Runtime JS/base64 tests and focused command/logging/service tests passed.
  • bash -n and git diff --check passed.
  • Real ./demo.sh verify was not faked. This checkout lacks the prepared ignored OpenShell runtime/CLI (projects/egress-gate/.workspaces/pi-attested-admission/OpenShell/scripts/bin/openshell), .env, and JSON log, so real E2E remains externally blocked until the documented prepare/serve workflow is run with provider credentials.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Documentation preview

View the deployed preview

Built from 4d6909f.

@johnnygreco

Copy link
Copy Markdown
Collaborator Author

Phase 4 Review Record

Reviewed head: 9b6b53850daa7b17e075b106289c4c986f03e06f

Scope remained POC-bounded: the launcher consumes and closes the inherited capability FD, the bridge adapter attaches one bearer header, the existing demo adds an unauthenticated negative control, and focused documentation/tests describe the capability and residual same-user memory-reading risk. Pi and provider behavior are untouched.

The final documentation follow-up replaces the stale middleware-field opt-out with the actual supervisor startup flag and updates the canonical architecture page. Its generated mirror was staged and byte-checked through the documented workflow.

Independent security and scope reviews: clean; no remaining blocker/high/medium findings.

Validation:

  • make check: 377 passed
  • make check-py311: 377 passed
  • focused example tests: 10/10
  • runtime JS test and focused TypeScript compile: pass
  • shell syntax, printed verify workflow, documentation staging/renderer/build/serve, and diff checks: pass
  • exact 43-byte token succeeds; missing/malformed delivery fails clearly before Pi starts
  • live demo.sh verify remains externally blocked by absent provider/gateway configuration; no mock fallback was introduced

@johnnygreco

Copy link
Copy Markdown
Collaborator Author

Final Review Record

Reviewed head: 0bf33ca
Upstream base merged: 743839dae47621c13a3bc339bad2a2c8d0167591

Independent Research, documentation, security, and holistic cross-repository reviews are clean: no remaining blocker, high, or medium findings. The final documentation accurately states the implemented POC boundaries, including assistant-thinking coverage, immutable assistant tool calls, transport-history fail-closed limits, capability residual risk, and the checked-in endpoint/catalog scope of verify.

Validation:

  • make check: 377/377
  • make check-py311: 377/377
  • example command tests: 10/10
  • documentation renderer: 11/11
  • strict docs build and served artifact: pass
  • final GitHub checks for Python 3.11/3.14, docs, license, Dev Notes, and long-horizon evals: pass

The real ./demo.sh verify was not replaced with a mock. It remains externally blocked on this host by absent prepared ignored workspaces/runtime and unset PI_MODEL_API_KEY, EGRESS_GATE_HOST_IP, and PI_MODELS_PATH. Exact commands are available through ./demo.sh --print verify; a configured environment must run ./demo.sh reset && ./demo.sh verify before promotion beyond draft.

The PR remains draft; no merge/readiness change was made.

@johnnygreco johnnygreco changed the title feat(egress-gate): add attested Pi prompt admission feat(egress-gate): no-fork Pi admission before history writes Sep 9, 2026
Signed-off-by: Johnny Greco <jogreco@nvidia.com>
Signed-off-by: Johnny Greco <jogreco@nvidia.com>
Delegate generation to omm with project validation and publish its generated bindings and manifest. Align project guidance with the issue-first policy for manager gaps.

The generation helper depends on the --check-command option in PR #60.
…avior

Preserve allowed native messages and replay metadata, use Pi's native compaction and thinking controls, and validate admission before history publication. Simplify unused admission abstractions and test scaffolding, protect registration ownership, and align the example and architecture docs.
@johnnygreco
johnnygreco force-pushed the johnny/pi-attested-admission branch from 221ab32 to 4d6909f Compare September 11, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant