Replace ramekin.ts extension with --append-system-prompt
The extension only appended static text — no dynamic behavior.
Using the CLI flag avoids writing a TypeScript file on every run.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/AGENTS.md b/AGENTS.md
index 40c01c6..4687527 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,7 +14,7 @@ src/
build.rs # Sets RAMEKIN_VERSION from env or git rev
assets/
Dockerfile # Agent container image (Node.js + pi + jj + Rust)
- ramekin.ts # Pi extension injected into the container
+ ramekin-prompt.md # System prompt appended inside the container
clippy.toml # Disallows std::fs in favor of fs-err
justfile # Local dev tasks (check, fmt, clippy, test, install)
.github/workflows/
@@ -46,7 +46,7 @@ just # All four
- XDG directories under the `ramekin` prefix store pi state: data in `$XDG_DATA_HOME/ramekin`, config in `$XDG_CONFIG_HOME/ramekin`.
- Each workspace gets a per-repo sessions directory keyed by a `<dirname>-<hash>` slug.
- If the workspace contains `.ramekin/Dockerfile`, the CLI builds it on top of `ramekin-agent` instead of using the base image directly.
-- The `ramekin.ts` extension is written into the agent config directory on every run. It injects container environment context into the pi system prompt.
+- The `ramekin-prompt.md` file is written into the agent config directory on every run and passed to pi via `--append-system-prompt`. It injects container environment context into the system prompt.
- Version is set at build time via the `RAMEKIN_VERSION` env var (used by CI) or falls back to `dev+<short-sha>`.
## Dependencies
diff --git a/assets/ramekin-prompt.md b/assets/ramekin-prompt.md
new file mode 100644
index 0000000..83e824f
--- /dev/null
+++ b/assets/ramekin-prompt.md
@@ -0,0 +1,15 @@
+# Ramekin Container Environment
+
+You are running inside a Docker container managed by **ramekin**.
+
+## Workspace
+
+The project workspace is bind-mounted at `/workspace`. This is the only directory where your changes are visible to the host.
+
+## Filesystem
+
+The container filesystem is ephemeral. Any files written outside `/workspace` will be lost when the session ends. System packages installed with `apt-get` do not persist across sessions — use a custom `.ramekin/Dockerfile` to add permanent dependencies.
+
+## Networking
+
+The container has unrestricted network access via the default Docker bridge network.
diff --git a/assets/ramekin.ts b/assets/ramekin.ts
deleted file mode 100644
index d431f31..0000000
--- a/assets/ramekin.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
-
-export default function (pi: ExtensionAPI) {
- const context = `
-# Ramekin Container Environment
-
-You are running inside a Docker container managed by **ramekin**.
-
-## Workspace
-
-The project workspace is bind-mounted at \`/workspace\`. This is the only directory where your changes are visible to the host.
-
-## Filesystem
-
-The container filesystem is ephemeral. Any files written outside \`/workspace\` will be lost when the session ends. System packages installed with \`apt-get\` do not persist across sessions — use a custom \`.ramekin/Dockerfile\` to add permanent dependencies.
-
-## Networking
-
-The container has unrestricted network access via the default Docker bridge network.
-`;
-
- pi.on("before_agent_start", async (event) => {
- return { systemPrompt: event.systemPrompt + "\n" + context };
- });
-}
diff --git a/src/main.rs b/src/main.rs
index f50db97..517ad85 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,7 @@ use tracing::{error, info};
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
const DOCKERFILE: &str = include_str!("../assets/Dockerfile");
-const RAMEKIN_EXTENSION: &str = include_str!("../assets/ramekin.ts");
+const RAMEKIN_PROMPT: &str = include_str!("../assets/ramekin-prompt.md");
const VERSION: &str = env!("RAMEKIN_VERSION");
@@ -160,10 +160,9 @@ impl Ramekin {
config.merged_pi().iter().map(|e| e.resolve()).collect();
config::assemble_pi(&agent_dir, &resolved_pi).wrap_err("failed to assemble pi config")?;
- // Always write ramekin.ts after assembly.
- let extensions_dir = agent_dir.join("extensions");
- fs_err::create_dir_all(&extensions_dir).into_diagnostic()?;
- fs_err::write(extensions_dir.join("ramekin.ts"), RAMEKIN_EXTENSION).into_diagnostic()?;
+ // Write the system prompt file so pi can read it via --append-system-prompt.
+ let prompt_path = agent_dir.join("ramekin-prompt.md");
+ fs_err::write(&prompt_path, RAMEKIN_PROMPT).into_diagnostic()?;
Ok(Self {
workspace,
@@ -406,7 +405,6 @@ struct AgentService {
stdin_open: bool,
tty: bool,
volumes: Vec<String>,
- #[serde(skip_serializing_if = "Vec::is_empty")]
command: Vec<String>,
}
@@ -425,14 +423,13 @@ fn generate_compose(
) -> String {
let volumes: Vec<String> = mounts.iter().map(|m| m.to_volume_string()).collect();
- // When extra args are provided, build the full command: `pi <args...>`
- let command: Vec<String> = if pi_args.is_empty() {
- vec![]
- } else {
- std::iter::once("pi".to_string())
- .chain(pi_args.iter().cloned())
- .collect()
- };
+ // Always pass --append-system-prompt for the ramekin container context.
+ // The prompt file is written into the agent dir which is mounted at /root/.pi/agent.
+ let prompt_path = "/root/.pi/agent/ramekin-prompt.md";
+ let command: Vec<String> = std::iter::once("pi".to_string())
+ .chain(["--append-system-prompt".to_string(), prompt_path.to_string()])
+ .chain(pi_args.iter().cloned())
+ .collect();
let config = ComposeConfig {
services: Services {