feat: instructionsFilePath config field + skill bundle materialization
- config-schema: add instructionsFilePath UI field (Core group, text type) - server/index.ts: set supportsInstructionsBundle=true, instructionsPathKey="instructionsFilePath" - execute.ts: read instructionsFilePath file + desired skill markdown files from PVC; pass to buildJobManifest as instructionsContent / skillsBundleContent - job-manifest.ts: accept instructionsContent + skillsBundleContent in JobBuildInput; prepend both to prompt via joinPromptSections; add instructionsChars + skillsBundleChars to promptMetrics - index.ts: document instructionsFilePath and skills injection in agentConfigurationDoc - CLAUDE.md: document skill materialization (ephemeral mode) and instructionsFilePath field - Bump version to 0.1.18 Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -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)
|
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
|
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
|
- Init container (busybox) that writes the prompt to an emptyDir volume
|
||||||
- Main opencode container that pipes the prompt via stdin
|
- 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
|
- Inherited env vars layered: Deployment env → PAPERCLIP_* vars → user overrides
|
||||||
- Resource requests/limits, security contexts, tolerations, nodeSelector applied from config
|
- Resource requests/limits, security contexts, tolerations, nodeSelector applied from config
|
||||||
4. **Job creation** — creates the Job in the target namespace
|
5. **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. **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. **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. **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
|
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`)
|
### Key State: SelfPodInfo (`src/server/k8s-client.ts`)
|
||||||
|
|
||||||
|
|||||||
Generated
+7
-7
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@farhoodliquor/paperclip-adapter-opencode-k8s",
|
"name": "paperclip-adapter-opencode-k8s",
|
||||||
"version": "0.1.16",
|
"version": "0.1.18",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@farhoodliquor/paperclip-adapter-opencode-k8s",
|
"name": "paperclip-adapter-opencode-k8s",
|
||||||
"version": "0.1.16",
|
"version": "0.1.18",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kubernetes/client-node": "^1.0.0"
|
"@kubernetes/client-node": "^1.0.0",
|
||||||
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@paperclipai/adapter-utils": "2026.415.0-canary.7",
|
"@paperclipai/adapter-utils": "2026.415.0-canary.7",
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
"vitest": "^4.1.4"
|
"vitest": "^4.1.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@paperclipai/adapter-utils": "2026.415.0-canary.7"
|
"@paperclipai/adapter-utils": ">=2026.415.0-canary.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
@@ -1574,7 +1575,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperclip-adapter-opencode-k8s",
|
"name": "paperclip-adapter-opencode-k8s",
|
||||||
"version": "0.1.17",
|
"version": "0.1.18",
|
||||||
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
|
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+4
-1
@@ -26,6 +26,7 @@ Core fields:
|
|||||||
- model (string, required): OpenCode model id in provider/model format (e.g. anthropic/claude-sonnet-4-6)
|
- 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
|
- 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
|
- 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
|
- promptTemplate (string, optional): run prompt template
|
||||||
- extraArgs (string[], optional): additional CLI args appended to the opencode command
|
- extraArgs (string[], optional): additional CLI args appended to the opencode command
|
||||||
- env (object, optional): KEY=VALUE environment variables; overrides inherited vars from the Deployment
|
- env (object, optional): KEY=VALUE environment variables; overrides inherited vars from the Deployment
|
||||||
@@ -54,7 +55,9 @@ Inherited from Deployment (no config needed):
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- Session resume works via the shared /paperclip PVC (HOME=/paperclip)
|
- 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
|
- 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
|
- 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
|
- OPENCODE_DISABLE_PROJECT_CONFIG=true is always set to prevent config file pollution
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ export function getConfigSchema(): AdapterConfigSchema {
|
|||||||
hint: "Provider-specific reasoning/profile variant passed as --variant",
|
hint: "Provider-specific reasoning/profile variant passed as --variant",
|
||||||
group: "Core",
|
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",
|
key: "dangerouslySkipPermissions",
|
||||||
label: "Skip Permission Checks",
|
label: "Skip Permission Checks",
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@ export function createServerAdapter(): ServerAdapterModule {
|
|||||||
supportsLocalAgentJwt: true,
|
supportsLocalAgentJwt: true,
|
||||||
agentConfigurationDoc,
|
agentConfigurationDoc,
|
||||||
getConfigSchema,
|
getConfigSchema,
|
||||||
supportsInstructionsBundle: false,
|
supportsInstructionsBundle: true,
|
||||||
instructionsPathKey: undefined,
|
instructionsPathKey: "instructionsFilePath",
|
||||||
requiresMaterializedRuntimeSkills: false,
|
requiresMaterializedRuntimeSkills: false,
|
||||||
sessionManagement: getAdapterSessionManagement("opencode_local") ?? {
|
sessionManagement: getAdapterSessionManagement("opencode_local") ?? {
|
||||||
supportsSessionResume: true,
|
supportsSessionResume: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user