docs: expand Claude contractor design
This commit is contained in:
530
docs/claude/IMPLEMENTATION.md
Normal file
530
docs/claude/IMPLEMENTATION.md
Normal file
@@ -0,0 +1,530 @@
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user