Metadata-Version: 2.5
Name: nebelus
Version: 0.1.15
Summary: Nebelus Agents API — build, edit, and ship governed AI agents from code
Author-email: Nebelus <support@nebelus.ai>
License: Proprietary
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.5
Provides-Extra: dev
Requires-Dist: langgraph>=0.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Description-Content-Type: text/markdown

# Nebelus Agents API — Python SDK

Build, edit, and ship governed AI agents from code. Every agent you create here is the
same artifact your team sees in the Nebelus portal — one construction service, every
surface, your organization's governance applied identically.

```python
from nebelus import Nebelus, AgentManifest

nb = Nebelus()  # NEBELUS_API_KEY + NEBELUS_BASE_URL (default https://api.nebelus.ai)

# Discover what this organization can build — machine-readable.
info = nb.describe()

manifest = AgentManifest(
    name="Return-policy concierge",
    model_id="claude-haiku-4-5",
    system_message="Answer from the return policy. Escalate anything ambiguous.",
)
agent = nb.apply(manifest)               # create-or-update, key-wise merge — never clobbers portal edits
print(nb.agents.probe(agent.id, "Can I return a jacket after 20 days?").reply)
# Deploying is a human act unless your org opted in to programmatic deployment:
# nb.agents.deploy(agent.id)             # needs the api.construction.deploy scope + the org opt-in
```

## Sign in

Two ways to authenticate:

```bash
nebelus login          # device flow: opens a browser, you approve a short code — no API key needed
```
`nebelus login` stores tokens in `~/.nebelus/credentials.json` (refreshed automatically);
`nebelus logout` removes them. Or set an API key from the portal (Settings → API keys):
`export NEBELUS_API_KEY=…` (an explicit key always takes precedence over a stored login).

Scopes come in two families: **`api.agents.read` / `api.agents.write`** *invoke* a deployed
agent (REST + WebSocket), while **`api.construction.read` / `api.construction.write`** *build and
edit* agents (and reach MCP); **`api.construction.deploy`** additionally allows programmatic
deploy where your org has opted in. ⚠️ A public **web-widget** embed ships its key to the
browser — put an **`api.agents`-only** key there, never one carrying `api.construction.*`. Your
Build Envelope (if your organization uses one) applies to code exactly as to every other surface.

## CLI

Everything above is also a command (`pip install nebelus` puts `nebelus` on your PATH):

```bash
nebelus login                          # sign in from the terminal (device flow)
nebelus describe                       # everything your org can build, machine-readable
nebelus build "a support agent that answers from our return policy and escalates ambiguous cases"
nebelus catalog --view tools --query crm
nebelus apply agent.py                 # a file defining `manifest = AgentManifest(...)`
nebelus diff agent.py                  # what apply would change ("in sync" when nothing)
nebelus validate <agent-id>            # pre-flight findings before you probe or deploy
nebelus probe <agent-id> "Hi there"    # run the draft through the real runtime
nebelus export <agent-id> > agent.py   # a live agent as a maintainable Python manifest
nebelus deploy <agent-id>              # needs the deploy scope + the org's opt-in
nebelus keys create --scope api.construction.read --scope api.construction.write
nebelus keys list                      # this org's keys (masked)
```

**`nebelus build "<prompt>"`** is AI-assisted: describe the agent in plain language and the
Nebelus Vibe Builder builds it for you — always as a **draft** — then hands it back so you
`nebelus export` it and own it in code. It's the one synthesising command (everything else is
deterministic — you specify the fields); it's billed as AI credits at the build rate. Same over
the SDK: `nb.build("…")`.

## References & other surfaces

- **Interactive API reference (Swagger):** `https://api.nebelus.ai/api/construction/docs/`
  (`/api/construction/schema/` for the raw OpenAPI). KSA: `api.ksa.nebelus.ai`.
