Un substrato simbólico
para agentes que compongan
¿Qué es un substrato?
Un substrato es la capa por debajo de los agentes y los workflows donde todo lo que producen queda tipado y consultable. No es una base de datos. No es un knowledge base con embeddings. Es un grafo donde cada decisión, cada output, cada juicio humano y cada inferencia que un agente afirma se deposita como un nodo con relaciones explícitas a los demás.
La diferencia con un asistente conversacional es directa: el asistente es amnésico, el substrato es coherente con su propio pasado. Cada respuesta nueva puede consultar todo lo que se decidió antes y razonar contra eso. La aprobación que diste ayer es contexto disponible para el agente que arranca mañana.
(subject, predicate, object), no nubes de números. Cada afirmación es defendible porque tiene estructura y provenance.↓ (nada queda registrado)
Workflow B arranca sin contexto
→ repite preguntas, contradice decisiones previas
- Cada conversación empieza de cero, incluso entre agentes del mismo equipo
- El usuario hace de memoria del sistema (le pegás contexto en cada prompt)
- Las decisiones se evaporan: nadie sabe quién aprobó qué cosa, ni por qué
- Imposible auditar afirmaciones: el modelo dijo X, nadie sabe de dónde lo sacó
↓ (grafo crece)
Workflow B lee Claims de A antes de empezar
→ razona contra decisiones previas, no contra cero
- Cada decisión humana se materializa como Claim consultable por workflows futuros
- El equipo de agentes acumula memoria estructurada — no solo logs, conocimiento tipado
- Trazabilidad nativa: cualquier afirmación tiene provenance (qué Trace la produjo, qué humano la validó)
- Network effects: cada workflow extra agrega contexto a todos los siguientes
Empezó con un reframe
que nos sacó del consumer
Las herramientas se desgastan; los substratos compuestan. Lo que el speaker nombraba como control plane no era un producto sino una capa por debajo de los agentes donde cada decisión, claim, intent y artifact queda trazado y disponible para futuras inferencias. Eso es lo que hace que un equipo de agentes mejore con el uso.
El punto era simple y duro a la vez: si construyo el consumer Agent Squad sin substrato, repito el patrón de cada SaaS de IA — los usuarios producen pixels pero el sistema no aprende. Si construyo el substrato primero, cada workflow que después monto encima compone valor en el grafo. Agent Squad consumer pasa a ser la primera ontology overlay, no el producto final.
Decidimos no construir un Photoshop. El producto es el substrato. La app que Miles está terminando es la primera demostración pública de lo que ese substrato permite. La idea de fondo: { Intent, Plan, Trace, Claim, Artifact } son los primitives que cualquier workflow agentic puede compilar a una DAG ejecutable, y cualquier decisión humana se vuelve contexto tipado para la próxima.
Siete decisiones estratégicas
tomadas con velocidad
Diez primitives y dos reglas duras
knowledge_access.requires_vectorDoce decisiones de tech
tomadas en una hora
| Capa | Tech | Por qué |
|---|---|---|
| Spec | JSON Schema + Zod + TypeScript | Spec abierta, no DSL propio. Cualquiera con JSON Schema válida outputs sin tocar el motor. |
| Motor | Inngest OSS self-hosted | Durable workflows con step.run, step.waitForEvent, step.sendEvent. Reemplaza Temporal/Trigger.dev con menos overhead. |
| Substrate DB | Postgres 16 + pgvector + pgvectorscale | DiskANN para 3072-dim embeddings (HNSW topa a 2000). Dedicada en Hetzner, NO en InsForge. |
| Workspace DB | InsForge | Sigue siendo para la app consumer (auth, user data, outputs feed). Separado del substrato por diseño. |
| LLM | Vercel AI SDK 6.0 + @ai-sdk/anthropic | Sonnet 4.5 para composers y scorers; Opus 4.7 para arquitectura; Haiku 4.5 para tactical futuro. |
| Embedding | OpenAI text-embedding-3-large (3072d) | Mejor precision/recall para retrieval semántico. Backup a R2 desde día uno por si migramos vector DB. |
| Reranker | Cohere Rerank v3.5 | Capa de relevancia sobre top-K. Activable cuando el grafo crezca. Hoy aún no lo necesita. |
| Evaluators | Propios en packages/substrate-spec | No LangSmith. Evaluators son código testeado que vive con el spec, no servicio externo. |
| Observability | Langfuse OSS v3 self-hosted | Web + worker + ClickHouse + MinIO + Redis + Postgres. Tracing por trace_id del substrato. |
| API runtime | Hono sobre Bun 1.3 | Cold-start nulo, footprint mínimo. Una sola Hono app expone /api/intents, /api/approvals, /api/health. |
| API contract | tRPC interno · REST + OpenAPI 3.1 externo | Internamente tipos compartidos. Externamente OpenAPI para que cualquier cliente implemente. |
| Compute | Apps en Vercel · Motor + DB + Obs en Hetzner | Hetzner 4CPU/8GB ya pagado. El motor stateful vive donde tenemos disco; las apps stateless siguen en Vercel. |
Una caja Hetzner,
todos los servicios encendidos
El backup corre nightly a las 3 AM vía cron sobre ~/substrate-infra/scripts/backup-substrate-db.sh. Genera dump a ~/backups/substrate/ y, si existe el remote rclone r2-substrate:, también empuja a Cloudflare R2. Las migraciones SQL del substrato viven en agent-squad-app/db/substrate/migrations/0001_init.sql: 15 tablas, índices DiskANN sobre vectores de Claims y Artifacts, partitioning por fecha en traces y step_executions.
Una librería de capabilities
de la que cualquier Plan tira
- trace.query@1.0.0
- artifact.list_recent@1.0.0
- artifact.publish@1.0.0
- claim.recall_decisions@1.0.0
- claim.recall_voice@1.0.0
- text.compose_narrative@2.0.0
- text.compose_brief@1.0.0
- evaluator.run@1.0.0
- human_gate.approve@1.0.0
- prospect.search@1.0.0
- prospect.score_batch@1.0.0
- url.fetch_transcript@1.0.0
- video.script_draft@1.0.0
- video.compose@1.0.0
- voice.tts@1.0.0
Cuatro DAGs productivos
cada uno con su propio agente
El gate durable
donde la persona es autoridad final
step.waitForEvent pausa el Plan por hasta 24 horas. Cuando llega el evento, valida que el artifact_id matchea y resume. Si no llega, aplica fallback. Cada decisión humana se materializa como Claim tipado y consultable.Bug crítico aprendido: el match en step.waitForEvent debe usar async.data.X, no event.data.X. event referencia el trigger original (plan.compiled) — usarlo ahí evalúa al tiempo de la suspension, no del evento futuro, y matchea con null. async.data referencia el evento que se está esperando. Sin esto, todos los gates se quedan colgados indefinidamente.
Lo que esto desbloquea: el humano puede aprobar/rechazar/agregar comment, y la decisión queda como Claim tipado consultable por futuros workflows. La próxima vez que armemos un brief sobre el mismo topic, claim.recall_decisions ya tiene "este angle ganó" como contexto. Esto es el efecto compuesto del substrato — no es retrieval, es construcción de memoria estructurada.
Cuatro templates activados
con Sonnet 4.5 verdadero
Lo no trivial: Sonnet razonó sobre el propio backlog del workspace. El narrative de standup-digest mencionó textualmente cuántos digests anteriores quedaron queued y awaiting_human, porque trace.query + artifact.list_recent + claim.recall le feedearon esos datos. La cadena retrieval → composer está validada end-to-end con datos reales del substrato.
Aprobamos el brief
y nacieron cinco Claims
"Substrato vs RAG"
Este es el momento donde el substrato deja de ser teoría y empieza a comportarse como capital. La próxima vez que armemos un brief sobre el mismo topic, claim.recall_decisions va a recuperar "Hook 2 ganó sobre los otros 3" como contexto del composer. Sonnet no necesita recordarlo — el grafo se lo entrega. Eso es lo que distingue un substrato de un RAG: no busca documentos parecidos, sirve afirmaciones tipadas con provenance.
Después de una sola sesión, el workspace de Roberto tiene cuatro PlanTemplates productivos, dieciséis Operations atómicas, sesenta y pico de Claims acumulados como contexto compuesto, y un costo real de operación de aproximadamente dos centavos por workflow ejecutado. La pieza importante no es eso. La pieza importante es que cada uno de esos Claims va a estar acá mañana, y dentro de un año, alimentando los próximos workflows que armemos.