diff --git a/.env.example b/.env.example index 54e906d..9049403 100644 --- a/.env.example +++ b/.env.example @@ -24,4 +24,4 @@ LOG_LEVEL=INFO DATA_DIR=./data # URL del core (la usa el dashboard) -AGENTFORGE_CORE_URL=http://core:8000 +FORJA_CORE_URL=http://core:8000 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e647f2c..813af04 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,14 +1,14 @@ -# AgentForge — Arquitectura técnica +# Forja — Arquitectura técnica > Documento "vivo" con las decisiones técnicas. El spec original está en -> [`docs/superpowers/specs/2026-05-09-agentforge-design.md`](docs/superpowers/specs/2026-05-09-agentforge-design.md). +> [`docs/superpowers/specs/2026-05-09- forja-design.md`](docs/superpowers/specs/2026-05-09- forja-design.md). ## Visión general Two-tier: -- `agentforge-core` — FastAPI 8000. Dominio de gobierno, runtime LangGraph, +- `forja-core` — FastAPI 8000. Dominio de gobierno, runtime LangGraph, guardrails y persistencia. -- `agentforge-dashboard` — Streamlit 8501. Cliente HTTP del core. +- `forja-dashboard` — Streamlit 8501. Cliente HTTP del core. ## Diagrama diff --git a/README.md b/README.md index 7fe98e5..e78fd79 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,9 @@ -# 🛡️ AgentForge +# 🔨 Forja -Plataforma profesional de **gobernanza de agentes IA**: catalogación, versionado -de prompts y políticas, guardrails runtime, ejecución stateful con -Human-in-the-Loop, observabilidad y trazabilidad end-to-end. - -## ¿Por qué? - -Poner agentes IA en producción sin una capa de gobierno produce sistemas -opacos: prompts que cambian sin historial, validaciones inconsistentes, -acciones de alto impacto sin supervisión, sin auditoría de decisiones. -AgentForge aporta el plano de control mínimo que un equipo de plataforma -necesita antes de operar agentes con impacto real. +**Forja de agentes IA** — plataforma de gobernanza genérica para agentes de cualquier tipo. +Catalogación y versionado (estilo Git) de definiciones, políticas y guardrails; +ejecución controlada con runtime stateful, Human-in-the-Loop y observabilidad completa. +Úsala como plano de control en cualquier proyecto que necesite agentes gobernados. ## Arquitectura @@ -18,13 +11,13 @@ necesita antes de operar agentes con impacto real. ┌───────────────────────── docker-compose ──────────────────────────┐ │ │ │ ┌─────────────────────┐ HTTP/JSON ┌────────────────────┐ │ -│ │ agentforge-dashboard│ ────────────────► │ agentforge-core │ │ +│ │ forja-dashboard │ ────────────────► │ forja-core │ │ │ │ Streamlit :8501 │ ◄──────────────── │ FastAPI :8000 │ │ │ └─────────────────────┘ └────────────────────┘ │ │ │ -│ Strategy pattern (Protocol) para LLMProvider, GuardrailEngine, │ -│ AgentRegistry. Persistencia mixta: YAML (definiciones), JSON │ -│ (registry), JSONL (logs append-only), SQLite (checkpoints). │ +│ Strategy pattern para LLMProvider, GuardrailEngine, Registry. │ +│ Persistencia: YAML (defs versionadas), JSONL (logs), SQLite │ +│ (checkpoints LangGraph + HITL). │ └────────────────────────────────────────────────────────────────────┘ ``` @@ -65,14 +58,14 @@ Azure OpenAI real, edita `.env`. | Feature | Ubicación | |---|---| -| Agent Registry | `core/src/agentforge_core/registry/repository.py` | -| Versionado tipo Git | `agents//versions/` + `registry/versioning.py` | -| Guardrails runtime | `core/src/agentforge_core/guardrails/` | -| LangGraph stateful + checkpointing | `core/src/agentforge_core/runtime/` | -| Human-in-the-Loop | nodo `approve_gate` + endpoints `/approve` y `/reject` | -| Observabilidad | `observability/logging.py` (structlog + trace_id) | -| LLM provider abstraction | `core/src/agentforge_core/llm/` | -| Política versionada | `policies/default/versions/v1.yaml` | +| Agent / Policy Registry | `core/src/forja_core/registry/` | +| Versionado Git-like (YAML) | `agents//` + `policies//` | +| Guardrails runtime (strategy) | `core/src/forja_core/guardrails/` | +| LangGraph + HITL + checkpoints | `core/src/forja_core/runtime/` | +| Observabilidad (trace + structlog) | `core/src/forja_core/observability/` | +| LLM abstraction | `core/src/forja_core/llm/` | +| Editores gráficos (nuevo) | dashboard páginas Forjar Agente / Política | +| API REST + dashboard | forja-core :8000 + forja-dashboard :8501 | ## Variables de entorno @@ -85,14 +78,14 @@ Ver [`docs/futuro.md`](docs/futuro.md). ## Estructura del repositorio ``` -agentforge/ -├── core/ servicio FastAPI -├── dashboard/ servicio Streamlit -├── agents/ definiciones declarativas (YAML) -├── policies/ políticas de guardrails -├── data/ estado runtime (gitignored) -├── tests/ pytest unit + integration -└── docs/ documentación adicional +forja/ +├── core/ servicio FastAPI (gobernanza + runtime) +├── dashboard/ Streamlit (UI + editores gráficos) +├── agents/ definiciones de agentes (YAML versionados) +├── policies/ políticas de guardrails (YAML versionados) +├── data/ runtime state (gitignored) +├── tests/ +└── docs/ ``` ## Tests diff --git a/core/Dockerfile b/core/Dockerfile index c879e24..68cca4b 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -33,4 +33,4 @@ EXPOSE 8000 HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 \ CMD curl -fsS http://localhost:8000/health || exit 1 -CMD ["uvicorn", "agentforge_core.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "forja_core.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/core/src/agentforge_core/api/policies.py b/core/src/agentforge_core/api/policies.py deleted file mode 100644 index b0c1e38..0000000 --- a/core/src/agentforge_core/api/policies.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Router /policies: listado de políticas y de sus versiones.""" - -from __future__ import annotations - -from fastapi import APIRouter, HTTPException - -from agentforge_core.api.deps import PolicyStoreDep -from agentforge_core.domain.policy import PolicyDefinition, PolicyVersionMeta - -router = APIRouter() - - -@router.get("", response_model=list[PolicyDefinition]) -def list_policies(store: PolicyStoreDep) -> list[PolicyDefinition]: - return store.list_policies() - - -@router.get("/{name}/versions", response_model=list[PolicyVersionMeta]) -def list_versions(name: str, store: PolicyStoreDep) -> list[PolicyVersionMeta]: - try: - return store.list_versions(name) - except FileNotFoundError as exc: - raise HTTPException(status_code=404, detail=str(exc)) from exc diff --git a/core/src/agentforge_core/__init__.py b/core/src/forja_core/__init__.py similarity index 100% rename from core/src/agentforge_core/__init__.py rename to core/src/forja_core/__init__.py diff --git a/core/src/agentforge_core/api/__init__.py b/core/src/forja_core/api/__init__.py similarity index 100% rename from core/src/agentforge_core/api/__init__.py rename to core/src/forja_core/api/__init__.py diff --git a/core/src/agentforge_core/api/agents.py b/core/src/forja_core/api/agents.py similarity index 64% rename from core/src/agentforge_core/api/agents.py rename to core/src/forja_core/api/agents.py index 41aa31e..67e4998 100644 --- a/core/src/agentforge_core/api/agents.py +++ b/core/src/forja_core/api/agents.py @@ -2,15 +2,22 @@ from __future__ import annotations -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, status +from pydantic import BaseModel -from agentforge_core.api.deps import RegistryDep -from agentforge_core.domain.agent import AgentDefinition, AgentVersionMeta -from agentforge_core.registry.versioning import DiffResult +from forja_core.api.deps import RegistryDep +from forja_core.domain.agent import AgentDefinition, AgentVersionMeta +from forja_core.registry.versioning import DiffResult router = APIRouter() +class CreateAgentVersionRequest(BaseModel): + agent: AgentDefinition + message: str + author: str = "dashboard" + + @router.get("", response_model=list[AgentDefinition]) def list_agents(registry: RegistryDep) -> list[AgentDefinition]: return registry.list_agents() @@ -43,3 +50,12 @@ def get_version(name: str, version: str, registry: RegistryDep) -> AgentDefiniti @router.get("/{name}/versions/{v_from}/diff/{v_to}", response_model=DiffResult) def diff_versions(name: str, v_from: str, v_to: str, registry: RegistryDep) -> DiffResult: return registry.diff_versions(name, v_from, v_to) + + +@router.post("/{name}/versions", response_model=AgentVersionMeta, status_code=status.HTTP_201_CREATED) +def create_agent_version(name: str, req: CreateAgentVersionRequest, registry: RegistryDep) -> AgentVersionMeta: + try: + return registry.upsert_version(name, req.agent, req.message, req.author) + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) from exc + diff --git a/core/src/agentforge_core/api/deps.py b/core/src/forja_core/api/deps.py similarity index 73% rename from core/src/agentforge_core/api/deps.py rename to core/src/forja_core/api/deps.py index a087202..0fa8d03 100644 --- a/core/src/agentforge_core/api/deps.py +++ b/core/src/forja_core/api/deps.py @@ -12,15 +12,15 @@ from typing import Annotated from fastapi import Depends -from agentforge_core.config import Settings -from agentforge_core.guardrails.base import GuardrailEngine -from agentforge_core.guardrails.factory import build_guardrail_engine -from agentforge_core.llm.base import LLMProvider -from agentforge_core.llm.factory import build_llm_provider -from agentforge_core.registry.factory import build_agent_registry, build_policy_store -from agentforge_core.registry.policy_store import FileSystemPolicyStore -from agentforge_core.registry.repository import FileSystemAgentRegistry -from agentforge_core.runtime.orchestrator import AgentOrchestrator +from forja_core.config import Settings +from forja_core.guardrails.base import GuardrailEngine +from forja_core.guardrails.factory import build_guardrail_engine +from forja_core.llm.base import LLMProvider +from forja_core.llm.factory import build_llm_provider +from forja_core.registry.factory import build_agent_registry, build_policy_store +from forja_core.registry.policy_store import FileSystemPolicyStore +from forja_core.registry.repository import FileSystemAgentRegistry +from forja_core.runtime.orchestrator import AgentOrchestrator @lru_cache(maxsize=1) diff --git a/core/src/agentforge_core/api/executions.py b/core/src/forja_core/api/executions.py similarity index 94% rename from core/src/agentforge_core/api/executions.py rename to core/src/forja_core/api/executions.py index 41524c3..9646285 100644 --- a/core/src/agentforge_core/api/executions.py +++ b/core/src/forja_core/api/executions.py @@ -9,23 +9,23 @@ from uuid import UUID from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field -from agentforge_core.api.deps import ( +from forja_core.api.deps import ( OrchestratorDep, PolicyStoreDep, RegistryDep, SettingsDep, ) -from agentforge_core.api.persistence import ( +from forja_core.api.persistence import ( append_execution, append_violation, read_execution_summaries, ) -from agentforge_core.domain.agent import AgentDefinition -from agentforge_core.domain.execution import AgentExecution, AgentExecutionSummary -from agentforge_core.domain.policy import PolicyDefinition -from agentforge_core.registry.policy_store import FileSystemPolicyStore -from agentforge_core.registry.repository import FileSystemAgentRegistry -from agentforge_core.runtime.orchestrator import AgentOrchestrator +from forja_core.domain.agent import AgentDefinition +from forja_core.domain.execution import AgentExecution, AgentExecutionSummary +from forja_core.domain.policy import PolicyDefinition +from forja_core.registry.policy_store import FileSystemPolicyStore +from forja_core.registry.repository import FileSystemAgentRegistry +from forja_core.runtime.orchestrator import AgentOrchestrator router = APIRouter() # POST /agents/{name}/invoke vive aquí pero se monta en main bajo el prefijo /agents. diff --git a/core/src/agentforge_core/api/middlewares.py b/core/src/forja_core/api/middlewares.py similarity index 91% rename from core/src/agentforge_core/api/middlewares.py rename to core/src/forja_core/api/middlewares.py index c88cd4b..0f32fe0 100644 --- a/core/src/agentforge_core/api/middlewares.py +++ b/core/src/forja_core/api/middlewares.py @@ -8,7 +8,7 @@ from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoin from starlette.requests import Request from starlette.responses import Response -from agentforge_core.observability.logging import bind_trace_id, clear_trace_id +from forja_core.observability.logging import bind_trace_id, clear_trace_id class TraceIdMiddleware(BaseHTTPMiddleware): diff --git a/core/src/agentforge_core/api/persistence.py b/core/src/forja_core/api/persistence.py similarity index 93% rename from core/src/agentforge_core/api/persistence.py rename to core/src/forja_core/api/persistence.py index 980c2f5..dbec601 100644 --- a/core/src/agentforge_core/api/persistence.py +++ b/core/src/forja_core/api/persistence.py @@ -8,8 +8,8 @@ from __future__ import annotations from pathlib import Path -from agentforge_core.domain.execution import AgentExecution, AgentExecutionSummary -from agentforge_core.domain.guardrail import GuardrailViolation +from forja_core.domain.execution import AgentExecution, AgentExecutionSummary +from forja_core.domain.guardrail import GuardrailViolation def append_violation(data_dir: Path, violation: GuardrailViolation) -> None: diff --git a/core/src/forja_core/api/policies.py b/core/src/forja_core/api/policies.py new file mode 100644 index 0000000..47c7f0b --- /dev/null +++ b/core/src/forja_core/api/policies.py @@ -0,0 +1,47 @@ +"""Router /policies: listado de políticas y de sus versiones.""" + +from __future__ import annotations + +from fastapi import APIRouter, HTTPException, status +from pydantic import BaseModel + +from forja_core.api.deps import PolicyStoreDep +from forja_core.domain.policy import PolicyDefinition, PolicyVersionMeta + +router = APIRouter() + + +class CreatePolicyVersionRequest(BaseModel): + policy: PolicyDefinition + message: str + author: str = "dashboard" + + +@router.get("", response_model=list[PolicyDefinition]) +def list_policies(store: PolicyStoreDep) -> list[PolicyDefinition]: + return store.list_policies() + + +@router.get("/{name}/versions", response_model=list[PolicyVersionMeta]) +def list_versions(name: str, store: PolicyStoreDep) -> list[PolicyVersionMeta]: + try: + return store.list_versions(name) + except FileNotFoundError as exc: + raise HTTPException(status_code=404, detail=str(exc)) from exc + + +@router.post("/{name}/versions", response_model=PolicyVersionMeta, status_code=status.HTTP_201_CREATED) +def create_policy_version(name: str, req: CreatePolicyVersionRequest, store: PolicyStoreDep) -> PolicyVersionMeta: + try: + return store.upsert_version(name, req.policy, req.message, req.author) + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) from exc + + +@router.get("/validators") +def list_validators() -> dict[str, list[str]]: + return { + "input": ["detect_pii", "prompt_injection", "toxic_language", "forbidden_topics"], + "output": ["schema_match", "pii_leakage", "forbidden_action_keywords", "telco_safety_rules"], + } + diff --git a/core/src/agentforge_core/api/violations.py b/core/src/forja_core/api/violations.py similarity index 81% rename from core/src/agentforge_core/api/violations.py rename to core/src/forja_core/api/violations.py index 7699774..92a9cf5 100644 --- a/core/src/agentforge_core/api/violations.py +++ b/core/src/forja_core/api/violations.py @@ -7,9 +7,9 @@ from uuid import UUID from fastapi import APIRouter, Query -from agentforge_core.api.deps import SettingsDep -from agentforge_core.api.persistence import read_violations -from agentforge_core.domain.guardrail import GuardrailViolation +from forja_core.api.deps import SettingsDep +from forja_core.api.persistence import read_violations +from forja_core.domain.guardrail import GuardrailViolation router = APIRouter() diff --git a/core/src/agentforge_core/config.py b/core/src/forja_core/config.py similarity index 100% rename from core/src/agentforge_core/config.py rename to core/src/forja_core/config.py diff --git a/core/src/agentforge_core/domain/__init__.py b/core/src/forja_core/domain/__init__.py similarity index 100% rename from core/src/agentforge_core/domain/__init__.py rename to core/src/forja_core/domain/__init__.py diff --git a/core/src/agentforge_core/domain/agent.py b/core/src/forja_core/domain/agent.py similarity index 83% rename from core/src/agentforge_core/domain/agent.py rename to core/src/forja_core/domain/agent.py index 3007b99..8513b2f 100644 --- a/core/src/agentforge_core/domain/agent.py +++ b/core/src/forja_core/domain/agent.py @@ -41,3 +41,9 @@ class AgentDefinition(BaseModel): output_schema: dict[str, Any] risk_threshold_for_hitl: int = Field(default=4, ge=1, le=5) updated_at: datetime + input_label: str | None = None + input_placeholder: str | None = None + category: str | None = None + tags: list[str] = Field(default_factory=list) + icon: str | None = None + template: str = "governed_llm" diff --git a/core/src/agentforge_core/domain/execution.py b/core/src/forja_core/domain/execution.py similarity index 96% rename from core/src/agentforge_core/domain/execution.py rename to core/src/forja_core/domain/execution.py index b8eec3a..cdf43f3 100644 --- a/core/src/agentforge_core/domain/execution.py +++ b/core/src/forja_core/domain/execution.py @@ -8,7 +8,7 @@ from uuid import UUID from pydantic import BaseModel, Field -from agentforge_core.domain.guardrail import GuardrailViolation +from forja_core.domain.guardrail import GuardrailViolation class ProposedAction(BaseModel): diff --git a/core/src/agentforge_core/domain/guardrail.py b/core/src/forja_core/domain/guardrail.py similarity index 100% rename from core/src/agentforge_core/domain/guardrail.py rename to core/src/forja_core/domain/guardrail.py diff --git a/core/src/agentforge_core/domain/policy.py b/core/src/forja_core/domain/policy.py similarity index 100% rename from core/src/agentforge_core/domain/policy.py rename to core/src/forja_core/domain/policy.py diff --git a/core/src/agentforge_core/guardrails/__init__.py b/core/src/forja_core/guardrails/__init__.py similarity index 100% rename from core/src/agentforge_core/guardrails/__init__.py rename to core/src/forja_core/guardrails/__init__.py diff --git a/core/src/agentforge_core/guardrails/base.py b/core/src/forja_core/guardrails/base.py similarity index 83% rename from core/src/agentforge_core/guardrails/base.py rename to core/src/forja_core/guardrails/base.py index 36c8dd5..a9b3df1 100644 --- a/core/src/agentforge_core/guardrails/base.py +++ b/core/src/forja_core/guardrails/base.py @@ -5,8 +5,8 @@ from __future__ import annotations from typing import Any, Protocol from uuid import UUID -from agentforge_core.domain.guardrail import GuardrailViolation -from agentforge_core.domain.policy import PolicyDefinition +from forja_core.domain.guardrail import GuardrailViolation +from forja_core.domain.policy import PolicyDefinition class GuardrailEngine(Protocol): diff --git a/core/src/agentforge_core/guardrails/composite.py b/core/src/forja_core/guardrails/composite.py similarity index 86% rename from core/src/agentforge_core/guardrails/composite.py rename to core/src/forja_core/guardrails/composite.py index 4f6b510..85e7e12 100644 --- a/core/src/agentforge_core/guardrails/composite.py +++ b/core/src/forja_core/guardrails/composite.py @@ -6,9 +6,9 @@ import asyncio from typing import Any from uuid import UUID -from agentforge_core.domain.guardrail import GuardrailViolation -from agentforge_core.domain.policy import PolicyDefinition -from agentforge_core.guardrails.base import GuardrailEngine +from forja_core.domain.guardrail import GuardrailViolation +from forja_core.domain.policy import PolicyDefinition +from forja_core.guardrails.base import GuardrailEngine class CompositeGuardrailEngine: diff --git a/core/src/agentforge_core/guardrails/factory.py b/core/src/forja_core/guardrails/factory.py similarity index 72% rename from core/src/agentforge_core/guardrails/factory.py rename to core/src/forja_core/guardrails/factory.py index d198257..78f1b2c 100644 --- a/core/src/agentforge_core/guardrails/factory.py +++ b/core/src/forja_core/guardrails/factory.py @@ -2,11 +2,11 @@ from __future__ import annotations -from agentforge_core.config import Settings -from agentforge_core.guardrails.base import GuardrailEngine -from agentforge_core.guardrails.composite import CompositeGuardrailEngine -from agentforge_core.guardrails.guardrails_ai import GuardrailsAIEngine -from agentforge_core.guardrails.nemo import NeMoGuardrailsEngine +from forja_core.config import Settings +from forja_core.guardrails.base import GuardrailEngine +from forja_core.guardrails.composite import CompositeGuardrailEngine +from forja_core.guardrails.guardrails_ai import GuardrailsAIEngine +from forja_core.guardrails.nemo import NeMoGuardrailsEngine def build_guardrail_engine(settings: Settings) -> GuardrailEngine: diff --git a/core/src/agentforge_core/guardrails/guardrails_ai.py b/core/src/forja_core/guardrails/guardrails_ai.py similarity index 94% rename from core/src/agentforge_core/guardrails/guardrails_ai.py rename to core/src/forja_core/guardrails/guardrails_ai.py index aed57c5..cd77e65 100644 --- a/core/src/agentforge_core/guardrails/guardrails_ai.py +++ b/core/src/forja_core/guardrails/guardrails_ai.py @@ -9,9 +9,9 @@ from uuid import UUID import structlog -from agentforge_core.domain.guardrail import GuardrailViolation -from agentforge_core.domain.policy import PolicyDefinition, PolicyValidator -from agentforge_core.guardrails.validators import ( +from forja_core.domain.guardrail import GuardrailViolation +from forja_core.domain.policy import PolicyDefinition, PolicyValidator +from forja_core.guardrails.validators import ( detect_pii, forbidden_action_keywords, forbidden_topics, diff --git a/core/src/agentforge_core/guardrails/nemo.py b/core/src/forja_core/guardrails/nemo.py similarity index 93% rename from core/src/agentforge_core/guardrails/nemo.py rename to core/src/forja_core/guardrails/nemo.py index 7d064af..23a8ba9 100644 --- a/core/src/agentforge_core/guardrails/nemo.py +++ b/core/src/forja_core/guardrails/nemo.py @@ -12,8 +12,8 @@ from uuid import UUID import structlog -from agentforge_core.domain.guardrail import GuardrailViolation -from agentforge_core.domain.policy import PolicyDefinition +from forja_core.domain.guardrail import GuardrailViolation +from forja_core.domain.policy import PolicyDefinition log = structlog.get_logger(__name__) diff --git a/core/src/agentforge_core/guardrails/validators.py b/core/src/forja_core/guardrails/validators.py similarity index 99% rename from core/src/agentforge_core/guardrails/validators.py rename to core/src/forja_core/guardrails/validators.py index 26b0b3a..3f17563 100644 --- a/core/src/agentforge_core/guardrails/validators.py +++ b/core/src/forja_core/guardrails/validators.py @@ -10,7 +10,7 @@ from uuid import UUID import jsonschema -from agentforge_core.domain.guardrail import GuardrailViolation +from forja_core.domain.guardrail import GuardrailViolation _INJECTION_PATTERNS = [ r"ignore (the )?(previous|prior|above) (instruction|prompt|message)", diff --git a/core/src/agentforge_core/llm/__init__.py b/core/src/forja_core/llm/__init__.py similarity index 100% rename from core/src/agentforge_core/llm/__init__.py rename to core/src/forja_core/llm/__init__.py diff --git a/core/src/agentforge_core/llm/azure.py b/core/src/forja_core/llm/azure.py similarity index 97% rename from core/src/agentforge_core/llm/azure.py rename to core/src/forja_core/llm/azure.py index 0b1264c..ac273c4 100644 --- a/core/src/agentforge_core/llm/azure.py +++ b/core/src/forja_core/llm/azure.py @@ -8,7 +8,7 @@ from typing import Any from openai import AsyncAzureOpenAI -from agentforge_core.llm.base import CompletionResult, Message +from forja_core.llm.base import CompletionResult, Message class AzureOpenAIProvider: diff --git a/core/src/agentforge_core/llm/base.py b/core/src/forja_core/llm/base.py similarity index 100% rename from core/src/agentforge_core/llm/base.py rename to core/src/forja_core/llm/base.py diff --git a/core/src/agentforge_core/llm/factory.py b/core/src/forja_core/llm/factory.py similarity index 76% rename from core/src/agentforge_core/llm/factory.py rename to core/src/forja_core/llm/factory.py index 687b34c..379bbc9 100644 --- a/core/src/agentforge_core/llm/factory.py +++ b/core/src/forja_core/llm/factory.py @@ -2,11 +2,11 @@ from __future__ import annotations -from agentforge_core.config import Settings -from agentforge_core.llm.azure import AzureOpenAIProvider -from agentforge_core.llm.base import LLMProvider -from agentforge_core.llm.mock import MockProvider -from agentforge_core.llm.openai import OpenAIProvider +from forja_core.config import Settings +from forja_core.llm.azure import AzureOpenAIProvider +from forja_core.llm.base import LLMProvider +from forja_core.llm.mock import MockProvider +from forja_core.llm.openai import OpenAIProvider def build_llm_provider(settings: Settings) -> LLMProvider: diff --git a/core/src/agentforge_core/llm/mock.py b/core/src/forja_core/llm/mock.py similarity index 98% rename from core/src/agentforge_core/llm/mock.py rename to core/src/forja_core/llm/mock.py index 024311c..eb15dcb 100644 --- a/core/src/agentforge_core/llm/mock.py +++ b/core/src/forja_core/llm/mock.py @@ -7,7 +7,7 @@ import json import time from typing import Any -from agentforge_core.llm.base import CompletionResult, Message +from forja_core.llm.base import CompletionResult, Message _CANONICAL_RESPONSES: dict[str, dict[str, Any]] = { "sip": { diff --git a/core/src/agentforge_core/llm/openai.py b/core/src/forja_core/llm/openai.py similarity index 96% rename from core/src/agentforge_core/llm/openai.py rename to core/src/forja_core/llm/openai.py index 457fbd4..aabdfdd 100644 --- a/core/src/agentforge_core/llm/openai.py +++ b/core/src/forja_core/llm/openai.py @@ -8,7 +8,7 @@ from typing import Any from openai import AsyncOpenAI -from agentforge_core.llm.base import CompletionResult, Message +from forja_core.llm.base import CompletionResult, Message class OpenAIProvider: diff --git a/core/src/agentforge_core/main.py b/core/src/forja_core/main.py similarity index 73% rename from core/src/agentforge_core/main.py rename to core/src/forja_core/main.py index 1d8efad..458ca49 100644 --- a/core/src/agentforge_core/main.py +++ b/core/src/forja_core/main.py @@ -1,12 +1,12 @@ -"""Composición raíz de la aplicación FastAPI (agentforge-core).""" +"""Composición raíz de la aplicación FastAPI (forja-core).""" from __future__ import annotations from fastapi import FastAPI -from agentforge_core.api.middlewares import TraceIdMiddleware -from agentforge_core.config import Settings -from agentforge_core.observability.logging import configure_logging +from forja_core.api.middlewares import TraceIdMiddleware +from forja_core.config import Settings +from forja_core.observability.logging import configure_logging def create_app() -> FastAPI: @@ -14,7 +14,7 @@ def create_app() -> FastAPI: settings = Settings() configure_logging(level=settings.log_level) app = FastAPI( - title="AgentForge Core", + title="Forja Core", version="0.1.0", description="Plataforma de gobernanza de agentes IA — API REST.", ) @@ -24,7 +24,7 @@ def create_app() -> FastAPI: async def health() -> dict[str, str]: return {"status": "ok"} - from agentforge_core.api import agents, executions, policies, violations + from forja_core.api import agents, executions, policies, violations app.include_router(agents.router, prefix="/agents", tags=["agents"]) app.include_router(executions.invoke_router, prefix="/agents", tags=["agents"]) diff --git a/core/src/agentforge_core/observability/__init__.py b/core/src/forja_core/observability/__init__.py similarity index 100% rename from core/src/agentforge_core/observability/__init__.py rename to core/src/forja_core/observability/__init__.py diff --git a/core/src/agentforge_core/observability/logging.py b/core/src/forja_core/observability/logging.py similarity index 100% rename from core/src/agentforge_core/observability/logging.py rename to core/src/forja_core/observability/logging.py diff --git a/core/src/agentforge_core/registry/__init__.py b/core/src/forja_core/registry/__init__.py similarity index 100% rename from core/src/agentforge_core/registry/__init__.py rename to core/src/forja_core/registry/__init__.py diff --git a/core/src/agentforge_core/registry/factory.py b/core/src/forja_core/registry/factory.py similarity index 73% rename from core/src/agentforge_core/registry/factory.py rename to core/src/forja_core/registry/factory.py index 05792aa..54f92b6 100644 --- a/core/src/agentforge_core/registry/factory.py +++ b/core/src/forja_core/registry/factory.py @@ -2,9 +2,9 @@ from __future__ import annotations -from agentforge_core.config import Settings -from agentforge_core.registry.policy_store import FileSystemPolicyStore -from agentforge_core.registry.repository import FileSystemAgentRegistry +from forja_core.config import Settings +from forja_core.registry.policy_store import FileSystemPolicyStore +from forja_core.registry.repository import FileSystemAgentRegistry def build_agent_registry(settings: Settings) -> FileSystemAgentRegistry: diff --git a/core/src/agentforge_core/registry/policy_store.py b/core/src/forja_core/registry/policy_store.py similarity index 56% rename from core/src/agentforge_core/registry/policy_store.py rename to core/src/forja_core/registry/policy_store.py index e81e03e..c1fde9b 100644 --- a/core/src/agentforge_core/registry/policy_store.py +++ b/core/src/forja_core/registry/policy_store.py @@ -2,13 +2,14 @@ from __future__ import annotations -from datetime import datetime +from datetime import UTC, datetime from pathlib import Path from typing import Any import yaml -from agentforge_core.domain.policy import PolicyDefinition, PolicyVersionMeta +from forja_core.domain.policy import PolicyDefinition, PolicyVersionMeta +from forja_core.registry.versioning import compute_hash class FileSystemPolicyStore: @@ -62,3 +63,48 @@ class FileSystemPolicyStore: if isinstance(value, datetime): return value return datetime.fromisoformat(value.replace("Z", "+00:00")) + + def upsert_version( + self, + name: str, + body: PolicyDefinition, + message: str, + author: str, + ) -> PolicyVersionMeta: + policy_dir = self._root / name + versions_dir = policy_dir / "versions" + versions_dir.mkdir(parents=True, exist_ok=True) + + version_id = body.version + yaml_text = yaml.safe_dump( + body.model_dump(mode="json"), + sort_keys=False, + allow_unicode=True, + ) + h = compute_hash(yaml_text) + + version_path = versions_dir / f"{version_id}.yaml" + version_path.write_text(yaml_text, encoding="utf-8") + + meta = PolicyVersionMeta( + id=version_id, + hash=h, + author=author, + message=message, + created_at=datetime.now(UTC), + ) + + index_path = policy_dir / "index.yaml" + if index_path.exists(): + with index_path.open(encoding="utf-8") as f: + index: dict[str, Any] = yaml.safe_load(f) + else: + index = {"name": name, "versions": [], "active_version": version_id} + + index["versions"] = [v for v in index["versions"] if v["id"] != version_id] + index["versions"].append(meta.model_dump(mode="json")) + index["active_version"] = version_id + + with index_path.open("w", encoding="utf-8") as f: + yaml.safe_dump(index, f, sort_keys=False, allow_unicode=True) + return meta diff --git a/core/src/agentforge_core/registry/repository.py b/core/src/forja_core/registry/repository.py similarity index 96% rename from core/src/agentforge_core/registry/repository.py rename to core/src/forja_core/registry/repository.py index 7f52b62..7ff7022 100644 --- a/core/src/agentforge_core/registry/repository.py +++ b/core/src/forja_core/registry/repository.py @@ -8,8 +8,8 @@ from typing import Any import yaml -from agentforge_core.domain.agent import AgentDefinition, AgentVersionMeta -from agentforge_core.registry.versioning import DiffResult, compute_hash, unified_diff +from forja_core.domain.agent import AgentDefinition, AgentVersionMeta +from forja_core.registry.versioning import DiffResult, compute_hash, unified_diff class FileSystemAgentRegistry: diff --git a/core/src/agentforge_core/registry/versioning.py b/core/src/forja_core/registry/versioning.py similarity index 100% rename from core/src/agentforge_core/registry/versioning.py rename to core/src/forja_core/registry/versioning.py diff --git a/core/src/agentforge_core/runtime/__init__.py b/core/src/forja_core/runtime/__init__.py similarity index 100% rename from core/src/agentforge_core/runtime/__init__.py rename to core/src/forja_core/runtime/__init__.py diff --git a/core/src/agentforge_core/runtime/checkpointer.py b/core/src/forja_core/runtime/checkpointer.py similarity index 100% rename from core/src/agentforge_core/runtime/checkpointer.py rename to core/src/forja_core/runtime/checkpointer.py diff --git a/core/src/agentforge_core/runtime/graph.py b/core/src/forja_core/runtime/graph.py similarity index 87% rename from core/src/agentforge_core/runtime/graph.py rename to core/src/forja_core/runtime/graph.py index b9054e9..2003fff 100644 --- a/core/src/agentforge_core/runtime/graph.py +++ b/core/src/forja_core/runtime/graph.py @@ -7,11 +7,11 @@ from typing import Any from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.graph import END, START, StateGraph -from agentforge_core.domain.agent import AgentDefinition -from agentforge_core.domain.policy import PolicyDefinition -from agentforge_core.guardrails.base import GuardrailEngine -from agentforge_core.llm.base import LLMProvider -from agentforge_core.runtime.nodes import ( +from forja_core.domain.agent import AgentDefinition +from forja_core.domain.policy import PolicyDefinition +from forja_core.guardrails.base import GuardrailEngine +from forja_core.llm.base import LLMProvider +from forja_core.runtime.nodes import ( build_node_approve_gate, build_node_finalize, build_node_llm_reason, @@ -19,7 +19,7 @@ from agentforge_core.runtime.nodes import ( build_node_validate_input, build_node_validate_output, ) -from agentforge_core.runtime.state import AgentState +from forja_core.runtime.state import AgentState def build_graph( diff --git a/core/src/agentforge_core/runtime/nodes.py b/core/src/forja_core/runtime/nodes.py similarity index 96% rename from core/src/agentforge_core/runtime/nodes.py rename to core/src/forja_core/runtime/nodes.py index 3c884e7..afe7519 100644 --- a/core/src/agentforge_core/runtime/nodes.py +++ b/core/src/forja_core/runtime/nodes.py @@ -12,11 +12,11 @@ from uuid import UUID import structlog from langgraph.types import interrupt -from agentforge_core.domain.agent import AgentDefinition -from agentforge_core.domain.policy import PolicyDefinition -from agentforge_core.guardrails.base import GuardrailEngine -from agentforge_core.llm.base import LLMProvider, Message -from agentforge_core.runtime.state import AgentState +from forja_core.domain.agent import AgentDefinition +from forja_core.domain.policy import PolicyDefinition +from forja_core.guardrails.base import GuardrailEngine +from forja_core.llm.base import LLMProvider, Message +from forja_core.runtime.state import AgentState log = structlog.get_logger(__name__) diff --git a/core/src/agentforge_core/runtime/orchestrator.py b/core/src/forja_core/runtime/orchestrator.py similarity index 93% rename from core/src/agentforge_core/runtime/orchestrator.py rename to core/src/forja_core/runtime/orchestrator.py index aed377a..92fb35f 100644 --- a/core/src/agentforge_core/runtime/orchestrator.py +++ b/core/src/forja_core/runtime/orchestrator.py @@ -18,14 +18,14 @@ from uuid import UUID, uuid4 import structlog from langgraph.types import Command -from agentforge_core.domain.agent import AgentDefinition -from agentforge_core.domain.execution import AgentExecution, DecisionStep, ProposedAction -from agentforge_core.domain.guardrail import GuardrailViolation -from agentforge_core.domain.policy import PolicyDefinition -from agentforge_core.guardrails.base import GuardrailEngine -from agentforge_core.llm.base import LLMProvider -from agentforge_core.runtime.checkpointer import build_checkpointer -from agentforge_core.runtime.graph import build_graph +from forja_core.domain.agent import AgentDefinition +from forja_core.domain.execution import AgentExecution, DecisionStep, ProposedAction +from forja_core.domain.guardrail import GuardrailViolation +from forja_core.domain.policy import PolicyDefinition +from forja_core.guardrails.base import GuardrailEngine +from forja_core.llm.base import LLMProvider +from forja_core.runtime.checkpointer import build_checkpointer +from forja_core.runtime.graph import build_graph log = structlog.get_logger(__name__) diff --git a/core/src/agentforge_core/runtime/state.py b/core/src/forja_core/runtime/state.py similarity index 100% rename from core/src/agentforge_core/runtime/state.py rename to core/src/forja_core/runtime/state.py diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile index 58f42da..c65968f 100644 --- a/dashboard/Dockerfile +++ b/dashboard/Dockerfile @@ -26,6 +26,6 @@ EXPOSE 8501 HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \ CMD curl -fsS http://localhost:8501/_stcore/health || exit 1 -CMD ["streamlit", "run", "/app/src/agentforge_dashboard/app.py", \ +CMD ["streamlit", "run", "/app/src/forja_dashboard/app.py", \ "--server.address=0.0.0.0", "--server.port=8501", \ "--server.headless=true", "--browser.gatherUsageStats=false"] diff --git a/dashboard/src/agentforge_dashboard/app.py b/dashboard/src/agentforge_dashboard/app.py deleted file mode 100644 index a890b45..0000000 --- a/dashboard/src/agentforge_dashboard/app.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Entry point del dashboard Streamlit con sidebar de branding y health.""" - -from __future__ import annotations - -import streamlit as st - -from agentforge_dashboard.client import CoreClient - - -@st.cache_resource -def get_client() -> CoreClient: - return CoreClient() - - -def main() -> None: - st.set_page_config( - page_title="AgentForge", - page_icon="🛡️", - layout="wide", - initial_sidebar_state="expanded", - ) - st.sidebar.markdown("## 🛡️ AgentForge") - st.sidebar.caption("Plataforma de gobernanza de agentes IA") - client = get_client() - try: - client.health() - st.sidebar.success("Core API: OK") - except Exception as exc: - st.sidebar.error(f"Core API unreachable: {exc}") - - st.title("🛡️ AgentForge") - st.markdown( - """ - Bienvenido al panel de gobernanza de agentes IA. - - Usa la barra lateral para navegar: - - **Registro**: catálogo de agentes y versiones. - - **Ejecutar**: lanzar un agente con guardrails completos. - - **Aprobaciones**: ejecuciones pausadas a la espera de revisión humana. - - **Historial**: trazas, violaciones y resultados. - - **Politicas**: políticas de guardrails y diff entre versiones. - """ - ) - - -if __name__ == "__main__": - main() diff --git a/dashboard/src/agentforge_dashboard/__init__.py b/dashboard/src/forja_dashboard/__init__.py similarity index 100% rename from dashboard/src/agentforge_dashboard/__init__.py rename to dashboard/src/forja_dashboard/__init__.py diff --git a/dashboard/src/forja_dashboard/app.py b/dashboard/src/forja_dashboard/app.py new file mode 100644 index 0000000..e9b612c --- /dev/null +++ b/dashboard/src/forja_dashboard/app.py @@ -0,0 +1,47 @@ +"""Entry point del dashboard Streamlit con sidebar de branding y health.""" + +from __future__ import annotations + +import streamlit as st + +from forja_dashboard.client import CoreClient + + +@st.cache_resource +def get_client() -> CoreClient: + return CoreClient() + + +def main() -> None: + st.set_page_config( + page_title="Forja", + page_icon="🔨", + layout="wide", + initial_sidebar_state="expanded", + ) + st.sidebar.markdown("## 🔨 Forja") + st.sidebar.caption("Forja de agentes IA — gobernanza para cualquier proyecto") + client = get_client() + try: + client.health() + st.sidebar.success("Core API: OK") + except Exception as exc: + st.sidebar.error(f"Core API unreachable: {exc}") + + st.title("🔨 Forja") + st.markdown( + """ + Bienvenido a la forja de agentes IA. + + Navega por la barra lateral: + - **Registro**: catálogo y versiones. + - **Ejecutar**: invocar con gobierno completo. + - **Aprobaciones** / **Historial**. + - **Politicas**. + - **🔨 Forjar Agente** y **🛡️ Forjar Politica**: editores gráficos (nuevo). + """ + ) + + +if __name__ == "__main__": + main() diff --git a/dashboard/src/agentforge_dashboard/client.py b/dashboard/src/forja_dashboard/client.py similarity index 79% rename from dashboard/src/agentforge_dashboard/client.py rename to dashboard/src/forja_dashboard/client.py index cdaa2ab..3cf8d67 100644 --- a/dashboard/src/agentforge_dashboard/client.py +++ b/dashboard/src/forja_dashboard/client.py @@ -12,7 +12,7 @@ class CoreClient: """Cliente sincrono (Streamlit es sync). 2 retries con httpx Transport.""" def __init__(self, base_url: str | None = None, timeout: float = 30.0) -> None: - self.base_url = base_url or os.getenv("AGENTFORGE_CORE_URL", "http://localhost:8000") + self.base_url = base_url or os.getenv("FORJA_CORE_URL", "http://localhost:8000") transport = httpx.HTTPTransport(retries=2) self._client = httpx.Client(base_url=self.base_url, timeout=timeout, transport=transport) @@ -56,6 +56,17 @@ class CoreClient: def list_policy_versions(self, name: str) -> list[dict]: return self._get(f"/policies/{name}/versions") + def create_agent_version(self, name: str, agent_def: dict, message: str, author: str = "dashboard") -> dict: + body = {"agent": agent_def, "message": message, "author": author} + return self._post(f"/agents/{name}/versions", body) + + def create_policy_version(self, name: str, policy_def: dict, message: str, author: str = "dashboard") -> dict: + body = {"policy": policy_def, "message": message, "author": author} + return self._post(f"/policies/{name}/versions", body) + + def list_validators(self) -> dict: + return self._get("/policies/validators") + # ----- helpers privados ----- def _get(self, path: str, params: dict | None = None) -> Any: diff --git a/dashboard/src/agentforge_dashboard/components/__init__.py b/dashboard/src/forja_dashboard/components/__init__.py similarity index 100% rename from dashboard/src/agentforge_dashboard/components/__init__.py rename to dashboard/src/forja_dashboard/components/__init__.py diff --git a/dashboard/src/agentforge_dashboard/components/diff_view.py b/dashboard/src/forja_dashboard/components/diff_view.py similarity index 100% rename from dashboard/src/agentforge_dashboard/components/diff_view.py rename to dashboard/src/forja_dashboard/components/diff_view.py diff --git a/dashboard/src/agentforge_dashboard/components/trace_view.py b/dashboard/src/forja_dashboard/components/trace_view.py similarity index 100% rename from dashboard/src/agentforge_dashboard/components/trace_view.py rename to dashboard/src/forja_dashboard/components/trace_view.py diff --git a/dashboard/src/agentforge_dashboard/components/violation_view.py b/dashboard/src/forja_dashboard/components/violation_view.py similarity index 100% rename from dashboard/src/agentforge_dashboard/components/violation_view.py rename to dashboard/src/forja_dashboard/components/violation_view.py diff --git a/dashboard/src/agentforge_dashboard/pages/1_🏛️_Registro.py b/dashboard/src/forja_dashboard/pages/1_🏛️_Registro.py similarity index 89% rename from dashboard/src/agentforge_dashboard/pages/1_🏛️_Registro.py rename to dashboard/src/forja_dashboard/pages/1_🏛️_Registro.py index 3a183b4..0e36a47 100644 --- a/dashboard/src/agentforge_dashboard/pages/1_🏛️_Registro.py +++ b/dashboard/src/forja_dashboard/pages/1_🏛️_Registro.py @@ -4,8 +4,8 @@ from __future__ import annotations import streamlit as st -from agentforge_dashboard.client import CoreClient -from agentforge_dashboard.components.diff_view import render_unified_diff +from forja_dashboard.client import CoreClient +from forja_dashboard.components.diff_view import render_unified_diff @st.cache_resource @@ -16,6 +16,7 @@ def get_client() -> CoreClient: client = get_client() st.title("🏛️ Registro de Agentes") st.caption("Catálogo central de agentes con versionado tipo Git.") +st.info("Usa **🔨 Forjar Agente** (barra lateral) para crear o versionar gráficamente.") agents = client.list_agents() if not agents: diff --git a/dashboard/src/agentforge_dashboard/pages/2_▶️_Ejecutar.py b/dashboard/src/forja_dashboard/pages/2_▶️_Ejecutar.py similarity index 81% rename from dashboard/src/agentforge_dashboard/pages/2_▶️_Ejecutar.py rename to dashboard/src/forja_dashboard/pages/2_▶️_Ejecutar.py index 45b05e1..d8368e4 100644 --- a/dashboard/src/agentforge_dashboard/pages/2_▶️_Ejecutar.py +++ b/dashboard/src/forja_dashboard/pages/2_▶️_Ejecutar.py @@ -6,9 +6,9 @@ from pathlib import Path import streamlit as st -from agentforge_dashboard.client import CoreClient -from agentforge_dashboard.components.trace_view import render_trace -from agentforge_dashboard.components.violation_view import render_violations +from forja_dashboard.client import CoreClient +from forja_dashboard.components.trace_view import render_trace +from forja_dashboard.components.violation_view import render_violations @st.cache_resource @@ -27,6 +27,7 @@ if not agents: names = [a["name"] for a in agents] agent_name = st.selectbox("Agente", names) +agent = client.get_agent(agent_name) # Cargar escenarios pregrabados si existen en el disco montado. examples_dir = Path("agents") / agent_name / "examples" @@ -40,12 +41,9 @@ with col_r: st.session_state["input_text"] = ef.read_text(encoding="utf-8") with col_l: - user_input = st.text_area( - "Descripción del incidente", - height=240, - key="input_text", - placeholder="Pega aquí la descripción del incidente o usa un escenario...", - ) + label = agent.get("input_label") or "Input para el agente" + ph = agent.get("input_placeholder") or "Escribe el input para el agente o usa un escenario..." + user_input = st.text_area(label, height=240, key="input_text", placeholder=ph) if st.button("🚀 Invocar agente", type="primary", disabled=not user_input): with st.spinner("Ejecutando..."): diff --git a/dashboard/src/agentforge_dashboard/pages/3_🤝_Aprobaciones.py b/dashboard/src/forja_dashboard/pages/3_🤝_Aprobaciones.py similarity index 98% rename from dashboard/src/agentforge_dashboard/pages/3_🤝_Aprobaciones.py rename to dashboard/src/forja_dashboard/pages/3_🤝_Aprobaciones.py index f316343..7683d2f 100644 --- a/dashboard/src/agentforge_dashboard/pages/3_🤝_Aprobaciones.py +++ b/dashboard/src/forja_dashboard/pages/3_🤝_Aprobaciones.py @@ -4,7 +4,7 @@ from __future__ import annotations import streamlit as st -from agentforge_dashboard.client import CoreClient +from forja_dashboard.client import CoreClient @st.cache_resource diff --git a/dashboard/src/agentforge_dashboard/pages/4_📜_Historial.py b/dashboard/src/forja_dashboard/pages/4_📜_Historial.py similarity index 89% rename from dashboard/src/agentforge_dashboard/pages/4_📜_Historial.py rename to dashboard/src/forja_dashboard/pages/4_📜_Historial.py index 113e219..df72d43 100644 --- a/dashboard/src/agentforge_dashboard/pages/4_📜_Historial.py +++ b/dashboard/src/forja_dashboard/pages/4_📜_Historial.py @@ -4,9 +4,9 @@ from __future__ import annotations import streamlit as st -from agentforge_dashboard.client import CoreClient -from agentforge_dashboard.components.trace_view import render_trace -from agentforge_dashboard.components.violation_view import render_violations +from forja_dashboard.client import CoreClient +from forja_dashboard.components.trace_view import render_trace +from forja_dashboard.components.violation_view import render_violations @st.cache_resource diff --git a/dashboard/src/agentforge_dashboard/pages/5_📐_Politicas.py b/dashboard/src/forja_dashboard/pages/5_📐_Politicas.py similarity index 89% rename from dashboard/src/agentforge_dashboard/pages/5_📐_Politicas.py rename to dashboard/src/forja_dashboard/pages/5_📐_Politicas.py index 1482eec..6537fbe 100644 --- a/dashboard/src/agentforge_dashboard/pages/5_📐_Politicas.py +++ b/dashboard/src/forja_dashboard/pages/5_📐_Politicas.py @@ -4,7 +4,7 @@ from __future__ import annotations import streamlit as st -from agentforge_dashboard.client import CoreClient +from forja_dashboard.client import CoreClient @st.cache_resource @@ -15,6 +15,7 @@ def get_client() -> CoreClient: client = get_client() st.title("📐 Politicas de guardrails") st.caption("Inventario de políticas y sus versiones (estilo Git).") +st.info("Usa **🛡️ Forjar Politica** (barra lateral) para componer y versionar gráficamente.") policies = client.list_policies() if not policies: diff --git a/dashboard/src/forja_dashboard/pages/6_🔨_Forjar_Agente.py b/dashboard/src/forja_dashboard/pages/6_🔨_Forjar_Agente.py new file mode 100644 index 0000000..acdd559 --- /dev/null +++ b/dashboard/src/forja_dashboard/pages/6_🔨_Forjar_Agente.py @@ -0,0 +1,115 @@ +"""Página de editor gráfico para crear o versionar agentes (Forja).""" + +from __future__ import annotations + +import json + +import streamlit as st + +from forja_dashboard.client import CoreClient + + +@st.cache_resource +def get_client() -> CoreClient: + return CoreClient() + + +client = get_client() + +st.title("🔨 Forjar Agente") +st.caption("Editor gráfico. Crea un agente nuevo o una nueva versión de uno existente. Todo se guarda como YAML versionado.") + +agents = client.list_agents() +agent_names = [a["name"] for a in agents] if agents else [] +mode = st.radio("Modo", ["Nuevo agente", "Nueva versión de existente"], horizontal=True) + +base_name = None +if mode == "Nueva versión de existente" and agent_names: + base_name = st.selectbox("Agente base", agent_names) +elif mode == "Nueva versión de existente": + st.warning("No hay agentes. Crea uno nuevo.") + st.stop() + +with st.form("forge_agent", clear_on_submit=False): + col1, col2 = st.columns(2) + with col1: + name = st.text_input("Nombre del agente", value=base_name or "mi-nuevo-agente") + version = st.text_input("Versión (ej. v1)", value="v1") + owner = st.text_input("Owner", value="usuario") + state = st.selectbox("Estado", ["draft", "active", "deprecated"], index=1) + with col2: + purpose = st.text_area("Propósito / descripción", value="Agente gobernado por Forja", height=80) + category = st.text_input("Categoría (opcional)", value="") + tags_str = st.text_input("Tags (coma separadas)", value="") + icon = st.text_input("Icono emoji (opcional)", value="🤖") + + st.subheader("LLM") + llm_col1, llm_col2 = st.columns(4) + with llm_col1: + provider = st.selectbox("Provider", ["mock", "azure", "openai"], index=0) + with llm_col2: + model = st.text_input("Modelo", value="gpt-4o") + with llm_col3: + temperature = st.number_input("Temperature", 0.0, 2.0, 0.2, 0.1) + with llm_col4: + max_tokens = st.number_input("Max tokens", 1, 128000, 2000) + + st.subheader("Prompt y Schema") + system_prompt = st.text_area("System prompt", value="Eres un asistente útil. Responde en JSON según el schema.", height=160) + schema_text = st.text_area("Output schema (JSON)", value=json.dumps({"type": "object", "properties": {"result": {"type": "string"}}}, indent=2), height=120) + + st.subheader("Guardrails y HITL") + policies = client.list_policies() + policy_names = [p["name"] for p in policies] if policies else ["default"] + guardrails = st.multiselect("Políticas de guardrails activas", policy_names, default=policy_names[:1] if policy_names else []) + risk = st.slider("Risk threshold para HITL", 1, 5, 4) + + st.subheader("Metadatos de versión") + message = st.text_input("Mensaje de versión (como commit)", value="Creación vía editor gráfico de Forja") + author = st.text_input("Autor", value="dashboard") + + submitted = st.form_submit_button("🔨 Forjar / Guardar versión", type="primary") + +if submitted: + try: + output_schema = json.loads(schema_text) + except Exception as e: + st.error(f"Schema JSON inválido: {e}") + st.stop() + + tags = [t.strip() for t in tags_str.split(",") if t.strip()] if tags_str else [] + + agent_def = { + "name": name, + "version": version, + "owner": owner, + "purpose": purpose, + "state": state, + "guardrails": guardrails or ["default"], + "llm": { + "provider": provider, + "model": model, + "temperature": float(temperature), + "max_tokens": int(max_tokens), + }, + "system_prompt": system_prompt, + "output_schema": output_schema, + "risk_threshold_for_hitl": int(risk), + "updated_at": "2026-05-23T00:00:00Z", + "input_label": "Input para el agente", + "input_placeholder": "Escribe aquí el input...", + "category": category or None, + "tags": tags, + "icon": icon or None, + "template": "governed_llm", + } + + try: + res = client.create_agent_version(name, agent_def, message, author) + if "api_error" in res: + st.error(f"Error API: {res['api_error']}") + else: + st.success(f"Versión {version} de {name} creada correctamente. Recarga Registro para verla.") + st.json(res) + except Exception as exc: + st.error(f"Error al forjar: {exc}") diff --git a/dashboard/src/forja_dashboard/pages/7_🛡️_Forjar_Politica.py b/dashboard/src/forja_dashboard/pages/7_🛡️_Forjar_Politica.py new file mode 100644 index 0000000..83c2f52 --- /dev/null +++ b/dashboard/src/forja_dashboard/pages/7_🛡️_Forjar_Politica.py @@ -0,0 +1,82 @@ +"""Página de editor gráfico para políticas de guardrails (Forja).""" + +from __future__ import annotations + +import json + +import streamlit as st + +from forja_dashboard.client import CoreClient + + +@st.cache_resource +def get_client() -> CoreClient: + return CoreClient() + + +client = get_client() + +st.title("🛡️ Forjar Política") +st.caption("Compositor gráfico de políticas. Añade validadores de entrada/salida, configura y versiona.") + +policies = client.list_policies() +policy_names = [p["name"] for p in policies] if policies else [] + +mode = st.radio("Modo", ["Nueva política", "Nueva versión de existente"], horizontal=True) +base = None +if mode == "Nueva versión de existente" and policy_names: + base = st.selectbox("Política base", policy_names) + +validators_meta = client.list_validators() +input_types = validators_meta.get("input", []) +output_types = validators_meta.get("output", []) + +def validator_form(stage: str, types: list[str], key_prefix: str) -> list[dict]: + st.markdown(f"**Validadores de {stage}**") + result = [] + num = st.number_input(f"Número de validadores {stage}", 0, 12, 2, key=f"num_{key_prefix}") + for i in range(int(num)): + with st.expander(f"Validador {i+1} ({stage})", expanded=True): + t = st.selectbox("Tipo", types, key=f"type_{key_prefix}_{i}") + cfg_text = st.text_area("Config (JSON)", value="{}", key=f"cfg_{key_prefix}_{i}", height=80) + try: + cfg = json.loads(cfg_text) + except Exception: + cfg = {} + result.append({"type": t, "config": cfg}) + return result + +with st.form("forge_policy"): + name = st.text_input("Nombre de la política", value=base or "mi-politica") + version = st.text_input("Versión", value="v1") + description = st.text_area("Descripción", value="Política creada con el editor gráfico de Forja") + on_error = st.selectbox("on_validator_error", ["fail_closed", "fail_open"], index=0) + + st.divider() + input_validators = validator_form("input", input_types, "in") + output_validators = validator_form("output", output_types, "out") + + st.subheader("Metadatos de versión") + message = st.text_input("Mensaje", value="Creada vía UI gráfica") + author = st.text_input("Autor", value="dashboard") + + submitted = st.form_submit_button("🛡️ Guardar versión de política", type="primary") + +if submitted: + policy_def = { + "name": name, + "version": version, + "description": description, + "input_validators": input_validators, + "output_validators": output_validators, + "on_validator_error": on_error, + } + try: + res = client.create_policy_version(name, policy_def, message, author) + if "api_error" in res: + st.error(res["api_error"]) + else: + st.success(f"Política {name}@{version} guardada.") + st.json(res) + except Exception as e: + st.error(str(e)) diff --git a/dashboard/src/agentforge_dashboard/pages/__init__.py b/dashboard/src/forja_dashboard/pages/__init__.py similarity index 100% rename from dashboard/src/agentforge_dashboard/pages/__init__.py rename to dashboard/src/forja_dashboard/pages/__init__.py diff --git a/docker-compose.yml b/docker-compose.yml index 9de6546..f27edd5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,8 @@ services: build: context: . dockerfile: core/Dockerfile - image: agentforge-core:dev - container_name: agentforge-core + image: forja-core:dev + container_name: forja-core ports: - "8000:8000" env_file: @@ -14,8 +14,8 @@ services: AGENTS_DIR: /app/agents POLICIES_DIR: /app/policies volumes: - - ./agents:/app/agents:ro - - ./policies:/app/policies:ro + - ./agents:/app/agents:rw + - ./policies:/app/policies:rw - ./data:/app/data healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:8000/health"] @@ -29,15 +29,15 @@ services: build: context: . dockerfile: dashboard/Dockerfile - image: agentforge-dashboard:dev - container_name: agentforge-dashboard + image: forja-dashboard:dev + container_name: forja-dashboard depends_on: core: condition: service_healthy ports: - "8501:8501" environment: - AGENTFORGE_CORE_URL: http://core:8000 + FORJA_CORE_URL: http://core:8000 volumes: - - ./agents:/app/agents:ro # para que el dashboard lea los escenarios + - ./agents:/app/agents:rw # rw para editores gráficos + escenarios restart: unless-stopped diff --git a/docs/componentes.md b/docs/componentes.md index 6b1d851..f1cfd90 100644 --- a/docs/componentes.md +++ b/docs/componentes.md @@ -1,4 +1,4 @@ -# Componentes de AgentForge y su interrelación (bajo nivel) +# Componentes de Forja y su interrelación (bajo nivel) > **Alcance.** Este documento es la **referencia de cableado**: módulos exactos, > firmas, el grafo de dependencias de imports, el grafo de inyección de @@ -9,7 +9,7 @@ > - ¿Las decisiones técnicas resumidas? → [`ARCHITECTURE.md`](../ARCHITECTURE.md). > - ¿Cómo arrancarlo? → [`README.md`](../README.md). > -> Rutas relativas a `core/src/agentforge_core/` salvo que se diga otra cosa. +> Rutas relativas a `core/src/ forja_core/` salvo que se diga otra cosa. > Refleja el estado del repo en `v0.1.0`. --- @@ -67,7 +67,7 @@ NIVEL 6 main ⇐ api (routers), api.middlewares, config, observability.logging → create_app(), app (separado, sin imports del paquete core) - dashboard/src/agentforge_dashboard/* ← habla con `main` por HTTP, no por import + dashboard/src/ forja_dashboard/* ← habla con `main` por HTTP, no por import ``` Reglas que se cumplen y conviene mantener: @@ -333,11 +333,11 @@ Hashing/versionado: `registry/versioning.compute_hash(yaml_text)` = SHA-256 del --- -## 6. Dashboard ↔ Core (`agentforge_dashboard`) +## 6. Dashboard ↔ Core (` forja_dashboard`) El dashboard no comparte código con el core: solo lo llama por HTTP a través de -`CoreClient` (`dashboard/src/agentforge_dashboard/client.py`, httpx síncrono con 2 -retries, `base_url = AGENTFORGE_CORE_URL`; mapea 404/409/422 → `{"error": }`). +`CoreClient` (`dashboard/src/ forja_dashboard/client.py`, httpx síncrono con 2 +retries, `base_url = FORJA_CORE_URL`; mapea 404/409/422 → `{"error": }`). | `CoreClient.` | Endpoint del core | Página(s) que lo usan | |-----------------------|-------------------|-----------------------| @@ -365,11 +365,11 @@ Componentes reutilizables: `components/diff_view.render_unified_diff(diff_text)` ## 7. Arranque y ciclo de vida -**Proceso core** (`uvicorn agentforge_core.main:app`): -1. Import de `agentforge_core.main` ⇒ se ejecuta `app = create_app()`: +**Proceso core** (`uvicorn forja_core.main:app`): +1. Import de ` forja_core.main` ⇒ se ejecuta `app = create_app()`: `Settings()` → `configure_logging(level=settings.log_level)` → `FastAPI(...)` → `app.add_middleware(TraceIdMiddleware)` → registra `GET /health` → - `from agentforge_core.api import agents, executions, policies, violations` → + `from forja_core.api import agents, executions, policies, violations` → `include_router` ×5. 2. Las dependencias (`deps.get_registry`, `get_policy_store`, `get_llm_provider`, `get_guardrail_engine`, `get_orchestrator`) **no** se construyen aún; se @@ -378,12 +378,12 @@ Componentes reutilizables: `components/diff_view.render_unified_diff(diff_text)` pueden disparar la construcción perezosa) → handler → respuesta con `X-Trace-Id`. **Contenedores** (`docker-compose.yml`): servicio `core` (`core/Dockerfile`, -`uvicorn agentforge_core.main:app --host 0.0.0.0 --port 8000`, `HEALTHCHECK` → +`uvicorn forja_core.main:app --host 0.0.0.0 --port 8000`, `HEALTHCHECK` → `curl /health`, monta `./agents:ro`, `./policies:ro`, `./data:rw`, env `DATA_DIR=/app/data`, `AGENTS_DIR=/app/agents`, `POLICIES_DIR=/app/policies`, `env_file: .env`); servicio `dashboard` (`dashboard/Dockerfile`, `streamlit run app.py`, `HEALTHCHECK` → `/_stcore/health`, `depends_on: core: service_healthy`, -env `AGENTFORGE_CORE_URL=http://core:8000`, monta `./agents:ro` para leer los +env `FORJA_CORE_URL=http://core:8000`, monta `./agents:ro` para leer los `examples/*.txt`). **`Settings` (env vars)** — `config.py`: diff --git a/docs/explicacion.md b/docs/explicacion.md index 9d03df2..22d3318 100644 --- a/docs/explicacion.md +++ b/docs/explicacion.md @@ -1,4 +1,4 @@ -# AgentForge explicado de principio a fin +# Forja explicado de principio a fin > **Para quién es esto.** Una guía didáctica para alguien que llega nuevo al > proyecto —técnico o no— y quiere entender *qué hace*, *por qué está hecho así* @@ -18,7 +18,7 @@ > opacos: prompts que cambian sin historial, validaciones inconsistentes, acciones > de alto impacto sin supervisión y ninguna auditoría de lo que decidió el agente. -**AgentForge es el "plano de control" que pones *delante* de tus agentes** antes de +**Forja es el "plano de control" que pones *delante* de tus agentes** antes de dejarlos tocar nada importante. No es un framework para *construir* agentes; es la capa que los **cataloga, versiona, valida, ejecuta de forma supervisada y audita**. @@ -47,13 +47,13 @@ Si entiendes estas seis ideas, entiendes el proyecto. Todo lo demás son detalle ## 3. Vista de pájaro: dos servicios -AgentForge son **dos procesos** que se hablan por HTTP/JSON: +Forja son **dos procesos** que se hablan por HTTP/JSON: ``` ┌───────────────────────────── docker-compose ──────────────────────────────┐ │ │ │ ┌──────────────────────┐ HTTP/JSON ┌────────────────────────┐ │ -│ │ agentforge-dashboard │ ───────────────► │ agentforge-core │ │ +│ │ forja-dashboard │ ───────────────► │ forja-core │ │ │ │ Streamlit :8501 │ ◄─────────────── │ FastAPI :8000 │ │ │ │ (la "consola") │ │ (el cerebro) │ │ │ └──────────────────────┘ └───────────┬────────────┘ │ @@ -69,9 +69,9 @@ AgentForge son **dos procesos** que se hablan por HTTP/JSON: └────────────────────────────────────────────────────────────────────────────┘ ``` -- **`agentforge-core`** (FastAPI, puerto 8000) — todo el dominio: registry de +- **`forja-core`** (FastAPI, puerto 8000) — todo el dominio: registry de agentes, motor de guardrails, runtime de ejecución, persistencia. No tiene UI. -- **`agentforge-dashboard`** (Streamlit, puerto 8501) — una consola visual. **No +- **`forja-dashboard`** (Streamlit, puerto 8501) — una consola visual. **No contiene lógica de negocio**: es un cliente HTTP del core con cinco páginas. La separación importa: el core podría servir a una CLI, a otro servicio, a un @@ -97,7 +97,7 @@ pipeline... el dashboard es solo una de las caras posibles. ## 4. Recorrido por los módulos (y quién depende de quién) -El código del core vive bajo `core/src/agentforge_core/`. Lo agrupo por capas, de +El código del core vive bajo `core/src/ forja_core/`. Lo agrupo por capas, de las más internas (sin dependencias) a las más externas. ### 4.1 `domain/` — el vocabulario del sistema @@ -278,7 +278,7 @@ inyectan). Dependen de él: la API (`api/executions.py` solo conoce el | `executions.py` | El más cargado: `POST /agents/{n}/invoke`, `GET /executions`, `GET /executions/{trace_id}`, `POST /executions/{trace_id}/approve`, `POST /executions/{trace_id}/reject`. Mantiene además `execution_index.json` (mapa `trace_id → agente/versión`) para poder reanudar tras un reinicio. | | `policies.py`, `violations.py` | Routers `/policies` y `/violations` (este con filtros por severidad, stage, etc.). | -Y la **raíz de la app**: `core/src/agentforge_core/main.py` → `create_app()` instancia +Y la **raíz de la app**: `core/src/ forja_core/main.py` → `create_app()` instancia `FastAPI`, añade el `TraceIdMiddleware`, monta los routers, y expone `/health`. Depende de: todo lo de arriba (vía `deps.py`). Dependen de él: el dashboard (por @@ -297,7 +297,7 @@ HTTP) y los tests de integración (`tests/integration/`, vía `TestClient`). | `pages/5_📐_Politicas.py` | Inventario de políticas: validadores de entrada/salida (cada uno expandible con su config) y versiones. | | `components/` | Trozos reutilizables de UI: `diff_view` (pinta `+`/`-`/`@@`), `trace_view` (el timeline del `decision_path`), `violation_view` (badges de severidad). | -Depende de: el `agentforge-core` por HTTP (vía `AGENTFORGE_CORE_URL`). **Nadie del +Depende de: el `forja-core` por HTTP (vía `FORJA_CORE_URL`). **Nadie del core depende del dashboard.** No tiene tests unitarios (mal coste/beneficio para Streamlit); su verificación es la checklist manual de [`docs/manual_qa.md`](manual_qa.md). @@ -464,7 +464,7 @@ sequenceDiagram ## 7. Persistencia: cuatro formas, cuatro razones -AgentForge no usa una sola base de datos; usa la herramienta adecuada para cada cosa. +Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa. | Qué | Cómo | Por qué así | |-----|------|-------------| @@ -499,9 +499,9 @@ AgentForge no usa una sola base de datos; usa la herramienta adecuada para cada ## 9. Mapa del repositorio y por dónde empezar a leer ``` -agentforge/ + forja/ ├── core/ -│ ├── src/agentforge_core/ +│ ├── src/ forja_core/ │ │ ├── domain/ ← los modelos de datos (empieza por execution.py) │ │ ├── config.py · observability/ ← cimientos transversales │ │ ├── llm/ ← proveedores LLM (Protocol + impls + factory) @@ -512,7 +512,7 @@ agentforge/ │ │ └── main.py ← create_app(): ensambla la app │ ├── Dockerfile · requirements.txt ├── dashboard/ -│ ├── src/agentforge_dashboard/ ← Streamlit (client + app + pages + components) +│ ├── src/ forja_dashboard/ ← Streamlit (client + app + pages + components) │ ├── Dockerfile · requirements.txt ├── agents/incident_analyzer/ ← el agente de ejemplo (YAMLs + escenarios .txt) ├── policies/default/ ← la política de guardrails de ejemplo diff --git a/docs/forja-progreso.html b/docs/forja-progreso.html new file mode 100644 index 0000000..57db408 --- /dev/null +++ b/docs/forja-progreso.html @@ -0,0 +1,419 @@ + + + + + + Forja — Progreso y Próximos Pasos + + + + + + +
+
+
+
+ +
+
+ Forja +
+
+ +
+
+
+ v0.2 — EN PRODUCCIÓN +
+
23 mayo 2026
+
+
+
+ +
+ + +
+
+
+ + GRAN REFACTORIZACIÓN COMPLETADA +
+ +