- **Machine-readable capabilities:** `nb.describe()` / `GET /api/v1/construction/describe/` —
  the capability registry (every settable feature and which surface can set it), the
  writable/rejected fields, and your Build Envelope.
- **Wiring an agent into your app:** `GET /api/v1/construction/agents/<id>/wiring/` returns the
  REST invoke URL, WebSocket URL, webhook URL, web-widget embed snippet and MCP connection —
  region-correct, in the exact formats to paste in.
- **Build over MCP:** `https://api.nebelus.ai/api/v1/construction/mcp/` — JSON-RPC 2.0 over
  streamable HTTP, ~48 build tools (**full build parity minus deploy** — there is no deploy tool
  over MCP, by design). Authenticate with an `api.construction.*` key or OAuth 2.1 (Claude.ai /
  Desktop connect via OAuth). Add it to Claude Code / Cursor:
  `claude mcp add --transport http nebelus https://api.nebelus.ai/api/v1/construction/mcp/ --header "Authorization: Bearer <api.construction key>"`
- **TypeScript:** `@nebelus/construction` is the TS peer of this SDK.

Everything the visual builders can configure is reachable from here too — model routing,
delivery guard, grounding trace, schedules, knowledge, connectors, governance and channels.

## Invoke a deployed agent

`nebelus deploy <agent-id>` makes an agent **active** — that's the whole step. There is no
separate per-channel deployment for REST or WebSocket: an active agent plus an `api.agents.*`
key is callable immediately. (Web widget and webhook are separate opt-in channels; see below.)

**REST** — OpenAI-style, at `POST /api/agents/<agent-id>/chat/` (note: **no** `/v1`):

```bash
curl -X POST 'https://api.nebelus.ai/api/agents/<AGENT_ID>/chat/' \
  -H 'Authorization: Bearer <api.agents key>' \
  -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Summarize the return policy."}],"stream":false}'
```

The response is a `chat.completion` shape with a top-level `thread_id`, `message` and `usage`.
Omit `thread_id` to start a conversation; pass the returned value back to continue — the server
keeps history, so you never resend prior turns (`session_id` is an alias). Add `"stream":true`
(or the header `Accept: text/event-stream`) for SSE: `message_start` carries the `thread_id`,
then `content_block` deltas, `message_delta`, `usage_metadata` (with prompt-cache read/creation),
`cost_update` (in-stream cost, EUR/USD/SAR + markup), `thread_title`, and `message_stop`.

**WebSocket** — `wss://api.nebelus.ai/ws/agents/<AGENT_ID>/chat/?api_key=<KEY>`. Browsers can't
set request headers on a WebSocket, so pass the key as `?api_key=`; native clients may send
`Authorization: Bearer <key>` instead. Send `{"type":"chat","content":"..."}` and you get the
same event frames as SSE. A pure API-key client works — it is not session/JWT-only.

