{"_id":"@arasandev/harness","_rev":"2-91741c09d0b6eec8e0ab63ac762275c2","name":"@arasandev/harness","dist-tags":{"latest":"0.1.0"},"versions":{"0.0.1":{"name":"@arasandev/harness","version":"0.0.1","keywords":["ai","agent","llm","harness","typescript","vendor-neutral"],"author":{"name":"Tamil Arasan"},"license":"MIT","_id":"@arasandev/harness@0.0.1","maintainers":[{"name":"arasandev","email":"aiwithtamil14@gmail.com"}],"homepage":"https://github.com/tamilarasanraja/agent-harness-sdk","bugs":{"url":"https://github.com/tamilarasanraja/agent-harness-sdk/issues"},"dist":{"shasum":"f6bd735b8fe7c069637aa181ffc09f999a973cf1","tarball":"https://registry.npmjs.org/@arasandev/harness/-/harness-0.0.1.tgz","fileCount":431,"integrity":"sha512-JsFWNHeK5Qo518Hb+rHkmT02WCLfzJZdE9Bev7l3nlJW/1AiPT+ge/SHKrOBc2cc8yQKEZJGQwsIR77YfuzTsg==","signatures":[{"sig":"MEQCIQCM+4XexNzy5rT+imycT8hGFDjPhNkOYNNrpMXdT04xvgIfLMqBzm5DRIKT8myWobgd/ynVIEShegqfodCCgMAx5A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1880748},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./mcp":{"types":"./dist/mcp/index.d.ts","import":"./dist/mcp/index.js"},"./teams":{"types":"./dist/teams/index.d.ts","import":"./dist/teams/index.js"},"./tools":{"types":"./dist/tools/index.d.ts","import":"./dist/tools/index.js"},"./preset":{"types":"./dist/preset/index.d.ts","import":"./dist/preset/index.js"},"./session":{"types":"./dist/session/index.d.ts","import":"./dist/session/index.js"},"./internal":{"types":"./dist/internal/index.d.ts","import":"./dist/internal/index.js"}},"gitHead":"8b433d1b28d7dbeb3f7ea26cbb0f5a4436a7a339","scripts":{"test":"vitest run --exclude 'ai_docs/**'","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run typecheck"},"_npmUser":{"name":"arasandev","email":"aiwithtamil14@gmail.com"},"repository":{"url":"git+https://github.com/tamilarasanraja/agent-harness-sdk.git","type":"git"},"_npmVersion":"11.16.0","description":"A clean-room TypeScript agent harness — vendor-neutral, composable kernel for building agentic systems. Inspired by general-purpose agent research; optimized for Anthropic-compatible endpoints.","directories":{},"_nodeVersion":"26.3.1","dependencies":{"zod-to-json-schema":"^3.23.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.0","vitest":"^2.1.0","typescript":"^5.7.0","@types/node":"^22.0.0","@anthropic-ai/sdk":"^0.39.0","@modelcontextprotocol/sdk":"^1.29.0"},"peerDependencies":{"zod":"^3.24.0","@anthropic-ai/sdk":"^0.39.0","@modelcontextprotocol/sdk":"^1.29.0"},"peerDependenciesMeta":{"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/harness_0.0.1_1782665885069_0.37024224757066393","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@arasandev/harness","version":"0.1.0","description":"A clean-room TypeScript agent harness — vendor-neutral, composable kernel for building agentic systems. Inspired by general-purpose agent research; optimized for Anthropic-compatible endpoints.","author":{"name":"Tamil Arasan"},"license":"MIT","keywords":["ai","agent","llm","harness","typescript","vendor-neutral"],"homepage":"https://github.com/tamilarasanraja/agent-harness-sdk","repository":{"type":"git","url":"git+https://github.com/tamilarasanraja/agent-harness-sdk.git"},"bugs":{"url":"https://github.com/tamilarasanraja/agent-harness-sdk/issues"},"publishConfig":{"access":"public"},"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./tools":{"import":"./dist/tools/index.js","types":"./dist/tools/index.d.ts"},"./mcp":{"import":"./dist/mcp/index.js","types":"./dist/mcp/index.d.ts"},"./preset":{"import":"./dist/preset/index.js","types":"./dist/preset/index.d.ts"},"./internal":{"import":"./dist/internal/index.js","types":"./dist/internal/index.d.ts"},"./session":{"import":"./dist/session/index.js","types":"./dist/session/index.d.ts"},"./teams":{"import":"./dist/teams/index.js","types":"./dist/teams/index.d.ts"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm run typecheck","test":"vitest run --exclude 'ai_docs/**'","test:watch":"vitest"},"peerDependencies":{"@anthropic-ai/sdk":"^0.39.0","@modelcontextprotocol/sdk":"^1.29.0","zod":"^3.24.0"},"peerDependenciesMeta":{"@modelcontextprotocol/sdk":{"optional":true}},"dependencies":{"zod-to-json-schema":"^3.23.0"},"devDependencies":{"@anthropic-ai/sdk":"^0.39.0","@modelcontextprotocol/sdk":"^1.29.0","@types/node":"^22.0.0","typescript":"^5.7.0","vitest":"^2.1.0","zod":"^3.24.0"},"gitHead":"a21d2c8434f7ed6d76a03081dbccbb87091f980d","_id":"@arasandev/harness@0.1.0","_nodeVersion":"26.3.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-D9XRr5Po/vXGVMgY9n7PttteisNsUF8B/DpSC4R6IG7pt3jit1SgDSYAXo6q4bI0AiugJn6Hn86n3jBuUBQ2wg==","shasum":"263c2cff24b1994ae8c4e83d67df4e54b73846ad","tarball":"https://registry.npmjs.org/@arasandev/harness/-/harness-0.1.0.tgz","fileCount":443,"unpackedSize":1935748,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAg1eiQbpdfDOmjyKxjKJqt3tNdiL1TQyN2i4xLt37pmAiBPUNTyiC32lvP16pAUiEoK0rOgseBoMZbuuk9GCSBalQ=="}]},"_npmUser":{"name":"arasandev","email":"aiwithtamil14@gmail.com"},"directories":{},"maintainers":[{"name":"arasandev","email":"aiwithtamil14@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/harness_0.1.0_1782686264523_0.6377774256554833"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-28T16:58:04.913Z","modified":"2026-06-28T22:37:44.799Z","0.0.1":"2026-06-28T16:58:05.267Z","0.1.0":"2026-06-28T22:37:44.687Z"},"bugs":{"url":"https://github.com/tamilarasanraja/agent-harness-sdk/issues"},"author":{"name":"Tamil Arasan"},"license":"MIT","homepage":"https://github.com/tamilarasanraja/agent-harness-sdk","keywords":["ai","agent","llm","harness","typescript","vendor-neutral"],"repository":{"type":"git","url":"git+https://github.com/tamilarasanraja/agent-harness-sdk.git"},"description":"A clean-room TypeScript agent harness — vendor-neutral, composable kernel for building agentic systems. Inspired by general-purpose agent research; optimized for Anthropic-compatible endpoints.","maintainers":[{"name":"arasandev","email":"aiwithtamil14@gmail.com"}],"readme":"# @arasandev/harness v0.0.1\n\nA clean-room TypeScript agent harness — vendor-neutral, composable kernel for building agentic systems. Implements the loop, tool execution, permission gating, hooks, subagent delegation, compaction, and transcript persistence as a typed library you import — not a subprocess you spawn.\n\n**Vendor-neutral:** Works with any Anthropic-compatible API endpoint (Anthropic, MiniMax, OpenRouter, custom). **Strictly typed:** No `any`, Zod validation on all tool inputs, required parameters enforced. **Production-ready:** 1657 tests, comprehensive error messages, hooks as composable algebra.\n\nInspired by general-purpose agent research; optimized for the `{baseURL, apiKey, model}` configuration triad.\n\n## Installation\n\n```sh\nnpm install @arasandev/harness @anthropic-ai/sdk zod\n```\n\n## Environment Setup\n\nThe SDK reads three environment variables (all optional if passed explicitly):\n- `ANTHROPIC_API_KEY` — Your API key (passed to `apiKey` option)\n- `ANTHROPIC_BASE_URL` — Custom endpoint (defaults to Anthropic's; use for MiniMax, OpenRouter, etc.)\n- `ANTHROPIC_MODEL` — Default model (falls back to hardcoded Sonnet; recommend setting this explicitly)\n\n```bash\n# Example: use MiniMax instead of Anthropic\nexport ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic\nexport ANTHROPIC_API_KEY=sk-...\nexport ANTHROPIC_MODEL=MiniMax-M3\n```\n\n## Quick start\n\n```typescript\nimport { createCodingAgent } from '@arasandev/harness/preset';\n\nconst agent = createCodingAgent({ apiKey: process.env.ANTHROPIC_API_KEY });\n\nfor await (const ev of agent.send('list the .ts files and count them')) {\n  if (ev.type === 'text_delta') process.stdout.write(ev.text);\n}\n```\n\n`createCodingAgent` wires coreTools + TodoWrite + WebFetch + a rules-based\npermission gate + cost tracking + compaction + subagent delegation in one call.\nAll options are optional; `apiKey` falls back to `ANTHROPIC_API_KEY`.\n\n## Build a custom tool\n\n```typescript\nimport { tool } from '@arasandev/harness';\nimport { z } from 'zod';\n\nconst weatherTool = tool({\n  name: 'Weather',\n  description: 'Get current weather for a city. Returns temperature and conditions.',\n  inputSchema: z.object({ city: z.string().describe('City name, e.g. \"London\"') }),\n  async call({ city }, ctx) {\n    const res = await fetch(`https://wttr.in/${city}?format=j1`, { signal: ctx.signal });\n    return res.json();\n  },\n  isReadOnly: true,\n  isOpenWorld: true,\n});\n```\n\nPass `tools: [weatherTool, ...coreTools]` to `new Agent(...)` or\n`extraTools: [weatherTool]` to `createCodingAgent(...)`.\n\n## Add permissions\n\n```typescript\nimport { rulesToCanUseTool, withPermissionCache } from '@arasandev/harness';\nimport type { PermissionRule } from '@arasandev/harness';\n\nconst rules: PermissionRule[] = [\n  // Always allow read-only inspection.\n  { field: 'tool', matcher: { type: 'equals', value: 'Read' }, decision: 'allow' },\n  // Always allow git commands.\n  { tool: 'Bash', field: 'input.command', matcher: { type: 'prefix', value: 'git ' }, decision: 'allow' },\n  // Hard-deny recursive root deletion.\n  {\n    tool: 'Bash',\n    field: 'input.command',\n    matcher: { type: 'regex', pattern: 'rm\\\\s+-rf?\\\\s+(/|~)' },\n    decision: 'deny',\n    reason: 'Refusing to delete a filesystem root.',\n  },\n];\n\nconst canUseTool = withPermissionCache(rulesToCanUseTool(rules)).canUseTool;\n// Pass to: new Agent({ canUseTool, ... })\n```\n\nRules evaluate first-match. Undecided calls are denied by default (fail-closed).\nCompose with a host fallback via `rulesToCanUseTool(rules, { fallback: askUser })`.\n\n## Add hooks\n\n```typescript\nimport { Agent } from '@arasandev/harness';\nimport type { Hooks } from '@arasandev/harness';\n\nconst hooks: Hooks = {\n  preToolUse: [\n    async (ctx) => {\n      console.log(`→ ${ctx.tool.name}`, ctx.input);\n      return { kind: 'allow' };           // or { kind: 'deny', reason: '...' }\n    },\n  ],\n  postToolUse: [\n    async (ctx) => {\n      console.log(`← ${ctx.tool.name} isError=${ctx.isError}`);\n      return {};                           // or { modifiedOutput: redacted }\n    },\n  ],\n};\n\nconst agent = new Agent({ model: 'claude-opus-4-8', tools: [], hooks });\n// Tip: import { MODELS } from '@arasandev/harness' and use MODELS.OPUS, MODELS.SONNET, MODELS.HAIKU\n```\n\nA `PreToolUse` hook can rewrite or block the call before it runs. A\n`PostToolUse` hook can rewrite the result the model sees. Both fire in\nregistration order.\n\n## Architecture\n\n```\nDeveloper\n    │\n    ▼\nAgent (per-turn owner)\n  owns: mutableMessages, usage, permissionMode, hookBus\n  API:  send(prompt) → AsyncGenerator<AgentEvent>\n        interrupt() · registerTool() · getMessages() · getCostUsd()\n    │\n    ▼\nrunLoop (per-model-call recursion)\n  while (true) { call model → execute tools → loop }\n  checks: abort signal, maxTurns, maxBudgetUsd, compaction\n    │\n    ├─────────────────┬──────────────────┬────────────────────┐\n    ▼                 ▼                  ▼                    ▼\nTransport          Tool             HookBus            CanUseToolFn\nanthropicTransport Tool<I,O>        preToolUse         permission gate\nmockTransport      tool() factory   postToolUse        rulesToCanUseTool\n                   coreTools        stop / session     withPermissionCache\n```\n\n## coreTools (7 built-in tools)\n\n`coreTools` is the I/O tool set you pass directly to\n`new Agent(...)`. It does not require any factory wiring.\n\n| Tool | What it does |\n|------|-------------|\n| `Bash` | Execute shell commands; supports `run_in_background` |\n| `Read` | Read files (text, image, notebook, PDF) |\n| `Write` | Create or overwrite files |\n| `Edit` | String-based targeted file edits with mtime check |\n| `MultiEdit` | Apply multiple edits to a file in one call |\n| `Glob` | Recursive file pattern matching |\n| `Grep` | Regex search across files with context lines |\n\n`createTodoWriteTool` and `createWebFetchTool` are factories — they require\nper-agent state wiring and are not in `coreTools`.\n\n## Design principles\n\n**1. Claude-Code-native naming.** Type names, event names, and tool names match\nreal Claude Code so agents trained on Claude Code can use this SDK without\ntranslation. `PreToolUseHook`, `tool_result`, `PermissionRule` are the same\nconcepts.\n\n**2. One obvious way.** `tool()` is the only entry point for building tools.\n`rulesToCanUseTool` is the only entry point for declarative policy. Helpers exist\nbut they compose rather than replace.\n\n**3. Self-describing contracts.** Every tool's `description` field is its only\ndocumentation from the model's perspective. `ToolContext` carries exactly what a\ntool needs (`cwd`, `env`, `signal`, `fileState`) and nothing else.\n\n**4. Fail-closed.** Undecided permission calls are denied. `denyAll` is the\nzero-config default for `canUseTool`. Security boundaries do not require\nexplicit opt-in.\n\n**5. Small surface, additive.** The required API is four fields on `Tool` and\none method on `Agent`. Optional fields are pure additions. Adding a field to\n`Tool` is non-breaking; removing one is not.\n\n## With MCP tools (streamable HTTP or stdio)\n\n```typescript\nimport { Agent, coreTools, MODELS } from '@arasandev/harness';\nimport { connectMcpServers, createSamplingHandler } from '@arasandev/harness/mcp';\n\nconst agent = new Agent({ model: MODELS.SONNET, tools: coreTools, apiKey: process.env.ANTHROPIC_API_KEY! });\n\nconst registry = await connectMcpServers({\n  configs: [\n    { name: 'my-server', transport: { kind: 'streamableHttp', url: 'https://my-mcp-server.example.com/mcp' } },\n  ],\n  // Apply sampling handler to all servers with '*' wildcard:\n  clientOptions: { '*': { sampling: createSamplingHandler({ agent }) } },\n});\nawait registry.bindToAgent(agent);\n// agent now has coreTools (7) + all tools from my-server\n\nfor await (const event of agent.send('Use your tools')) {\n  if (event.type === 'text_delta') process.stdout.write(event.text);\n}\nawait registry.disconnectAll();\n```\n\nNote: `loadMcpConfig`, `connectMcpServers`, and `createSamplingHandler` are imported from\n`'@arasandev/harness/mcp'` — not the front door.\n\n## Non-goals for 0.0.1\n\n- **Session tree / multi-root conversations.** The `Agent` owns a single linear\n  message history. Branching (fork-and-rejoin, speculative execution) is out of\n  scope.\n- **Daemon bridge / long-lived process model.** There is no persistent daemon,\n  no IPC channel, no reconnect protocol. The agent is a library object in your\n  process.\n- **50-tool catalog.** The SDK ships `coreTools` (7 I/O tools) plus factories\n  for TodoWrite, WebFetch, Bash with sandbox, plan-mode tools, and subagent\n  delegation. A broad catalog of domain tools (browser, calendar, Slack,\n  database connectors) is a host responsibility.\n","readmeFilename":"README.md"}