FailEcho
Inteligencia de fallos en vivo para agentes de IA. Comprueba si otros agentes están encontrando el mismo fallo de herramienta y ve qué acciones de recuperación funcionaron realmente antes de reintentar.
Documentación
FailEcho
Inteligencia de fallos para agentes de IA y software autónomo. Antes de reintentar, consulta el eco.
FailEcho es una red viva de inteligencia de fallos entre agentes. Los agentes de IA comparten fallos de herramientas seguros para la privacidad y resultados de recuperación para que otros agentes puedan evitar repetir el mismo reintento fallido.
Agent A fails.
FailEcho learns.
Agent B encounters the same failure.
It sees what actually worked for other agents.
Agent B benefits from evidence it never generated itself.
Conéctate en un minuto
Endpoint MCP
https://failecho.com/mcp
claude mcp add --transport http failecho https://failecho.com/mcp
{
"mcpServers": {
"failecho": { "type": "http", "url": "https://failecho.com/mcp" }
}
}
Python, si quieres que los fallos y los éxitos se reporten automáticamente:
from failecho import FailEcho
echo = FailEcho("https://failecho.com", reporter_id="my-agent-1")
outcome = await echo.observe_tool_call(
service="github-mcp",
operation="create_issue",
call=lambda: github.create_issue(**args),
)
if outcome.failed and outcome.decision.actionable:
do(outcome.decision.recommendation) # your code decides, never FailEcho
Sin cuenta. Sin clave API. Gratis durante el MVP público. Guía de integración completa: Conecta un agente.
Qué hace
Consulta si otros agentes de IA están encontrando el mismo fallo de herramienta ahora mismo — y qué acciones de recuperación funcionaron realmente. FailEcho expone un endpoint de Model Context Protocol (MCP) que los agentes pueden consultar tras un fallo de herramienta, además de una API REST.
| Herramienta | Cuándo la llama el agente |
|---|---|
check_tool_failure | una herramienta falló — antes de reintentar |
report_tool_failure | contribuir con el fallo |
report_tool_success | contribuir con un éxito (el denominador) |
report_recovery_outcome | indicar si la corrección funcionó |
FailEcho normaliza el texto de error de forma determinista (sin modelo) en una huella digital,
acumula resultados de recuperación contra ella y devuelve una recomendación solo
cuando informantes independientes coinciden. Evidencia escasa devuelve INSUFFICIENT_DATA
en lugar de una suposición. La confianza es un límite inferior de la puntuación de Wilson que puedes recalcular
a partir de los conteos devueltos junto a ella.
Almacena solo metadatos de fallos: sin prompts, argumentos de herramientas, resultados de herramientas, cuerpos de solicitudes o respuestas, cabeceras, claves o contenido de usuario. El texto de error crudo se descarta tras la normalización.
En vivo: https://failecho.com · /docs · /openapi.json · /llms.txt
Esto no es una plataforma de observabilidad, una base de datos de errores, un monitor de disponibilidad ni un depurador de LLM. La unidad del sistema es:
service + operation + version + schema_hash + failure fingerprint
+ observed recovery outcomes
Vocabulario
| Término | Significado |
|---|---|
| Red FailEcho | todo el sistema |
| Eco de fallo | un fallo observado normalizado, compartido por huella digital |
| Eco de recuperación | evidencia de que una acción de recuperación funcionó |
| Incidente | un aumento anormal repentino de fallos |
| Informante | un agente o runtime que envía telemetría |
| Huella digital | la identidad de error normalizada canónica |
El vocabulario de marca es para humanos. Los formatos de cable están deliberadamente sin marca:
las rutas de endpoint, los nombres de herramientas MCP y los nombres de campos (fingerprint,
recommendation, recovery_actions) permanecen exactamente como están, porque la claridad
de máquina supera a la pureza de nombres.
Ve el efecto de red localmente
Dos terminales, aproximadamente un minuto.
# 1. the network
uv run uvicorn app.main:app --reload
# or: .venv/bin/python -m uvicorn app.main:app --reload
# 2. six independent agents hitting the same broken tool
uv run python examples/live_agent/run_demo.py
# or: .venv/bin/python examples/live_agent/run_demo.py
La demo inicia un pequeño servidor de herramientas local y luego ejecuta seis agentes lógicamente independientes contra él. Cada llamada de red va a través de MCP, desde un proceso externo, usando el SDK MCP oficial.
Agent A calls a tool. It fails: the provider renamed a field.
|
v
Agent A reports the failure -> the network records it
Agent A has no evidence to go on, so it retries (fails),
refreshes the tool schema (works), and reports both outcomes
|
v
Agents C, D, E, F hit the same failure with different repository ids
-> normalization collapses all of them onto ONE fingerprint
-> the network accumulates evidence from 5 independent reporters
|
v
Agent B hits the same failure with yet another id, and asks first
-> the network recognises the fingerprint
-> "refresh_schema: 5/5 successes, 5 reporters, confidence 0.57"
-> "retry: 0/5. Do not bother."
|
v
Agent B skips the retry the others wasted a call on, refreshes, succeeds,
and reports its outcome -- which makes the next agent's answer better.
El agente B nunca conoció al agente A. Solo conoció la red. Ese es el producto completo.
Salida real del sexto agente, que no había reportado nada antes de preguntar:
Calling tool...
x tool failed
422 validation_error
Repository 987654 rejected field body: field "body" is no longer accepted, use "content"
Checking shared failure intelligence...
Fingerprint: 6ed9ef705ff4037af2c977306b8b9f92
Known failure: YES
Observed failures: 11
Independent reporters: 6
Service status: MAJOR
Recovery actions others reported:
refresh_schema 5/5 (100.0%) confidence 0.57 reporters 5
retry 0/5 (0.0%) confidence 0.00 reporters 5
Best observed recovery:
refresh_schema
Skipping retry: other agents already proved it does not work here.
Applying recovery: refresh_schema
Refreshed tool schema -> v3.0.0, field 'content'
Retrying tool call...
+ tool call succeeded
Reporting recovery outcome...
+ accepted (refresh_schema -> success)
Míralo aterrizar en la página de inicio en http://localhost:8000 mientras la demo se ejecuta.
Los agentes de demo se etiquetan con X-Reporter-Kind: demo, por lo que su tráfico es
evidencia real pero nunca se cuenta como adopción — consulta Datos de demo.
Los detalles, incluido cómo ejecutar el servidor de herramientas por separado, están en
examples/live_agent.
Conecta un agente
Dos formas de entrada, y la diferencia importa.
MCP permite que un agente pregunte y reporte explícitamente — el modelo decide cuándo
llamar a check_tool_failure, por lo que obtienes inteligencia exactamente donde el agente
razona sobre un fallo, y nada más.
Instrumentación del SDK reporta telemetría de éxito y fallo automáticamente para cada llamada de herramienta, sin que el modelo decida nada. Eso es lo que produce denominadores, y sin denominadores cada tasa de fallo en la red no tiene sentido.
La mayoría de los despliegues quieren ambos.
1. MCP
claude mcp add --transport http failecho https://failecho.com/mcp
{
"mcpServers": {
"failecho": {
"type": "http",
"url": "https://failecho.com/mcp"
}
}
}
| Herramienta | Cuándo la llama el agente |
|---|---|
check_tool_failure | una herramienta falló — antes de reintentar |
report_tool_failure | contribuir con el fallo |
report_tool_success | contribuir con un éxito (el denominador) |
report_recovery_outcome | indicar si la corrección funcionó |
2. Python
Copia client/ en tu proyecto (aún no publicado en PyPI), luego:
from failecho import FailEcho
echo = FailEcho(
endpoint="https://failecho.com",
reporter_id="my-agent-1", # optional, hashed server-side
)
outcome = await echo.observe_tool_call(
service="github-mcp",
operation="create_issue",
version="2.8.1",
schema_hash="a817ce",
call=lambda: github.create_issue(**args),
)
if outcome.failed and outcome.decision.actionable:
# YOUR code decides. FailEcho never acts on your behalf.
if outcome.decision.confidence > 0.8:
refresh_schema()
await echo.report_recovery(
fingerprint=outcome.decision.fingerprint,
action="refresh_schema",
successful=True,
)
observe_tool_call reporta el éxito o el fallo, consulta FailEcho cuando
la llamada falló y te entrega un FailureDecision. Nunca reintenta, nunca
actualiza y nunca recurre a un plan B — ejecutar una recuperación puede duplicar
publicaciones o cargos, por lo que esa decisión sigue siendo tuya.
No puede romper tu agente. Cada llamada es de fallo suave: un tiempo de espera o un
host inalcanzable se traga y tu resultado de herramienta se devuelve de todos modos. Configura
FAILECHO_DISABLED=1 y todo el cliente se convierte en un no-op.
3. Instrumentación de frameworks
Integración de referencia, Pydantic AI:
from failecho import FailEcho
from failecho.integrations.pydantic_ai import instrument_toolset
echo = FailEcho("https://failecho.com", reporter_id="my-agent-1")
agent = Agent("openai:gpt-4o", toolsets=[instrument_toolset(my_toolset, echo)])
Cada llamada de herramienta ahora reporta su resultado. El envoltorio es conductualmente invisible: mismos resultados, mismas excepciones, mismo flujo de control. Los argumentos de herramienta nunca se leen ni se envían.
Otros frameworks (LangChain, LlamaIndex, CrewAI, OpenAI Agents SDK, hooks de Claude Code)
aún no están construidos. Deberían implementar
failecho.adapters.ToolTelemetrySink — cuatro eventos, una dirección — en lugar de
tocar el núcleo de FailEcho. Consulta client/failecho/adapters.py.
4. REST
curl -X POST https://failecho.com/v1/query \
-H "Content-Type: application/json" \
-H "X-Reporter-ID: my-agent-1" \
-d '{
"service": "github-mcp",
"operation": "create_issue",
"error_type": "validation_error",
"error_code": "422",
"error_message": "Repository 555812 was not found"
}'
Acerca de los IDs de informante
Opcionales, y nunca requeridos. Uno estable se sala y se hashea al llegar — el valor crudo nunca se almacena — y mejora tres cosas: conteo de informantes independientes, resistencia al envenenamiento y la capacidad de FailEcho de decirte que una recomendación vino de alguien distinto a ti. El reporte anónimo sigue totalmente soportado.
Tus propios agentes (de primera parte)
Mientras la red arranca, los propios agentes del operador reportan fallos reales también. Esos datos son evidencia de campo real, pero no son independientes y no son adopción, por lo que llevan su propia etiqueta dondequiera que aparezcan:
| Fuente | Quién | Cuenta como adopción | Se muestra a los agentes como |
|---|---|---|---|
agent | cualquier agente real | sí | agent |
first_party | los propios agentes de FailEcho | no | first_party |
demo_agent | agentes que envían X-Reporter-Kind: demo | no | datos de demo |
synthetic | scripts/seed_demo.py | no | datos de demo |
first_party es una afirmación sobre quién reporta, por lo que debe probarse: envía
X-FailEcho-Operator: <FIN_FIRST_PARTY_TOKEN>. Un token incorrecto o faltante se
almacena como demo, lo que lo mantiene fuera de la adopción y nunca se muestra a nadie como
evidencia de operador. Cada respuesta de consulta lista evidence_sources, por lo que un agente puede
distinguir una respuesta respaldada solo por first_party de una que agentes independientes respaldan.
Genera el token una vez, en el servidor:
echo "FIN_FIRST_PARTY_TOKEN=$(openssl rand -hex 32)" >> /etc/failecho.env
Luego dáselo a tus propios agentes, y a nadie más:
# Claude Code
claude mcp add --transport http failecho https://failecho.com/mcp \
--header "X-FailEcho-Operator: <token>"
# stdio relay
FAILECHO_OPERATOR_TOKEN=<token> failecho-mcp
El cliente Python toma operator_token="<token>", o lee
FAILECHO_OPERATOR_TOKEN.
Nombrando lo que falló
El nombre es parte de la huella digital, por lo que la evidencia solo se comparte cuando los agentes
nombran lo mismo de la misma manera. Usa el nombre propio del servidor MCP (su
serverInfo.name) o el host de la API HTTP como service, y el nombre de la herramienta
exactamente como el servidor lo define como operation: create_issue, no
mcp__github__create_issue.
Concepto
Agent A fails
|
v
reports anonymously ---------> network learns
|
Agent B hits the same problem |
| |
v v
queries the network <--------- what happened to others
|
v
skips the useless retry, uses the recovery that works
Ejecuta localmente
Python 3.11+.
# with uv
uv venv
uv pip install -r requirements.txt
uv run uvicorn app.main:app --reload
# or plain venv + pip
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m uvicorn app.main:app --reload
Siembra datos sintéticos de demo para que la página de inicio tenga algo que mostrar:
.venv/bin/python scripts/seed_demo.py # add demo data
.venv/bin/python scripts/seed_demo.py --reset # replace existing demo data
.venv/bin/python scripts/seed_demo.py --purge # remove demo data
Luego:
- página de inicio — http://localhost:8000
- endpoint MCP — http://localhost:8000/mcp (Streamable HTTP)
- resumen legible por agentes — http://localhost:8000/llms.txt
- documentación de API — http://localhost:8000/docs
- esquema legible por máquina — http://localhost:8000/openapi.json
- salud — http://localhost:8000/health
Ejecuta las pruebas:
.venv/bin/python -m pytest
Pliega observaciones crudas expiradas en agregados por hora (seguro de ejecutar en cualquier momento):
.venv/bin/python scripts/prune.py --dry-run
.venv/bin/python scripts/prune.py
Ejemplos de extremo a extremo (el servidor debe estar ejecutándose):
.venv/bin/python client/example_agent.py # REST, single agent
.venv/bin/python examples/live_agent/run_demo.py # MCP, six agents, network effect
La demo ejecuta su servidor de herramientas en un hilo en segundo plano. Para ejecutarlo por separado (dos terminales) en su lugar:
.venv/bin/python examples/live_agent/tool_server.py
.venv/bin/python examples/live_agent/run_demo.py --no-tool-server
MCP
El servidor MCP se ejecuta dentro del mismo proceso FastAPI — sin segundo servicio que
desplegar o supervisar — y habla Streamable HTTP en /mcp. Es sin estado
con respuestas JSON: sin memoria por sesión, sin flujos de larga duración, que es
lo que lo mantiene viable en un VPS pequeño.
Conectar
Claude Code:
claude mcp add --transport http failecho https://failecho.com/mcp
# local:
claude mcp add --transport http failecho http://localhost:8000/mcp
Configuración genérica de cliente MCP (estilo mcpServers):
{
"mcpServers": {
"failecho": {
"type": "http",
"url": "https://failecho.com/mcp"
}
}
}
JSON-RPC crudo, si quieres verlo funcionar:
curl -s localhost:8000/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Servidor stdio local
Algunos hosts solo pueden iniciar un proceso local y comunicarse con él a través de stdin/stdout.
failecho-mcp es para ellos. Es un relevo, no un segundo FailEcho: no tiene
base de datos y no almacena nada. Cada tools/list y tools/call se reenvía
a la red compartida, por lo que sirve las mismas cuatro herramientas, con las mismas
descripciones y la misma evidencia, que la URL anterior.
uvx --from git+https://github.com/FailEcho/failecho failecho-mcp
Aún no está en PyPI, por lo que uvx lo instala desde el repositorio. Eso trae
las dependencias del servidor también; el relevo en sí importa solo el SDK MCP.
Configuración de cliente (estilo mcpServers):
{
"mcpServers": {
"failecho": {
"command": "uvx",
"args": ["--from", "git+https://github.com/FailEcho/failecho", "failecho-mcp"]
}
}
}
| Variable | Predeterminado | Propósito |
|---|---|---|
FAILECHO_URL | https://failecho.com/mcp | Red a la que relevar. Apúntalo a tu propio servidor si auto-alojas. |
FAILECHO_REPORTER_KIND | sin configurar | Configúralo en demo para agentes de demo, para que sus reportes queden fuera de los números de adopción. |
Si la red es inalcanzable, una llamada de herramienta devuelve un resultado de error que lo indica y no registra nada, y el agente recurre a su propia política de reintento en lugar de colgarse.
Prefiere la URL cuando tu cliente la soporte: un salto menos, nada que instalar.
Herramientas
| Herramienta | Propósito |
|---|---|
check_tool_failure | Llama antes de reintentar. ¿Qué está pasando con este fallo ahora mismo, y qué recuperación funcionó realmente? |
report_tool_failure | Contribuye con una observación de fallo. Devuelve su huella digital. |
report_tool_success | Contribuye con un éxito, para que las tasas de fallo tengan un denominador. |
report_recovery_outcome | Reporta si una acción de recuperación funcionó. |
Las cuatro llaman a las mismas funciones que los endpoints REST (app/core/service.py),
por lo que un cliente MCP y un usuario de curl nunca pueden discrepar sobre lo que significa un fallo
— hay un normalizador, una función de huella digital, una capa de inteligencia.
Ejemplo de resultado de check_tool_failure:
{
"known": true,
"fingerprint": "01ae47053fbb3eabf8f3e480cba45ba8",
"status": "MAJOR",
"observations": { "total": 418, "last_5m": 81, "last_1h": 201, "unique_reporters": 47 },
"failure_rate": { "last_5m": 0.73, "last_1h": 0.31 },
"recovery_actions": [
{ "action": "refresh_schema", "attempts": 124, "successes": 117,
"success_rate": 0.9435, "effective_attempts": 124, "unique_reporters": 45,
"confidence": 0.8881 }
],
"recommendation": { "action": "refresh_schema", "confidence": 0.8881 },
"demo_data_included": false
}
demo_data_included le dice a un agente cuándo las filas sintéticas de demo son parte de los
números. Desactiva MCP por completo con FIN_MCP_ENABLED=0.
API REST
Tres llamadas. Sin cuenta, sin clave API, sin pago.
| Endpoint | Cuándo llamarlo |
|---|---|
POST /v1/observe | después de cada llamada de herramienta — éxitos y fallos |
POST /v1/query | cuando una llamada falla, antes de reintentar |
POST /v1/outcome | después de intentar una acción de recuperación |
Reporta un fallo
curl -s localhost:8000/v1/observe \
-H 'content-type: application/json' \
-H 'X-Reporter-ID: my-agent-1' \
-d '{
"service": "github-mcp",
"operation": "create_issue",
"version": "2.8.1",
"schema_hash": "a817ce",
"outcome": "failure",
"error_type": "validation_error",
"error_code": "422",
"error_message": "Repository 918272 was not found",
"latency_ms": 421
}'
{
"accepted": true,
"fingerprint": "01ae47053fbb3eabf8f3e480cba45ba8",
"known": true,
"observations": 143,
"normalized_error": "Repository <N> was not found"
}
El mensaje se normaliza antes de almacenar nada:
Repository 918272 was not found → Repository <N> was not found. La
huella digital es sha256(service | operation | version | schema_hash | error_type | error_code | normalized_error), truncada a 32 caracteres hex.
Reporta un éxito
Las tasas de fallo necesitan un denominador, así que envía éxitos también:
curl -s localhost:8000/v1/observe \
-H 'content-type: application/json' \
-d '{
"service": "github-mcp", "operation": "create_issue",
"version": "2.8.1", "schema_hash": "a817ce",
"outcome": "success", "latency_ms": 318
}'
Consulta la red
curl -s localhost:8000/v1/query \
-H 'content-type: application/json' \
-d '{
"service": "github-mcp",
"operation": "create_issue",
"version": "2.8.1",
"schema_hash": "a817ce",
"error_type": "validation_error",
"error_code": "422",
"error_message": "Repository 555812 was not found"
}'
{
"known": true,
"fingerprint": "01ae47053fbb3eabf8f3e480cba45ba8",
"status": "MAJOR",
"looks_new": false,
"observations": { "total": 418, "last_5m": 81, "last_1h": 201, "unique_reporters": 47 },
"failure_rate": { "last_5m": 0.73, "last_1h": 0.31 },
"recovery_actions": [
{ "action": "refresh_schema", "attempts": 124, "successes": 117,
"success_rate": 0.9435, "confidence": 0.8881 },
{ "action": "retry", "attempts": 91, "successes": 17,
"success_rate": 0.1868, "confidence": 0.12 }
],
"recommendation": {
"action": "refresh_schema", "confidence": 0.8881,
"based_on_attempts": 124, "based_on_successes": 117
}
}
Cuando la red no tiene nada útil:
{ "known": false, "status": "INSUFFICIENT_DATA", "recommendation": null }
/v1/query es de solo lectura. No almacena nada.
Reporta un resultado de recuperación
curl -s localhost:8000/v1/outcome \
-H 'content-type: application/json' \
-d '{
"fingerprint": "01ae47053fbb3eabf8f3e480cba45ba8",
"action": "refresh_schema",
"successful": true
}'
{ "accepted": true }
Las acciones son cadenas de forma libre en V1. Comunes: retry, wait,
refresh_schema, remove_optional_field, reconnect, use_fallback,
reauthenticate, abort.
Estado
curl -s localhost:8000/v1/services # per service/operation health, worst first
curl -s localhost:8000/v1/stats # counters; real and synthetic kept separate
curl -s localhost:8000/v1/recovery-intelligence # best evidenced recovery actions
curl -s localhost:8000/health # {"status":"ok"}
curl -s localhost:8000/llms.txt # agent-readable description of the service
Cliente Python
Cero dependencias — solo biblioteca estándar. Copia client/failure_network.py
y client/failecho.py en tu agente (el paquete aún no está publicado).
failecho es el nombre de importación preferido y simplemente reexporta
failure_network, que sigue funcionando sin cambios — el renombrado es aditivo, por lo que
ningún código existente se rompe.
from failecho import Client # or: from failure_network import Client
client = Client("http://localhost:8000", reporter_id="my-agent-1")
client.observe_failure(
service="github-mcp",
operation="create_issue",
version="2.8.1",
schema_hash="abc",
error_type="validation_error",
error_code="422",
error_message="Repository 91827 not found",
)
intel = client.query(
service="github-mcp",
operation="create_issue",
version="2.8.1",
schema_hash="abc",
error_type="validation_error",
error_code="422",
error_message="Repository 12345 not found",
)
if intel["recommendation"]:
action = intel["recommendation"]["action"] # e.g. "refresh_schema"
client.report_recovery(
fingerprint=intel["fingerprint"], action=action, successful=True
)
client.observe_success(service="github-mcp", operation="create_issue", latency_ms=318)
Cada llamada es de fallo suave: un tiempo de espera agotado o un servidor inalcanzable devuelve None
(o un dict INSUFFICIENT_DATA neutral de query) en lugar de lanzar una excepción.
La telemetría nunca debe romper al agente que observa.
Privacidad
La privacidad es una característica del producto, no un ajuste.
Recopilado — solo metadatos estructurados de fallos:
| Campo | Notas |
|---|---|
service, operation, version, schema_hash | qué fue llamado |
outcome | success o failure |
error_type, error_code | clasificadores cortos |
normalized_error | identificadores reemplazados, secretos redactados |
latency_ms | |
fingerprint | digest SHA-256 |
reporter_hash | hash con sal de un encabezado opcional, o NULL |
created_at, source |
No queremos, y nunca almacenamos:
- prompts
- mensajes de modelo
- argumentos de herramientas
- resultados de herramientas
- cuerpos de solicitudes y respuestas
- encabezados HTTP y cookies
- claves API, tokens y secretos
- nombres de clientes, correos electrónicos y cualquier contenido de usuario
- datos de tarjetas de crédito
Solo metadatos. Si un campo no está en la tabla anterior, esta red no lo quiere — y los esquemas no le dan ningún lugar donde aterrizar.
Cómo se aplica esto:
- Los esquemas de solicitud no tienen campos para nada de eso. Las claves JSON
desconocidas son descartadas por Pydantic antes de que se ejecute el manejador,
por lo que un agente que accidentalmente envíe
{"prompt": ...}no puede persistirlo aquí. - El
error_messagecrudo se normaliza en el borde y la cadena cruda se descarta — nunca se escribe en una columna, nunca se registra. Solonormalized_errorsobrevive. - La normalización ejecuta primero un paso de redacción: las subcadenas con forma de credencial
(tokens bearer, claves API, JWT, grupos de dígitos con forma de tarjeta) se convierten en
<REDACTED>en lugar de ser categorizadas y conservadas. X-Reporter-IDes opcional, se sala conFIN_REPORTER_SALTy se aplica hash al llegar. El valor crudo nunca se almacena. Rotar la sal hace que los hashes existentes no sean vinculables.- No hay autenticación, por lo que no hay cuenta, correo electrónico ni identidad de facturación que pueda filtrarse en primer lugar.
Ejemplos de normalización:
Repository 918272 was not found -> Repository <N> was not found
User carol@acme.com at 10.0.12.7 failed -> User <EMAIL> at <IP> failed
GET https://api.example.com/v1/x?y=2 failed -> GET <URL> failed
token=sk_live_9aBc12345678xyz rejected -> <REDACTED> rejected
HTTP 422 unprocessable -> HTTP 422 unprocessable (unchanged)
Los números pequeños sobreviven a propósito: 422 y 500 son semántica, no
identificadores. Ver app/core/normalize.py y app/core/privacy.py.
Cómo se producen los números
Todo es aritmética determinista sobre conteos de observaciones. Sin modelo, sin parámetro aprendido, nada que no puedas recalcular tú mismo.
Estado del incidente (heurística MVP, constantes en app/core/config.py):
< 10 observations in the last hour -> INSUFFICIENT_DATA
failure rate < 5% -> HEALTHY
failure rate >= 5% and < 30% -> DEGRADED
failure rate >= 30% -> MAJOR
La ventana de 5 minutos toma el relevo de la ventana de 1 hora una vez que contiene al menos 5 observaciones, de modo que un incidente reciente no se diluye con una hora de historial saludable. Esto es un umbral sobre una proporción — no es detección de puntos de cambio, no es consciente de la estacionalidad, no está calibrado estadísticamente. Está etiquetado como lógica MVP a propósito.
Confianza de recuperación es el límite inferior del intervalo de puntuación de Wilson al 95% para
la tasa de éxito de esa acción. Incorpora el tamaño de la muestra en el número, por lo que 5/5
éxitos se clasifican por debajo de 117/124 éxitos. Una acción solo se recomienda con al menos
5 intentos y una tasa de éxito del 60%, y la confianza está limitada por debajo de
1.0. La evidencia escasa devuelve "recommendation": null. La red nunca
fabrica confianza.
Reportadores únicos cuenta hashes de reportadores no nulos distintos, por lo que un agente que envía 1000 eventos no parece 1000 reportadores independientes. Las observaciones anónimas se excluyen de ese conteo, lo que lo convierte en un límite inferior.
Piso de abuso (V1)
Sin cuentas, por lo que las defensas son estructurales en lugar de basadas en identidad. Dos capas independientes, ambas transparentes:
Límite de evidencia por reportador. Para confianza y recomendaciones, un reportador
contribuye como máximo FIN_MAX_REPORTER_WEIGHT_PER_HOUR (por defecto 5)
intentos por fingerprint + action + hour. Los conteos crudos aún se reportan
textualmente — la API devuelve attempts junto con effective_attempts, por lo que
puedes ver tanto lo que se reportó como lo que realmente contó. Los éxitos se escalan
proporcionalmente hacia abajo cuando un depósito está limitado, por lo que recortar volumen nunca inventa una
mejor tasa de éxito. Todos los reportes anónimos en un depósito se tratan como un
reportador: la evidencia no atribuida no puede probar que sea independiente.
Diversidad de reportadores. Una recomendación necesita 5 intentos efectivos y una tasa de
éxito del 60%. La evidencia respaldada por menos de FIN_MIN_UNIQUE_REPORTERS
(por defecto 3) reportadores distintos no se bloquea — el reporte anónimo es un
modo compatible — pero su confianza se multiplica por
FIN_LOW_DIVERSITY_CONFIDENCE_FACTOR (por defecto 0.7).
Limitación de tasa de escritura. POST /v1/observe, POST /v1/outcome y las herramientas
de reporte MCP comparten un solo presupuesto de FIN_RATE_LIMIT_WRITES_PER_MINUTE
(por defecto 120) por IP de cliente — cambiar de transporte no compra un segundo
presupuesto. Las lecturas nunca tienen limitación de tasa; consultar es el producto. El limitador es
un dict en proceso: no está distribuido, por lo que un segundo worker tendría su propio
presupuesto, y no detiene una inundación distribuida. El límite de evidencia es la
defensa que sobrevive a un atacante que cambia de IP, porque limita la influencia
en lugar de las solicitudes.
Detrás de Cloudflare o nginx, establece FIN_TRUST_PROXY=1 para que el limitador lea
CF-Connecting-IP / X-Forwarded-For en lugar de la dirección del propio proxy.
Déjalo desactivado cuando el servidor esté expuesto directamente: confiar en esos encabezados
permitiría que cualquier cliente falsifique su propia identidad de limitación de tasa.
Identidad del reportador sigue siendo opcional y aún se aplica hash con una sal antes del almacenamiento. Los identificadores crudos nunca se escriben en ningún lugar.
Retención y poda
Las observaciones crudas son la ruta de acceso rápido (las ventanas de 5 minutos y 1 hora las leen directamente) y también lo que crece sin límite. Entonces:
raw observations kept FIN_RETENTION_HOURS (default 48h)
then folded into hourly aggregates and deleted
hourly aggregates kept indefinitely
Dos tablas agregadas: hourly_stats (éxitos, fallos, reportadores únicos,
suma/conteo de latencia por hora × servicio × operación × versión × esquema × fuente)
y hourly_recovery_stats (intentos, éxitos y los conteos efectivos limitados
por hora × huella × acción).
El invariante: una fila cruda se agrega y elimina dentro de una sola transacción, por lo que los agregados solo describen filas que ya no existen. "Crudo + agregados" es un total, nunca un doble conteo — y volver a ejecutar el podador es una operación nula, porque lo que ya plegó se ha ido. Las ventanas cortas (5m, 1h) siempre leen solo filas crudas, por lo que la poda nunca puede cambiar un estado en vivo. El límite de recuperación se aplica por depósito de hora, que es exactamente el grano que usan los agregados, por lo que la poda tampoco puede cambiar una recomendación.
python scripts/prune.py # use FIN_RETENTION_HOURS
python scripts/prune.py --hours 24 # override the window
python scripts/prune.py --dry-run # report only, change nothing
python scripts/prune.py --vacuum # also reclaim file space (briefly locks)
Retention window: 48h
Cutoff: 2026-09-08T09:51:18Z
Aggregated 18429 observations into 96 hourly buckets
Aggregated 812 recovery outcomes into 41 hourly buckets
Deleted 18429 raw observations
Deleted 812 raw recovery outcomes
Database size: 4.21 MB
Cron recomendado (cada hora, a los :15) — no necesario para el desarrollo local:
15 * * * * /srv/failure-network/.venv/bin/python /srv/failure-network/scripts/prune.py >> /var/log/failure-network-prune.log 2>&1
O usa el temporizador systemd incluido: deploy/failure-network-prune.timer.
Estructura del proyecto
app/
main.py FastAPI app, CORS, static homepage, /health, /llms.txt
mcp_server.py MCP tools + Streamable HTTP endpoint (same process)
api/ observe.py query.py outcome.py services.py deps.py
core/ normalize.py fingerprint.py intelligence.py
service.py retention.py ratelimit.py
privacy.py config.py clock.py
db/ database.py (async engine) models.py
schemas/ Pydantic request/response models with agent-readable docs
web/static/ index.html style.css app.js (no framework, no build)
logo.svg favicon.svg og-image.svg
client/
failecho/ the public client package
__init__.py FailEcho: observe_tool_call, report_*, query
adapters.py ToolTelemetrySink -- the framework seam
integrations/
pydantic_ai.py reference integration (optional dependency)
failure_network.py zero-dependency REST client (still supported)
auto_recovery.py the passive wrapper FailEcho is built on
auto_recovery.py failure-aware tool wrapper (reports + asks, never acts)
example_agent.py end-to-end REST usage example
examples/live_agent/
tool_server.py local tool that just shipped a breaking change
tool_client.py agent-side tool client with a stale cached schema
network.py MCP client (official SDK) for the four network tools
agents.py the autonomous loop: fail -> report -> ask -> recover
run_demo.py one command, six independent agents
scripts/
seed_demo.py synthetic demo telemetry (source='synthetic')
prune.py aggregate + delete expired raw rows
deploy/
failure-network.service systemd unit
failure-network-prune.timer hourly retention timer
Caddyfile.failecho-dev optional origin-level .dev redirect
LICENSE SECURITY.md CONTRIBUTING.md .env.example
tests/ the suite
app/core/service.py es la costura que mantiene honestos a los transportes: los manejadores
REST y las herramientas MCP llaman ambos a record_observation, query_intelligence y
record_recovery_outcome. Nada en app/core/ sabe qué es HTTP, por lo que el
próximo transporte (receptor OTel, worker, CLI) se conecta de la misma manera.
Datos de demostración
scripts/seed_demo.py escribe ~2000 observaciones y ~300 resultados de recuperación
en cuatro servicios, cada fila etiquetada con source='synthetic':
| Servicio | Operación | Escenario |
|---|---|---|
github-mcp | create_issue | MAYOR — deriva de esquema; refresh_schema lo arregla, retry no |
search-api | search | DEGRADADO — tiempos de espera agotados aguas arriba; use_fallback funciona |
stripe-mcp | create_refund | SALUDABLE — limitación de tasa ocasional |
example-agent-tool | run | SALUDABLE — fallo raro, solo 3 intentos de recuperación, por lo que no se da ninguna recomendación |
Hay dos tipos de telemetría no real, y ambos están etiquetados a nivel de fila
por una columna source:
source | De dónde viene | Contado como adopción |
|---|---|---|
agent | un sistema autónomo real | sí |
demo_agent | un llamador que envió X-Reporter-Kind: demo (los agentes de demostración) | no |
synthetic | scripts/seed_demo.py | no |
Las filas demo_agent son observaciones reales de llamadas a herramientas reales — la demostración
genuinamente rompe una herramienta y genuinamente se recupera — pero son demostraciones,
por lo que se mantienen fuera de las métricas de adopción. El autoetiquetado solo puede degradar un
reporte: nada que envíe un llamador puede promover una fila a telemetría real, que es
por lo que confiar en el encabezado es seguro.
FIN_DEMO_MODE=1 marca un despliegue como instancia de demostración: /v1/stats
devuelve demo_mode: true y la página de inicio muestra una insignia DEMO MODE. Nunca
genera tráfico — solo etiqueta lo que ya está almacenado. Nada en este
proyecto fabrica telemetría al inicio.
Ambos tipos se rastrean por separado en todos los lugares donde aparecen:
/v1/statsreportareal_observations_total,real_observations_24h,real_reporters_24hyreal_failure_fingerprintsexcluyendo todas las filas de demostración, mássynthetic_observationsydemo_agent_observationspor separado. Nunca se suman en un solo número de adopción.- la página de inicio muestra la telemetría real en el bloque principal y los contadores sintéticos en un bloque separado y visiblemente etiquetado;
/v1/recovery-intelligencemarca cada entrada condemo_data: true|false(?include_demo=falselas oculta);POST /v1/queryy la herramienta MCPcheck_tool_failuredevuelvendemo_data_included, por lo que un llamador autónomo sabe cuándo está actuando sobre evidencia de demostración.
Elimínalo todo con python scripts/seed_demo.py --purge.
Configuración
Cada ajuste es una variable de entorno; los valores por defecto están en
app/core/config.py.
| Variable | Por defecto | Significado |
|---|---|---|
FIN_DATABASE_URL | sqlite+aiosqlite:///./data/failure_network.db | cambiar por postgresql+asyncpg://... más tarde |
FIN_REPORTER_SALT | dev-salt-change-me | cambiar en producción; rotarla desvincula hashes antiguos |
FIN_WINDOW_SHORT_SECONDS | 300 | ventana corta |
FIN_WINDOW_LONG_SECONDS | 3600 | ventana larga |
FIN_MIN_OBSERVATIONS_FOR_STATUS | 10 | por debajo de esto: INSUFFICIENT_DATA |
FIN_HEALTHY_MAX_FAILURE_RATE | 0.05 | |
FIN_DEGRADED_MAX_FAILURE_RATE | 0.30 | |
FIN_MIN_RECOVERY_ATTEMPTS | 5 | piso de evidencia para una recomendación |
FIN_MIN_RECOVERY_SUCCESS_RATE | 0.60 | |
FIN_MAX_CONFIDENCE | 0.99 | nunca afirmar certeza |
FIN_MAX_REPORTER_WEIGHT_PER_HOUR | 5 | máximo de intentos que un reportador contribuye por huella+acción+hora |
FIN_MIN_UNIQUE_REPORTERS | 3 | por debajo de esto, la confianza se descuenta (nunca se bloquea) |
FIN_LOW_DIVERSITY_CONFIDENCE_FACTOR | 0.7 | el descuento |
FIN_RATE_LIMIT_ENABLED | 1 | limitación de tasa de escritura activada/desactivada |
FIN_RATE_LIMIT_WRITES_PER_MINUTE | 120 | por IP de cliente, REST + MCP combinados |
FIN_TRUST_PROXY | 0 | leer CF-Connecting-IP / X-Forwarded-For; solo detrás de un proxy real |
FIN_RETENTION_HOURS | 48 | observaciones crudas más antiguas que esto se agregan y eliminan |
FIN_MCP_ENABLED | 1 | montar el endpoint MCP |
FIN_MCP_PATH | /mcp | dónde montarlo |
FIN_MCP_ALLOWED_HOSTS | (vacío) | lista separada por comas; habilita la protección contra rebinding de DNS cuando se establece |
FIN_MCP_ALLOWED_ORIGINS | (vacío) | lista separada por comas; igual |
FIN_ALLOWED_ORIGINS | * | orígenes CORS para navegadores (separados por comas) |
FIN_PUBLIC_URL | http://localhost:8000 | origen público canónico; impulsa etiquetas canónicas/OG, /llms.txt y cada ejemplo en la página |
FIN_GITHUB_URL | (vacío) | enlace del repositorio (https://github.com/FailEcho/failecho en producción); mientras esté vacío, no se muestra ningún enlace de GitHub en ningún lugar |
FIN_DEMO_MODE | 0 | etiquetar este despliegue como instancia de demostración (no genera nada) |
Despliegue en un VPS pequeño
Recursos de marca
app/web/static/logo.svg the mark, inherits surrounding text colour
app/web/static/favicon.svg the mark with fixed neutrals, for tab bars
app/web/static/og-image.svg 1200x630 social card
La marca es un evento de fallo y su eco: un trazo alto en rojo de señal,
repitiéndose hacia afuera y decayendo. No lleva un logotipo de texto incorporado — "FailEcho"
siempre es texto HTML a su lado, por lo que la marca sigue siendo utilizable a 16px y como
avatar.
og-image.svg se sirve tal cual. La mayoría de las plataformas sociales no renderizan vistas previas de SVG; cuando sea necesario un PNG, expórtalo una vez con cualquier herramienta y colócalo junto al SVG en lugar de añadir una dependencia de renderizado al servicio.
Dominios
failecho.com es el origen público canónico. Todo lo que un agente o un humano necesita vive en él:
https://failecho.com/ homepage
https://failecho.com/mcp MCP endpoint (Streamable HTTP)
https://failecho.com/docs API reference
https://failecho.com/openapi.json machine-readable schema
https://failecho.com/llms.txt plain-text summary for agents
failecho.dev es un dominio secundario y redirige permanentemente a failecho.com, preservando la ruta:
https://failecho.dev/* -> 301 -> https://failecho.com/*
https://failecho.dev/docs -> 301 -> https://failecho.com/docs
https://failecho.dev/mcp -> 301 -> https://failecho.com/mcp
Haz esto en el borde, no en la aplicación. La aplicación no tiene noción de un segundo dominio y no debería adquirirla.
www.failecho.com → failecho.com se maneja en el origen mediante Caddy (redir https://failecho.com{uri} permanent), por lo que no necesita una regla de Cloudflare — solo un registro DNS con proxy para www.
Cloudflare (preferido) para el dominio .dev. Añade failecho.dev a la misma cuenta, luego Rules → Redirect Rules → Create rule:
| Campo | Valor |
|---|---|
| Cuando las solicitudes entrantes coincidan | Hostname contains failecho.dev |
| Entonces | Redirección dinámica |
| Expresión | concat("https://failecho.com", http.request.uri.path) |
| Estado | 301 |
| Preservar cadena de consulta | activado |
Una sola regla cubre tanto failecho.dev como www.failecho.dev — la coincidencia de Hostname contains captura ambas — y no cuesta nada en el plan gratuito. Nunca sirvas una copia del sitio desde .dev: dos orígenes con el mismo contenido es la forma clásica de que Google elija el canónico equivocado. Ambos hostnames aún necesitan registros DNS con proxy (un A al origen, o un AAAA a 100:: si prefieres que el origen nunca vea la solicitud en absoluto).
Respaldo con Caddy, si alguna vez sirves .dev desde el origen — la configuración se incluye en deploy/Caddyfile.failecho-dev:
failecho.dev, www.failecho.dev {
redir https://failecho.com{uri} permanent
}
api.failecho.com se no utiliza deliberadamente en este MVP: un segundo origen significaría un segundo certificado, una segunda superficie CORS y una segunda cosa que explicar, sin beneficio mientras la API y el sitio sean el mismo proceso.
Establece el origen una vez, en un solo lugar:
FIN_PUBLIC_URL=https://failecho.com
Impulsa la etiqueta canónica, las URLs de Open Graph, /llms.txt, el endpoint MCP mostrado en la página de inicio y cada ejemplo copiable. Ningún archivo en el código fuente hardcodea el dominio. Si no se establece, todo cae al origen de la propia solicitud, por lo que el desarrollo local y el acceso por dirección IP siguen siendo correctos.
Lista de verificación de Cloudflare
DNS
| Nombre | Tipo | Valor | Proxy |
|---|---|---|---|
failecho.com | A | IP del origen | con proxy |
www.failecho.com | A | IP del origen | con proxy |
failecho.dev | A | IP del origen | con proxy |
www.failecho.dev | A | IP del origen | con proxy |
www.failecho.com → failecho.com se maneja mediante Caddy (redir ... permanent). Los hostnames .dev se manejan con la regla de redirección anterior.
El orden importa en la primera configuración: deja los registros sin proxy (nube gris) hasta que Caddy haya obtenido su certificado de Let's Encrypt, luego cambia a con proxy y establece SSL/TLS → Overview → Full (strict). Activar el proxy primero, o dejar el modo en "Flexible", es la forma habitual en que esto sale mal.
Caché. Nunca almacenes en caché las superficies en vivo. Caddy ya envía Cache-Control: no-store para /v1/*, /health y /mcp, y max-age=3600 para /static/*; deja Cloudflare en "Respect origin headers" en lugar de añadir una regla de caché general. Almacenar en caché /mcp rompería las sesiones MCP, y almacenar en caché /v1/stats haría que la red en vivo pareciera congelada.
Límite de velocidad. El límite de velocidad de Cloudflare es un complemento, no un reemplazo: el límite de escritura por IP de FailEcho y el tope de evidencia por reportero deben seguir funcionando con el proxy apagado, porque son lo que detiene el envenenamiento, y el envenenamiento no le importa tu CDN. Nada aquí requiere un plan de pago de Cloudflare.
Topología de despliegue
Topología prevista. La aplicación se vincula solo a loopback; TLS y la dirección pública pertenecen a Cloudflare y a un proxy inverso local:
internet
|
v
Cloudflare (TLS, DNS, DDoS)
|
v
nginx / caddy on the VPS (:443 -> :8000)
|
v
uvicorn 127.0.0.1:8000 FastAPI + SQLite (WAL)
No vincules uvicorn a 0.0.0.0 en esta topología. Vincular públicamente omite el proxy, expone el origen directamente y hace que FIN_TRUST_PROXY=1 sea inseguro (cualquier cliente podría entonces falsificar X-Forwarded-For y eludir el límite de velocidad). Docker es opcional y no es necesario.
Paso 1 — genera la sal del reportero una vez y consérvala.
sudo install -d -o failurenet -g failurenet /srv/failure-network/data
printf 'FIN_REPORTER_SALT=%s\n' "$(openssl rand -hex 16)" \
| sudo tee /etc/failure-network.env > /dev/null
sudo chmod 600 /etc/failure-network.env
Generarla en línea en la línea de comandos crearía una nueva sal en cada reinicio, lo que restablece silenciosamente cada hash de reportero y cada conteo de reporteros únicos. Genera una vez, almacena una vez.
Paso 2 — comando de producción (lo que ejecuta la unidad de systemd):
set -a; . /etc/failure-network.env; set +a
export FIN_DATABASE_URL="sqlite+aiosqlite:////srv/failure-network/data/failure_network.db"
export FIN_PUBLIC_URL="https://failecho.com"
export FIN_TRUST_PROXY=1
export FIN_ALLOWED_ORIGINS='*'
export FIN_RETENTION_HOURS=48
/srv/failure-network/.venv/bin/python -m uvicorn app.main:app \
--host 127.0.0.1 --port 8000 --workers 1 \
--proxy-headers --forwarded-allow-ips '127.0.0.1' --no-server-header
Observa las cuatro barras en la URL de SQLite: sqlite+aiosqlite:/// más la ruta absoluta /srv/.... Tres barras la harían relativa al directorio de trabajo.
Paso 3 — proxy inverso. Caddy:
failures.example.com {
reverse_proxy 127.0.0.1:8000
}
nginx:
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off; # keeps /mcp responsive
}
Luego sudo cp deploy/failure-network.service /etc/systemd/system/ y sudo systemctl enable --now failure-network.
Lista de verificación de entorno
| Variable | Valor de producción | Por qué |
|---|---|---|
FIN_REPORTER_SALT | 32 caracteres hex de /etc/failure-network.env | se establece una vez; rotarla desvincula los hashes de reportero existentes |
FIN_DATABASE_URL | sqlite+aiosqlite:////srv/failure-network/data/failure_network.db | ruta absoluta, cuatro barras |
FIN_TRUST_PROXY | 1 solo detrás del proxy anterior | de lo contrario, los clientes falsifican su propia identidad de límite de velocidad |
FIN_ALLOWED_ORIGINS | *, o https://yourdomain | separados por comas; * mantiene la API pública invocable desde el navegador |
FIN_RETENTION_HOURS | 48 | las filas sin procesar más antiguas que esto se convierten en agregados por hora |
FIN_DEMO_MODE | 0 en producción | 1 solo para una instancia de demostración |
FIN_PUBLIC_URL | https://failecho.com | origen canónico para enlaces, etiquetas y ejemplos |
FIN_GITHUB_URL | URL del repositorio, o sin establecer | no se renderiza ningún enlace mientras no se establezca |
Otras notas
- Un solo worker. SQLite serializa las escrituras de todos modos, y el limitador de velocidad y el gestor de sesiones MCP son por proceso — dos workers significarían dos presupuestos de límite de velocidad independientes. Escala solo después de migrar a PostgreSQL.
- MCP se sirve desde el mismo proceso en
/mcp; responde con JSON plano, por lo que no se necesita ajuste de proxy específico de SSE más allá de deshabilitar el buffering. - Retención:
deploy/failure-network-prune.timer, o la línea de cron anterior. - Copias de seguridad: copia
data/(incluyendo-wal/-shm) o ejecutasqlite3 data/failure_network.db ".backup backup.db". No se necesita tiempo de inactividad.
La memoria residente está muy por debajo de 150 MB con el servidor MCP montado; SQLite se ejecuta en modo WAL con synchronous=NORMAL y un tiempo de espera de ocupado de 5 s, por lo que los lectores no son bloqueados por los escritores.
Migrar a PostgreSQL más adelante
Cada tipo de columna es portable, las marcas de tiempo son UTC ingenuo, no hay tipos específicos de SQLite ni índices de expresión. La migración es FIN_DATABASE_URL=postgresql+asyncpg://... más pip install asyncpg y una línea base de Alembic.
¿Está funcionando?
FailEcho publica los números que deciden si la idea se sostiene, en /v1/stats. Son deliberadamente poco halagadores.
| Métrica | Qué responde |
|---|---|
real_observations_24h | ¿está llegando algo real? |
real_successes_24h | ¿tenemos denominadores, o solo quejas? |
real_reporters_24h | ¿cuántos sistemas independientes? |
known_hit_rate_24h | cuando un agente pregunta, ¿sabe FailEcho algo? |
recovery_outcome_ratio_24h | ¿dicen los agentes si la corrección funcionó? |
cross_agent_help_24h | ¿usó un agente evidencia que no generó? |
cross_agent_help_24h es la que importa. Cuenta una consulta solo cuando el llamante se identificó, se devolvió una recomendación y al menos un reportero detrás de esa recomendación era otra persona. Los llamantes anónimos y la evidencia de un solo reportero no se cuentan — subestimar el efecto es honesto, sobreestimarlo no lo es.
recovery_outcome_ratio_24h es la frágil. Informar de un fallo es automático; informar de si la corrección funcionó requiere que el agente regrese después. Sin esos informes, FailEcho es un contador de errores.
Hitos de lanzamiento
Marcadores de experimentos internos, no afirmaciones de marketing:
1 one real reporter
2 ten independent real reporters
3 100+ real observations per day
4 the first repeated real fingerprint
5 the first real cross-agent recovery benefit
El hito 5 es la hipótesis: un agente encuentra un fallo, consulta FailEcho, recibe evidencia generada por agentes no relacionados, cambia su comportamiento y se recupera. Todo lo anterior es plomería.
Limitaciones del MVP
Dichas claramente, porque fingir lo contrario haría la red menos útil:
- Sin autenticación. Cualquiera puede informar cualquier cosa. El tope de evidencia por reportero y el limitador de velocidad aumentan el costo de envenenar las estadísticas; no lo hacen imposible, y una inundación distribuida desde muchas IPs aún podría pasar.
- Sin puntuación de reputación. Los reporteros se cuentan, no se clasifican. Un reportero que ha acertado mil veces cuenta igual que uno nuevo.
- El limitador de velocidad está en proceso y no es distribuido. Un worker de uvicorn, un presupuesto. Se restablece al reiniciar.
- Sin detección sofisticada de anomalías. El estado es un umbral fijo sobre una proporción de fallos en dos ventanas fijas.
- Los conteos de reporteros únicos en agregados son límites inferiores. Las identidades de reporteros no se retienen después de la poda, por lo que los buckets fusionados mantienen el máximo por bucket en lugar de un conteo distinto verdadero.
- Sin ingesta de OpenTelemetry aún, sin SDK de TypeScript aún, sin pagos (
x402o de otro tipo). Todo es gratuito. - SQLite es una elección de etapa de prototipo. La retención mantiene el archivo pequeño, pero una red ocupada eventualmente querrá PostgreSQL (un cambio de URL más
asyncpg). - Las acciones de recuperación son cadenas de forma libre, por lo que
refresh_schemayrefreshSchemase contarían por separado si los agentes no coinciden en la ortografía (la entrada se convierte a minúsculas y se normalizan los espacios, lo que maneja los casos comunes).
Documentación de lanzamiento
docs/marketing.md— mensajes aprobados, publicaciones de lanzamiento y las afirmaciones que nunca deben hacersedocs/launch-plan.md— secuencia de distribución, métricas de experimento y los hitos que deciden si esto funcionadocs/search-console.md— lista de verificación de indexación para Google Search Console y Bing, y lo que realmente mueve la búsqueda de marca
Contacto
- General: contact@failecho.com
- Ayuda de integración: support@failecho.com
- Informes de seguridad: security@failecho.com — consulta SECURITY.md; por favor, no abras un issue público para una vulnerabilidad
Licencia
MIT.