← 返回
未分类 中文

Claude Handoff

Writes a structured handoff package when local agent determines cloud Claude Code is needed. This is the ONLY path from local pipeline to cloud — the agent n...
Writes a structured handoff package when local agent determines cloud Claude Code is needed. This is the ONLY path from local pipeline to cloud — the agent n...
stephenlthorn stephenlthorn 来源
未分类 clawhub v1.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 382
下载
💾 0
安装
1
版本
#latest

概述

Claude Code Handoff

The principle: never auto-invoke Claude

Claude Max quota is finite. Auto-invoking on every hard task burns it

unnecessarily and removes user control. This skill ALWAYS produces a file

and notifies the user — it never calls the Claude API directly.

The user reviews the handoff, decides if they want to spend quota on it,

then runs Claude Code themselves. This is the contract.

Handoff package format

Written to {project_root}/.openclaw-skills/handoffs/{timestamp}-{slug}.md

---
timestamp: 2026-04-21T14:32:18Z
task_slug: add-swiftdata-cloudkit-migration
reason: iOS domain (hard gate)
orchestrator_version: 1.0.0
local_model: m27-jangtq-crack
---

# Handoff: Add SwiftData + CloudKit migration for iOS 26

## Original request
[verbatim user request]

## Why this is being handed off
**Reason**: iOS domain hard gate

Local M2.7 JANGTQ-CRACK's Swift training data is stale on SwiftData + CloudKit
migrations for iOS 26. This is a case where Claude Sonnet 4.6's more recent
training plus 1M context beats retrieved RAG snippets.

## Context gathered locally

### Files read
- `App/AppDelegate.swift` (112 lines)
- `Models/User.swift` (48 lines)
- `Models/UserDataSource.swift` (204 lines)
- `Views/ContentView.swift` (89 lines)

### Codebase patterns identified
- Project uses SwiftData with `@Model` macro
- Existing SwiftData stack in `Models/UserDataSource.swift:42`
- No CloudKit integration yet
- Deployment target: iOS 18, need to upgrade to iOS 26 first

### Relevant Apple documentation retrieved
1. [SwiftData CloudKit integration guide (iOS 26)](ref-link)
2. [iOS 26 migration patterns for SwiftData](ref-link)
3. [CloudKit schema synchronization](ref-link)

### Related Stack Overflow patterns
[if any]

## Proposed approach (from local planning)

Sub-problems identified:
1. **Update deployment target to iOS 26** (AppDelegate, Info.plist)
2. **Add CloudKit entitlement** (project settings)
3. **Migrate UserDataSource to use CloudKit-backed ModelContainer**
   - Depends on sub-problem 1
4. **Add migration plan for existing local data**
   - Depends on sub-problem 3
5. **Update Views to handle CloudKit sync state**
   - Depends on sub-problem 3

APIs involved:
- `ModelConfiguration(cloudKitDatabase:)` — iOS 26+
- `CKContainer.applicationDefault()` — iOS 8+
- `SchemaMigrationPlan` — iOS 26+

Risks:
- HIGH: Existing local user data could be lost if migration not handled
- MEDIUM: CloudKit development vs production environment confusion
- LOW: iOS version compatibility for existing users on < 26

## What local attempted and failed

[if build_failed]
Generated code that attempted the migration. Compile errors:
- Error 1: `cannot find 'cloudKitDatabase' in scope at ModelConfiguration.swift:12`
  (Fixed in iteration 2 — was missing import)
- Error 2: `'SchemaMigrationPlan' is only available in iOS 26.0 or newer`
  (Unable to fix — need guidance on availability strategy)

## Suggested Claude Code prompt

Copy-paste this into Claude Code:

Read /Users/stephen/projects/myapp/.openclaw-skills/handoffs/2026-04-21T14:32:18Z-add-swiftdata-cloudkit-migration.md

Then implement the 5 sub-problems in order. You have write access to the

project. Run swift build after each sub-problem to verify.

