From 3508879fb012c08e7ccc1a5007f772220303b45f Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Sat, 25 Apr 2026 08:30:07 -0400 Subject: [PATCH 1/2] docs: record R graphics device decision --- ...cs-device-for-incremental-plot-emission.md | 78 +++++++++++++------ docs/index.md | 2 +- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/docs/futurework/r-graphics-device-for-incremental-plot-emission.md b/docs/futurework/r-graphics-device-for-incremental-plot-emission.md index 305fdae3..1b3bd94b 100644 --- a/docs/futurework/r-graphics-device-for-incremental-plot-emission.md +++ b/docs/futurework/r-graphics-device-for-incremental-plot-emission.md @@ -2,8 +2,17 @@ ## Summary -The current R plot-capture path is good enough for many cases, but it cannot -reliably surface a plot as soon as it becomes visible on an interactive device. +A custom R graphics device was investigated as a way to emit plot updates closer +to draw time. It is not currently recommended. + +The known plot-before-later-stdout ordering bug was a server-side timeline +problem, not a graphics-device problem. That bug is now handled by timeline +reconstruction, and a custom device would not remove the need for that ordering +logic. + +The current R plot-capture path still has one real limitation: it cannot reliably +surface a plot as soon as it becomes visible on an interactive device inside a +single grouped top-level expression. The most obvious failure shape is a grouped top-level expression such as: @@ -15,8 +24,8 @@ In an interactive R session, the plot is already visible before the later `cat("done\n")`. In `mcp-repl`, the current hook-based capture path can delay the plot image until later than that. -This future work item covers a more direct graphics-capture design that can -emit plots incrementally, without waiting for top-level task completion. +This note is retained as a decision record for why that limitation is not enough +to justify a custom graphics device today. ## Current Design @@ -32,7 +41,7 @@ Today, `mcp-repl` captures R plots by: That path is intentionally lightweight and avoids implementing a full graphics device in Rust or C. -## Why This Is Insufficient +## Remaining Limitation The current hook set is too coarse for some timing-sensitive cases. @@ -47,13 +56,38 @@ So for a grouped expression, the current design has no precise “plot is now on screen” signal. That means server-side timeline fixes can only help after the image exists; they cannot make an image arrive earlier than the worker emits it. -## Candidate Approaches +That limitation is separate from ordinary plot/stdout ordering across separate +input lines. The server timeline can already place an emitted `plot_image` +before later stdout when the sideband facts contain that ordering. + +## Investigation Outcome + +Do not implement a custom graphics device now. + +The reasons are: + +- The main observed ordering failure was fixed in server-side timeline + reconstruction. +- A custom device would not by itself solve cross-stream ordering; the server + would still need to merge stdout/stderr pipes with sideband image events. +- The remaining grouped-expression limitation is narrower than the original + ordering problem. +- A device implementation would add a large native graphics surface: page + lifecycle, clipping, text, rasters, sizing, platform behavior, and replay + compatibility. +- Lower-level graphics-engine hooks without a device do not appear to expose a + clean "current page changed" signal for the existing architecture. + +Keep the hook/replay path unless there is a concrete product requirement for +mid-expression plot visibility. + +## Investigated Approaches ### 1. Real MCP graphics device Implement a real R graphics device for `mcp-repl`. -Why this is attractive: +Why this looked attractive: - drawing callbacks run as graphics operations happen, - the device can know immediately when a page becomes dirty, @@ -69,6 +103,8 @@ Tradeoffs: sizing, - likely more cross-platform complexity. +Current decision: not recommended. + ### 2. Custom offscreen capture device Implement a narrower custom device whose job is only to capture the active page @@ -86,6 +122,9 @@ Tradeoffs: - still needs careful handling of page boundaries and device state, - may end up close in complexity to a full custom device anyway. +Current decision: not recommended. The narrower version still carries most of +the hard device-lifecycle work. + ### 3. Deeper graphics-engine integration without a device Investigate whether a lower-level hook into the R graphics engine could observe @@ -101,24 +140,17 @@ Current read on this option: This option should be treated as a long shot, not the default plan. -## Recommendation - -If `mcp-repl` needs grouped expressions to surface plots before later stdout -text, the serious path is a custom graphics device. - -If the goal is the smallest architectural leap from today’s code: +## Revisit Criteria -- prefer a custom offscreen capture device first, -- keep the emitted artifact as PNG, -- dedupe on content hash as today, -- leave server-side ordering logic unchanged except for consuming earlier image - arrivals. +Revisit this only if there is a concrete public requirement for plots to appear +while a single grouped R expression is still running. -If the goal is the cleanest long-term architecture: +Before changing the graphics device, require: -- implement a real `mcp-repl` graphics device explicitly, -- stop depending on `recordPlot()` / `replayPlot()` for timing-sensitive plot - emission. +- a public API regression test that demonstrates the user-visible gap, +- a prototype showing materially earlier image emission for that case, +- evidence that the improvement is worth the native-device complexity, +- no regression to current base and grid plot capture. ## Relationship To Other Work @@ -132,7 +164,7 @@ specifically about the plot-capture mechanism. ## Non-Goals -- Fixing plot/stdout ordering only in the server timeline. +- Reopening the fixed plot/stdout ordering bug. - Expanding the existing `recordPlot()` hook set indefinitely. - Redesigning Python plotting. - Redefining request completion as part of the same work item. diff --git a/docs/index.md b/docs/index.md index 47073a22..a9eee6ff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,7 +34,7 @@ checked-in execution plans without relying on stale notes. - `docs/futurework/offline-manual-surfaces.md`: deferred design note on exposing R manuals and future Python manuals through better offline model-facing surfaces than the current inline `RShowDoc()` flow. - `docs/futurework/per-turn-history-bundles.md`: design brief for always-materialized per-turn REPL history bundles. - `docs/futurework/r-embedding-minimal-callbacks.md`: deferred note on reducing custom embedded-R callbacks while keeping readline integration. -- `docs/futurework/r-graphics-device-for-incremental-plot-emission.md`: deferred design note on replacing hook/replay plot capture with a device-level path that can emit plots before grouped expressions finish. +- `docs/futurework/r-graphics-device-for-incremental-plot-emission.md`: decision record for not currently replacing hook/replay plot capture with a custom graphics device. - `docs/futurework/worker-session-tempdir-rotation.md`: deferred design note on rotating worker tempdir paths per launch so stale temp trees do not block respawn. - `docs/futurework/stronger-worker-child-containment.md`: deferred design note on tighter worker descendant containment, especially on Windows. - `docs/futurework/unified-output-timeline-pipeline.md`: deferred design note for converging pager and files mode onto one shared resolved timeline pipeline. From a9f9fa378a18e9eb723b591130437f53d3173ebd Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Sat, 25 Apr 2026 12:17:30 -0400 Subject: [PATCH 2/2] docs: add plugin skill bundle futurework --- .../portable-plugin-skill-mcp-bundle.md | 360 ++++++++++++++++++ docs/index.md | 1 + 2 files changed, 361 insertions(+) create mode 100644 docs/futurework/portable-plugin-skill-mcp-bundle.md diff --git a/docs/futurework/portable-plugin-skill-mcp-bundle.md b/docs/futurework/portable-plugin-skill-mcp-bundle.md new file mode 100644 index 00000000..ace71b3a --- /dev/null +++ b/docs/futurework/portable-plugin-skill-mcp-bundle.md @@ -0,0 +1,360 @@ +# Portable Plugin Skill MCP Bundle + +## Summary + +Package `mcp-repl` as a client plugin that installs both: + +- an MCP server configuration for the `mcp-repl` tools, +- a skill that teaches agents when and how to use those tools. + +The preferred direction is a single portable plugin directory with shared +root-level `skills/` and `.mcp.json`, plus both Codex and Claude manifest +flavors pointing at the same files. + +This is a future distribution surface, not a replacement for +`mcp-repl install`. The current installer owns the known-good direct MCP +configuration path. A plugin should start as a small, testable wrapper around +the same executable and the same tool behavior. + +## Research Findings + +This note comes from a local review of Codex plugin/skill support and current +Claude plugin documentation on 2026-04-25. + +### `.agents` vs `.codex` + +- Codex configuration and plugin cache state are still Codex-owned under + `~/.codex` or `$CODEX_HOME`. +- Codex reads user-installed skills from `~/.agents/skills` in addition to the + older `$CODEX_HOME/skills` location. +- Codex discovers repo skills from `.agents/skills`. +- `~/.agents/.skill-lock.json` is not a Codex plugin manifest. It is Skills CLI + state from the wider Agent Skills ecosystem. The observed upstream schema is + `vercel-labs/skills` `src/skill-lock.ts`, version 3, with entries keyed by + skill name and fields such as `source`, `sourceType`, `sourceUrl`, + `skillPath`, `skillFolderHash`, `installedAt`, `updatedAt`, and optional + `pluginName`. Treat it as installer/update metadata, not as a package + declaration for `mcp-repl`. + +### Plugin Format Support + +Plugins are not OpenAI-only. Claude Code has a plugin system too, but the +formats are not one universal spec. + +Codex-native layout: + +- plugin manifest: `.codex-plugin/plugin.json` +- marketplace manifest: `.agents/plugins/marketplace.json` + +Claude-native layout: + +- plugin manifest: `.claude-plugin/plugin.json` +- marketplace manifest: `.claude-plugin/marketplace.json` + +Current Codex compatibility is useful for a shared prototype: + +- Codex discovers both `.codex-plugin/plugin.json` and + `.claude-plugin/plugin.json`. +- Codex discovers both `.agents/plugins/marketplace.json` and + `.claude-plugin/marketplace.json`. +- Codex plugin manifests currently model `skills`, `mcpServers`, and `apps` as + path fields. For `mcpServers`, use a path to a JSON file rather than relying + on inline MCP config. +- Codex loads the default `skills/` directory and also adds the manifest + `skills` path when present. +- Codex loads `.mcp.json` by default, or the file referenced by `mcpServers`. + The file may use a wrapped `{"mcpServers": {...}}` shape or a flat server map. +- Codex normalizes relative `cwd` values in plugin MCP server config against the + plugin root. + +Claude compatibility constraints: + +- Claude plugins use the same root-level `skills/` and `.mcp.json` concepts. +- Claude supports `.mcp.json` at the plugin root or inline `mcpServers` in + `plugin.json`. +- Claude documents `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}` for + plugin-relative paths and persisted plugin data. +- Claude plugin components must live at the plugin root, not inside + `.claude-plugin/`. + +The common subset is therefore: + +- root-level `skills//SKILL.md`, +- root-level `.mcp.json`, +- a manifest that points to `./skills/` and `./.mcp.json`, +- no reliance on inline `mcpServers`, +- no reliance on `${CLAUDE_PLUGIN_ROOT}` in the shared `.mcp.json`, +- no reliance on Codex-only sandbox metadata in the shared `.mcp.json`. + +Marketplace files are not the common subset. Keep the plugin directory shared, +but expect separate marketplace declarations for Codex and Claude if the plugin +needs to be distributed through both plugin managers. + +## Intended Shape + +Use one plugin root: + +```text +mcp-repl-plugin/ + .codex-plugin/plugin.json + .claude-plugin/plugin.json + skills/ + mcp-repl/ + SKILL.md + .mcp.json +``` + +Use the same minimal manifest shape for both clients when possible: + +```json +{ + "name": "mcp-repl", + "version": "0.1.0", + "description": "REPL skills plus MCP tools.", + "skills": "./skills/", + "mcpServers": "./.mcp.json" +} +``` + +Use wrapped MCP config so the same `.mcp.json` can be consumed by both clients: + +```json +{ + "mcpServers": { + "r": { + "type": "stdio", + "command": "mcp-repl", + "args": ["--interpreter", "r"] + }, + "python": { + "type": "stdio", + "command": "mcp-repl", + "args": ["--interpreter", "python"] + } + } +} +``` + +This shared config assumes `mcp-repl` is already invokable by the client +process. That can be through `PATH`, a package runner, or a wrapper command, but +the first prototype should not depend on plugin-relative executable paths. + +The skill should stay declarative and client-neutral: + +```markdown +--- +name: mcp-repl +description: Use when iterating in persistent R or Python REPL sessions through mcp-repl. +--- + +Use the bundled `mcp-repl` MCP tools for interactive R and Python runtime +inspection, plotting, help, debugging, and short verification loops. +Prefer these tools over ad hoc shell execution when session state, plots, or +runtime-specific help are useful. +``` + +## Portability Rule + +The most portable plugin uses an MCP server executable that is already +invokable through one of these routes: + +- `PATH`, +- `npx`, +- `uvx`, +- `docker`. + +For `mcp-repl`, the first slice should assume the binary is available on `PATH` +or installed separately by the existing installer. A plugin should not silently +replace the current install path until the cross-client packaging behavior is +verified. + +Avoid putting client-specific tool names into the shared skill. Codex and +Claude often expose MCP tools through similar names, but the skill should say +"use the bundled `mcp-repl` MCP tools" rather than hard-code a particular +`mcp__server__tool` spelling. + +## Plugin-Relative Server Code + +Bundling the MCP server implementation inside the plugin is weaker +cross-client ground. + +Claude documents plugin-root substitution through `${CLAUDE_PLUGIN_ROOT}`. +Codex currently handles plugin-relative MCP config mainly by resolving a +relative `cwd` against the plugin root. + +If `mcp-repl` needs plugin-root-relative server paths, keep the skill shared but +split the MCP config files: + +```text +mcp-repl-plugin/ + .codex-plugin/plugin.json + .claude-plugin/plugin.json + skills/ + mcp-repl/ + SKILL.md + .mcp.codex.json + .mcp.claude.json +``` + +```json +{ + "name": "mcp-repl", + "skills": "./skills/", + "mcpServers": "./.mcp.codex.json" +} +``` + +```json +{ + "name": "mcp-repl", + "skills": "./skills/", + "mcpServers": "./.mcp.claude.json" +} +``` + +Split only for concrete path-resolution differences. Keep one shared `.mcp.json` +when the server is externally invokable. + +Likely reasons to split: + +- The server command must reference a binary or script inside the plugin + directory. +- The server needs a plugin-persistent data directory. +- The Codex config needs `--sandbox inherit` while Claude needs explicit default + sandbox behavior. +- The client requires different environment variables or approval metadata. + +For plugin-relative code, a Codex-specific config can use a relative `cwd` that +Codex resolves against the plugin root. A Claude-specific config can use +`${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}`. Do not assume either +client expands the other client's placeholders. + +## Relationship To Current Install + +The current `mcp-repl install` command writes client-specific MCP config for +Codex and Claude. A plugin would be a higher-level distribution surface: + +- the MCP config starts the same tools, +- the bundled skill carries richer operational guidance, +- users can install one artifact instead of separately configuring tools and + copying instructions. + +This should not remove the existing installer. The plugin path is additive +until it is proven to cover the same client-specific sandbox and oversized-output +defaults. + +## Prototype Plan + +1. Create a checked-in `plugins/mcp-repl/` prototype with shared `skills/` and + shared `.mcp.json`. +2. Start with `command = "mcp-repl"` and document that the binary must already + be installed. +3. Move the high-signal guidance from + `docs/futurework/repl-tool-description-extras.md` into the first skill draft. +4. Add `.codex-plugin/plugin.json` and `.claude-plugin/plugin.json` with the + same `name`, `description`, `skills`, and `mcpServers` fields. +5. Add a Codex local marketplace entry at + `.agents/plugins/marketplace.json` in the test repository. Point + `source.path` at `./plugins/mcp-repl`. +6. Add a Claude local marketplace entry at + `.claude-plugin/marketplace.json` in the same test repository, or use the + Claude CLI's local marketplace add flow. +7. Install from both plugin managers and verify: + - the skill appears, + - the MCP servers start, + - the R and Python tools are visible, + - a minimal one-call REPL smoke test works for each interpreter, + - uninstall/disable leaves the existing `mcp-repl install` path unaffected. +8. Only after that, evaluate whether a packaged executable route such as `npx`, + `uvx`, or a release-binary wrapper is worth supporting. + +## Example Marketplace Stubs + +Codex repo-local marketplace: + +```json +{ + "name": "local-mcp-repl", + "interface": { + "displayName": "Local mcp-repl" + }, + "plugins": [ + { + "name": "mcp-repl", + "source": { + "source": "local", + "path": "./plugins/mcp-repl" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} +``` + +Claude repo-local marketplace: + +```json +{ + "name": "local-mcp-repl", + "owner": { + "name": "mcp-repl maintainers" + }, + "plugins": [ + { + "name": "mcp-repl", + "source": "./plugins/mcp-repl", + "description": "REPL skills plus MCP tools." + } + ] +} +``` + +These marketplace files are distribution adapters. The shared artifact is still +the plugin root under `plugins/mcp-repl/`. + +## Source References + +- Codex plugin docs: + `https://developers.openai.com/codex/plugins` +- Codex plugin build docs: + `https://developers.openai.com/codex/plugins/build` +- Claude plugin docs: + `https://docs.claude.com/en/docs/claude-code/plugins` +- Claude plugin marketplace docs: + `https://docs.claude.com/en/docs/claude-code/plugin-marketplaces` +- Claude plugin reference: + `https://docs.claude.com/en/docs/claude-code/plugins-reference` +- Codex source areas reviewed: + - `codex-rs/core-skills/src/loader.rs` + - `codex-rs/utils/plugins/src/plugin_namespace.rs` + - `codex-rs/core-plugins/src/marketplace.rs` + - `codex-rs/core-plugins/src/manifest.rs` + - `codex-rs/core-plugins/src/loader.rs` + +## Open Questions + +- Should the plugin expose one server per interpreter, or one server with a + selected default interpreter? +- How should plugin install preserve Codex `--sandbox inherit` while using + explicit sandbox defaults for Claude? +- Should the plugin be generated from the existing install code, or checked in + as static assets? +- What should be the release vehicle: repo artifact, package registry, or + generated files from `mcp-repl install`? +- Should the shared skill be terse and client-neutral, with richer client- + specific guidance in separate optional skills, or should one skill carry all + operational details? +- Should plugin packaging be tested in CI with fake Codex/Claude plugin roots, + or only by live manual smoke tests until plugin behavior stabilizes? + +## Non-Goals + +- Replacing the existing `mcp-repl install` command in the first slice. +- Bundling R or Python themselves. +- Encoding client-specific path hacks in the shared skill. +- Assuming plugin-relative executable paths are portable without testing both + clients. diff --git a/docs/index.md b/docs/index.md index a9eee6ff..19e45988 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,6 +33,7 @@ checked-in execution plans without relying on stale notes. - `docs/futurework/composable-tool-descriptions.md`: deferred design note on replacing the current multi-file `repl` description matrix with one composable template plus runtime interpolation. - `docs/futurework/offline-manual-surfaces.md`: deferred design note on exposing R manuals and future Python manuals through better offline model-facing surfaces than the current inline `RShowDoc()` flow. - `docs/futurework/per-turn-history-bundles.md`: design brief for always-materialized per-turn REPL history bundles. +- `docs/futurework/portable-plugin-skill-mcp-bundle.md`: futurework note on packaging a shared skill plus MCP server config as a portable Codex/Claude plugin. - `docs/futurework/r-embedding-minimal-callbacks.md`: deferred note on reducing custom embedded-R callbacks while keeping readline integration. - `docs/futurework/r-graphics-device-for-incremental-plot-emission.md`: decision record for not currently replacing hook/replay plot capture with a custom graphics device. - `docs/futurework/worker-session-tempdir-rotation.md`: deferred design note on rotating worker tempdir paths per launch so stale temp trees do not block respawn.