Metadata-Version: 2.4
Name: breakpoint-arcus
Version: 0.1.0
Summary: SDK de instrumentación y gobierno para Arcus Control Plane (traces, spans y policy enforcement).
Author-email: Break Point <no-reply@breakpoint.biz>
License: Apache-2.0
Project-URL: Homepage, https://breakpoint.biz
Project-URL: Repository, https://github.com/TechAdminBK/arcus
Keywords: arcus,control-plane,observability,agents,ai-governance,tracing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Arcus SDK (Python)

Instrumentación de agentes para el **Arcus Control Plane**. Reporta traces y spans
(OTel-friendly) a Arcus vía la API REST. **Sin dependencias** (solo stdlib).

## Instalar
```bash
pip install -e ./sdk/python        # local
# (futuro) pip install arcus-sdk
```

## Conseguir una API key
En Arcus: registrá el agente (Agent Registry) y generá una **API key** para él
(`POST /api/v1/agents/{agent_id}/api-keys`, o desde la UI). La key se muestra una sola vez.

## Uso
```python
import arcus_sdk as arcus

arcus.configure(
    base_url="https://api.breakpoint.biz/api/v1",
    api_key="arc_xxx",          # API key del agente
    agent_id="<uuid-del-agente>",
)

@arcus.trace("mi agente")
def run(prompt):
    with arcus.llm_call("claude-sonnet-4-6", tokens_in=1200, tokens_out=300):
        ...  # llamada al modelo
    with arcus.tool_call("CRM API"):
        ...  # invocación de herramienta
    return "ok"
```

Cada ejecución de `run()` crea un **trace** con un span padre `invoke_agent` + los
spans hijos (`llm_call`, `tool_call`, `retrieval`). Latencia, estado y errores se
capturan automáticamente. Aparecen en **Observability**.

## Tipos de span
`invoke_agent` · `llm_call` · `tool_call` · `retrieval` · `policy_evaluation` · `hitl_gate`

Span genérico:
```python
with arcus.span("retrieval", "búsqueda docs", attributes={"k": 5}):
    ...
```

## Convención de dependencias (Dependency Map automático)

Arcus **infiere el grafo de dependencias de tus traces**: cada llamada externa que
instrumentás se vuelve una arista (agente → recurso) con estado ok/error. Usá los
helpers según el tipo de recurso para que el destino quede bien identificado:

```python
with arcus.llm_call("mistral-large-latest"):     # → nodo model:mistral-large
    ...
with arcus.tool_call(url="https://api.stripe.com/v1/charges"):  # → nodo api:stripe
    ...
with arcus.tool_call(tool_name="calculadora"):   # → nodo tool:calculadora
    ...
with arcus.retrieval("pinecone-prod"):           # → nodo datastore:pinecone-prod
    ...
with arcus.agent_call("<uuid-otro-agente>"):     # → arista agente → agente
    ...
```

En **error**, el span se marca `status="error"` y se completa `attributes["error_type"]`
automáticamente (clase de la excepción). Podés overridearlo:
```python
with arcus.tool_call(url="https://api.stripe.com/x") as rec:
    resp = http.post(...)
    if resp.status_code >= 500:
        rec["attributes"]["http_status"] = resp.status_code
        raise RuntimeError("stripe 5xx")
```

> ⚠️ **Anti-patrón:** atrapar la excepción de una llamada externa **sin** marcar el
> span como `error` hace que Arcus registre la dependencia como **sana**. Un módulo
> caído quedaría verde en el grafo. Si tragás el error, marcá `rec["status"]="error"`
> y `rec["attributes"]["error_type"]` a mano.

## Enforcement de políticas (gate de control)

Además de observar, el SDK puede **hacer cumplir** las políticas de Arcus **antes** de
ejecutar una acción de riesgo. Consulta al Policy Engine (`/evaluate`) y aplica la decisión:

```python
# Como context manager
with arcus.enforce(action="charge_customer", resource="stripe", risk_level="high"):
    stripe.charge(...)          # solo corre si la política dice allow

# Como decorador
@arcus.guard(action="delete_record", resource="db", risk_level="high")
def borrar(id): ...
```

Decisiones:
- **allow** → ejecuta el bloque/función.
- **deny** → lanza `arcus.PolicyDenied` (no ejecuta).
- **escalate** → crea una aprobación HITL (`/approvals`) y lanza `arcus.PolicyEscalated` (pausa).
- **Arcus inalcanzable** → `fail_closed=True` (default) lanza `PolicyDenied`; `fail_closed=False`
  deja pasar (fail-open). Elegí según el riesgo de la acción.

Cada evaluación deja además un span `policy_evaluation` en el trace, así que la decisión
queda auditada. Manejá las excepciones según tu flujo (abortar, reintentar tras aprobación, etc.):

```python
try:
    with arcus.enforce(action="wire_transfer", resource="bank", risk_level="critical"):
        transferir(...)
except arcus.PolicyEscalated:
    notificar("pendiente de aprobación humana")
except arcus.PolicyDenied:
    abortar("acción no permitida por política")
```

## Garantías
- **Best-effort:** si Arcus no responde, la instrumentación no lanza excepción ni
  frena al agente.
- **Sin deps:** usa `urllib` de la stdlib.
- Para desactivar: `arcus.configure(..., enabled=False)`.

## Pendiente
- SDK TypeScript (`@arcus/sdk`).
- Soporte async (`async def`).
