Micro AGI
Micro AGI
Sección titulada «Micro AGI»Construya un agente automodificable que crea sus propias herramientas en tiempo de ejecución — leyendo documentación, escribiendo Lua, registrando entradas en el registro y cargándolas en la sesión activa.
Lo Que Vamos a Construir
Sección titulada «Lo Que Vamos a Construir»Un agente de terminal que:
- Responde preguntas usando un LLM con streaming
- Busca en la documentación de Wippy para aprender APIs
- Inspecciona el registro para descubrir capacidades existentes
- Construye nuevas herramientas sobre la marcha cuando carece de una capacidad
- Gestiona su propia ventana de contexto mediante compresión
flowchart LR User -->|prompt| Agent Agent -->|step| LLM[GPT-5.1] LLM -->|tool_calls| Agent Agent -->|funcs.call| Tools Tools -->|result| Agent Agent -->|text| User
subgraph Tools doc_search registry_list registry_read create_tool load_tool endArquitectura
Sección titulada «Arquitectura»El agente se ejecuta como un proceso de Wippy con acceso al registro. Cuando el LLM decide que necesita una capacidad que no tiene, usa el bucle de automodificación:
sequenceDiagram participant U as User participant A as Agent participant L as LLM participant R as Registry
U->>A: "what time is it?" A->>L: step(conversation) L->>A: tool_call: doc_search("lua/core/time") A->>A: execute doc_search A->>L: step(conversation + tool result) L->>A: tool_call: create_tool(name, source, schema) A->>R: evaluate deny policies + changeset create R->>A: ok A->>L: step(conversation + tool result) L->>A: tool_call: load_tool("app.generated:current_time") A->>A: ctx:add_tools() + reload agent A->>L: step(conversation + tool result) L->>A: tool_call: current_time() A->>A: execute new tool A->>L: step(conversation + tool result) L->>A: text: "The current time is..." A->>U: stream responseLa idea clave: las herramientas son entradas del registro. Crear una herramienta es simplemente escribir una entrada function.lua con código Lua en línea en data.source. El runtime del agente la compila y carga como cualquier otra entrada.
Estructura del Proyecto
Sección titulada «Estructura del Proyecto»micro-agi/├── .wippy.yaml├── wippy.yaml└── src/ ├── _index.yaml ├── README.md ├── agent.lua └── tools/ ├── _index.yaml ├── doc_search.lua ├── registry_list.lua ├── registry_read.lua ├── create_tool.lua └── load_tool.luaInfraestructura
Sección titulada «Infraestructura»Cree .wippy.yaml:
version: "1.0"
logger: encoding: consoleDefiniciones de Entradas
Sección titulada «Definiciones de Entradas»Cree src/_index.yaml con infraestructura, políticas de seguridad, modelos, agente y proceso:
version: "1.0"namespace: app
entries: - name: definition kind: ns.definition readme: file://README.md meta: title: Micro AGI description: Self-modifying development agent that builds its own tools at runtime depends_on: [wippy/llm, wippy/agent]
- name: os_env kind: env.storage.os
- name: processes kind: process.host lifecycle: auto_start: true
- name: __dep.llm kind: ns.dependency component: wippy/llm version: "*" parameters: - name: env_storage value: app:os_env - name: process_host value: app:processes
- name: __dep.agent kind: ns.dependency component: wippy/agent version: "*" parameters: - name: process_host value: app:processesPolíticas de Seguridad
Sección titulada «Políticas de Seguridad»Dos entradas security.policy restringen los namespaces en los que puede escribir el agente:
- name: deny_core_ns kind: security.policy policy: actions: "*" resources: "app:*" effect: deny groups: - agent_security
- name: deny_tools_ns kind: security.policy policy: actions: "*" resources: "app.tools:*" effect: deny groups: - agent_securityEstas políticas se cargan como un ámbito con nombre (app:agent_security) por create_tool y se evalúan antes de cualquier escritura en el registro. El agente puede escribir en app.generated:* (ninguna política deny coincide), pero no puede escribir en app:* (entradas core, modelos, definición del agente) ni en app.tools:* (herramientas integradas).
Vea Modelo de Seguridad para detalles sobre la evaluación de políticas.
Modelos
Sección titulada «Modelos»Dos modelos cumplen propósitos diferentes:
- name: gpt-5.1 kind: registry.entry meta: name: gpt-5.1 type: llm.model title: GPT-5.1 comment: Reasoning model capabilities: [generate, tool_use, structured_output, vision, thinking] class: [reasoning] priority: 210 max_tokens: 128000 output_tokens: 32768 pricing: input: 2.5 output: 10 providers: - id: wippy.llm.openai:provider options: reasoning_model_request: true provider_model: gpt-5.1 thinking_effort: 10
- name: gpt-4.1-nano kind: registry.entry meta: name: gpt-4.1-nano type: llm.model title: GPT-4.1 Nano comment: Compression model capabilities: [generate, tool_use, structured_output] class: [fast] priority: 100 max_tokens: 1047576 output_tokens: 32768 pricing: input: 0.1 output: 0.4 providers: - id: wippy.llm.openai:provider provider_model: gpt-4.1-nanoGPT-5.1 maneja el razonamiento y el uso de herramientas. GPT-4.1 Nano maneja la compresión de contexto a un costo 25 veces menor.
Definición del Agente
Sección titulada «Definición del Agente» - name: dev_assistant kind: registry.entry meta: type: agent.gen1 name: dev_assistant title: Dev Assistant comment: Wippy development assistant prompt: | Self-modifying Wippy development agent. You run inside Wippy runtime with access to docs, registry, and dynamic tool creation.
Rules: - NEVER fabricate, guess, or hallucinate facts. If you need real data, use or build a tool to get it. Only state what a tool actually returned. - Maximum 2-3 sentences per response. No bullet lists. No disclaimers. - Never say "I can't" or "I don't have". Build the tool and do it. - Act first, explain only if asked.
To gain new capabilities: doc_search the API, create_tool with Lua source, load_tool, call it. All in one turn. model: gpt-5.1 max_tokens: 2048 tools: - "app.tools:*"El prompt es deliberadamente escueto. Reglas clave:
- Sin alucinaciones — el agente debe usar herramientas para obtener datos reales
- Automodificación — construir herramientas en lugar de rechazar
- Acción sobre explicación — actuar primero, explicar si se pregunta
Proceso
Sección titulada «Proceso» - name: agent kind: process.lua meta: command: name: agent short: Start dev assistant source: file://agent.lua method: main modules: [io, json, process, funcs, registry, time, security] imports: prompt: wippy.llm:prompt agent_context: wippy.agent:context compress: wippy.llm.util:compressEl proceso se ejecuta como un comando de terminal. La aplicación de la seguridad ocurre dentro de create_tool, que carga el grupo de políticas agent_security y lo evalúa antes de escribir.
Imports:
prompt— constructor de conversacionesagent_context— carga de agente y gestión dinámica de herramientascompress— compresión de texto basada en LLM para gestión de contexto
Herramientas
Sección titulada «Herramientas»Cree src/tools/_index.yaml con cinco herramientas:
doc_search
Sección titulada «doc_search»Obtiene la documentación de Wippy mediante la API wippy.ai/llm. Admite dos modos: obtener una página por ruta, o buscar por consulta.
local http_client = require("http_client")local json = require("json")
local BASE_URL = "https://wippy.ai/llm"local MAX_CHARS = 8000
local function fetch_page(path) local url = BASE_URL .. "/path/en/" .. path local resp, err = http_client.get(url, { headers = { ["User-Agent"] = "wippy-agent/1.0" }, }) if err then return nil, tostring(err) end if resp.status_code ~= 200 then return nil, "HTTP " .. resp.status_code end
local body = resp.body or "" if #body > MAX_CHARS then body = body:sub(1, MAX_CHARS) .. "\n... (truncated)" end return body, nilend
local function search_docs(query) local url = BASE_URL .. "/search?q=" .. query local resp, err = http_client.get(url, { headers = { ["User-Agent"] = "wippy-agent/1.0" }, }) if err then return { error = tostring(err) } end if resp.status_code ~= 200 then return { error = "HTTP " .. resp.status_code } end
local body = resp.body or "" if #body > MAX_CHARS then body = body:sub(1, MAX_CHARS) .. "\n... (truncated)" end
return { results = body }end
local function handler(input) if input.path then local content, err = fetch_page(input.path) if err then return { error = err } end return { path = input.path, content = content } end
if input.query then return search_docs(input.query) end
return { error = "provide either 'path' or 'query'" }end
return { handler = handler }create_tool
Sección titulada «create_tool»El núcleo de la automodificación. Evalúa las políticas deny de namespace y crea una entrada function.lua en el registro con código Lua en línea.
El campo modules en la entrada generada controla a qué puede acceder la herramienta. Los módulos no listados simplemente no existen para esa entrada — no hay nada que bloquear o escanear.
local registry = require("registry")local json = require("json")local security = require("security")
local NAMESPACE = "app.generated"local MAX_SOURCE_LEN = 16000local MAX_NAME_LEN = 64
local ALLOWED_MODULES = { time = true, json = true, http_client = true, expr = true, text = true, base64 = true, yaml = true, crypto = true, hash = true, uuid = true, url = true,}Evaluación de políticas — create_tool carga el ámbito con nombre agent_security y evalúa las políticas deny contra el ID de la entrada objetivo. Las escrituras en app:* o app.tools:* se deniegan; las escrituras en app.generated:* pasan (ninguna política deny coincide):
local actor = security.new_actor("service:agent", { role = "agent" })local scope, scope_err = security.named_scope("app:agent_security")if scope_err then return { error = "failed to load security scope: " .. tostring(scope_err) }end
local result = scope:evaluate(actor, action, id)if result == "deny" then return { error = "policy denied: " .. action .. " on " .. id }endEscritura en el registro — la entrada se escribe con el código fuente en data.source y solo los módulos permitidos:
local entry = { id = id, kind = "function.lua", meta = { type = "tool", title = input.name, comment = input.description, input_schema = schema, llm_alias = input.name, llm_description = input.description, }, data = { source = input.source, modules = modules, method = "handler", },}
local snap = registry.snapshot()local changes = snap:changes()if existing then changes:update(entry)else changes:create(entry)endchanges:apply()Sin archivos en disco. La herramienta vive enteramente en el registro.
load_tool
Sección titulada «load_tool»Valida que la entrada es una herramienta y le indica al bucle del agente que recargue:
local function handler(input) local entry, err = registry.get(input.id) if err then return { error = tostring(err) } end if not entry then return { error = "not found: " .. input.id } end if not entry.meta or entry.meta.type ~= "tool" then return { error = "not a tool (meta.type != 'tool'): " .. input.id } end
return { loaded = true, id = entry.id, alias = entry.meta.llm_alias or input.id, description = entry.meta.llm_description or "", }endEl bucle del agente detecta loaded = true en el resultado y llama a ctx:add_tools(id) seguido de ctx:load_agent() para recompilar el agente con la nueva herramienta.
Bucle del Agente
Sección titulada «Bucle del Agente»El bucle del agente en src/agent.lua maneja streaming, ejecución de herramientas, carga dinámica y compresión de contexto.
Streaming
Sección titulada «Streaming»Usa el mismo patrón de coroutine + canal del tutorial de Agente LLM:
coroutine.spawn(function() local response, err = session.runner:step(session.conversation, { stream_target = { reply_to = process.pid(), topic = STREAM_TOPIC, }, }) done_ch:send({ response = response, err = err })end)Ejecución de Herramientas
Sección titulada «Ejecución de Herramientas»Las herramientas se llaman mediante funcs.call() con pcall por seguridad:
local ok, result = pcall(funcs.call, tc.registry_id, args)Carga Dinámica de Herramientas
Sección titulada «Carga Dinámica de Herramientas»Cuando load_tool retorna loaded = true, el agente se recarga a sí mismo:
flowchart TD A[load_tool returns loaded=true] --> B[ctx:add_tools id] B --> C[ctx:load_agent] C --> D[New runner with added tool] D --> E[Conversation preserved] E --> F[Next LLM step sees new tool]local function handle_tool_loading(tool_calls, results) local reload_needed = false for _, tc in ipairs(tool_calls) do if tc.name == "load_tool" then local result = results[tc.id] if result and result.loaded then session.ctx:add_tools(result.id) reload_needed = true end end end if reload_needed then reload_agent() endendLa conversación se preserva entre recargas porque vive en el constructor de prompts, no en el runner.
Compresión de Contexto
Sección titulada «Compresión de Contexto»Cuando los tokens del prompt exceden 96K (75% de la ventana de contexto de 128K), la conversación se comprime usando GPT-4.1 Nano:
if response.tokens and response.tokens.prompt_tokens and response.tokens.prompt_tokens > PROMPT_TOKEN_LIMIT then try_compress()endLa compresión extrae el contenido de los mensajes, llama a compress.to_size() apuntando a 4000 caracteres, y reemplaza la conversación con un resumen:
local summary = compress.to_size(COMPRESS_MODEL, full_text, COMPRESS_TARGET)session.conversation = prompt.new()session.conversation:add_system("Conversation summary:\n\n" .. summary)Modelo de Seguridad
Sección titulada «Modelo de Seguridad»El agente está protegido mediante políticas deny de namespace y control de acceso a nivel de módulo.
flowchart TD LLM[LLM generates tool] --> P{Namespace Deny Policies} P -->|scope:evaluate| Check{Target namespace?} Check -->|app.generated:*| OK[No deny match] Check -->|app:* or app.tools:*| Deny[Policy Denied]
OK --> M{Module Allowlist} M -->|only granted modules| R[Registry write] M -->|unknown module requested| Err[Rejected]Políticas Deny de Namespace
Sección titulada «Políticas Deny de Namespace»| Política | Recursos | Efecto |
|---|---|---|
deny_core_ns | app:* | deny |
deny_tools_ns | app.tools:* | deny |
create_tool carga el grupo de políticas agent_security y evalúa contra el ID de la entrada objetivo. Como las políticas deny solo coinciden con app:* y app.tools:*, las escrituras en app.generated:* pasan (resultado es undefined, lo que significa “no denegado”).
Esto evita que el agente:
- Modifique su propio prompt o definición de agente (
app:dev_assistant) - Sobrescriba sus herramientas integradas (
app.tools:*) - Cambie entradas de infraestructura (
app:processes, etc.)
Control de Acceso a Módulos
Sección titulada «Control de Acceso a Módulos»Las herramientas generadas declaran sus modules en data.modules. Solo se permiten módulos del conjunto ALLOWED_MODULES. El runtime de Wippy lo aplica a nivel de módulo — si un módulo no está listado en la entrada, require() retorna un error. No hay escaneo de código fuente porque no hay nada que escanear: los módulos no concedidos no existen en el contexto de ejecución.
Ejecutar
Sección titulada «Ejecutar»Ejecutar directamente desde el hub:
wippy run wippy/micro-agi agentO clonar y ejecutar localmente:
cd micro-agiwippy init && wippy updatewippy run agentdev assistant (quit to exit)
> what time is it? [doc_search] ok [create_tool] ok [load_tool] ok [+] app.generated:current_time_utc [current_time_utc] okThe current UTC time is 2026-02-13T03:13:41Z.
> fetch https://httpbin.org/get and show my ip [create_tool] ok [load_tool] ok [+] app.generated:http_get [http_get] okYour IP is 203.0.113.42.Siguientes Pasos
Sección titulada «Siguientes Pasos»- Agente LLM — Construir un agente básico desde cero
- Módulo de Agente — Referencia del framework de agentes
- Registro — Cómo funciona el registro
- Modelo de Seguridad — Políticas de seguridad declarativas
- Tipos de Entrada — Tipos de entrada disponibles