Files
ContractorAgent/docs/claude/IMPLEMENTATION.md
hzhang 76a7931f97 feat: implement MCP proxy for OpenClaw tool access in contractor agent
Complete the MCP tool call chain:
- contractor-agent bridge exposes /mcp/execute endpoint for tool callbacks
- openclaw-mcp-server.mjs proxies OpenClaw tool defs to Claude as MCP tools
- sdk-adapter passes --mcp-config on first turn with all OpenClaw tools
- tool-test plugin registers contractor_echo in globalThis tool handler map
- agent-config-writer auto-sets tools.profile=full so OpenClaw sends tool defs
- Fix --mcp-config arg ordering: prompt must come before <configs...> flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:05:03 +01:00

550 lines
14 KiB
Markdown

# Claude Contractor Implementation Plan
## Purpose
Turn the architecture decisions into an implementation-oriented plan for phase 1.
This document is intentionally practical. It focuses on file structure, core modules, interfaces, and build order for the Claude-only phase.
## Phase 1 implementation goal
Deliver a working Claude-backed contractor agent in OpenClaw that can:
1. be provisioned through `openclaw contractor-agents add`
2. use `contractor-claude-bridge` as its primary model
3. map an OpenClaw session to a Claude Code session
4. forward the latest user message into Claude Code
5. return Claude output through normal OpenClaw response flow
Gemini is out of scope for this phase.
## Proposed repository structure
```text
ContractorAgent/
├── docs/
│ ├── claude/
│ │ ├── PLAN.md
│ │ ├── MODEL.md
│ │ ├── CLI.md
│ │ ├── ARCHITECTURE.md
│ │ └── IMPLEMENTATION.md
│ └── gemini/
│ └── .gitkeep
├── src/
│ ├── index.ts
│ ├── config/
│ │ ├── schema.ts
│ │ └── contractor-config.ts
│ ├── cli/
│ │ ├── register-cli.ts
│ │ ├── contractor-agents-add.ts
│ │ └── contractor-agents-status.ts
│ ├── model/
│ │ ├── register-bridge-model.ts
│ │ ├── contractor-claude-bridge.ts
│ │ ├── input-filter.ts
│ │ └── response-normalizer.ts
│ ├── contractor/
│ │ ├── metadata-resolver.ts
│ │ ├── runtime-state.ts
│ │ ├── session-map-store.ts
│ │ ├── bootstrap.ts
│ │ └── recovery.ts
│ ├── claude/
│ │ ├── acp-adapter.ts
│ │ ├── claude-session.ts
│ │ └── claude-types.ts
│ ├── openclaw/
│ │ ├── agent-config-writer.ts
│ │ ├── agents-add-runner.ts
│ │ └── session-context.ts
│ └── types/
│ ├── contractor.ts
│ ├── session-map.ts
│ └── model.ts
├── package.json
├── openclaw.plugin.json
└── tsconfig.json
```
Not every file needs to exist on day 1, but this is a good target shape.
## Module responsibilities
## 1. `src/index.ts`
Plugin entry point.
Responsibilities:
- export the OpenClaw plugin entry
- register the bridge model
- register plugin-owned CLI commands
- register any supporting services needed by the plugin
Expected shape:
```ts
export default definePluginEntry({
id: "contractor-agent",
name: "Contractor Agent",
description: "Claude-backed contractor agents for OpenClaw",
register(api) {
// register bridge model
// register CLI
// register helpers/services if needed
},
})
```
## 2. `src/model/register-bridge-model.ts`
Registers the custom model id used by contractor-backed agents.
Primary concern:
- expose `contractor-claude-bridge` to OpenClaw as a valid model selection target
This is the key seam that makes contractor-backed agents look normal to OpenClaw.
## 3. `src/model/contractor-claude-bridge.ts`
Core bridge model implementation.
Responsibilities:
- receive the model request from OpenClaw
- identify agent id and session key
- resolve contractor metadata
- resolve or create Claude session mapping
- filter input
- call Claude adapter
- normalize result
- return OpenClaw-compatible model output
This file is the heart of the system.
## 4. `src/model/input-filter.ts`
Convert raw OpenClaw model input into the payload actually sent to Claude.
Possible exports:
```ts
export function extractLatestActionableMessage(...)
export function buildClaudeTurnPayload(...)
```
This module should be pure and testable.
## 5. `src/model/response-normalizer.ts`
Convert Claude-side output into OpenClaw-compatible model response data.
Possible exports:
```ts
export function normalizeClaudeResponse(...)
export function normalizeClaudeError(...)
```
This should also be mostly pure and testable.
## 6. `src/contractor/metadata-resolver.ts`
Read agent config and decide whether the current agent is a contractor-backed Claude agent.
Responsibilities:
- inspect agent config
- return structured contractor runtime metadata
- reject unsupported or misconfigured contractor setups
Possible export:
```ts
export function resolveContractorAgentMetadata(...): ContractorAgentMetadata
```
## 7. `src/contractor/session-map-store.ts`
Read and write the OpenClaw session to Claude session mapping file.
Responsibilities:
- find by OpenClaw session key
- insert new mapping
- update timestamps
- replace mapping on reset/recovery
- mark mapping orphaned
Possible interface:
```ts
export interface SessionMapStore {
get(openclawSessionKey: string): Promise<SessionMapEntry | null>
put(entry: SessionMapEntry): Promise<void>
markOrphaned(openclawSessionKey: string): Promise<void>
remove(openclawSessionKey: string): Promise<void>
list(agentId: string): Promise<SessionMapEntry[]>
}
```
## 8. `src/contractor/runtime-state.ts`
Resolve and initialize plugin runtime state directories.
Responsibilities:
- compute state directory from workspace
- ensure directories exist
- provide path helpers for session-map and related files
Possible exports:
```ts
export function getContractorStateDir(workspace: string): string
export function getSessionMapPath(workspace: string): string
export async function ensureContractorStateDir(workspace: string): Promise<void>
```
## 9. `src/contractor/bootstrap.ts`
Create the initial contractor bootstrap payload for new Claude sessions.
Responsibilities:
- generate initial instruction text
- include workspace metadata
- include contractor role expectations
- avoid repeating per-turn content
Possible export:
```ts
export function buildClaudeBootstrap(...): string
```
## 10. `src/contractor/recovery.ts`
Handle missing or invalid Claude sessions.
Responsibilities:
- mark mappings orphaned
- trigger fresh Claude session creation
- optionally build a minimal recovery summary
Possible export:
```ts
export async function recoverClaudeSession(...)
```
## 11. `src/claude/acp-adapter.ts`
Claude runtime adapter used by the bridge model.
Responsibilities:
- create Claude session
- resume Claude session
- send message to Claude
- close Claude session if needed
Possible interface:
```ts
export interface ClaudeAcpAdapter {
createSession(input: CreateClaudeSessionInput): Promise<CreateClaudeSessionResult>
sendMessage(input: SendClaudeMessageInput): Promise<SendClaudeMessageResult>
closeSession?(sessionId: string): Promise<void>
}
```
This should hide ACP/runtime-specific details from the bridge model.
## 12. `src/openclaw/agents-add-runner.ts`
Encapsulate the logic for creating base OpenClaw agents from the contractor CLI.
Responsibilities:
- run or emulate `openclaw agents add <agent-id> --workspace <workspace> --model contractor-claude-bridge --non-interactive`
- translate failures into contractor CLI errors
Possible export:
```ts
export async function createBaseAgent(...): Promise<void>
```
## 13. `src/openclaw/agent-config-writer.ts`
Write contractor metadata into OpenClaw config after the base agent is created.
Responsibilities:
- update target agent config entry
- write contractor runtime stanza
- preserve unrelated config
Possible export:
```ts
export async function markAgentAsClaudeContractor(...): Promise<void>
```
## 14. `src/cli/register-cli.ts`
Register the plugin-owned CLI root.
Responsibilities:
- register `openclaw contractor-agents`
- attach subcommands such as `add`, later `status`, `sessions`, `reset-session`
## 15. `src/cli/contractor-agents-add.ts`
Implementation of the phase 1 provisioning flow.
Responsibilities:
- parse args
- validate contractor kind
- create base agent
- write contractor metadata
- initialize runtime state directory
- initialize empty session map
- return human-readable or JSON result
## Primary types
## `src/types/contractor.ts`
```ts
export type ContractorKind = "claude" | "gemini"
export type ContractorAgentMetadata = {
agentId: string
contractor: "claude"
bridgeModel: string
workspace: string
backend: "acp"
mode: "persistent" | "oneshot"
}
```
## `src/types/session-map.ts`
```ts
export type SessionMapState = "active" | "closed" | "orphaned"
export type SessionMapEntry = {
openclawSessionKey: string
agentId: string
contractor: "claude"
claudeSessionId: string
workspace: string
createdAt: string
lastActivityAt: string
state: SessionMapState
}
export type SessionMapFile = {
version: 1
sessions: SessionMapEntry[]
}
```
## `src/types/model.ts`
```ts
export type ContractorBridgeModelRequest = {
agentId: string
openclawSessionKey: string
workspace: string
latestUserMessage: string
rawInput: unknown
}
```
Exact fields depend on how OpenClaw provider/model requests are exposed in runtime.
## Implementation notes from probe testing
See BRIDGE_MODEL_FINDINGS.md for full details. Key constraints that affect implementation:
- The sidecar MUST support SSE streaming (`stream: true` always sent by OpenClaw). Non-streaming
responses cause OpenClaw to drop assistant history from subsequent turns.
- OpenClaw sends the full system prompt (~28K chars) rebuilt on every turn. The input filter
must strip this and extract only the latest user message.
- User message format from OpenClaw: `[Day YYYY-MM-DD HH:MM TZ] message text`
- Claude session continuation uses UUID from `@anthropic-ai/claude-agent-sdk` `message.session_id`.
Resume via `options: { resume: sessionId }`.
- Custom model is registered via `openclaw.json` provider config, not a plugin SDK call.
The install script must write the provider entry and set `baseUrl` to the sidecar port.
## Revised milestone order
Milestone 3 (bridge model) is moved before Milestone 2 (CLI) because the bridge sidecar is the
load-bearing component. Everything else depends on confirming it works end-to-end.
## Suggested implementation order
## Milestone 1, project skeleton
Create:
- package metadata
- plugin manifest
- `src/index.ts`
- empty docs-backed source tree
Success condition:
- plugin package structure exists
- build can run
## Milestone 2, CLI provisioning path
Implement:
- `register-cli.ts`
- `contractor-agents-add.ts`
- `agents-add-runner.ts`
- `agent-config-writer.ts`
- `runtime-state.ts`
- empty `session-map-store.ts`
Success condition:
- `openclaw contractor-agents add ... --contractor claude` provisions a contractor-marked agent successfully
## Milestone 3, bridge model registration
Implement:
- `register-bridge-model.ts`
- minimal `contractor-claude-bridge.ts`
Success condition:
- OpenClaw recognizes `contractor-claude-bridge` as a valid model id
## Milestone 4, session mapping and Claude dispatch
Implement:
- session-map CRUD
- metadata resolver
- initial ACP adapter
- input filter
- response normalizer
Success condition:
- a contractor-backed agent can receive a real turn and return a Claude-generated response
## Milestone 5, bootstrap and recovery
Implement:
- `bootstrap.ts`
- `recovery.ts`
- reset semantics
Success condition:
- first-turn session creation and broken-session recovery are both handled predictably
## Testing strategy
## Unit-test first
Best candidates for unit tests:
- metadata resolver
- input filter
- response normalizer
- session-map-store read/write/update behavior
- bootstrap payload builder
## Integration-test later
Integration targets:
- contractor CLI add flow
- bridge model request path
- session creation and reuse
- reset and orphan recovery
## Failure handling rules
The implementation should clearly separate:
### Provisioning failures
Examples:
- invalid workspace
- base agent creation failed
- contractor metadata write failed
Recommended behavior:
- fail loudly
- roll back base agent creation if practical and safe
### Runtime failures
Examples:
- Claude session missing
- ACP adapter unavailable
- response normalization failed
Recommended behavior:
- mark runtime mapping state explicitly
- recover when safe
- surface user-visible error when recovery is not possible
## Minimum viable internal contracts
To prevent overdesign, phase 1 only needs a few stable internal boundaries:
1. bridge model -> metadata resolver
2. bridge model -> session map store
3. bridge model -> Claude ACP adapter
4. contractor CLI -> base agent creator
5. contractor CLI -> agent config writer
If these boundaries are clean, later Gemini support can reuse most of the same architecture.
## Design choices to keep intentionally simple in phase 1
- single contractor kind implemented, Claude
- JSON file-backed session map
- no aggressive streaming support initially
- no large-scale tool bridge yet
- no complicated migration logic yet
## Things to avoid early
- overabstracting for Gemini before Claude works
- mixing static config with dynamic session map state
- rebuilding full OpenClaw context every turn
- making the bridge model pretend to be a normal stateless LLM provider
## Open questions to resolve during implementation
1. What exact OpenClaw plugin/provider registration API should the bridge model use?
2. How should the plugin call underlying agent creation, shelling out to CLI vs internal config mutation?
3. What exact payload does OpenClaw pass to a custom model implementation?
4. What exact continuation identifier should be stored for Claude sessions?
5. How should Claude tool requests later be bridged into OpenClaw-hosted tool execution?
## Immediate next coding step
If starting implementation now, I would begin with:
1. plugin manifest and `src/index.ts`
2. CLI registration for `contractor-agents add`
3. base agent creation + contractor metadata write
4. empty state dir and empty session map initialization
That gets provisioning working before the runtime bridge itself is added.