01
El agente propone. Un humano aprueba.
Duct puede preparar un cambio en tus sistemas en vivo: pausar una campaña, añadir palabras clave negativas, marcar un evento clave de GA4. Te muestra una vista previa y una verificación de seguridad. Luego se detiene.
No hay una herramienta de aprobación o aplicación orientada al agente. Ni una deshabilitada, ni una oculta tras un flag. No existe en el registro de herramientas que ve el modelo, por lo que el modelo no puede llamarla, no se le puede convencer de llamarla, ni se le puede obligar a llamarla con una inyección de prompt. El bloqueo de lo destructivo es absoluto: ninguna configuración permite que una operación destructiva se autoaplique. Revertir es parte del contrato de cada ejecutor, no un añadido de última hora.
Mayor autonomía es una postura, no una lista de permitidos más amplia. Cambia la frecuencia con la que el agente te interrumpe. Nunca cambia lo que se puede aplicar sin un clic.
backend/service/execution/policy.py
02
Duct alquila un arnés de agente. No se casa con uno.
Los frameworks de agentes son la capa que más cambia y menos diferencia de este stack. Están en 0.x, publican versiones cada semana y todos han obligado a alguien a una migración con cambios incompatibles. Construir tu producto sobre uno es construir sobre cimientos alquilados.
Así que el código de dominio aquí no importa ningún framework de agente. Prompts, herramientas, esquemas, objetivos, puntuación y el contrato de artefacto son Python puro. Las importaciones del framework solo residen en ejecutores y enlazadores, y una prueba aplica el límite con una lista de permitidos explícita. Añadir un archivo a esa lista de permitidos es un acto deliberado, no la forma de arreglar una prueba fallida.
Deliberadamente no hay interfaz AgentHarness, y nunca debería haberla. Toma la intersección y pierdes subagentes, sistema de archivos, habilidades, granularidad HITL y compactación, que son justo la razón para alquilar un arnés. Toma la unión y estás escribiendo un framework.
backend/agents/core/ports/__init__.py
03
Escribe el adaptador con la segunda implementación, no con la primera.
Un puerto con una implementación es una suposición. Un puerto con dos es un hecho. Las abstracciones construidas antes de que exista el segundo caso codifican lo que el primer caso necesitó, y luego todo lo demás lucha contra esa forma.
Cada puerto en la capa de agente tiene dos adaptadores, excepto uno, y ese lo indica en su propio docstring y nombra la implementación que lo merecería. Dejar el hueco a la vista es lo que evita que se rellene en silencio con una suposición.
STYLE.md
04
La autorización es membresía, y una clave API no es un límite.
La clave API de la app se envía al navegador. Prueba que “esta solicitud provino de la app de Duct”. Nunca ha probado que “quien llama es dueño de esa fila”, y tratarla como si lo hiciera es la forma en que se filtran datos en un sistema multiinquilino.
Así que cada ruta que afecta a una fila con alcance de proyecto resuelve un usuario con sesión iniciada y comprueba la membresía. Un no miembro recibe 404, no 403, porque 403 le dice que la fila existe. Una prueba recorre cada ruta en el árbol y falla la compilación si alguna queda sin protección sin una razón por escrito.
backend/tests/test_route_auth_boundaries.py
05
Tu máquina, tus claves, tus datos.
Duct se ejecuta como una app de escritorio que incluye el backend como un sidecar local: SQLite en disco, solo loopback, claves de proveedor en el llavero del SO. Sin cuenta, sin servidor, nada sale de la máquina. O autoaloja el backend. O usa la versión alojada en getduct.ai, que ejecuta el mismo código que puedes leer.
Trae también tu propio modelo. Anthropic, OpenAI, Gemini o cualquier modelo de OpenRouter, asignado por tarea en tres niveles para que el razonamiento pesado y los resúmenes baratos no paguen el mismo precio. Una herramienta que lee todo tu negocio no debería decidir también quién es tu proveedor.
desktop/README.md
06
Los comentarios llevan el porqué. El diff ya muestra el qué.
Un comentario que repite la línea de abajo es peor que ningún comentario, porque enseña a los lectores a saltarse los comentarios. Los que vale la pena escribir llevan lo que el código no puede: la restricción, el contrato, el fallo que motivó la línea, la alternativa que fue rechazada y por qué.
Esto importa más, no menos, en una base de código escrita parcialmente por agentes. Un conjunto de cambios individualmente defendibles que juntos disuelven una arquitectura es la forma específica en que fallan las bases de código aceleradas por agentes: cada cambio se justifica a sí mismo y nada defiende el conjunto. El razonamiento que queda en el archivo es lo que permite a la siguiente persona, o al siguiente modelo, notar la diferencia.
STYLE.md
07
Las contribuciones escritas por agentes son bienvenidas. Se revisan con más rigor, no menos.
Muchos proyectos están prohibiendo silenciosamente las pull requests asistidas por IA. Duct hace lo contrario: la plantilla de PR pregunta dónde ayudó un agente, para que un revisor sepa dónde mirar con más atención. No es un castigo. Es la misma razón por la que dices de qué parte de un cambio estabas menos seguro.
Duct es un producto de agente construido con agentes. Fingir lo contrario en el proceso de contribución sería deshonesto, y desecharía la señal más útil que un revisor puede obtener.
CONTRIBUTING.md