`ClaudeTranslator` implementa el trait `IntentTranslator` igual que el `MockTranslator`, así que el `Orchestrator` no cambia: se traduce una intención NL a un `.swm` válido vía `/v1/messages`. Diseño: - Síncrono (ureq + rustls), consistente con `AgentClient`. Tokio entraría sólo si el resto del crate lo pidiera. - Trait `HttpClient` inyectable ⇒ tests sin red contra una fake que captura el request y devuelve un body pre-armado. - Modelo por defecto: `claude-opus-4-8` (Opus 4.8, el más capaz al día de hoy). Adaptive thinking + `effort=high` por defecto. Override por env (`HAMMER_LLM_MODEL`, `HAMMER_LLM_EFFORT`, `HAMMER_LLM_BASE_URL`). - System prompt documenta el shape exacto del `.swm` (4 variantes de mutación) y obliga JSON puro. Parseamos con `serde_json::from_str:: <Swm>` + `verify_schema()` como gate adicional. Manejamos `refusal`, error envelopes de la API y code-fence markdown. Activación: - Sin feature: el módulo declara los tipos pero `ClaudeTranslator::new` está bajo cfg. El binario compila sin red. - Con `--features llm-claude`: trae `ureq` con rustls, y la CLI activa `hammer ai --llm`. CLI: - `hammer ai` gana `--llm` (mutuamente excluyente con `--catalog`). Refactor de `run_ai` en helpers `run_with_mock_translator` / `run_with_llm_translator` para mantener legible el dispatch. - Sin la feature, `--llm` falla con un mensaje claro pidiendo recompilar. Tests (7 unit): happy path con verificación de URL/headers/body, unwrap de markdown, `refusal`, error envelope, SWM mal formado, verify_schema, helpers de strip_code_fence.
9.5 KiB
SDD 10 — Roadmap
Estrategia: validar la capa AI-nativa sobre Alpine antes de la distro propia
La innovación de hammer no es la distro — es el substrato AI-nativo (build determinista +
mutable + diario + .swm). Construir bootstrap + init + userland de cero antes de validar esa
capa sería arriesgar meses contra una hipótesis no probada. Por eso:
Fase 0–6: montamos
hammersobre Alpine (musl + busybox + FHS mutable, ya cocidos). Validamos el bucle completo en semanas. Track posterior: una vez probado, bajamos a distro propia (init propio, bootstrap propio, userland propio) reusando todo el tooling sin retrabajo.
Ver ADR 0002.
Fases (sobre Alpine)
Fase 0 — Laboratorio de build ✅
hammer-core: tiposRecipe,ArtifactHash,Store(BLAKE3, layout/store).hammer-build: sandbox conbubblewrap+zig cc→ binario musl estático.- CAS: hashing de entrada, caché por hash, sellado en el store.
hammer build <recipe.toml>→ imprime el hash del artefacto.- Fuente git y tarball (sha256 fijado), patches opcionales.
- Heurística de build system: autotools / cmake / meson / make plano.
- Caché persistente de zig entre builds (~30s/build ahorrados).
Fase 1 — Hidratación ✅
hydrate(hash, target, mode): hardlink a FHS,patchelfpara el caso dinámico.hammer hydrate <hash> --into /.
🎯 Primer entregable (Fase 0 + 1) ✅
GNU grep 3.12 compilado estático desde el tarball upstream, sellado en el store
(b3:b81e893e…), hidratado por hardlink a /usr/bin/grep y ejecutado dentro del rootfs
Alpine vía bwrap — confirma el corazón "fábrica funcional → FHS mutable" sobre el
substrato Alpine. La VM/LXC dedicada queda como ejercicio de empaque, no como
pre-requisito de validación.
Fase 2 — Overlay de experimentación ✅
- Crate
hammer-overlay:try/commit/discard/statussobreoverlayfs. - Manifiesto persistente en
<state_root>/<id>/state.json;statusenumera. commitpromociona archivos y procesa whiteouts (char dev 0/0) como remove.- Subcomandos del CLI:
hammer try [targets…],commit <id>,discard <id>,status. - Tests E2E con bwrap+user-ns, gateados en
HAMMER_OVERLAY_TESTS=1(kernel-dependiente). - Hook al diario en
commit(Fase 3 lo añadió víacommit_with_journal). - Anidación real de overlays (un solo overlay activo por target hoy).
Fase 3 — Diario de mutaciones ▶ en progreso
- Crate
hammer-journal:MutationEvent,Source(HammerHydrate/HammerCommit/External), append/read/tail/follow JSON-líneas. RFC 3339 sin chrono. hammerdconfanotify(FAN_CLOSE_WRITE) sobre/bin,/sbin,/usr/bin,/usr/sbin,/lib,/usr/lib,/etc. Filtra eventos bajo overlays activos. Fallback graceful sin CAP_SYS_ADMIN.hammer journal [--tail N] [--follow] [--format pretty|json].hammer commitregistra cada archivo promocionado víacommit_with_journal.- Refinar op del watcher (Create vs Replace vs Edit con FAN_REPORT_DFID_NAME).
- Hash del contenido tras la mutación (
content_hash) y de-dup idempotente.
Fase 4 — Formato y flujo .swm ▶ en progreso
Swmde/serialización YAML estable (roundtrip).verify_schema(estructura + invariantes por mutación) yverify_base(distro_version + pins) conBaseRef/BaseCompat.apply_config_edit(hunks-/+con búsqueda exacta, no-fuzz) yapply_file_drop(base64 inline + verificación BLAKE3 vscontent_hash).- Bridge
Mutation::SourcePatch→Recipe+build+ hidratación. - CLI:
hammer apply [--prefix DIR] [--base-ref base.json],hammer swm-verify,hammer export --journal DIR > out.swm. patch_url/content_urlremotos (hoy sólo inline).- Provenance en
export: mapa artefacto→receta para emitirsource_patchen vez defile_drop. Hoy se emiten file_drops concontent_b64, lo que reproduce byte-a-byte pero pierde la receta original. - Firma
signature(ed25519) yTrustStorelocal. - Hecho cuando: exportas un cambio, lo aplicas en otra máquina y reproduce idéntico.
✅ Demostrado en
crates/hammer-cli/tests/swm_roundtrip.rsparaconfig_edit+file_drop.source_patchreusa el camino de Fase 0/1 (gated enHAMMER_NETWORK_TESTS).
Fase 5 — Bus de agente ▶ en progreso
/run/init.control(FIFO humano):mkfifo, reader que loguea cada línea, yhammer ctl <line>que escribe al FIFO con error claro si no existe./run/agent.sock(JSON-líneas) con handshakeHello/Welcome, auth viaSO_PEERCREDyCapsPolicypor UID.- Comandos
Compile/Inject/Query/Init; eventosWelcome/BuildReady/BuildFailed/Injected/QueryResult/InitAck/Modified/Crashed/Error. - EventBus in-process: el watcher publica
Modifiedy todas las conexiones lo reciben. - Subsistemas independientes en
hammerd::main(FIFO/watcher/bus en threads); si uno falla en init, los demás siguen. - Política expresiva: hoy es código (
default_policy), pendiente leerla de/etc/hammer/agent-caps.toml. CRASHEDreal (requiere supervisión de servicios, que llega con el init propio del track posterior).BuildFailed.log_tailcon cola real del lab (hoy esNone; elreasonviene delErrorde Rust pero no se conserva el log textual del sandbox).- Hecho cuando: un cliente externo dispara un build y recibe el evento de fin por el
socket. ✅ Camino implementado (
Compile→BuildReady/BuildFailed) y la maquinaria alrededor cubierta porcrates/hammerd/tests/bus_e2e.rs: handshake con peer creds, gatingno_cap,Query,Modifiedfan-out,Init→FIFO. UnCompilereal reusa el camino de Fase 0/1 (gated enHAMMER_NETWORK_TESTS).
Fase 6 — Integración de la IA ▶ en progreso
- Crate
hammer-agentcon tres piezas: -AgentClient: cliente síncrono del bus (handshake,compile/inject/query/initbloqueantes, drenado de eventos asíncronos). -IntentTranslator(trait) +MockTranslatorcargado desde unIntentCatalogYAML (intent →.swmpre-armado). La integración con un LLM real se enchufa detrás del mismo trait sin tocar el bucle. -Orchestratorconrun(intent) → Proposal: plan → schema → base → try (overlay o prefix) → apply (mutaciones puras + source_patch vía bus opcional) → verify → propose. - CLI:
hammer ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK]. - Tipos del protocolo del bus movidos a
hammer-core::protopara quehammerdyhammer-agentlos compartan. - Tests:
- 10 unit en
hammer-agent(translator + catalog + orchestrator). - 3 e2e del bucle agéntico (prefix tmp → archivos esperados en disco). - 1 e2e del cliente contra un stub del bus (handshake + Compile→BuildReady + Modified asíncrono). - Traductor LLM real (Claude API u otro), opcional vía feature flag o crate aparte.
ClaudeTranslatordetrás de la featurellm-claude, conureq + rustls. Modelo por defectoclaude-opus-4-8, adaptive thinking +effort=high. TraitHttpClientinyectable para tests sin red. CLI:hammer ai --llm(mutuamente excluyente con--catalog). Sin la feature, el flag falla con mensaje claro. - Lenguaje de consulta del sistema (SDD 08 §6) para que la IA refiera servicios y
archivos sin rutas frágiles. Forma
kind:value(bin,file,pin,service,depends), evaluable local (hammer query <expr>) y remoto vía bus (Command::Query{what:"expr"}). Parser ELF64 mínimo para extraerDT_NEEDED. - Bucle de auto-reparación (cliente reacciona a
Crashedcon un nuevo plan).Orchestrator::run_with_repair(intent, &RepairPolicy)drena async events del bus tras cada apply; si llegaCrashed, formatea una nueva intención y reentra hastamax_attempts. ElProposalfinal lleva elrepair_chaincompleto para que el humano vea la evolución antes de hacer commit. CLI:hammer ai --repair-max-attempts. - Hecho cuando: una intención en lenguaje natural produce un cambio probado en overlay,
presentado para
commithumano. ✅ Demostrado porhammer aiconMockTranslator: intent →.swm→ mutaciones aplicadas →Proposalconoverlay_id+ checks. El humano decidecommit/discard. El upgrade a LLM real reusa todo el bucle.
Track posterior — distro propia
- Reemplazar el init de Alpine por tu init (bus por pipes nativo).
- Bootstrap from-scratch con
zig/musl-cross-make(Stage 0/1/2 → imagen destino). - Userland propio (busybox/toybox a elección),
/etc/service/*propios. - Decidir particionado/montaje de
/storey/var/lib/hammer. - Log de transparencia para compartir en comunidad (SDD 09 §4).
- Mini-lenguaje de consulta del sistema para la IA (SDD 08 §6).
Estado actual
- ✅ Repo y workspace Rust inicializados.
- ✅ SDDs y ADRs redactados.
- ✅ Esqueletos de crates que compilan (
hammer buildstub). - ⏭️ Siguiente: implementar el sandbox de build real (Fase 0) y montar la VM/LXC Alpine.
Notas de entorno
- Desarrollo principal: laptop del autor.
- Host actual: Proxmox (
gioser.net). La VM/LXC Alpine de pruebas vivirá aquí o en la laptop. - Repo:
https://gitea.gioser.net/sergio/hammer.