- proto: mover hammerd::proto a hammer-core::proto para que hammerd y hammer-agent
compartan los tipos del bus sin duplicar.
- hammer-agent (crate nuevo):
* client: AgentClient síncrono. Handshake hello/welcome; compile/inject/query/init
bloqueantes con timeout; reader thread interno demultiplexa async events (Modified/
Crashed) en una cola que el caller drena vía drain_async/next_async.
* translator: trait IntentTranslator + MockTranslator (HashMap<intent, Swm>) +
IntentCatalog YAML (swm_inline o swm_path). El traductor LLM real se enchufa
detrás del mismo trait sin cambios al orquestador.
* orchestrator: Orchestrator::run(intent) -> Proposal con plan -> schema -> base ->
try (overlay|prefix) -> apply (config_edit/file_drop con hammer-core::apply,
source_patch via bus opcional) -> verify (spot-checks) -> propose. Devuelve
overlay_id (para `hammer commit`) o prefix usado.
- hammer-cli: subcomando `hammer ai <intent> --catalog F [--prefix DIR --base-ref F
--bus SOCK --state-root DIR]`. Imprime el Proposal y el siguiente paso humano.
- Tests:
* 10 unit (translator + catalog + orchestrator).
* 3 e2e del bucle agéntico (intent -> archivos esperados bajo un prefix tmp).
* 1 e2e del cliente contra un stub bus (handshake + Compile -> BuildReady +
Modified asíncrono), sin depender de hammerd ni del lab.
- Docs: SDD 08 actualizado con el API del crate; roadmap marca lo cerrado y lo
pendiente (LLM real, lenguaje de consulta, bucle de auto-reparación con Crashed).
6.9 KiB
SDD 08 — Integración de la IA
La IA es un artesano hiperveloz en el taller; el humano es el dueño que decide si el mueble entra a la casa o se va a la basura. Esta sección define el bucle agéntico, sus garantías de seguridad, y cómo una intención en lenguaje natural se vuelve un cambio real.
1. La terminal como runtime de la IA
La IA no es un chat en una ventana que te dice qué teclear. Opera directamente sobre el sistema a través del bus de agente (SDD 07) y de un overlay (SDD 04). Su entorno de trabajo es el sistema real, con red de seguridad.
2. El bucle agéntico
intención (NL)
│ "que grep ignore binarios por defecto y use regex Perl,
│ y que la red use IP estática 192.168.1.100"
▼
┌─────────────┐
│ PLAN │ la IA traduce la intención a un .swm: qué herramienta,
│ │ qué patch de fuente, qué flags, qué config editar
└──────┬──────┘
▼
┌─────────────┐
│ BUILD │ COMPILE por el bus → el lab clona repo@commit, aplica patch,
│ (lab) │ compila estático con zig cc → artifact_hash
└──────┬──────┘
▼
┌─────────────┐
│ TRY │ hammer try → overlay temporal; INJECT del artefacto y de las
│ (overlay) │ ediciones de config en la capa. El sistema real intacto.
└──────┬──────┘
▼
┌─────────────┐
│ VERIFY │ corre el test harness; escucha BUILD_FAILED / CRASHED por el bus.
│ │ si falla → corrige fuente → recompila (vuelve a BUILD).
└──────┬──────┘
▼
┌─────────────┐
│ PROPOSE │ la IA avisa: "listo, probé esto, aquí el diff y el resultado".
│ │ presenta el .swm y la evidencia.
└──────┬──────┘
▼
┌─────────────┐
│ HUMANO │ tú decides: hammer commit (promueve + diario) o hammer discard.
│ decide │ La IA NUNCA promueve al FHS real por su cuenta (ver §4).
└─────────────┘
3. Por qué esta arquitectura se presta (y otras no)
- Determinismo del lab → la IA puede recompilar y volver atrás sin dramas; el resultado es predecible.
- Texto plano + binarios atómicos → la IA lee/escribe archivos e inspecciona binarios con
readelf/objdump; no parsea bases de datos opacas. - musl legible → el código C de la libc base es comprensible en el contexto de un LLM (glibc es un laberinto de macros).
- Enlazado estático → una herramienta mutada corre sin romper enlaces de otros programas.
- Mutabilidad + overlay → agencia real con red de seguridad; sin rollbacks pesados.
En distros inmutables/declarativas, la IA tendría que lidiar con generadores de config y
políticas read-only; en tradicionales, no tendría provenance. hammer mantiene las tuberías
expuestas y de baja entropía, que es justo lo que un agente necesita.
4. Garantías de seguridad (no negociables)
- La IA trabaja en overlay por defecto. Sin capacidad
inject-real, no puede tocar el FHS base. El flujo es siempre overlay →commithumano. - Capacidades mínimas y explícitas. El humano concede
query/compile/injectpor separado vía la política del bus (SDD 07). - Toda acción es reversible y registrada.
discardrevierte el overlay; elcommitentra al diario firmado (SDD 05). - Recetas, no binarios. La IA produce
.swm(fuente + flags + config), no binarios opacos. Lo que comparte es verificable por terceros (SDD 06). - El humano tiene la última palabra. Ningún cambio se vuelve permanente sin
commit.
5. De la intención al .swm: el rol del modelo
El traductor intención→.swm se conecta detrás del trait IntentTranslator del crate
hammer-agent. Contrato:
- Entrada: intención en NL +
SystemContext(lo que el orquestador recolecta porQUERY:BaseReflocal, extras JSON). - Salida: un
Swmválido (SDD 06).
Fase 6 entrega un MockTranslator que resuelve intentos contra un IntentCatalog YAML
(intent exacto → .swm inline o swm_path relativo). Eso permite probar el bucle sin LLM:
el humano que descubrió la intención con un modelo real congela el resultado y los CI lo
reproducen byte-a-byte. El traductor LLM (Claude API u otro) implementa el mismo trait y se
enchufa sin tocar el Orchestrator.
El modelo nunca ejecuta nada directamente: emite comandos por el bus, que hammerd
valida contra las capacidades concedidas, y el Orchestrator traduce las mutaciones a
hammer-core::apply o a Compile por el bus.
6. Lenguaje de consulta/transformación (visión, Fase posterior)
Para que la IA refiera partes del sistema sin rutas absolutas frágiles, un mini-lenguaje de consulta sobre el grafo del sistema:
find /service/web -where "depends_on(libssl)" -> replace_with my_tls
El daemon traduce esa consulta estructurada a acciones reales del bus. La terminal sigue siendo el lugar de validación final. Esto es visión, no Fase 0 — se diseñará cuando el bucle básico esté probado.
7. Interfaz
El crate hammer-agent (Fase 6) expone:
// hammer_agent::client
pub struct AgentClient { pub welcome: Welcome, /* … */ }
impl AgentClient {
pub fn connect(sock: &Path) -> Result<Self>;
pub fn compile(&mut self, recipe: RecipeInline, timeout: Duration) -> Result<String>; // artifact
pub fn inject(&mut self, artifact: &str, target: &str, overlay: Option<&str>, t: Duration) -> Result<usize>;
pub fn query_file(&mut self, path: &str, timeout: Duration) -> Result<serde_json::Value>;
pub fn init(&mut self, cmd: &str, timeout: Duration) -> Result<()>;
pub fn drain_async(&self) -> Vec<Event>;
pub fn next_async(&self, timeout: Duration) -> Result<Event>;
}
// hammer_agent::translator
pub trait IntentTranslator { fn translate(&self, intent: &str, ctx: &SystemContext) -> Result<Swm, TranslateError>; }
pub struct MockTranslator { /* HashMap<intent, Swm> */ }
pub struct IntentCatalog { pub intents: Vec<CatalogEntry> } // YAML loader
// hammer_agent::orchestrator
pub struct Orchestrator<T: IntentTranslator> { /* … */ }
impl<T> Orchestrator<T> {
pub fn new(t: T, base: BaseRef, apply: ApplyTarget, compile: CompileMode) -> Self;
pub fn run(&self, intent: &str) -> Result<Proposal>;
}
Los tipos del protocolo (Command/Event/Cap/Peer/RecipeInline) viven en
hammer-core::proto para ser compartidos por hammerd, hammer-agent y cualquier cliente
externo escrito en Rust.