+ Forja
+ está lista +

+ +

+ Plataforma de gobernanza genérica para agentes IA de cualquier tipo. + Renombrada, generalizada y con editores gráficos completos. +

+ +
+ + + Ver en GitHub + + +
+
+ +
+
+
Progreso general
+ +
+
78
+
/100
+
+ +
+
+
+ +
+
+
Core
+
100%
+
+
+
UI Gráfica
+
95%
+
+
+
Docs
+
65%
+
+
+
+
+
+ + +
+
+
+
+
+
Renombrado global
+
483 → 0 referencias antiguas
+
+
+
+
+
+
+
+
Editores gráficos
+
2 páginas nuevas funcionales
+
+
+
+
+
+
+
+
Generalización
+
Cualquier tipo de agente
+
+
+
+
+
+
+
+
Write APIs
+
POST /versions + upsert
+
+
+
+
+ + +
+

+ Lo que se completó + FASE 1 + 2 + 3 +

+ +
+ +
+
+
+ + Renombrado completo a "Forja" +
+
    +
  • Paquetes Python: forja_core y forja_dashboard
  • +
  • Imágenes Docker, contenedores y URLs (forja-core:dev)
  • +
  • Variables de entorno (FORJA_CORE_URL)
  • +
  • pyproject, Dockerfiles, docker-compose, Makefile
  • +
  • 300+ archivos actualizados (código + docs activos)
  • +
