diff --git a/CLAUDE.md b/CLAUDE.md index e1db074..627d6b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,16 +25,26 @@ This is a Paperclip adapter plugin that runs OpenCode agents as isolated Kuberne 1. **Concurrency guard** — checks for existing running Jobs for the same agent (shared PVC/session enforcement) 2. **Self-pod introspection** (`getSelfPodInfo`) — queries own pod to inherit image, imagePullSecrets, DNS config, PVC mount, and all env vars from the Deployment -3. **Job manifest build** (`buildJobManifest`) — constructs a K8s Job with: +3. **Instructions + skill bundle resolution** — reads `instructionsFilePath` from config and desired skill markdown files from the PVC; content is prepended to the prompt at build time +4. **Job manifest build** (`buildJobManifest`) — constructs a K8s Job with: - Init container (busybox) that writes the prompt to an emptyDir volume - Main opencode container that pipes the prompt via stdin + - Prompt assembled as: `[instructionsContent] + [skillsBundleContent] + bootstrapPrompt + wakePrompt + sessionHandoff + heartbeatPrompt` - Inherited env vars layered: Deployment env → PAPERCLIP_* vars → user overrides - Resource requests/limits, security contexts, tolerations, nodeSelector applied from config -4. **Job creation** — creates the Job in the target namespace -5. **Pod scheduling wait** — polls for the pod to be scheduled, checking init container states and image pull issues -6. **Log streaming + completion wait** — streams pod logs to the Paperclip UI while waiting for Job completion (with configurable timeout) -7. **JSONL parsing** (`parseOpenCodeJsonl`) — extracts session ID, usage tokens, cost, summary, and errors from OpenCode JSONL output -8. **Result synthesis** — returns exit code, usage metrics, session params for resume, and billing type inference +5. **Job creation** — creates the Job in the target namespace +6. **Pod scheduling wait** — polls for the pod to be scheduled, checking init container states and image pull issues +7. **Log streaming + completion wait** — streams pod logs to the Paperclip UI while waiting for Job completion (with configurable timeout) +8. **JSONL parsing** (`parseOpenCodeJsonl`) — extracts session ID, usage tokens, cost, summary, and errors from OpenCode JSONL output +9. **Result synthesis** — returns exit code, usage metrics, session params for resume, and billing type inference + +### Skill Materialization (`src/server/skills.ts` + `src/server/execute.ts`) + +Skills operate in **ephemeral** mode: no symlinks are written to PVC. Instead, `execute()` reads the markdown content of each desired skill at run time using `readPaperclipRuntimeSkillEntries` + `entry.source`, concatenates them (separated by `---`), and passes the bundle to `buildJobManifest` as `skillsBundleContent`. The content is prepended to the prompt so OpenCode receives it as system context. + +### `instructionsFilePath` Config Field + +Set `instructionsFilePath` to an absolute path (typically on the PVC, e.g. `/paperclip/.claude/projects/COMPANY/agents/AGENT/AGENTS.md`). The file is read by the adapter server before Job creation and its content prepended to every run prompt. `supportsInstructionsBundle: true` enables the Paperclip UI bundle editor for this field. ### Key State: SelfPodInfo (`src/server/k8s-client.ts`) diff --git a/package-lock.json b/package-lock.json index 586fe28..ae6bde7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { - "name": "@farhoodliquor/paperclip-adapter-opencode-k8s", - "version": "0.1.16", + "name": "paperclip-adapter-opencode-k8s", + "version": "0.1.18", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@farhoodliquor/paperclip-adapter-opencode-k8s", - "version": "0.1.16", + "name": "paperclip-adapter-opencode-k8s", + "version": "0.1.18", "license": "MIT", "dependencies": { - "@kubernetes/client-node": "^1.0.0" + "@kubernetes/client-node": "^1.0.0", + "picocolors": "^1.1.1" }, "devDependencies": { "@paperclipai/adapter-utils": "2026.415.0-canary.7", @@ -18,7 +19,7 @@ "vitest": "^4.1.4" }, "peerDependencies": { - "@paperclipai/adapter-utils": "2026.415.0-canary.7" + "@paperclipai/adapter-utils": ">=2026.415.0-canary.7" } }, "node_modules/@emnapi/core": { @@ -1574,7 +1575,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { diff --git a/package.json b/package.json index b1c227f..d47509d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "paperclip-adapter-opencode-k8s", - "version": "0.1.17", + "version": "0.1.18", "description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs", "license": "MIT", "type": "module", diff --git a/src/index.ts b/src/index.ts index 0036501..7060dbc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,7 @@ Core fields: - model (string, required): OpenCode model id in provider/model format (e.g. anthropic/claude-sonnet-4-6) - variant (string, optional): provider-specific reasoning/profile variant passed as --variant - dangerouslySkipPermissions (boolean, optional): inject runtime config with permission.external_directory=allow; defaults to true +- instructionsFilePath (string, optional): absolute path to a markdown instructions file (e.g. AGENTS.md on the PVC); content is prepended to every run prompt as system instructions - promptTemplate (string, optional): run prompt template - extraArgs (string[], optional): additional CLI args appended to the opencode command - env (object, optional): KEY=VALUE environment variables; overrides inherited vars from the Deployment @@ -54,7 +55,9 @@ Inherited from Deployment (no config needed): Notes: - Session resume works via the shared /paperclip PVC (HOME=/paperclip) -- Skills are bundled in the container image +- Skills configured in Paperclip have their markdown content read from the PVC and prepended to each run prompt +- Desired skills are resolved from config (paperclipSkills / paperclipRuntimeSkills) at execute time +- instructionsFilePath content is prepended before skill content, then before the task prompt - Prompts are delivered via a busybox init container writing to an emptyDir volume - Runtime config (permission.external_directory=allow) is written inside the Job container - OPENCODE_DISABLE_PROJECT_CONFIG=true is always set to prevent config file pollution diff --git a/src/server/config-schema.ts b/src/server/config-schema.ts index 2e116af..0c2f111 100644 --- a/src/server/config-schema.ts +++ b/src/server/config-schema.ts @@ -11,6 +11,13 @@ export function getConfigSchema(): AdapterConfigSchema { hint: "Provider-specific reasoning/profile variant passed as --variant", group: "Core", }, + { + key: "instructionsFilePath", + label: "Instructions File Path", + type: "text", + hint: "Absolute path to a markdown file (e.g. AGENTS.md) prepended as system instructions before the task prompt", + group: "Core", + }, { key: "dangerouslySkipPermissions", label: "Skip Permission Checks", diff --git a/src/server/index.ts b/src/server/index.ts index a189ef3..1f6aa1d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -20,8 +20,8 @@ export function createServerAdapter(): ServerAdapterModule { supportsLocalAgentJwt: true, agentConfigurationDoc, getConfigSchema, - supportsInstructionsBundle: false, - instructionsPathKey: undefined, + supportsInstructionsBundle: true, + instructionsPathKey: "instructionsFilePath", requiresMaterializedRuntimeSkills: false, sessionManagement: getAdapterSessionManagement("opencode_local") ?? { supportsSessionResume: true,