When done, produce a summary of what changed and any remaining concerns.


Or run from terminal:

cd /Users/stephen/projects/myapp

claude "Read .openclaw-skills/handoffs/2026-04-21T14:32:18Z-add-swiftdata-cloudkit-migration.md and implement the plan"


## Estimated Claude quota cost
Based on context size: ~12K input tokens + ~8K output = ~20K tokens.
Sonnet 4.6: ~20K × $3/$15 per MTok = ~$0.15 in direct API equivalent.
Via Max plan: draws modestly from your daily quota.

---
*Generated by coding-orchestrator v1.0.0*

Execution

import asyncio
import json
import re
from pathlib import Path
from datetime import datetime, timezone


HANDOFF_TEMPLATE = """---
timestamp: {timestamp}
task_slug: {slug}
reason: {reason_detail}
orchestrator_version: 1.0.0
local_model: m27-jangtq-crack
---

# Handoff: {task_title}

## Original request
{task}

## Why this is being handed off
**Reason**: {reason_detail}

{reason_explanation}

## Context gathered locally

### Files read
{files_read_section}

### Codebase patterns identified
{patterns_section}

### Relevant documentation retrieved
{docs_section}

## Proposed approach (from local planning)
{plan_section}

## What local attempted and failed
{attempt_section}

## Suggested Claude Code prompt

Copy-paste this into Claude Code:

Read {handoff_path}

Then implement the plan above. You have write access to the project.

{execution_guidance}

When done, produce a summary of what changed and any remaining concerns.


Or run from terminal:

cd {project_root}

claude "Read {handoff_path_relative} and implement the plan"


## Estimated Claude quota cost
Based on context size: ~{input_tokens}K input + ~{output_tokens}K output = ~{total_tokens}K tokens.

---
*Generated by coding-orchestrator v1.0.0*
"""


REASON_EXPLANATIONS = {
    "hard_gate": """Local M2.7 JANGTQ-CRACK's Swift training data is 2+ years stale.
iOS/Swift work consistently benefits from Sonnet 4.6's more recent training
and deeper Apple framework knowledge.""",

    "build_failed": """Local model generated code that failed to compile after 3
iterations. The remaining errors suggest the model is missing context that
isn't in our RAG — likely an API interaction or constraint not present in
training or retrieved docs.""",

    "reflection_low_confidence": """After reflection passes, local model's confidence
in its own output is LOW. This usually means the model recognizes the code
might work but isn't sure — a strong signal to get a second opinion.""",

    "user_request": """User explicitly requested Claude Code handoff.""",

    "context_overflow": """Task requires more context than M2.7's practical window can
accommodate. Sonnet 4.6's 1M context is the right tool.""",

    "soft_gate": """Escalation score exceeded threshold based on task complexity,
file count, and uncertainty signals.""",
}