+
+ +
+
+ + Generalización para cualquier tipo de agente +
+
+
+
Campos UI opcionales en AgentDefinition
+
input_label / placeholder / category / tags / icon
+
Execute page 100% genérico
+
Posicionamiento como plano de control reutilizable
+
+
+
+
+ + +
+
+
+ + Editores gráficos completos (la gran novedad) +
+ +
+
+
+ + 6_🔨_Forjar_Agente.py +
+
Form completo + LLM config + schema JSON + multiselect de guardrails + fork + versionado
+
+ +
+
+ + 7_🛡️_Forjar_Politica.py +
+
Constructor dinámico de validadores (input/output) + /validators endpoint + configs JSON
+
+
+
+ +
+
Backend de escritura
+
+
POST /agents/{name}/versions + upsert
+
POST /policies/{name}/versions + upsert simétrico
+
GET /policies/validators (metadata para UI)
+
FileSystemPolicyStore.upsert_version()
+
✓ Docker mounts cambiados a :rw
+
+
+
+
+
+ + +
+

Estado actual (23 mayo 2026)

+ +
+
+
+
Compila y funciona
+
    +
  • Todos los imports y paquetes renombrados
  • +
  • py_compile 100% limpio
  • +
  • Editores guardan YAMLs reales
  • +
  • Execute page usa metadata del agente
  • +
