tl;dr. the system prompt has three modes — keep
claude code's default and append, replace it entirely
with a string, or build your own from scratch. settingSources
controls which on-disk settings layer in. skills are markdown files
under .claude/skills/ that the agent loads on demand —
the right place for "how we do x in this repo" knowledge.
three system prompt modes
by default the sdk uses an empty system prompt — bare api, no claude-code conventions, no tool documentation beyond what tools declare themselves. that's rarely what you want. there are two upgrades.
- preset + appendload the claude code default (which knows about the file tools, the agent loop, the conventions) and append your own constraints. the right default for any agent doing software work.
- replace with a stringcomplete override. you write the whole prompt. correct for non-code agents (writing, research, analysis) where the claude code defaults are wrong.
- no system promptempty. only useful when you really, really want a clean slate — testing, eval harnesses, niche cases.
import { query } from "@anthropic-ai/claude-agent-sdk";
// the most common pattern: keep claude code's default system prompt,
// append your own additions. nothing of the agent's wiring is lost.
const run = query({
prompt: "...",
options: {
systemPrompt: {
type: "preset",
preset: "claude_code",
append: `
You are running inside scalable.dk's deploy bot.
- Always run \`npm run typecheck\` after editing typescript files.
- Never modify files outside src/ or scripts/.
- When you push, push to the deploy branch, never main.
`,
},
},
}); // total override — useful when the agent's job has nothing to do with code.
// you lose the claude code defaults (file io conventions, tool descriptions, etc),
// so prefer "append" unless you really mean it.
const run = query({
prompt: "draft an outage post-mortem from the slack threads in /tmp/threads/",
options: {
systemPrompt:
"You are an SRE writing post-mortems. Output should be a markdown doc following our template at /tmp/template.md. No commentary, no preamble.",
},
}); settingSources — which on-disk settings count
claude code reads layered settings: user (~/.claude/),
project (./.claude/), and local
(./.claude/settings.local.json). the sdk doesn't load
any of these by default — your query() is a clean
surface unless you opt in. that opt-in is settingSources.
// settingSources controls which on-disk settings the sdk inherits.
// default is "none" — nothing from the host machine bleeds in.
const run = query({
prompt: "...",
options: {
// load .claude/settings.json from this project, but ignore user / global
settingSources: ["project"],
// alternatives: ["user"], ["project", "user", "local"], or omit entirely
},
});
enable "project" when you want the agent to inherit
repo-level conventions. enable "user" only if the agent
is running on the developer's own machine. for a deployed agent the
safe default is to enable "project" only — host-machine
settings should never leak into a server-side run.
skills
skills are markdown files under .claude/skills/<name>/SKILL.md
that document a piece of know-how — "how we write migrations,"
"the company's brand voice," "the steps to roll a new release."
the agent loads them by description-match: when a user prompt or
in-flight context implies a skill is relevant, it gets pulled into
the agent's working context. if it isn't relevant, the skill stays
off the prompt and you don't pay for the tokens.
// .claude/skills/postgres-migrations/SKILL.md
// -----
// ---
// name: postgres-migrations
// description: write and run knex postgres migrations following our conventions
// ---
//
// when writing a migration:
// - file name format: `<unix-timestamp>_<snake_case_description>.ts`
// - always provide both up() and down()
// - never use `alterTable` for column drops — use a new migration with a guard
// - run `npm run db:migrate` after writing
// - if migration fails, run `npm run db:rollback` and edit; do not edit a migration that's already been run on staging
//
// helper code lives in scripts/migrations.ts.
// in code, just enable skills — the agent picks them up by description match.
const run = query({
prompt: "add a 'last_login' timestamp column to users",
options: {
settingSources: ["project"],
// The agent reads .claude/skills/*/SKILL.md and treats them as available
// know-how. It pulls relevant skills into context only when they apply.
},
}); structuring a SKILL.md
- namea short, kebab-case identifier. shows up in logs and traces.
- descriptionone sentence — when does the agent need this skill? this is what the model matches against, so write it like a search query, not a marketing tagline.
- bodythe actual know-how. bullets, examples, anti-patterns, file paths to follow. shorter is better — the body lands in context every time the skill is loaded.
what to notice
- prefer skills over long system prompts.skills are loaded conditionally; system prompts are paid for on every turn. a 50-line skill that fires for 5% of runs costs less than a 50-line system prompt that fires always.
- settingSources is opt-in for a reason.defaulting to "load no host settings" makes deployed agents reproducible. flip it on consciously, per environment.
- append text wins on overlap.if your
appendcontradicts the preset, your text wins — it's appended, so it's the more recent instruction. but don't lean on it; restate, don't contradict. - treat SKILL.md files like docs you'd actually read.if a skill is unreadable to a new engineer, it's unreadable to the agent. the rules of good docs (concrete, scannable, no fluff) are the rules of good skills.