El problema de los workflows multi-IDE no es la calidad del modelo, es que cada sesión empieza desde cero.
Tienes tres asistentes de IA en tu flujo de trabajo. Kiro para triage y razonamiento sobre el codebase, Cursor para implementación rápida, y Claude Code para sesiones largas de refactor donde necesitas un modelo con ventana de contexto grande. Cada uno hace bien su parte.
El problema está en el medio.
Kiro encuentra el bug, analiza el stack trace, entiende el contexto. Cambias a Cursor o a Claude Code, pegas el stack trace de nuevo, explicas el contexto de nuevo, y tres mensajes después el segundo asistente tiene suficiente para empezar. Duplicaste el trabajo, quemaste tokens, y si el proyecto tiene código propietario lo enviaste a APIs cloud distintas sin necesidad.
Este artículo explica qué patrón resuelve eso y cómo implementarlo, ya sea construyendo tu propio harness desde cero o usando uno existente como punto de partida.
Repo de referencia:
Sin servidor central, sin base de datos, sin cuenta externa. Solo archivos en tu repo.
Veamos cada pieza.
Cómo funciona por dentro: las tres piezas
1. Colección de contexto (collect.py)
¿Qué incluir en el bundle? Si metes todo el codebase, es ruidoso. Si metes poco, le falta contexto al asistente. La solución: configuración por proyecto.
# .context-harness/config.json
{
"orchestrator": "mock",
"context_sources": {
"error_logs": ["logs/error.log", "logs/app.log"],
"source_files": ["src/**/*.py", "src/**/*.ts"],
"recent_diffs": true,
"max_files": 10
}
}
El colector lee los archivos que matchean los globs, el diff reciente y los logs:
# src/shared/collect.py
def collect_context(config: dict) -> dict:
context = {}
for log_path in config.get("error_logs", []):
if os.path.exists(log_path):
context["errors"] = read_recent_lines(log_path, n=50)
if config.get("recent_diffs", True):
context["diff"] = run_command("git diff HEAD~1 --stat")
context["files"] = collect_files_by_glob(config.get("source_files", []))
return context
Python leyendo archivos y corriendo git diff. Vive en tu repo y lo ajustas cuando el proyecto lo necesite.
2. Orquestación (orchestrator.py)
El orquestador toma el contexto crudo y lo reduce a lo relevante para el prompt. Tiene tres implementaciones con la misma interfaz:
# src/shared/orchestrator.py
class Orchestrator:
def orchestrate(self, prompt: str, context: dict) -> dict:
raise NotImplementedError
class MockOrchestrator(Orchestrator):
"""Reglas determinísticas. Zero dependencias. Instantáneo."""
def orchestrate(self, prompt: str, context: dict) -> dict:
agent = self._route_by_keywords(prompt)
bundle = self._filter_context(prompt, context)
return {"agent": agent, "bundle": bundle, "latency_ms": 0}
class OllamaOrchestrator(Orchestrator):
"""LLM local. El código nunca sale de tu máquina."""
def orchestrate(self, prompt: str, context: dict) -> dict:
response = requests.post("http://localhost:11434/api/generate", json={
"model": "llama3.2",
"prompt": build_orchestration_prompt(prompt, context),
"stream": False
})
return parse_orchestration_response(response.json())
class BedrockOrchestrator(Orchestrator):
"""Cloud. Para equipos que quieren API compartida."""
def orchestrate(self, prompt: str, context: dict) -> dict:
client = boto3.client("bedrock-runtime")
response = client.invoke_model(
modelId="amazon.nova-lite-v1:0",
body=json.dumps({"prompt": build_orchestration_prompt(prompt, context)})
)
return parse_orchestration_response(response)
Lo importante: cambias el orquestador en config.json y el resto del sistema no se entera. El mismo patrón que en ORMs o drivers de bases de datos.
def get_orchestrator(config: dict) -> Orchestrator:
mode = config.get("orchestrator", "mock")
if mode == "ollama":
return OllamaOrchestrator()
elif mode == "bedrock":
return BedrockOrchestrator()
return MockOrchestrator()
3. Los hooks: el punto de integración con los IDEs
Los hooks son la pieza que conecta el harness con Kiro, Cursor y Claude Code. Cuando escribes /harness en el chat del IDE, el hook intercepta el prompt, corre la orquestación, y el asistente recibe el contexto reducido antes de procesar tu mensaje.
Kiro usa un hook de tipo promptSubmit:
// .kiro/hooks/context-harness.json
{
"name": "context-harness-orchestrate",
"version": "1.0.0",
"when": {
"type": "promptSubmit"
},
"then": {
"type": "runCommand",
"command": "bash integrations/hooks/kiro-orchestrate.sh"
}
}
El script del hook captura el prompt, corre harness-cli.py orchestrate, y escribe el bundle orquestado. Kiro lo recibe como contexto adicional al prompt original vía stdout.
Cursor usa un hook de sessionStart combinado con una rule:
// .cursor/hooks.json
{
"sessionStart": {
"command": "python3 scripts/harness-cli.py init --assistant cursor"
}
}
// .cursor/rules/context-harness.mdc
Antes de responder cualquier prompt que contenga `/harness`,
lee el archivo `.context-harness/active-bundle.md`.
Ese bundle contiene el contexto reducido y el handoff de la sesión anterior.
Cursor no puede inyectar texto en el prompt directamente como lo hace Kiro, esa es una limitación real de la API de Cursor. El workaround es file-based: el bundle se escribe a disco y la rule le dice al agente que lo lea. Funciona, aunque es un paso más indirecto.
Claude Code usa un archivo CLAUDE.md en la raíz del repo como steering document, combinado con un comando slash personalizado:
<!-- CLAUDE.md — Claude Code lo lee automáticamente al inicio de cada sesión -->
## Context Harness
Este proyecto usa Context Harness para compartir contexto entre IDEs.
Antes de responder cualquier prompt que contenga `/harness`,
lee `.context-harness/active-bundle.md`.
Ese archivo contiene el contexto reducido y el handoff de la sesión anterior.
# .claude/commands/harness.md — define el comando /harness en Claude Code
Corre: python3 scripts/harness-cli.py orchestrate --assistant claude-code --prompt "$ARGUMENTS"
Luego lee `.context-harness/active-bundle.md` y úsalo como contexto para tu respuesta.
Claude Code tiene una ventaja sobre Cursor en este aspecto: CLAUDE.md se carga automáticamente en cada sesión sin configuración adicional, así que el harness se convierte en parte del contexto base del proyecto desde el primer mensaje.
Ahora que sabes cómo cada IDE consume el bundle, hablemos de cuándo usar cada orquestador.
Cuándo usar cada orquestador
| Orquestador | Cómo funciona | Cuándo usarlo |
|---|---|---|
| mock | Reglas de keywords + filtrado por globs | Empezar, CI, proyectos sin reqs de privacidad |
| ollama | LLM en localhost, sin salida de red | Codebases propietarios |
| bedrock | LLM en cloud vía AWS | Equipos con API compartida |
Mock
Reducción de ~44-67% por filtrado de globs, sin LLM, sin latencia. Para muchos proyectos es suficiente.
Ollama
ollama pull llama3.2
# config.json → "orchestrator": "ollama"
python3 scripts/harness-cli.py doctor
El prompt de orquestación nunca sale de tu máquina. La reducción sube a ~96% a cambio de ~7 segundos de latencia. Y si Ollama no está corriendo, cae a mock sin romper nada:
# Fallback automático en orchestrator.py
try:
return self._call_ollama(prompt, context)
except requests.exceptions.ConnectionError:
return MockOrchestrator().orchestrate(prompt, context)
Bedrock
CDK deploy con Nova Lite como modelo por defecto. Útil cuando el equipo necesita una API compartida y la latencia cloud (~1-2s) es aceptable.
El handoff en la práctica: Kiro → Cursor → Claude Code
"Kiro triage. Cursor o Claude Code implementa. El harness recuerda, localmente si quieres."
¿Cómo manejas el handoff de contexto entre IDEs hoy? ¿Pegas el prompt completo, usas archivos compartidos, o simplemente empiezas de cero cada vez? Los comentarios están abiertos.
SOCIAL SHARE CARD GENERATOR