+
+ +
+
Listo para usar
+
    +
  • docker compose up (con :rw)
  • +
  • Registro + Ejecución + Aprobaciones
  • +
  • Editores en barra lateral (páginas 6 y 7)
  • +
  • README y docs principales actualizados
  • +
+
+ +
+
Pendiente de verificación completa
+
    +
  • make test-all (requiere deps pesadas)
  • +
  • Smoke test con Docker real (guardrails-ai)
  • +
  • Actualización de walkthrough.html
  • +
  • Tests específicos de los nuevos endpoints
  • +
+
+
+
+
+ + +
+

Próximos pasos recomendados

+ +
+ +
+
1
+
+
Verificación completa de calidad
+
Ejecutar make install && make lint && make test-all + smoke con docker compose en entorno limpio.
+
+
+ + +
+
2
+
+
Mejora de los editores (UX)
+
Añadir editor JSON más amigable (ace o monaco), validación en tiempo real de schemas, preview de agente antes de guardar, y botón “Probar inmediatamente”.
+
+
+ + +
+
3
+
+
Añadir más plantillas de agentes
+
Crear 2-3 agentes genéricos de ejemplo (resumidor, revisor de código, chatbot con guardrails) + sus políticas base.
+
+
+ +
+
4
+
+
Soporte de namespaces / multi-proyecto
+
Estructura opcional agents/<proyecto>/<agente> para que una sola instancia de Forja sirva a múltiples equipos.
+
+
+ +
+
5
+
+
Documentación y walkthrough actualizado
+
Regenerar o actualizar docs/walkthrough.html y docs/explicacion.md con los editores y el nuevo nombre.
+
+
+
+
+ + +
+
+
Paquetes: forja_core / forja_dashboard
+
Python: 3.11+
+
UI: Streamlit 1.38 + Tailwind (editores)
+
Runtime: LangGraph + FastAPI
+
Persistencia: YAML versionado + SQLite checkpoints
+
+
Este documento fue generado automáticamente tras la sesión de refactorización del 23 de mayo de 2026.
+
+
+ + + + diff --git a/docs/walkthrough.html b/docs/walkthrough.html index 8937fe0..ac1c1dc 100644 --- a/docs/walkthrough.html +++ b/docs/walkthrough.html @@ -3,8 +3,8 @@ - -AgentForge · Walkthrough + +Forja · Walkthrough