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
change uplxzpqtyuomuqsyvotmzmkpnxpsznvw
commit 904f108e58a92efc93250e2a983343198c070410
author Alpha Chen <alpha@kejadlen.dev>
date
parent rmzuqnnr
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 {