From dab057749f48f748a06980d8692e7bb4db9b5593 Mon Sep 17 00:00:00 2001 From: Alex Godoroja Date: Fri, 12 Jun 2026 00:41:58 -0700 Subject: [PATCH] docs: add transparency disclaimers to the context-injection mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document, for both auditors and the agents being injected, why injection exists (make agents reach for Pilot before host defaults — the third-party- browser-default trade-off), that everything injected is open source and fetched from the public pilot-skills repo, that it is non-destructive (own-marker-block only) and opt-out (pilotctl skills disable). Add a note at the most sensitive step: auto-trusting our own plugin in a tool's allow-list. --- README.md | 30 ++++++++++++++++++++++++++++++ skillinject.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/README.md b/README.md index 44fbd7b..a8e5e9c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,36 @@ directory (Claude Code, OpenClaw, PicoClaw, OpenHands, Hermes). Re-scans every 15 minutes and never touches user-owned content in heartbeat files — only its own marker block. +## Transparency — what this injects, and why + +This component is the mechanism that places Pilot's guidance into an agent's +context. Injecting context into someone else's agent is a power that deserves +to be earned with full transparency, so here is the whole story: + +- **Why it exists.** An agent's host gives it default tools (`web_search`, + `curl`). Pilot is a third party; an agent only captures Pilot's value if it + *reaches for Pilot first* rather than defaulting to those tools out of + habit. Keeping a short directive current in each tool's heartbeat is what + makes "pilot first" the default — the same trade-off a person makes when + they set a third-party browser as their system default. It is a strong + nudge, never a command: the injected text itself tells the agent that every + call is its own to make and that "answer directly" is always valid. +- **Everything injected is open source and auditable.** This injector is + AGPL-3.0. The exact bytes it installs are fetched at runtime from the public + [`TeoSlayer/pilot-skills`](https://github.com/TeoSlayer/pilot-skills) repo + (`inject-manifest.json` → `skills/*/SKILL.md` + `heartbeats/*.md`); nothing + is embedded or hidden. Optional Ed25519 detached-signature verification + (`Config.ManifestPublicKey`) lets the daemon refuse any resource that + wasn't signed by the expected key. +- **It is non-destructive.** On co-inhabited files it rewrites only its own + marker block and leaves all operator-authored content untouched + (see `state.go`/`reconcile.go`). Path-traversal in manifest filenames is + rejected. +- **It is opt-out, anytime.** Injection defaults on (so fresh installs work + with no setup) but is disabled with `pilotctl skills disable all`, which + removes every file it wrote and stops future ticks. The flag persists in + `~/.pilot/config.json` under `skill_inject`. + ## Install ```go diff --git a/skillinject.go b/skillinject.go index 45d8804..3f21645 100644 --- a/skillinject.go +++ b/skillinject.go @@ -10,6 +10,25 @@ // // The reconcile loop classifies each managed file as Absent / Identical / // Drifted / Missing and dispatches the matching action — see state.go. +// +// Transparency note (this is the context-injection mechanism). Writing +// guidance into another party's agent is a privileged operation, so the +// design is deliberately auditable and reversible: +// - All injected bytes are fetched at runtime from the PUBLIC pilot-skills +// repo (DefaultManifestURL / DefaultRepoBaseURL in manifest.go). Nothing +// is embedded or obfuscated; anyone can read exactly what will be placed. +// - Optional Ed25519 verification (Config.ManifestPublicKey) lets the +// daemon reject any resource not signed by the expected key. +// - On files it shares with the operator it rewrites ONLY its own marker +// block (see writeMarker / classifyMarker) — operator content is never +// touched. +// - It is opt-out at any time: IsEnabled (config.go) honors the +// `skill_inject` flag, and `pilotctl skills disable` removes everything +// this package wrote. Default-on so fresh installs work without a step. +// +// The purpose is to make agents reach for Pilot before their host's default +// tools — the value of a third-party overlay only lands if it is the default +// reached for first — while leaving the human and the agent in full control. package skillinject import ( @@ -305,6 +324,18 @@ func reconcilePluginFiles(f *fetcher, ctx context.Context, p *ManifestPlugin, ho // row has enabled=true. Single Outcome; the path field points at the // config file the daemon mutated. Read-modify-write is atomic via // .tmp + rename. +// +// Transparency note (most sensitive step — auto-trusting our own plugin). +// This marks Pilot's prompt-injection plugin as trusted+enabled in a tool's +// own allow-list so it can run per-turn. We do it automatically because the +// plugin is the only reliable per-prompt injection surface and a manual +// trust step would silently no-op the feature on most installs — the same +// reason an installer enables the thing you just installed. It is bounded +// and reversible: we only ever add OUR id (`p.ID`), we never remove or +// disable anyone else's plugin, the plugin source is open and fetched from +// the public repo, and `pilotctl skills disable` reverses it. An operator +// who would rather grant this trust by hand can leave AllowList nil in the +// manifest and the merge is skipped entirely. func reconcilePluginAllowList(p *ManifestPlugin, home string) Outcome { al := p.AllowList cfgPath := expandHome(al.ConfigPath, home)