AI SDK
HarnessAgent builds a Vercel AI SDK ToolLoopAgent from activated packs, with grant-filtered tools, approvals on writes and Skills loaded on demand.
For a host built on the Vercel AI SDK or any TypeScript tool loop, the reference
SDK's HarnessAgent builds the agent from the activated packs, so its tools,
approvals and instructions all come from what the packs declare. Create it with
a runtime and a model, then render its approvals in your host.
Create#
import { Hcp, HarnessAgent } from '@harnesscontextprotocol/sdk'
const hcp = await Hcp.create({
packDirs: ['./packs/transit', './packs/ledger'],
sandbox: true,
grants,
})
await hcp.activate(['transit', 'ledger'])
const agent = await HarnessAgent.create({
hcp,
harness: 'my-product-v1', // teaching harness id
runtime: 'ai-sdk',
model,
maxSteps: 12,
context: { skillLeadChars: 480 },
})
const run = await agent.send('List the ledger and plan a route from A to B')
for await (const ev of run.stream()) {
if (ev.type === 'tool') console.log(ev.name, ev.input)
}
await run.wait()Options#
hcprequiredHcpThe runtime. Packs must be activated.
runtime'ai-sdk' | 'opencode'default ai-sdk'ai-sdk' builds a ToolLoopAgent; 'opencode' drives an OpenCode session over the same packs.
modelLanguageModelAI SDK language model (ai-sdk runtime).
harnessstringIdentifier for the teaching harness. It selects instructions, not capabilities.
instructionsstringExtra system instructions appended after the generated stable prefix.
maxStepsnumberTool-loop step cap.
cwdstringWorking directory (opencode runtime).
adoptOpenCodebooleandefault trueWrite native OpenCode intake before the run.
contextContextOptimizationOptionsContext optimisation knobs; see below.
What gets generated#
| From | To |
|---|---|
| Activated packs + Policy + grants | Grant-filtered tool map: one tool per executable command, named hcpToolName(pack_id, verb_key) |
HARNESS.md leads | A stable instruction prefix (prompt-cache friendly) |
| Full Skills | hcp__read_skill tool for on-demand loading |
Commands with operation: write | toolApproval with needsApproval: true |
| Long tool results | prepareStep compaction between steps |
Context optimization explains each layer and
the context knobs. buildHcpContextBundle(hcp, options) builds the bundle, and
you can call it directly to assemble your own ToolLoopAgent:
import { buildHcpContextBundle } from '@harnesscontextprotocol/sdk'
const bundle = await buildHcpContextBundle(hcp, { skillLeadChars: 480 })
// bundle.tools · bundle.instructions · bundle.toolApproval · bundle.prepareStep · bundle.toolOrderApprovals#
Every write command arrives with needsApproval: true. Your host shows the
approval, and when the user agrees the tool call proceeds with confirm: true
in its args. The runtime still enforces the gate, so the approval in the host
and confirm in the envelope are two views of one rule.
Events#
type HarnessAgentEvent =
| { type: 'text'; text: string }
| { type: 'tool'; name: string; input: unknown; output?: unknown }
| { type: 'error'; error: string }
| { type: 'context'; stablePrefixChars: number; tools: number; progressiveSkills: boolean }
| { type: 'done' }Why not hand-registered tools#
You can call hcp.exec from any tool you write yourself, for example with
generateText, but you lose everything the pack declared: grant filtering,
approvals from Policy, Skill leads and availability. HarnessAgent keeps the
pack as the integration identity, so no separate tool list can drift from it.