async def claude_handoff(
    task: str,
    reason: str,
    context_gathered: dict = None,
    proposed_approach: dict = None,
    build_errors: list = None,
    critique_history: list = None,
    files_in_scope: list = None,
    project_root: str = None,
):
    timestamp = datetime.now(timezone.utc).isoformat()
    slug = _slugify(task)[:50]
    project_root = Path(project_root or ".")

    # Build each section
    sections = {
        "timestamp": timestamp,
        "slug": slug,
        "reason_detail": _reason_to_human(reason),
        "reason_explanation": REASON_EXPLANATIONS.get(reason, ""),
        "task": task,
        "task_title": _summarize_task(task),
        "files_read_section": _format_files(context_gathered),
        "patterns_section": _format_patterns(context_gathered),
        "docs_section": _format_docs(context_gathered),
        "plan_section": _format_plan(proposed_approach),
        "attempt_section": _format_attempt(build_errors, critique_history),
        "input_tokens": _estimate_input_tokens(context_gathered, proposed_approach) // 1000,
        "output_tokens": 8,
        "total_tokens": 20,
        "execution_guidance": _execution_guidance(proposed_approach, files_in_scope),
    }

    # Write handoff file
    handoff_dir = project_root / ".openclaw-skills" / "handoffs"
    handoff_dir.mkdir(parents=True, exist_ok=True)
    handoff_path = handoff_dir / f"{timestamp.replace(':', '-')}-{slug}.md"

    sections["handoff_path"] = str(handoff_path.absolute())
    sections["handoff_path_relative"] = str(handoff_path.relative_to(project_root))
    sections["project_root"] = str(project_root.absolute())

    content = HANDOFF_TEMPLATE.format(**sections)
    handoff_path.write_text(content)

    # Build the paste-ready command
    command = (
        f'cd {project_root.absolute()} && '
        f'claude "Read {sections["handoff_path_relative"]} and implement the plan"'
    )

    # Summary for user notification
    summary = f"Handoff ready: {_summarize_task(task)} → Claude Code"

    # Notify user via OpenClaw messaging channels
    await notify_user(
        title="🤖 Claude Code handoff ready",
        body=f"{summary}\n\nPath: {sections['handoff_path_relative']}\n\n"
             f"Run: {command}",
        level="info"
    )

    return {
        "handoff_path": str(handoff_path.absolute()),
        "command": command,
        "summary": summary
    }


def _slugify(text):
    """Convert task text to filesystem-safe slug."""
    slug = re.sub(r'[^\w\s-]', '', text.lower())
    slug = re.sub(r'[\s_-]+', '-', slug)
    return slug.strip('-')


def _reason_to_human(reason):
    mapping = {
        "hard_gate": "iOS domain (hard gate)",
        "build_failed": "Build errors unfixable locally",
        "reflection_low_confidence": "Low reflection confidence",
        "user_request": "User requested handoff",
        "context_overflow": "Task exceeds local context",
        "soft_gate": "Complexity threshold exceeded",
    }
    return mapping.get(reason, reason)


def _summarize_task(task):
    """Extract first sentence or 80 chars for title."""
    first_sentence = task.split('.')[0].strip()
    if len(first_sentence) > 80:
        return first_sentence[:77] + "..."
    return first_sentence


def _format_files(ctx):
    if not ctx or "files_read" not in ctx:
        return "(none)"
    lines = []
    for f in ctx["files_read"]:
        if isinstance(f, dict):
            lines.append(f"- `{f['path']}` ({f.get('lines', '?')} lines)")
        else:
            lines.append(f"- `{f}`")
    return "\n".join(lines)


def _format_patterns(ctx):
    if not ctx or "patterns" not in ctx:
        return "(none identified)"
    return "\n".join(f"- {p}" for p in ctx["patterns"])


def _format_docs(ctx):
    if not ctx or "retrieved_docs" not in ctx:
        return "(none retrieved)"
    return "\n".join(
        f"{i+1}. {d.get('title', d['url'])}"
        for i, d in enumerate(ctx["retrieved_docs"])
    )


def _format_plan(plan):
    if not plan:
        return "(no plan developed — task escalated before planning)"

    output = ["Sub-problems identified:"]
    for i, sp in enumerate(plan.get("sub_problems", []), 1):
        output.append(f"{i}. **{sp['title']}**")
        output.append(f"   {sp.get('description', '')}")
        if sp.get("depends_on"):
            output.append(f"   Depends on: {', '.join(sp['depends_on'])}")
        output.append("")

    if plan.get("apis"):
        output.append("\nAPIs involved:")
        for api in plan["apis"]:
            output.append(f"- `{api['name']}` — {api.get('version_requirement', 'any')}")

    if plan.get("risks"):
        output.append("\nRisks:")
        for r in plan["risks"]:
            output.append(f"- {r.get('severity', 'MEDIUM').upper()}: {r['risk']}")
            if r.get("mitigation"):
                output.append(f"  Mitigation: {r['mitigation']}")

    return "\n".join(output)


