Skip to content

require trust before installing project python skills - #2176

Draft
kevinjosethomas wants to merge 2 commits into
mainfrom
eng-5338-project-skill-trust
Draft

require trust before installing project python skills#2176
kevinjosethomas wants to merge 2 commits into
mainfrom
eng-5338-project-skill-trust

Conversation

@kevinjosethomas

@kevinjosethomas kevinjosethomas commented Sep 9, 2026

Copy link
Copy Markdown
Member

Context

Linear: ENG-5338 — https://linear.app/primeintellect/issue/ENG-5338

Opening a repository was enough to run code from it. loadSkills discovers project skills under <cwd>/.prime/agent/skills, and a pyproject.toml marks a Python skill. On session startup (prewarmIpythonKernel) ensureKernelPython ran uv pip install --editable <skill dir> for every project Python skill into the shared user venv ~/.prime/agent/kernel-venv (executing the skill's build backend), and the kernel bootstrap cell imported the package. There was no trust decision, and the package stayed installed and importable from the shared venv after opening an unrelated project.

Changes

  • core/project-skill-trust.ts (new): persisted per-project trust store (~/.prime/agent/project-skill-trust.json, 0600, keyed by canonical project path; corrupt/unreadable fails closed), applyProjectSkillTrust (downgrades project-scoped Python skills to markdown skills unless trusted), status/prompt helpers.
  • AgentSession: _modelVisibleSkills() applies the trust decision, so untrusted project Python skills reach neither the kernel bootstrap nor the python_import prompt entry (the SKILL.md stays readable as a markdown skill). New getProjectSkillTrust(), setProjectSkillTrust(), promptProjectSkillTrust(). When a UI is bound, the session asks once via the extension UI bridge (trust / not now / never); headless sessions (print, JSON, ACP, subagents) never prompt and stay denied.
  • Daemon: sessions bind before a client attaches, so the bind-time selector has nobody to answer; attach/worker_subscribe re-ask once a client that renders extension UI is attached (no protocol change).
  • Built-in /trust-project-skills [status|on|off|reset] extension command (always loaded, also under --no-extensions) to change the decision later; it reloads so the kernel picks up or drops the packages.
  • Kernel bootstrap: PythonSkillRuntimeInfo.scope is carried to ensureKernelPython; when any project-scoped skill is installed the kernel uses a per-project venv ~/.prime/agent/kernel-venv-projects/<name>-<hash> (getProjectKernelVenvDir). The shared venv only ever receives user-level and bundled skills, so project packages and their dependencies never persist into other projects' sessions.
  • Suite harness defaults to an in-memory trust store so tests never touch the user's real store. Docs (skills.md, rlm.md) and changelog fragment.

Model-facing surface unchanged: tool names, system prompt structure, and the kernel recursion API are untouched; an untrusted project skill is simply listed as <type>markdown</type> without a python_import, exactly as a markdown skill is today.

Validation

Local: npm run check clean. test/project-skill-trust.test.ts (8), test/kernel-bootstrap.test.ts (25), test/skills.test.ts (32), test/suite/regressions/5338-project-skill-trust.test.ts (8), plus sdk-skills, agent-session-services, system-prompt, ipython-provisioner, ipython-bootstrap, herdr-agent-state, agent-session-recursion, repl-kernel-startup, suite agent-session-model-extension, agent-session-runtime, 2781-skill-collision-precedence, daemon-extension-binding, daemon-mode, agent-connection-daemon: all pass. test/resource-loader.test.ts has 4 failures that reproduce identically on unpatched origin/main (extension files under tmp not loaded) and are unrelated.

Prime sandbox (node:24-bookworm, user tester, real uv/Python kernel, fixture skill whose setup.py build hook and __init__.py write markers; headless createAgentSession({ prewarmIpythonKernel: true }) with bindExtensions({}), i.e. no UI):

Step Unpatched main This branch
Open fixture project, no decision build marker written by editable_wheel, import marker written inside rlm.repl, marker_skill bound in kernel namespace, <python_import>marker_skill</python_import> in prompt, importable from shared venv no markers, marker_skill absent from kernel namespace, prompt entry <type>markdown</type>, import marker_skill from shared venv fails, no project venv created, trust status undecided
Same project, trust persisted n/a both markers written, kernel runs from kernel-venv-projects/proj-fixture-<hash>/bin/python, marker_skill importable from the project venv only; shared venv .bootstrap-version has no marker_skill and import marker_skill from it still fails
Open an unrelated clean project marker_skill still importable from the shared venv (persistence) shared venv still free of marker_skill, kernel namespace has no marker_skill, no project venv for the clean project

The same test files were run in the sandbox on this branch: all pass except the 4 pre-existing resource-loader failures (identical on main in the sandbox) and one agent-session-services telemetry test that fails only because the sandbox env exports PI_OFFLINE=1 (passes there without it, and fails identically on main with it). Sandbox deleted afterwards.

Not validated end to end: the interactive TUI selector itself (covered by the suite tests through the extension UI bridge with a mocked select), and the daemon attach re-prompt in a live daemon (covered by promptProjectSkillTrust tests; daemon test files pass).

Note

Require trust before installing project Python skills

  • Project Python skills from the repository are no longer installed or imported into the kernel until explicitly trusted. Untrusted skills are downgraded to markdown-only metadata.
  • Adds a /trust-project-skills command to report and change trust decisions (status, on, off, reset), and prompts interactively when a session starts with undecided project skills.
  • Trust decisions are persisted per-project in the agent directory and trigger a runtime and system prompt rebuild when changed.
  • Trusted project skills use a per-project kernel virtual environment, isolating them from the shared environment.
  • Risk: applyProjectSkillTrust in project-skill-trust.ts removes python_import and other Python runtime metadata from untrusted skills; existing setups relying on automatic project skill execution will need to grant explicit trust.

Macroscope summarized 49d6db8.

Project-scoped Python skills are downgraded to markdown skills until the
project is trusted (persisted per canonical project path, prompted once in
UI sessions, denied by default headless). Trusted project skills install
into a per-project kernel venv instead of the shared kernel-venv.

Linear: ENG-5338
… attaches

Daemon sessions bind extensions before any client is attached, so the
bind-time selector resolves with no answer. Keep the project undecided in
that case and prompt again once a client that renders extension UI attaches.

Linear: ENG-5338
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Prime Agent performance — completed

PR 49d6db80 compared with main f9c7e06b.
↓ improved · ↑ regressed · ≈ no clear change · — unavailable

Metric Main This PR Change Change % Result
Cold startup 3,086.0 ms 3,758.8 ms ↑ +672.8 ms +21.80% slower
Warm startup 1,817.7 ms 2,139.5 ms ≈ +321.8 ms +17.70% no clear change
Installation 31.52 s 37.61 s ≈ +6.10 s +19.34% no clear change
Compressed release artifacts 11.10 MB 11.12 MB ≈ +0.02 MB +0.19% no clear change
Installed footprint 597.35 MB 597.45 MB ≈ +0.10 MB +0.02% no clear change
Idle memory, summed RSS 1,112.66 MB 1,125.36 MB ≈ +12.71 MB +1.14% no clear change

Python runtime

Metric Main This PR Change Change % Result
Python kernel startup 131.8 ms 145.0 ms ≈ +13.2 ms +10.01% no clear change
Python cell round trip 0.620 ms 0.594 ms ≈ -0.027 ms -4.33% no clear change
Empty bash command 13.6 ms 13.2 ms ≈ -0.3 ms -2.33% no clear change
Bash git status 19.5 ms 19.9 ms ≈ +0.3 ms +1.60% no clear change
Bash 32 KiB output 14.0 ms 14.6 ms ≈ +0.5 ms +3.77% no clear change
35 cells / 9 shell calls 197.3 ms 196.1 ms ≈ -1.2 ms -0.61% no clear change
Python interrupt to done 1.714 ms 1.770 ms ≈ +0.056 ms +3.24% no clear change
Python state snapshot 26.9 ms 29.8 ms ≈ +2.9 ms +10.82% no clear change
Python state restore 383.5 ms 408.9 ms ≈ +25.5 ms +6.64% no clear change
Python idle RSS 35.44 MB 37.06 MB ≈ +1.62 MB +4.58% no clear change
Python RSS after pandas workload 96.89 MB 99.15 MB ≈ +2.25 MB +2.33% no clear change

Sandbox cost: ~$0.1056 — no inference calls.
Run, logs, and downloadable raw results

Methodology and samples

Main resolved at 2026-09-09T23:54:04.647965+00:00. Harness f9c7e06b.
Linux x64, 4 vCPU, 8 GB RAM, 20 GB disk; region us.
Image: node:24-bookworm@sha256:be23f54a88d34e8824c741b19b91064094f92c1c97b194144bfc8b50d67258e2.
Stock tools, skills, daemon, and Python bootstrap enabled; fresh homes and a fixed Git fixture.
Onboarding is dismissed; the editor starts without a selected model or submitted prompt.
Medians shown. Arrows require a 20% timing/memory change plus absolute floors and IQR.
These practical noise floors are not a statistical significance test.
Cold means stopped Prime processes; OS filesystem caches are not flushed.
No model requests or credentials. Installation excludes build/setup time.
Installer tarballs use loopback; npm/Python downloads use the network with fresh caches.
Artifact size counts release tarballs; footprint after first use includes registry packages.
MB is decimal. Summed RSS can double-count shared pages; PSS is recorded when available.
Provisioning, setup, and build durations are recorded separately in the raw results.
Kernel probes use the installed JSONL runtime, outside the TUI/TypeScript host.
Per trial: 50 Python cells, 5 calls per shell case, and one 35-cell mix (9 git status calls).
Cell/shell values are batch means; other runtime timings are single operations.
State fixture: a 10,000-row × 8-column integer DataFrame and a 10,000-integer list.
Restore runs in a fresh kernel, including pandas imports; kernel startup is excluded.
Kernel RSS covers the isolated Python process; loaded RSS follows the pandas workload.
Costs estimate full sandbox lifetimes at configured rates, including setup and build.
Budget target: $1; not a billing cap. Checks are informational.

Metric Main successful/attempted PR successful/attempted Main spread PR spread
Cold startup 10/10 10/10 IQR 223.1 ms IQR 216.1 ms
Warm startup 10/10 10/10 IQR 69.7 ms IQR 93.0 ms
Installation 3/3 3/3 range 1.27 s range 2.47 s
Compressed release artifacts 1/1 1/1
Installed footprint 1/1 1/1
Idle memory, summed RSS 10/10 10/10 IQR 47.45 MB IQR 19.51 MB
Python kernel startup 10/10 10/10 IQR 5.2 ms IQR 19.7 ms
Python cell round trip 10/10 10/10 IQR 0.064 ms IQR 0.050 ms
Empty bash command 10/10 10/10 IQR 1.0 ms IQR 1.3 ms
Bash git status 10/10 10/10 IQR 1.7 ms IQR 2.7 ms
Bash 32 KiB output 10/10 10/10 IQR 0.8 ms IQR 1.1 ms
35 cells / 9 shell calls 10/10 10/10 IQR 22.4 ms IQR 28.5 ms
Python interrupt to done 10/10 10/10 IQR 0.258 ms IQR 0.501 ms
Python state snapshot 10/10 10/10 IQR 4.5 ms IQR 13.9 ms
Python state restore 10/10 10/10 IQR 38.3 ms IQR 17.8 ms
Python idle RSS 10/10 10/10 IQR 1.85 MB IQR 1.23 MB
Python RSS after pandas workload 10/10 10/10 IQR 2.68 MB IQR 2.99 MB

Comment on lines +399 to +402
path
.basename(canonical)
.replace(/[^A-Za-z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "")
.replace(/^-+|-+$/g, "")
.slice(0, 32) || "project";
const digest = createHash("sha256").update(canonical).digest("hex").slice(0, 12);
return path.join(path.dirname(baseVenv), `${path.basename(baseVenv)}-projects`, `${slug}-${digest}`);
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.

2 participants