@skelm/codex
v0.4.3
Published
OpenAI Codex backend for skelm via the official @openai/codex-sdk
Readme
@skelm/codex
OpenAI Codex backend for skelm — wraps the official
@openai/codex-sdkwith full skelm permission enforcement, MCP injection, skill loading, and streaming.
Part of skelm.
Codex authenticates via the host codex CLI (codex login) or the CODEX_API_KEY env var. The SDK spawns codex under the hood and exchanges JSONL events — skelm enforces permissions at the boundary, pins the workspace, optionally routes egress through the gateway's CONNECT proxy, and emits audit events as Codex completes commands, file changes, and MCP tool calls.
| Capability | Value |
|--------------------|----------------------------------------------------------------|
| prompt | false (codex-sdk is agent-loop only) |
| streaming | true (agent_message text deltas flow to onPartial) |
| sessionLifecycle | true (request.sessionId → Codex.resumeThread) |
| mcp | true (skelm MCP servers injected via config.mcp_servers) |
| skills | true (skill bodies concatenated into the system prompt) |
| toolPermissions | 'native' (Codex enforces sandbox / approval / network in its own process; skelm checks at the boundary) |
Prerequisites
codexCLI on PATH (codex --version≥ 0.130.0)- Authenticated session —
codex loginonce, orCODEX_API_KEYin env
Install
npm install @skelm/codexQuick start
// skelm.config.ts
import { defineConfig } from 'skelm'
export default defineConfig({
backends: {
agent: 'codex',
codex: {
model: 'gpt-5.3-codex',
modelReasoningEffort: 'medium',
skipGitRepoCheck: true,
},
},
defaults: {
permissions: {
networkEgress: 'deny',
allowedTools: [],
allowedExecutables: [],
allowedSkills: [],
allowedMcpServers: [],
fsRead: [],
fsWrite: [],
},
},
})// codex-smoke.pipeline.mts
import { agent, pipeline } from 'skelm'
import { z } from 'zod'
export default pipeline({
id: 'codex-smoke',
input: z.object({ task: z.string() }),
output: z.object({ result: z.string() }),
steps: [
agent({
id: 'work',
backend: 'codex',
prompt: (ctx) => (ctx.input as { task: string }).task,
permissions: {
// For a real read-write workflow grant fsWrite roots + relevant tools.
// The mapper refuses fsWrite: ['*'] unless approval policy is empty.
fsRead: [],
fsWrite: [],
networkEgress: 'deny',
},
}),
],
})skelm run codex-smoke.pipeline.mts --input '{"task":"say ok"}'Permission mapping
The boundary-time mapper translates a resolved skelm policy into Codex SDK options. If the policy can't be honored safely, it throws CodexPermissionError before any Codex invocation.
| Skelm policy | Codex SDK option |
|---------------------------------------------|---------------------------------------------------------------|
| fsWrite: [], fsRead: [] | sandboxMode: 'read-only' |
| fsWrite: [<roots>] | sandboxMode: 'workspace-write', first root → workingDirectory, rest → additionalDirectories |
| request.cwd set | overrides workingDirectory |
| fsWrite: ['*'] AND no approval policy | sandboxMode: 'danger-full-access' |
| fsWrite: ['*'] AND approval policy set | refused — never silently escalate |
| networkEgress: 'deny' | networkAccessEnabled: false |
| networkEgress: 'allow' or { allowHosts } | networkAccessEnabled: true (gateway proxy enforces hosts) |
| approval.on covers tool / executable | approvalPolicy: 'untrusted' |
| anything else | approvalPolicy: 'on-request' |
MCP, skills, streaming
- MCP —
request.mcpServersis filtered bypolicy.allowedMcpServers, then passed toCodex({ config: { mcp_servers: { … } } }). Stdio transports are translated today; HTTP/SSE transports are dropped withpermission.deniedaudit so the gap is visible. - Skills — When
request.skillsis set and the policy permits, the backend callscontext.loadSkill(id)for each id and concatenates the formatted skill blocks into the system prompt. - Streaming —
agent_message.textdeltas flow toBackendContext.onPartial.command_execution,file_change, andmcp_tool_callitems surface viaonItemfor audit emission.
API surface
createCodexBackend(options?: CodexBackendOptions): SkelmBackend— the factory.mapPermissionsToCodex({ policy, workingDirectory })— boundary mapper; throwsCodexPermissionErroron unsafe widening.buildAuditEntry(...)— hash-chained-audit-ready record of the mapping decision.filterIds(ids, allowlist)— partition step-requested ids by an allowlist.- Re-exports:
CodexPermissionError, typesCodexBackendOptions,MappedCodexPolicy,CodexPermissionAuditEntry.
Live integration test
codex login
SKELM_CODEX_INTEGRATION=1 pnpm test packages/codex/test/integration.test.tsThe skill-injection test registers a magic-word skill and asserts the agent surfaces the skill-provided answer — a real end-to-end verification.
License
MIT