def _format_attempt(build_errors, critique_history):
    if not build_errors and not critique_history:
        return "(no prior attempts — escalated before code generation)"

    output = []
    if build_errors:
        output.append("Build errors remaining after fix attempts:")
        for err in build_errors[:10]:  # cap at 10
            output.append(f"- `{err['file']}:{err['line']}`: {err['message']}")
        if len(build_errors) > 10:
            output.append(f"- ... and {len(build_errors) - 10} more")

    if critique_history:
        output.append("\nReflection pass findings:")
        for pass_result in critique_history:
            output.append(f"\n**Pass {pass_result['pass']}**:")
            output.append(pass_result['critique'][:500])

    return "\n".join(output)


def _execution_guidance(plan, files):
    if not plan:
        return "Investigate the codebase, form a plan, then implement."

    order = plan.get("order", [])
    if order:
        return f"Implement the {len(order)} sub-problems in the listed order. "\
               f"Run tests after each sub-problem."
    return "Implement the proposed approach."


def _estimate_input_tokens(ctx, plan):
    """Rough token estimate for quota budgeting."""
    total = 1000  # base overhead
    if ctx:
        for f in ctx.get("files_read", []):
            total += f.get("lines", 100) * 8  # ~8 tokens per line
        for d in ctx.get("retrieved_docs", []):
            total += 500
    if plan:
        total += 1000
    return total


async def notify_user(title, body, level="info"):
    """Send notification via OpenClaw's messaging channels."""
    # OpenClaw notifies via WhatsApp, Telegram, Discord based on user config
    await openclaw.notify(title=title, body=body, level=level)
    # Also write to a dashboard file for status checking
    status_file = Path(".openclaw-skills/last_notification.json")
    status_file.write_text(json.dumps({
        "title": title, "body": body, "timestamp": datetime.now().isoformat()
    }))

Integration with other skills

  • Invoked by coding-orchestrator at multiple points (hard gate, after

failed build, after failed reflection)

  • Reads context from task.scratchpad populated by earlier steps
  • Writes to project's .openclaw-skills/handoffs/ directory
  • Adds entry to global handoff log at ~/.openclaw-skills/handoff-log.sqlite

Handoff log for analytics

Every handoff gets logged to SQLite:

CREATE TABLE handoff_log (
    id INTEGER PRIMARY KEY,
    timestamp TEXT NOT NULL,
    task_slug TEXT NOT NULL,
    reason TEXT NOT NULL,
    project_root TEXT,
    estimated_tokens INTEGER,
    user_ran_claude BOOLEAN,  -- updated when user runs claude
    claude_succeeded BOOLEAN,  -- updated manually or via claude output log
    notes TEXT
);

Query this log periodically to understand your escalation patterns:

  • Which reasons are most common? (if "build_failed" dominates, improve

your iOS system prompt or RAG)

  • How often do handoffs actually get run? (if low, your escalation threshold

is too aggressive)

  • How often does Claude Code succeed? (if it fails too, the task genuinely

needs architectural input from you)

What handoff does NOT do

  • Does not execute Claude Code
  • Does not read your Claude Max quota
  • Does not cache or share handoffs between users
  • Does not modify source code (only writes to .openclaw-skills/handoffs/)

These are all explicit user actions. The skill's job is to prepare the best

possible brief and then step aside.

版本历史

共 1 个版本

  • v1.0.0 当前
    2026-05-07 09:59 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

Route Specialist

stephenlthorn
Classifies tasks by domain using deterministic and LLM methods, then routes to specialized prompts with tuned models and
★ 0 📥 412

Rag Retrieve

stephenlthorn
在 TiDB 上执行域和版本感知的混合向量+BM25+元数据检索,支持多查询扩展及可选的多跳检索链。
★ 0 📥 409

Decompose Plan

stephenlthorn
Forces M2.7 to produce an explicit structured plan before writing code. This makes Tree-of-Thought reasoning explicit in
★ 0 📥 432