An Obsidian plugin for desktop and mobile that opens a native-feeling chat pane backed by bundled Pi agent libraries. It does not require, launch, or shell out to a pi binary installed on the host machine.
Note Pi has three parts. AgentController is the product-level application layer: it receives UI intents, owns provider/session/extension policy, and publishes UI-facing snapshots and events. PiAgentRuntime is its thin adapter around Pi's core runtime.
| Part | Responsibility | Examples |
|---|---|---|
| Plugin configuration | Persistent, vault-local configuration that is analogous to setting up Pi before a session. | Per-provider API key/token storage, connection testing, removing a credential, agent directory, auto-context toggle. |
| Chat UI | The interactive session surface. It sends user input, renders streamed output, and applies session-level harness controls. | Chat messages, cancel, composer model picker, extension slash commands; future /model and /agents. |
| AgentController + Embedded Pi Runtime | The application layer and bundled Pi integration. The controller applies configuration and session policy; the runtime creates Pi agents, adapts provider streaming, and exposes core tools. | AgentController, PiAgentRuntime, Pi model catalog, credentials, provider transport, transcript, cancellation. |
Obsidian's current renderer runtime cannot launch a reliable Node child process or worker thread, so this first slice loads the harness in-process behind a narrow UI-to-harness interface. This is a structural separation, not a security boundary. A future process-isolated harness remains an optional deployment evolution when the host supports it.
The release bundles the JavaScript libraries @earendil-works/pi-agent-core and @earendil-works/pi-ai into main.js. It deliberately does not include the full Pi CLI/coding-agent application or a platform-specific pi executable.
That means:
PiAgentRuntime to access Pi's Agent, model catalog, provider adapters, credential abstraction, streaming events, and cancellation API directly in the Obsidian Electron process.Pi agent directory: Plugin settings stores a vault-relative agentDir, defaulting to _pi/agent; paths outside the vault are rejected. The underscore keeps the folder visible in Obsidian's file explorer. The harness receives the resolved <vault>/_pi/agent path and discovers extensions only from its extensions/ directory (see below). Skills, prompts, packages, and themes remain future resource-loader work.
Note Pi resolves extensions the same way pi-coding-agent does, rooted at the vault-local agent directory instead of ~/.pi/agent:
<agentDir>/extensions/*.ts|*.js load directly.<agentDir>/extensions/<name>/index.ts|index.js load as subdirectory entries.<agentDir>/extensions/<name>/package.json with a pi.extensions field loads its declared paths.Discovery never recurses beyond one level and never touches global Pi locations. Extensions are TypeScript or JavaScript modules whose default export is a factory function receiving a subset of Pi's ExtensionAPI:
import { Type } from "typebox";
export default function (pi) {
pi.on("session_start", async (_event, ctx) => ctx.ui.notify("loaded"));
pi.registerTool({ name: "my_tool", description: "…", parameters: Type.Object({}), async execute(id, params) { /* … */ } });
pi.registerCommand("mycommand", { description: "…", handler: async (args, ctx) => "result" });
}
Supported handlers: session_start, session_shutdown, turn_start, turn_end, tool_call (may return { block: true, reason }), and tool_result. Registered tools join the agent's tool list next to the vault read tool, and /name args typed in the composer invokes a registered command. typebox and the bundled Pi packages resolve as virtual modules, so CLI-style imports work unchanged. A header chip lists loaded extensions and any load failures. The Extensions settings tab lists each loaded extension's name and description. Configure the Pi agent directory from the General tab.
This is compatible with the shared Pi extension model, not a drop-in copy of the full Coding Agent runtime. A limited compatibility shim allows common inspection extensions to import the Coding Agent's agent-directory and tool/command helpers, but terminal UI, keyboard shortcuts, CLI flags, package installation, and full Coding-Agent session/settings APIs remain unavailable because Note Pi deliberately embeds pi-agent-core.
Extensions execute arbitrary code with the plugin's permissions. Only place extensions you trust in the vault's agent directory.
Plugin configuration Chat UI Embedded Pi Harness
──────────────────── ─────── ───────────────────
provider API keys ───────► apply persistent configuration ─────────────► configure providers + credentials
│
├─ model picker / future `/model` ─► apply session model
├─ future `/agents` ───────────────► apply agent configuration
├─ send message ───────────────────► Agent.prompt()
└─ Escape / cancel ────────────────► Agent.abort()
◄────────────────────────────────── Pi streaming events
render message deltas
Note Pi settings are split into three tabs. General holds the agent directory, the auto-context toggle, and the response font size (12–20px, default 14px; applies to assistant messages and updates the open chat immediately). Extensions lists the extensions currently loaded from that directory. API Provider is a pure key manager: pick a provider, paste its key, and save; each stored key can be replaced, connection-tested, or removed. Multiple providers can hold keys at the same time, and key management never touches the chat session.
The composer bar contains the current model picker, the UI equivalent of a basic /model control. It lists the bundled Pi model catalog for every configured provider, grouped by provider. Model choice is session-only: it is held by the harness, preserves the current transcript, and is never written to Obsidian plugin data. If the active provider's key is removed, the harness falls back to any remaining configured provider.
When auto-context is enabled (the default), the currently focused note is added as a context chip at the start of each chat session. The chip is a regular context entry: remove it with its × control to exclude the note from the session, or use "Add current note" to attach a note at any time.
/agents and other Pi-style session controls are part of the intended Chat UI contract but are not implemented yet. The active note name is displayed as UI context; the current implementation does not send note contents to the model.
Obsidian's renderer fetch is governed by Chromium's network policy and cannot reliably call all model-provider endpoints. The harness therefore sends Pi provider requests through bundled Node networking (node-fetch) and adapts the Node response stream to the Web-stream interface Pi expects. API keys remain in local Obsidian plugin data and each key is passed only to requests for the provider that owns it.
Mobile support is a distinct runtime target. The shipped main.js is a universal bundle: src/entry.ts checks Platform.isMobile and lazily require()s the desktop or mobile subtree, so the unselected runtime's module scope — including the desktop tree's Node imports — never evaluates. (isMobile, not isMobileApp, so mobile emulation on a desktop host also exercises the mobile runtime.) The two runtimes form a parallel stack:
Desktop: ObsidianAgentView -> AgentController -> PiAgentRuntime -> pi-agent-core Agent
Mobile: MobileAgentView -> MobileAgentController -> MobileAgentRuntime -> pi-agent-core Agent (validated subset)
The mobile runtime runs in Obsidian's iOS WebView. npm run build also emits mobile.js, the mobile subtree bundled alone with esbuild's browser platform, so npm run verify:mobile can prove the mobile code is free of node: imports, node-fetch, the Node execution environment, and the jiti extension loader. Its boundaries:
fetch: the Gemini endpoint is CORS-enabled for the app://obsidian.md origin, and pi-ai's Google adapters reject a custom fetch. Anthropic, Kimi Code, and OpenAI route through Obsidian's requestUrl, which bypasses WebView CORS restrictions. requestUrl buffers responses, so provider output is not token-streamed over the wire, and an in-flight HTTP request cannot be aborted (the local agent loop still cancels immediately).On mobile the settings show only the capabilities the mobile plugin implements: the API Provider key manager and a General tab with the response font size. The agent directory, extension inventory, and auto-context controls appear only on desktop.
@ followed by a note name; select a result to create a removable context chip./ to browse commands from loaded extensions; select one to complete it in the composer.Escape while a response is streaming to cancel it, or while a suggestion list is open to dismiss the list.The model menu spans the bundled Pi model catalogs of every provider with a saved key, grouped by provider. Each provider's credentials and model list are independent of the others.
npm install
npm run verify
To try it in Obsidian, install or symlink the built main.js, manifest.json, styles.css, and the vendored runtime/ directory into a desktop vault's .obsidian/plugins/note-pi/ directory, enable the plugin, open Note Pi settings to save a provider credential, then open the Note Pi chat and select a model. Obsidian plugin data is local storage, not OS keychain-backed secret storage.
With the Obsidian CLI enabled, npm run deploy:testing builds, copies the artifacts into the shared testing vault, and hot-reloads the plugin in the running app.
CH-YYK/note-pi and select the latest release.BRAT installs the release's main.js, manifest.json, and styles.css assets. Note Pi restores its bundled extension runtime on first load, so Pi extensions remain available in a BRAT installation.
Note Pi intentionally supports API keys and tokens only. A Gemini API key can use Google AI Studio free-tier quota when available. Kimi Code (https://api.kimi.com/coding) is a distinct Pi provider with its own credential and model catalog.
main.js and source maps are build artifacts and intentionally excluded from Git. A distributable plugin release must contain the built main.js alongside manifest.json and styles.css.
npm run spike:surface compares published package metadata for the minimal pi-agent-core path and the full pi-coding-agent path.