**Webhook** (event-driven, async) — external channels unlock only after **identity
verification** (self-serve / Nebelus Developers orgs get the web widget and in-portal testing
immediately, but webhooks and non-widget deployments wait on verification). The `/w/<key>/` URL
also **requires an auth token** by default — send `X-Webhook-Token: <secret>` as a header (or
`?X-Webhook-Token=<secret>` when the webhook's method is query_param); the URL alone returns 401.
It is fire-and-forget: it returns `{"message":"Webhook received successfully","event_id":…}` and
runs the agent in the background rather than replying inline.

KSA / GCC orgs use `api.ksa.nebelus.ai`; the region follows the org, and `get_wiring` returns the
region-correct URLs to paste in.

> **Copy-paste note:** keep JSON bodies in single quotes, and avoid `?` inside example messages
> (an unquoted `?` triggers zsh globbing). If a command lands mangled, check for editor/terminal
> "smart quotes" — the API needs straight `"` and `'`.

## Two-way with the portal

`nebelus export` (or `nebelus.export_to_code`) turns any live agent — including one a
colleague built visually — into a Python manifest you own in git. The full round trip is
`nebelus export <id> > agent.py` → edit → `nebelus diff agent.py` → `nebelus apply agent.py`;
as of **SDK 0.1.14** `apply` updates the exported agent **in place** (it no longer forks a
duplicate). The merge contract makes this safe in both directions: a manifest only manages the
fields it declares, so portal edits to everything else survive every apply, and `diff` never
reports server-side normalization as drift.

## Coming from LangGraph

Nebelus agents are **declarative**; a LangGraph node is **arbitrary Python**. The code
path never imports or runs your compiled graph — `from_langgraph` translates its
*topology* into a Nebelus `workflow` manifest, and you re-declare what each node does.
Nodes, edges, and conditional-edge targets map mechanically (both sides share the
`__start__`/`__end__` sentinels). What a node *does* and how a router *decides* live in
your Python, so you declare those explicitly.

What you supply, and the rules the translator enforces:

- Pass the **uncompiled** `StateGraph`, plus a `node_map` (each node → a declarative node,
  e.g. `{"type": "agent", "config": {…}}`) and a `router_map` (each conditional branch → a
  condition with a `routes` value→target map and/or an `expression`, plus a `default`).
- **Every node and every branch must be mapped.** Unmapped ones return named diagnostics
  and **no manifest** — nothing is guessed. Non-fatal coverage gaps come back as advisories.
- Conditional edges need a `path_map` on `add_conditional_edges(…)` so their targets are
  visible outside the Python callable; without it you get a blocking diagnostic.
- Node logic with no declarative equivalent **stays in your code** and attaches to the agent
  as a tool — an MCP server or a custom API endpoint. Nebelus never runs arbitrary code
  inside a node.

Incomplete translations return named diagnostics instead of a manifest — nothing is
guessed:

```python
from nebelus import Nebelus, from_langgraph

t = from_langgraph(
    my_state_graph,
    node_map={"triage": {"type": "agent", "config": {"system_prompt": "...", "model_id": "claude-haiku-4-5"}}},
    router_map={"triage": {"field": "triage_out",
                           "routes": {"billing": "billing", "general": "general"},
                           "default": "general"}},
    manifest_id="triage-v1", name="Triage", model_id="claude-haiku-4-5",
)
if t.complete:
    Nebelus().apply(t.manifest)
else:
    print("\n".join(t.diagnostics))   # names every unmapped node and undeclared router
```

Install the source-graph dependency with `pip install "nebelus[langgraph]"`. And if your
"LangGraph agent" is really a single ReAct loop (one model + tools), you don't need
`from_langgraph` at all — that's just an `AgentManifest` with a `system_message`,
`model_id`, and `needed_tools`.

A complete, runnable walkthrough — build a `StateGraph`, translate it, handle diagnostics,
`apply` and `probe` — is in [`examples/langgraph_to_nebelus.py`](examples/langgraph_to_nebelus.py).

## GitHub Action

Keep manifests in git and let CI hold them in sync — PRs show the diff, merges apply it
(see `examples/agents-apply.yml`):

```yaml
- uses: Nebelus/nebelus-python/action@main
  with:
    manifests: agents/*.py
    api-key: ${{ secrets.NEBELUS_API_KEY }}
    mode: ${{ github.event_name == 'push' && 'apply' || 'diff' }}
```

## Rate limits

The API rate-limits per key (generous defaults; probes are tighter since each spends real
model money). The SDK waits out short `Retry-After` pauses automatically and raises
`nebelus.RateLimited` (with `.retry_after`) when the pause is too long to hold in-process.

## Version support

The API supports the current SDK minor and one behind. The SDK sends its version
in the `User-Agent`; when your version falls below the floor the API responds
`426` and the SDK raises `nebelus.UpgradeRequired` (`pip install -U nebelus`).
Before that, a deprecation warning is emitted so upgrades are never a surprise.
