docs: expand Claude contractor design
This commit is contained in:
436
docs/claude/ARCHITECTURE.md
Normal file
436
docs/claude/ARCHITECTURE.md
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
# Claude Contractor Architecture
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Describe the phase 1 architecture for integrating Claude Code into OpenClaw as a contractor-backed agent.
|
||||||
|
|
||||||
|
This document assumes the phase 1 decisions already made:
|
||||||
|
|
||||||
|
- Claude only, Gemini deferred
|
||||||
|
- plugin-owned CLI root: `openclaw contractor-agents ...`
|
||||||
|
- contractor-backed agents use a custom primary model
|
||||||
|
- that custom model bridges OpenClaw turns into Claude Code sessions
|
||||||
|
|
||||||
|
## High-level architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
User / Channel
|
||||||
|
↓
|
||||||
|
OpenClaw routing + agent selection
|
||||||
|
↓
|
||||||
|
Agent primary model = contractor-claude-bridge
|
||||||
|
↓
|
||||||
|
ContractorAgent plugin
|
||||||
|
├─ contractor metadata resolver
|
||||||
|
├─ session map store
|
||||||
|
├─ Claude ACP adapter
|
||||||
|
├─ input filter / context reducer
|
||||||
|
└─ response normalizer
|
||||||
|
↓
|
||||||
|
Claude Code session
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core architectural principle
|
||||||
|
|
||||||
|
OpenClaw remains the outer control plane.
|
||||||
|
Claude Code remains the live reasoning runtime.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
### OpenClaw owns
|
||||||
|
|
||||||
|
- agent registration
|
||||||
|
- workspace ownership
|
||||||
|
- routing and channel bindings
|
||||||
|
- visible transcript/session files
|
||||||
|
- permissions and approvals
|
||||||
|
- plugin loading and bridge model selection
|
||||||
|
|
||||||
|
### Claude Code owns
|
||||||
|
|
||||||
|
- live session continuity
|
||||||
|
- evolving reasoning context for a contractor task
|
||||||
|
- internal coding workflow and execution trajectory
|
||||||
|
|
||||||
|
The plugin exists to bridge those two worlds without forcing both systems to maintain full independent copies of the same conversation state.
|
||||||
|
|
||||||
|
## Main components
|
||||||
|
|
||||||
|
## 1. Contractor metadata resolver
|
||||||
|
|
||||||
|
This component determines whether the current agent is a contractor-backed Claude agent and, if so, what runtime settings apply.
|
||||||
|
|
||||||
|
Expected inputs:
|
||||||
|
|
||||||
|
- OpenClaw agent id
|
||||||
|
- agent config entry
|
||||||
|
- plugin config defaults
|
||||||
|
|
||||||
|
Expected outputs:
|
||||||
|
|
||||||
|
- whether contractor runtime is enabled
|
||||||
|
- contractor kind, currently `claude`
|
||||||
|
- bridge model id
|
||||||
|
- backend type, likely ACP
|
||||||
|
- runtime mode, likely persistent
|
||||||
|
- workspace path
|
||||||
|
|
||||||
|
### Proposed config direction
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: "contractor-dev",
|
||||||
|
workspace: "/workspace/contractor-dev",
|
||||||
|
model: "contractor-claude-bridge",
|
||||||
|
runtime: {
|
||||||
|
type: "contractor",
|
||||||
|
contractor: {
|
||||||
|
kind: "claude",
|
||||||
|
backend: "acp",
|
||||||
|
mode: "persistent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact config location can still evolve, but the architecture assumes there is a stable way to resolve contractor runtime settings from agent identity.
|
||||||
|
|
||||||
|
## 2. Bridge model
|
||||||
|
|
||||||
|
The bridge model is the primary execution seam inside OpenClaw.
|
||||||
|
|
||||||
|
Suggested model id:
|
||||||
|
|
||||||
|
- `contractor-claude-bridge`
|
||||||
|
|
||||||
|
### Why it exists
|
||||||
|
|
||||||
|
OpenClaw expects a primary model for each agent. The bridge model satisfies that contract while redirecting actual reasoning work to Claude Code.
|
||||||
|
|
||||||
|
### What it does
|
||||||
|
|
||||||
|
- receive the agent turn as a model request
|
||||||
|
- identify the target OpenClaw agent and session
|
||||||
|
- resolve contractor metadata
|
||||||
|
- call the session map store
|
||||||
|
- call the Claude ACP adapter
|
||||||
|
- normalize Claude output into an OpenClaw model response
|
||||||
|
|
||||||
|
### What it does not do
|
||||||
|
|
||||||
|
- behave like a normal stateless LLM provider
|
||||||
|
- replay the full OpenClaw prompt envelope every turn
|
||||||
|
- replace OpenClaw routing or channel delivery
|
||||||
|
|
||||||
|
## 3. Session map store
|
||||||
|
|
||||||
|
This component persists the mapping between OpenClaw sessions and Claude sessions.
|
||||||
|
|
||||||
|
Suggested location:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<agent-workspace>/.openclaw/contractor-agent/session-map.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested record shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type SessionMapEntry = {
|
||||||
|
openclawSessionKey: string
|
||||||
|
agentId: string
|
||||||
|
contractor: "claude"
|
||||||
|
claudeSessionId: string
|
||||||
|
workspace: string
|
||||||
|
createdAt: string
|
||||||
|
lastActivityAt: string
|
||||||
|
state: "active" | "closed" | "orphaned"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsibilities
|
||||||
|
|
||||||
|
- lookup mapping by OpenClaw session key
|
||||||
|
- create mapping after first successful Claude session creation
|
||||||
|
- update timestamps and state
|
||||||
|
- mark sessions orphaned on recoverable failures
|
||||||
|
- remove or replace mappings during reset flows
|
||||||
|
|
||||||
|
### Non-responsibilities
|
||||||
|
|
||||||
|
- storing full conversation text
|
||||||
|
- storing OpenClaw config
|
||||||
|
- being the canonical transcript source
|
||||||
|
|
||||||
|
## 4. Input filter and context reducer
|
||||||
|
|
||||||
|
This is one of the most important parts of the architecture.
|
||||||
|
|
||||||
|
The bridge model should not forward full OpenClaw-managed input blindly to Claude Code. Instead it should convert the incoming turn into a contractor payload.
|
||||||
|
|
||||||
|
### Contractor payload should include
|
||||||
|
|
||||||
|
- latest user message
|
||||||
|
- OpenClaw session key
|
||||||
|
- agent id
|
||||||
|
- workspace path
|
||||||
|
- minimal runtime hints needed by Claude
|
||||||
|
|
||||||
|
### Contractor payload should exclude or heavily reduce
|
||||||
|
|
||||||
|
- repeated OpenClaw prompt wrappers
|
||||||
|
- large historical message dumps already represented in Claude session state
|
||||||
|
- redundant policy text already injected at Claude session creation
|
||||||
|
|
||||||
|
### Why this matters
|
||||||
|
|
||||||
|
Without this reducer, the system drifts into dual-context management:
|
||||||
|
|
||||||
|
- OpenClaw reconstructs context every turn
|
||||||
|
- Claude maintains its own session context
|
||||||
|
- both copies diverge over time
|
||||||
|
|
||||||
|
The reducer prevents that by making Claude the primary owner of live context after bootstrap.
|
||||||
|
|
||||||
|
## 5. Claude ACP adapter
|
||||||
|
|
||||||
|
This component talks to Claude Code through the chosen ACP/runtime path.
|
||||||
|
|
||||||
|
Architecture assumption for phase 1:
|
||||||
|
|
||||||
|
- Claude Code is hosted through OpenClaw-compatible ACP infrastructure
|
||||||
|
- the plugin uses persistent Claude sessions whenever possible
|
||||||
|
|
||||||
|
### Responsibilities
|
||||||
|
|
||||||
|
- create new Claude sessions
|
||||||
|
- resume existing Claude sessions
|
||||||
|
- send new user messages to Claude
|
||||||
|
- collect Claude output
|
||||||
|
- surface recoverable failures to the bridge model
|
||||||
|
|
||||||
|
### Expected operations
|
||||||
|
|
||||||
|
- `createSession(...)`
|
||||||
|
- `resumeSession(...)`
|
||||||
|
- `sendMessage(...)`
|
||||||
|
- `closeSession(...)`
|
||||||
|
- `healthCheck(...)`
|
||||||
|
|
||||||
|
Exact API shape depends on implementation details and available runtime hooks.
|
||||||
|
|
||||||
|
## 6. Response normalizer
|
||||||
|
|
||||||
|
Claude output may not match the exact shape OpenClaw expects from a model provider. The normalizer converts it into the response format used by the bridge model.
|
||||||
|
|
||||||
|
### Responsibilities
|
||||||
|
|
||||||
|
- convert plain assistant text
|
||||||
|
- preserve useful structured output if present
|
||||||
|
- flatten multi-part response payloads where needed
|
||||||
|
- translate adapter/runtime failures into OpenClaw-visible model errors
|
||||||
|
|
||||||
|
### Desired outcome
|
||||||
|
|
||||||
|
From OpenClaw's point of view, the result should feel like a normal model completion even though it came from a stateful Claude session.
|
||||||
|
|
||||||
|
## Turn lifecycle
|
||||||
|
|
||||||
|
## A. Agent provisioning lifecycle
|
||||||
|
|
||||||
|
Triggered by:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents add --agent-id <agent-id> --workspace <workspace> --contractor claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
|
||||||
|
1. create OpenClaw agent with model `contractor-claude-bridge`
|
||||||
|
2. write contractor runtime metadata to agent config
|
||||||
|
3. create contractor state directory
|
||||||
|
4. initialize empty session map store
|
||||||
|
|
||||||
|
No Claude session is required at provisioning time.
|
||||||
|
|
||||||
|
## B. First conversation turn
|
||||||
|
|
||||||
|
1. OpenClaw routes message to contractor-backed agent
|
||||||
|
2. OpenClaw invokes primary model `contractor-claude-bridge`
|
||||||
|
3. bridge model resolves agent metadata
|
||||||
|
4. bridge model checks session map for current OpenClaw session
|
||||||
|
5. no mapping found
|
||||||
|
6. bridge model asks Claude ACP adapter to create a new Claude session
|
||||||
|
7. plugin injects bootstrap instructions and minimal OpenClaw metadata
|
||||||
|
8. plugin sends latest user message
|
||||||
|
9. Claude replies
|
||||||
|
10. plugin stores mapping and returns normalized output to OpenClaw
|
||||||
|
|
||||||
|
## C. Later conversation turns
|
||||||
|
|
||||||
|
1. OpenClaw invokes `contractor-claude-bridge`
|
||||||
|
2. metadata resolver confirms contractor-backed Claude agent
|
||||||
|
3. session map returns mapped Claude session id
|
||||||
|
4. input reducer extracts newest actionable message
|
||||||
|
5. Claude ACP adapter resumes session and sends message
|
||||||
|
6. Claude replies
|
||||||
|
7. response normalizer emits OpenClaw model response
|
||||||
|
8. session map timestamp is updated
|
||||||
|
|
||||||
|
## D. Reset flow
|
||||||
|
|
||||||
|
If the user resets the session:
|
||||||
|
|
||||||
|
1. plugin invalidates or replaces mapping for the OpenClaw session key
|
||||||
|
2. next turn creates a fresh Claude session
|
||||||
|
3. bootstrap instructions are injected again
|
||||||
|
|
||||||
|
Recommended behavior:
|
||||||
|
|
||||||
|
- keep OpenClaw agent and workspace stable
|
||||||
|
- replace only contractor session continuity
|
||||||
|
|
||||||
|
## E. Recovery flow
|
||||||
|
|
||||||
|
If Claude session is missing or unusable:
|
||||||
|
|
||||||
|
1. mark mapping as `orphaned`
|
||||||
|
2. create a new Claude session
|
||||||
|
3. inject bootstrap instructions
|
||||||
|
4. optionally pass a minimal recovery summary derived from recent OpenClaw-visible context
|
||||||
|
5. replace mapping
|
||||||
|
|
||||||
|
This should be treated as a degraded fallback path, not the normal operating path.
|
||||||
|
|
||||||
|
## Bootstrap strategy
|
||||||
|
|
||||||
|
Bootstrap text should be injected when a Claude session is first created.
|
||||||
|
|
||||||
|
It should include:
|
||||||
|
|
||||||
|
- contractor role inside OpenClaw
|
||||||
|
- workspace root
|
||||||
|
- expectations for message style back to OpenClaw
|
||||||
|
- constraints around approvals and tools
|
||||||
|
- guidance on using session continuity instead of expecting full replay every turn
|
||||||
|
|
||||||
|
It should not be re-injected every turn.
|
||||||
|
|
||||||
|
## Storage design
|
||||||
|
|
||||||
|
## Static config
|
||||||
|
|
||||||
|
Static agent configuration belongs in OpenClaw config and is managed by the contractor CLI.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- agent id
|
||||||
|
- workspace
|
||||||
|
- bridge model id
|
||||||
|
- contractor kind
|
||||||
|
- contractor runtime defaults
|
||||||
|
|
||||||
|
## Dynamic state
|
||||||
|
|
||||||
|
Dynamic session mapping belongs in plugin runtime state under the agent workspace.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- OpenClaw session key to Claude session id mapping
|
||||||
|
- session state flags
|
||||||
|
- timestamps
|
||||||
|
|
||||||
|
This separation keeps declarative config and runtime state from bleeding into each other.
|
||||||
|
|
||||||
|
## Why not mirror full Claude state into OpenClaw session files
|
||||||
|
|
||||||
|
Because doing so would:
|
||||||
|
|
||||||
|
- create duplicated context systems
|
||||||
|
- encourage replay-based turn handling
|
||||||
|
- increase token cost and failure complexity
|
||||||
|
- still fail to capture Claude's real internal runtime state perfectly
|
||||||
|
|
||||||
|
Instead:
|
||||||
|
|
||||||
|
- OpenClaw transcript remains the visible conversation record
|
||||||
|
- Claude session remains the live cognitive state for the contractor runtime
|
||||||
|
|
||||||
|
## Approval and tool interaction
|
||||||
|
|
||||||
|
Phase 1 should keep this conservative.
|
||||||
|
|
||||||
|
Recommended initial stance:
|
||||||
|
|
||||||
|
- start with limited or no complex tool bridging beyond what is required for basic operation
|
||||||
|
- preserve OpenClaw as the place where approvals and policy are enforced
|
||||||
|
- design later tool bridging so Claude requests are translated into OpenClaw-hosted tool execution rather than bypassing OpenClaw directly
|
||||||
|
|
||||||
|
## Key risks
|
||||||
|
|
||||||
|
### Risk 1, poor boundary between model and runtime
|
||||||
|
|
||||||
|
If the bridge model tries to imitate a normal provider too literally, implementation gets awkward. It must be treated as a stateful pseudo-model.
|
||||||
|
|
||||||
|
### Risk 2, session mapping drift
|
||||||
|
|
||||||
|
If mappings are lost or stale, the user may silently lose Claude-side continuity. Recovery behavior must be explicit.
|
||||||
|
|
||||||
|
### Risk 3, over-forwarding context
|
||||||
|
|
||||||
|
If too much OpenClaw-managed prompt content is forwarded each turn, the design collapses back into dual-context management.
|
||||||
|
|
||||||
|
### Risk 4, under-specified reset semantics
|
||||||
|
|
||||||
|
Reset, new, and recovery must be clearly defined or the plugin will become unpredictable.
|
||||||
|
|
||||||
|
## Phase 1 implementation priorities
|
||||||
|
|
||||||
|
1. Register bridge model id
|
||||||
|
2. Implement contractor metadata resolver
|
||||||
|
3. Implement contractor CLI add flow
|
||||||
|
4. Implement session map store
|
||||||
|
5. Implement create/resume/send behavior in Claude ACP adapter
|
||||||
|
6. Implement response normalization
|
||||||
|
7. Add reset and recovery semantics
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
### OpenClaw Claude via ACP
|
||||||
|
|
||||||
|
> "ACP sessions let OpenClaw run external coding harnesses (for example ... Claude Code ...) through an ACP backend plugin."
|
||||||
|
|
||||||
|
> "For Claude Code through ACP, the stack is:
|
||||||
|
> 1. OpenClaw ACP session control plane
|
||||||
|
> 2. bundled `acpx` runtime plugin
|
||||||
|
> 3. Claude ACP adapter
|
||||||
|
> 4. Claude-side runtime/session machinery"
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/tools/acp-agents
|
||||||
|
|
||||||
|
### OpenClaw plugin extensibility
|
||||||
|
|
||||||
|
> "Plugins extend OpenClaw with new capabilities: channels, model providers, ... agent tools, or any combination."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/building-plugins
|
||||||
|
|
||||||
|
### Plugin entry contract
|
||||||
|
|
||||||
|
> "For provider plugins, tool plugins, hook plugins, and anything that is not a messaging channel."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/sdk-entrypoints
|
||||||
|
|
||||||
|
### Plugin CLI surface
|
||||||
|
|
||||||
|
> "For plugin-owned root CLI commands, prefer `api.registerCli(..., { descriptors: [...] })` ..."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/sdk-entrypoints
|
||||||
307
docs/claude/CLI.md
Normal file
307
docs/claude/CLI.md
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# Contractor Agents CLI Design
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define the plugin-owned CLI surface for managing contractor-backed OpenClaw agents.
|
||||||
|
|
||||||
|
Phase 1 supports Claude only. Gemini remains reserved in the command shape but is not implemented yet.
|
||||||
|
|
||||||
|
## Command root
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents
|
||||||
|
```
|
||||||
|
|
||||||
|
This should be implemented as a plugin-owned CLI root, not as a patch to `openclaw agents add`.
|
||||||
|
|
||||||
|
## Why a separate command root
|
||||||
|
|
||||||
|
Current OpenClaw plugin docs show support for plugin CLI registration, but do not document support for extending existing core commands with new flags.
|
||||||
|
|
||||||
|
Relevant doc statements:
|
||||||
|
|
||||||
|
> "Common registration methods: ... `registerCommand` / `registerCli` | CLI commands"
|
||||||
|
|
||||||
|
> "For plugin-owned root CLI commands, prefer `api.registerCli(..., { descriptors: [...] })` ..."
|
||||||
|
|
||||||
|
This suggests the supported extension seam is plugin-owned CLI trees.
|
||||||
|
|
||||||
|
Sources:
|
||||||
|
- https://docs.openclaw.ai/tools/plugin
|
||||||
|
- https://docs.openclaw.ai/plugins/sdk-entrypoints
|
||||||
|
|
||||||
|
## Phase 1 command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents add --agent-id <agent-id> --workspace <workspace> --contractor <claude|gemini>
|
||||||
|
```
|
||||||
|
|
||||||
|
Phase 1 restrictions:
|
||||||
|
|
||||||
|
- `--contractor` must currently be `claude`
|
||||||
|
- `gemini` is accepted only as a reserved future value if desired, but should currently fail with a clear not-yet-implemented message unless you intentionally decide to reject it at parsing time
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `--agent-id <agent-id>`
|
||||||
|
- `--workspace <workspace>`
|
||||||
|
- `--contractor <claude|gemini>`
|
||||||
|
|
||||||
|
### Optional, possible later
|
||||||
|
|
||||||
|
Not required for first implementation, but likely useful later:
|
||||||
|
|
||||||
|
- `--bridge-model <model-id>`
|
||||||
|
- `--backend <acp-backend>`
|
||||||
|
- `--mode <persistent|oneshot>`
|
||||||
|
- `--json`
|
||||||
|
- `--non-interactive`
|
||||||
|
|
||||||
|
For now, phase 1 can hardcode the bridge model and runtime defaults.
|
||||||
|
|
||||||
|
## Behavior of `add`
|
||||||
|
|
||||||
|
For:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents add --agent-id my-agent --workspace /path/to/ws --contractor claude
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI should do the following.
|
||||||
|
|
||||||
|
### Step 1, validate inputs
|
||||||
|
|
||||||
|
- ensure `agent-id` is provided
|
||||||
|
- ensure workspace exists or is creatable, based on your desired policy
|
||||||
|
- ensure contractor kind is supported
|
||||||
|
- ensure target agent does not already exist unless overwrite behavior is explicitly added later
|
||||||
|
|
||||||
|
### Step 2, create the base OpenClaw agent
|
||||||
|
|
||||||
|
Run the equivalent of:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw agents add <agent-id> --workspace <workspace> --model contractor-claude-bridge --non-interactive
|
||||||
|
```
|
||||||
|
|
||||||
|
Important note:
|
||||||
|
|
||||||
|
Earlier review of `openclaw agents add` docs showed these supported options:
|
||||||
|
|
||||||
|
> `--workspace <dir>`
|
||||||
|
> `--model <id>`
|
||||||
|
> `--agent-dir <dir>`
|
||||||
|
> `--bind <channel[:accountId]>`
|
||||||
|
> `--non-interactive`
|
||||||
|
> `--json`
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/cli/agents
|
||||||
|
|
||||||
|
This means the contractor CLI can compose the existing agent creation flow rather than replacing it.
|
||||||
|
|
||||||
|
### Step 3, mark the agent as a contractor-backed agent
|
||||||
|
|
||||||
|
After agent creation, write contractor metadata so runtime selection and session bridging can identify this agent as contractor-managed.
|
||||||
|
|
||||||
|
Proposed config direction:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: "my-agent",
|
||||||
|
model: "contractor-claude-bridge",
|
||||||
|
runtime: {
|
||||||
|
type: "contractor",
|
||||||
|
contractor: {
|
||||||
|
kind: "claude",
|
||||||
|
backend: "acp",
|
||||||
|
mode: "persistent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Exact schema is still TBD, but the CLI should be the mechanism that writes this state.
|
||||||
|
|
||||||
|
### Step 4, initialize contractor runtime state
|
||||||
|
|
||||||
|
Prepare runtime state for the agent, including a place to store session mappings.
|
||||||
|
|
||||||
|
Suggested path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<workspace>/.openclaw/contractor-agent/
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested initial files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
session-map.json
|
||||||
|
runtime.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Session mapping responsibility
|
||||||
|
|
||||||
|
The CLI does not create Claude sessions immediately unless you explicitly want eager setup.
|
||||||
|
|
||||||
|
Recommended phase 1 behavior:
|
||||||
|
|
||||||
|
- create agent metadata
|
||||||
|
- create runtime state directory
|
||||||
|
- initialize empty mapping file
|
||||||
|
- defer real Claude session creation to first actual agent turn
|
||||||
|
|
||||||
|
This keeps `add` lightweight and avoids unused contractor sessions.
|
||||||
|
|
||||||
|
## Suggested future commands
|
||||||
|
|
||||||
|
These are not required immediately, but the command tree should reserve room for them.
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents status --agent-id <agent-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Show:
|
||||||
|
- contractor kind
|
||||||
|
- bridge model id
|
||||||
|
- runtime backend
|
||||||
|
- session mapping count
|
||||||
|
- recent activity
|
||||||
|
|
||||||
|
### List sessions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents sessions --agent-id <agent-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Show session mappings for the given contractor-backed agent.
|
||||||
|
|
||||||
|
### Reset mapping
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents reset-session --agent-id <agent-id> --openclaw-session <session-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Drop a specific OpenClaw-to-Claude mapping so the next message recreates the Claude session.
|
||||||
|
|
||||||
|
### Disable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw contractor-agents disable --agent-id <agent-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Either:
|
||||||
|
- remove contractor metadata but keep agent
|
||||||
|
- or mark the contractor runtime disabled
|
||||||
|
|
||||||
|
Semantics TBD.
|
||||||
|
|
||||||
|
## JSON output
|
||||||
|
|
||||||
|
I recommend all subcommands eventually support `--json` so this can be scripted.
|
||||||
|
|
||||||
|
Example success response shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"agentId": "my-agent",
|
||||||
|
"workspace": "/path/to/ws",
|
||||||
|
"contractor": "claude",
|
||||||
|
"model": "contractor-claude-bridge",
|
||||||
|
"runtimeStateDir": "/path/to/ws/.openclaw/contractor-agent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Failure cases
|
||||||
|
|
||||||
|
The CLI should fail clearly for:
|
||||||
|
|
||||||
|
- unsupported contractor kind
|
||||||
|
- agent already exists
|
||||||
|
- workspace invalid or inaccessible
|
||||||
|
- bridge model plugin not available
|
||||||
|
- failure in underlying `openclaw agents add`
|
||||||
|
- failure writing contractor metadata
|
||||||
|
|
||||||
|
If the agent is created successfully but metadata writing fails, the CLI should either:
|
||||||
|
|
||||||
|
- roll back the created agent if safe and simple
|
||||||
|
- or return a partial-failure error with explicit cleanup instructions
|
||||||
|
|
||||||
|
Phase 1 recommendation: prefer explicit rollback if implementation is reliable.
|
||||||
|
|
||||||
|
## Interaction with the bridge model
|
||||||
|
|
||||||
|
The CLI should not directly run Claude Code or create a Claude contractor session by default.
|
||||||
|
|
||||||
|
Its main job is to provision an agent whose primary model is the contractor bridge model:
|
||||||
|
|
||||||
|
- `contractor-claude-bridge`
|
||||||
|
|
||||||
|
That model then handles runtime dispatch during actual conversation turns.
|
||||||
|
|
||||||
|
## Implementation notes
|
||||||
|
|
||||||
|
The plugin should register a root command owned by the plugin.
|
||||||
|
|
||||||
|
Relevant doc statements:
|
||||||
|
|
||||||
|
> "For plugin-owned root CLI commands, prefer `api.registerCli(..., { descriptors: [...] })` when you want the command to stay lazy-loaded without disappearing from the root CLI parse tree."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/sdk-entrypoints
|
||||||
|
|
||||||
|
So the intended shape is:
|
||||||
|
|
||||||
|
- plugin registers `contractor-agents`
|
||||||
|
- plugin owns parsing and handling of its subcommands
|
||||||
|
- plugin composes `openclaw agents add` behavior internally or via shared config/runtime helpers
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
1. Should the CLI shell out to `openclaw agents add` or use a lower-level config/runtime helper directly?
|
||||||
|
2. Where should contractor metadata live, `agents.list[].runtime`, plugin config, or both?
|
||||||
|
3. Should `gemini` be rejected now or accepted with a not implemented error?
|
||||||
|
4. Should add create the workspace if it does not exist?
|
||||||
|
5. Should agent creation be rolled back if contractor metadata writing fails?
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
### Plugin CLI support
|
||||||
|
|
||||||
|
> "Common registration methods: ... `registerCommand` / `registerCli` | CLI commands"
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/tools/plugin
|
||||||
|
|
||||||
|
### Plugin-owned CLI descriptors
|
||||||
|
|
||||||
|
> "For plugin-owned root CLI commands, prefer `api.registerCli(..., { descriptors: [...] })` ..."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/sdk-entrypoints
|
||||||
|
|
||||||
|
### Existing agent creation command
|
||||||
|
|
||||||
|
> `agents add [name]`
|
||||||
|
|
||||||
|
> `--workspace <dir>`
|
||||||
|
> `--model <id>`
|
||||||
|
> `--agent-dir <dir>`
|
||||||
|
> `--bind <channel[:accountId]>`
|
||||||
|
> `--non-interactive`
|
||||||
|
> `--json`
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/cli/agents
|
||||||
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.
|
||||||
238
docs/claude/MODEL.md
Normal file
238
docs/claude/MODEL.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# Claude Bridge Model Design
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Define how Claude-backed contractor agents integrate into OpenClaw using a custom model seam.
|
||||||
|
|
||||||
|
This document is specific to the Claude phase of ContractorAgent.
|
||||||
|
|
||||||
|
## Why this must be a custom model
|
||||||
|
|
||||||
|
OpenClaw expects every agent to have a primary model. For contractor-backed agents, the actual reasoning engine is not a normal OpenClaw LLM provider call, but a Claude Code session managed outside the usual stateless completion flow.
|
||||||
|
|
||||||
|
Therefore the contractor integration should present itself to OpenClaw as a custom model id, while internally acting as a stateful bridge to Claude Code.
|
||||||
|
|
||||||
|
Recommended model id for phase 1:
|
||||||
|
|
||||||
|
- `contractor-claude-bridge`
|
||||||
|
|
||||||
|
## Mental model
|
||||||
|
|
||||||
|
From OpenClaw's perspective:
|
||||||
|
|
||||||
|
- the agent has a normal primary model
|
||||||
|
- a model request is issued for each turn
|
||||||
|
- the model returns assistant output
|
||||||
|
|
||||||
|
From the plugin's perspective:
|
||||||
|
|
||||||
|
- the model request is actually a routing event
|
||||||
|
- the request identifies the OpenClaw session and agent
|
||||||
|
- the plugin resolves a mapped Claude Code session
|
||||||
|
- the plugin forwards only the newest actionable turn to Claude Code
|
||||||
|
- the plugin returns Claude's output as the model response
|
||||||
|
|
||||||
|
So this is a pseudo-model backend with sessionful behavior.
|
||||||
|
|
||||||
|
## Responsibilities of the bridge model
|
||||||
|
|
||||||
|
The bridge model should:
|
||||||
|
|
||||||
|
1. accept model requests for contractor-backed agents
|
||||||
|
2. identify the current OpenClaw session and contractor agent id
|
||||||
|
3. resolve or create the mapped Claude Code session
|
||||||
|
4. filter OpenClaw-managed envelope/context
|
||||||
|
5. forward only the latest actionable message plus minimal metadata
|
||||||
|
6. collect Claude Code output
|
||||||
|
7. normalize that output into an OpenClaw model response
|
||||||
|
8. persist session mapping metadata
|
||||||
|
|
||||||
|
The bridge model should not:
|
||||||
|
|
||||||
|
- act like a generic stateless LLM provider
|
||||||
|
- replay full OpenClaw session history every turn unless recovery is required
|
||||||
|
- try to mirror all Claude internal state into OpenClaw session files
|
||||||
|
|
||||||
|
## Model lifecycle
|
||||||
|
|
||||||
|
### First turn in an OpenClaw session
|
||||||
|
|
||||||
|
1. OpenClaw selects `contractor-claude-bridge` as the agent's primary model
|
||||||
|
2. bridge model receives the turn input
|
||||||
|
3. plugin extracts:
|
||||||
|
- agent id
|
||||||
|
- OpenClaw session key
|
||||||
|
- workspace
|
||||||
|
- latest user message
|
||||||
|
4. no session mapping exists yet
|
||||||
|
5. plugin creates a Claude Code session through ACP/runtime integration
|
||||||
|
6. plugin injects initial contractor instructions and minimal OpenClaw metadata
|
||||||
|
7. plugin sends the latest user message
|
||||||
|
8. Claude responds
|
||||||
|
9. plugin stores the new mapping and returns assistant output to OpenClaw
|
||||||
|
|
||||||
|
### Later turns
|
||||||
|
|
||||||
|
1. bridge model receives next turn
|
||||||
|
2. plugin resolves existing mapping
|
||||||
|
3. plugin resumes or continues the Claude Code session
|
||||||
|
4. plugin forwards latest user message only
|
||||||
|
5. Claude responds
|
||||||
|
6. plugin returns normalized response and updates mapping timestamps
|
||||||
|
|
||||||
|
### Reset or new session
|
||||||
|
|
||||||
|
Reset semantics should be explicit.
|
||||||
|
|
||||||
|
Recommended behavior:
|
||||||
|
|
||||||
|
- OpenClaw `/new` or reset-equivalent for this agent should create a fresh Claude session transcript
|
||||||
|
- keep the OpenClaw agent identity and routing stable
|
||||||
|
- replace the existing mapping for the OpenClaw session key
|
||||||
|
- reinject initial contractor instructions
|
||||||
|
|
||||||
|
## Session mapping contract
|
||||||
|
|
||||||
|
Bridge model depends on a persistent mapping store.
|
||||||
|
|
||||||
|
Suggested record shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ContractorSessionMapEntry = {
|
||||||
|
openclawSessionKey: string
|
||||||
|
agentId: string
|
||||||
|
contractor: "claude"
|
||||||
|
claudeSessionId: string
|
||||||
|
workspace: string
|
||||||
|
createdAt: string
|
||||||
|
lastActivityAt: string
|
||||||
|
state: "active" | "closed" | "orphaned"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input filtering
|
||||||
|
|
||||||
|
The bridge model must not blindly forward the entire model input payload to Claude Code.
|
||||||
|
|
||||||
|
Instead it should derive a contractor payload with:
|
||||||
|
|
||||||
|
- latest user-visible message
|
||||||
|
- minimal agent metadata
|
||||||
|
- minimal workspace metadata
|
||||||
|
- optional turn metadata needed for reply formatting or approvals
|
||||||
|
|
||||||
|
It should ignore or strip:
|
||||||
|
|
||||||
|
- repeated long-form system envelope generated by OpenClaw
|
||||||
|
- redundant chat history already owned by Claude session memory
|
||||||
|
- internal bookkeeping not useful to Claude Code
|
||||||
|
|
||||||
|
This keeps Claude as the owner of live context.
|
||||||
|
|
||||||
|
## Initial contractor instructions
|
||||||
|
|
||||||
|
When creating a fresh Claude session, inject a stable bootstrap payload that includes:
|
||||||
|
|
||||||
|
- identity of the contractor role inside OpenClaw
|
||||||
|
- workspace path
|
||||||
|
- allowed interaction style with OpenClaw
|
||||||
|
- tool/skill access strategy
|
||||||
|
- output expectations for OpenClaw replies
|
||||||
|
- rule that session continuity lives primarily in Claude Code session state
|
||||||
|
|
||||||
|
This injection should happen on session creation and hard resets, not every turn.
|
||||||
|
|
||||||
|
## Output normalization
|
||||||
|
|
||||||
|
Claude output must be normalized before returning to OpenClaw.
|
||||||
|
|
||||||
|
Normalization should handle:
|
||||||
|
|
||||||
|
- plain assistant text
|
||||||
|
- tool-use related status text
|
||||||
|
- multi-part responses if Claude adapter streams or chunks
|
||||||
|
- failures or blocked actions
|
||||||
|
- explicit clarification requests
|
||||||
|
|
||||||
|
The custom model should emit output in a way OpenClaw can treat as a normal assistant response.
|
||||||
|
|
||||||
|
## Session file interaction
|
||||||
|
|
||||||
|
The current working assumption is:
|
||||||
|
|
||||||
|
- OpenClaw session files remain the system of record for visible conversation state
|
||||||
|
- Claude session state remains the system of record for internal live reasoning context
|
||||||
|
|
||||||
|
Therefore the bridge model should inject Claude responses back into OpenClaw's normal session response path, rather than bypassing it.
|
||||||
|
|
||||||
|
This means the plugin should preserve OpenClaw's normal transcript behavior while avoiding full upstream context replay.
|
||||||
|
|
||||||
|
## Recovery behavior
|
||||||
|
|
||||||
|
### Missing Claude session
|
||||||
|
|
||||||
|
If the mapped Claude session is missing or invalid:
|
||||||
|
|
||||||
|
1. mark mapping as `orphaned`
|
||||||
|
2. create a fresh Claude session
|
||||||
|
3. inject bootstrap context
|
||||||
|
4. optionally summarize minimal recent OpenClaw-visible context for recovery
|
||||||
|
5. store replacement mapping
|
||||||
|
|
||||||
|
This should be treated as degraded recovery, not the normal path.
|
||||||
|
|
||||||
|
### Mapping corruption
|
||||||
|
|
||||||
|
If mapping storage cannot be read:
|
||||||
|
|
||||||
|
- fail closed for the turn if data integrity is uncertain
|
||||||
|
- or create a new Claude session only when policy allows and user impact is acceptable
|
||||||
|
|
||||||
|
## Why not use a normal provider model directly
|
||||||
|
|
||||||
|
Because a normal provider model implies:
|
||||||
|
|
||||||
|
- stateless prompt in
|
||||||
|
- stateless completion out
|
||||||
|
- OpenClaw-owned context reconstruction each turn
|
||||||
|
|
||||||
|
That is the opposite of the intended contractor design.
|
||||||
|
|
||||||
|
The custom model seam is useful precisely because it lets OpenClaw keep the agent abstraction while delegating real session continuity to Claude Code.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
1. Which provider/plugin registration seam is best for the bridge model id?
|
||||||
|
2. What exact request shape does the plugin receive for a model turn in OpenClaw runtime?
|
||||||
|
3. Where should bootstrap instructions be stored, generated text, template file, or config?
|
||||||
|
4. How should streaming behavior be represented if Claude session output arrives incrementally?
|
||||||
|
5. How should tool requests from Claude be surfaced back into OpenClaw tool execution?
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
### OpenClaw plugin capability
|
||||||
|
|
||||||
|
> "Plugins extend OpenClaw with new capabilities: channels, model providers, ... agent tools, or any combination."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/building-plugins
|
||||||
|
|
||||||
|
### Plugin entry model
|
||||||
|
|
||||||
|
> "For provider plugins, tool plugins, hook plugins, and anything that is not a messaging channel."
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/plugins/sdk-entrypoints
|
||||||
|
|
||||||
|
### Claude Code via ACP
|
||||||
|
|
||||||
|
> "ACP sessions let OpenClaw run external coding harnesses (for example ... Claude Code ...) through an ACP backend plugin."
|
||||||
|
|
||||||
|
> "For Claude Code through ACP, the stack is:
|
||||||
|
> 1. OpenClaw ACP session control plane
|
||||||
|
> 2. bundled `acpx` runtime plugin
|
||||||
|
> 3. Claude ACP adapter
|
||||||
|
> 4. Claude-side runtime/session machinery"
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- https://docs.openclaw.ai/tools/acp-agents
|
||||||
@@ -23,6 +23,16 @@ Desired properties:
|
|||||||
|
|
||||||
Use a session proxy runtime rather than replaying full OpenClaw-managed context into Claude Code on every turn.
|
Use a session proxy runtime rather than replaying full OpenClaw-managed context into Claude Code on every turn.
|
||||||
|
|
||||||
|
Important implementation note: in OpenClaw this should be delivered as a **custom model**.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
- OpenClaw expects each agent to have a primary model
|
||||||
|
- for this special contractor agent, the real primary intelligence is provided by Claude Code
|
||||||
|
- therefore the most natural integration seam is a custom model that accepts OpenClaw messages, routes them into the mapped Claude Code session, and then injects Claude's response back into OpenClaw session state
|
||||||
|
|
||||||
|
So although the architecture is conceptually a session proxy runtime, the concrete integration form inside OpenClaw should be a **bridge model** or **pseudo-model backend** for Claude-backed agents.
|
||||||
|
|
||||||
Core idea:
|
Core idea:
|
||||||
|
|
||||||
- map each OpenClaw session to a Claude Code session
|
- map each OpenClaw session to a Claude Code session
|
||||||
@@ -30,6 +40,7 @@ Core idea:
|
|||||||
- resume or continue that Claude session
|
- resume or continue that Claude session
|
||||||
- forward only the newest actionable message plus minimal metadata
|
- forward only the newest actionable message plus minimal metadata
|
||||||
- avoid rebuilding full prompt history on every turn
|
- avoid rebuilding full prompt history on every turn
|
||||||
|
- return the Claude response through the custom model seam so it becomes part of normal OpenClaw agent flow
|
||||||
|
|
||||||
This reduces:
|
This reduces:
|
||||||
|
|
||||||
@@ -137,6 +148,26 @@ This supports the idea that OpenClaw skills and tooling may be adapted into Clau
|
|||||||
Source:
|
Source:
|
||||||
- https://code.claude.com/docs/en/plugins-reference
|
- https://code.claude.com/docs/en/plugins-reference
|
||||||
|
|
||||||
|
## Bridge Model Direction
|
||||||
|
|
||||||
|
The contractor-agent implementation should expose one or more plugin-provided custom model ids for OpenClaw agents to use as their primary model.
|
||||||
|
|
||||||
|
For Claude phase 1, the working assumption is a model id in the shape of:
|
||||||
|
|
||||||
|
- `contractor-claude-bridge`
|
||||||
|
|
||||||
|
Behavior of this bridge model:
|
||||||
|
|
||||||
|
1. receive the agent turn from OpenClaw
|
||||||
|
2. identify the current OpenClaw session key and target contractor agent
|
||||||
|
3. resolve or create the mapped Claude Code session
|
||||||
|
4. filter OpenClaw-managed envelope/context so only the latest actionable message and minimal metadata are forwarded
|
||||||
|
5. send the turn to Claude Code through the chosen ACP/session continuation path
|
||||||
|
6. capture Claude's response
|
||||||
|
7. inject the response back into OpenClaw through the model response path and session file flow
|
||||||
|
|
||||||
|
This approach fits OpenClaw's requirement that every agent has a primary model while still allowing Claude Code to act as the true reasoning engine for contractor-backed agents.
|
||||||
|
|
||||||
## Initial CLI Plan
|
## Initial CLI Plan
|
||||||
|
|
||||||
Introduce a plugin-owned command:
|
Introduce a plugin-owned command:
|
||||||
|
|||||||
Reference in New Issue
Block a user