Metadata-Version: 2.4
Name: nineth
Version: 0.6.48
Summary: model SDK built by the 9th district at Tooig
Project-URL: Homepage, https://github.com/districtt/rooster
Project-URL: Bug Tracker, https://github.com/districtt/rooster/issues
Author-email: "Tooig, Inc" <tooighq@gmail.com>, Oyebamijo <boy@oyebamijo.com>
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.27.2
Description-Content-Type: text/markdown

# nineth

nineth is the Python SDK for the 1984 model API.

This document is caller-facing and SDK-focused. It is designed as a cookbook: start simple, then layer in advanced runtime controls.

## Install

```bash
pip install nineth
export NINETH_API_KEY="your-api-key"
```

Optional env vars:

```bash
export NINETH_BASE_URL="https://weirdpablo--rooster-api.modal.run"
export NINETH_DEFAULT_MODEL="1984-m3-0317"
```

## Public SDK Surface

Most apps only need these public objects:

- NinethClient (sync)
- AsyncNinethClient (async)
- client.health()
- client.model.request(...)
- AVAILABLE_MODELS
- NinethAPIError

## Models

Current SDK model list:

- 1984-m0-brute
- 1984-m0-sm
- 1984-m1-unified
- 1984-m2-light
- 1984-m2-preview
- 1984-m3-0317
- 1984-m3-0404
- 1984-m3-0421
- 1984-m3-0424
- 1984-c0-0427

## 1) Create a Client Once and Reuse It

### Sync client

```python
import httpx
from nineth import NinethClient

client = NinethClient(
    base_url="https://weirdpablo--rooster-api.modal.run",
    api_key="nt_live_xxx",
    default_model="1984-m3-0317",
    timeout=httpx.Timeout(300.0, connect=10.0),
    stream_timeout=httpx.Timeout(connect=10.0, read=None, write=60.0, pool=60.0),
    headers={"X-App-Name": "my-app"},
)

# use client.model.request(...) repeatedly
client.close()
```

### Async client

```python
import asyncio
from nineth import AsyncNinethClient

async def main() -> None:
    async with AsyncNinethClient(
        api_key="nt_live_xxx",
        default_model="1984-m3-0317",
    ) as client:
        response = await client.model.request("Say hello in one short sentence.")
        print(response["final_response"])

asyncio.run(main())
```

## 2) Health Check

```python
from nineth import NinethClient

with NinethClient() as client:
    print(client.health())
```

Typical response:

```json
{
  "status": "ok",
  "timestamp": "2026-05-24T12:00:00+00:00"
}
```

## 3) Basic Buffered Request

```python
from nineth import NinethClient

with NinethClient(default_model="1984-m3-0317") as client:
    response = client.model.request("Give me a concise BTC market brief.")
    print(response["final_response"])
```

Typical response shape:

```json
{
  "final_response": "BTC is range-bound with elevated event risk.",
  "iterations": 1,
  "thinking": [],
  "service_calls": [],
  "service_responses": [],
  "artifacts": [],
  "usage": {
    "prompt_tokens": 123,
    "completion_tokens": 41,
    "total_tokens": 164
  },
  "events": []
}
```

## 4) Streaming Request

```python
from nineth import NinethClient

with NinethClient(default_model="1984-m3-0317") as client:
    for event in client.model.request("Summarize oil market headlines.", stream=True):
        if event["type"] == "model_delta":
            print(event["data"]["text"], end="", flush=True)
        elif event["type"] == "result":
            print("\n\nFinal:", event["data"]["final_response"])
```

Typical stream event sequence:

```json
{"type": "accepted", "data": {"ok": true}}
{"type": "model_delta", "data": {"text": "Crude prices"}}
{"type": "model_delta", "data": {"text": " rose on..."}}
{"type": "result", "data": {"final_response": "Crude prices rose on...", "iterations": 1}}
```

## 5) Generation Controls

Use these to tune model behavior per request:

- reasoning: disabled | low | medium | high
- show_reasoning: bool
- temperature
- top_p, min_p, top_k
- repetition_penalty, presence_penalty, frequency_penalty
- seed

Example:

```python
response = client.model.request(
    "Compare two risk scenarios in 5 bullets.",
    reasoning="high",
    show_reasoning=False,
    temperature=0.2,
    top_p=0.9,
    min_p=0.01,
    top_k=40,
    repetition_penalty=1.05,
    presence_penalty=0.1,
    frequency_penalty=0.1,
    seed=1337,
)
```

## 6) Loop Controls

- max_iterations caps model turns.
- continuous controls idle-loop persistence.

```python
response = client.model.request(
    "Monitor and update only when new data arrives.",
    max_iterations=20,
    continuous=True,
)
```

## 7) Cache and Session Reuse

Use cache=True to persist and receive session_id (mapped from process_id).

```python
first = client.model.request(
    "Remember my preferred output format: CSV.",
    cache=True,
)
sid = first["session_id"]

second = client.model.request(
    "Now summarize open tasks.",
    cache=True,
    session_id=sid,
)
```

Important:

- session_id requires cache=True unless resuming callback with client_service_results.

## 8) policy vs guardrail

- policy: caller runtime policy text for the request.
- guardrail: ADAM extension text for default SDK/API ingress path.

```python
response = client.model.request(
    "Draft an operations note.",
    policy="Keep response under 120 words and use plain language.",
    guardrail="Never include secrets or credentials.",
)
```

## 9) base_system Provider Path

base_system defaults to True.

Set False only when you explicitly want runtime default provider fallback.

```python
response = client.model.request(
    "Run with runtime provider fallback.",
    base_system=False,
)
```

## 10) Built-in Services with default_service

default_service is the main built-in service switch:

- False: built-ins disabled
- True: built-ins enabled
- list[str]: built-in allowlist

It also supports alias groups in SDK payload shaping, including browser, knowledge, computer, workspace, voice, trading, and shop.

```python
response = client.model.request(
    "Search for the latest shipping regulations and summarize.",
    default_service=["browser", "search_knowledge"],
)
```

## 11) include_service (Local and Callback)

include_service supports three practical patterns:

1. Local schema.py path (SDK executes locally)
2. Inline schema dict list
3. Callback block with URL and schema list

### A) Local schema path

```python
response = client.model.request(
    "Get weather for Lagos using local service.",
    include_service=["./services/weather/schema.py"],
)
```

### B) Inline schema

```python
weather_schema = {
    "name": "get_weather",
    "description": "Return weather forecast.",
    "parameters": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"],
        "additionalProperties": False,
    },
}

response = client.model.request(
    "Use inline service schema for weather.",
    include_service=[weather_schema],
)
```

### C) Callback URL block

```python
response = client.model.request(
    "Call my hosted weather callback.",
    include_service={
        "callback": {"url": "https://my-app.example.com/include-service-callback"},
        "schema": [weather_schema],
    },
)
```

When callback flow pauses, server can return awaiting_client_services with pending_client_calls.

## 12) callback_url as Global Callback

callback_url can be reused across callback-capable surfaces and can be inherited by inbound email templates that omit template url.

```python
response = client.model.request(
    "Register inbound mailbox setup.",
    callback_url="https://my-app.example.com/rooster-callback",
    include_service=[weather_schema],
)
```

## 13) Manual Callback Resume with client_service_results

For caller-managed continuation:

```python
paused = client.model.request(
    "Pause on client services.",
    include_service=[weather_schema],
)

if paused.get("status") == "awaiting_client_services":
    resumed = client.model.request(
        "Resume after local service execution.",
        include_service=[weather_schema],
        client_service_results=[
            {
                "call_id": "client_1_1",
                "service_name": "get_weather",
                "success": True,
                "result": {"location": "Lagos", "forecast": "sunny"},
            }
        ],
        session_id=paused.get("process_id"),
    )
```

## 14) response_format and compute

response_format="json" parses final_response when valid JSON and preserves original raw_response.

```python
response = client.model.request(
    "Return JSON with keys trend and confidence.",
    response_format="json",
    compute=True,
)

print(response["final_response"])  # parsed object if valid JSON
print(response.get("raw_response"))
print(response.get("compute"))
```

Typical parsed response:

```json
{
  "final_response": {
    "trend": "neutral",
    "confidence": 0.64
  },
  "raw_response": "{\"trend\":\"neutral\",\"confidence\":0.64}",
  "compute": 241
}
```

## 15) Messaging Configuration

messaging attaches transport defaults for email and telegram.

```python
response = client.model.request(
    "Send a concise status update.",
    messaging={
        "email": {
            "address": "ops@resident.tooig.com",
            "name": "Ops Bot",
            "instruction": "Always ask for ticket id before escalation.",
            "reasoning_effort": "high",
        },
        "telegram": {
            "botId": "bot-1",
            "chatId": "12345678",
            "reasoning_effort": "disabled",
        },
    },
)
```

Auto-enable behavior:

- telegram messaging enables telegram send/edit services.
- email messaging enables email send services when outbound-capable.
- inbound-only template registration does not auto-enable email send services.

## 16) Template-Scoped Email Payloads

Canonical message payload location:

- messaging.email.templates[*].message
- messaging.email.templates[*].messages

Inbound template example:

```python
response = client.model.request(
    "Register inbound mailbox template.",
    messaging={
        "email": {
            "address": "agent@resident.tooig.com",
            "instruction": "Handle review requests.",
            "templates": [
                {
                    "type": "inbound",
                    "name": "review_request",
                    "standalone": True,
                    "messages": [
                        {
                            "from": "applicant@example.com",
                            "to": "agent@resident.tooig.com",
                            "subject": "Application follow-up",
                            "body": "Could you share my status?",
                        }
                    ],
                }
            ],
        }
    },
    callback_url="https://my-app.example.com/inbound-callback",
)
```

Outbound template example:

```python
response = client.model.request(
    "Send outbound applicant decision.",
    messaging={
        "email": {
            "address": "agent@resident.tooig.com",
            "templates": [
                {
                    "type": "outbound",
                    "name": "decision_email",
                    "messages": [
                        {
                            "to": "applicant@example.com",
                            "subject": "Application Decision",
                            "body": "Thanks for your time. We would like to proceed.",
                            "cc": ["hr@example.com"],
                            "bcc": ["audit@example.com"],
                        }
                    ],
                }
            ],
        }
    },
)
```

## 17) Audio and Images

```python
response = client.model.request(
    "Transcribe audio and summarize key action items.",
    audio=[
        {
            "data": "<base64-audio>",
            "mime_type": "audio/mpeg",
            "filename": "meeting.mp3",
        }
    ],
)
```

```python
response = client.model.request(
    "Describe this chart.",
    images=["<base64-image>"],
)
```

## 18) Streaming with Service Progress

When services are called, stream can include synthetic progress lines and service call/response events.

```python
for event in client.model.request("Research and summarize.", stream=True, default_service=["browser"]):
    et = event["type"]
    if et == "model_delta":
        print(event["data"]["text"], end="")
    elif et == "service_call":
        print("\nservice call:", event["data"]) 
    elif et == "service_response":
        print("\nservice response:", event["data"]) 
```

## 19) Error Handling

All API and stream errors are raised as NinethAPIError.

```python
from nineth import NinethAPIError

try:
    response = client.model.request("Run protected operation.")
except NinethAPIError as exc:
    print("nineth error:", str(exc))
```

SDK behavior includes sanitizing provider/URL fragments from surfaced errors and masking some model subscription-availability patterns.

## 20) End-to-End Example: Cached Session + Services + JSON Output

```python
from nineth import NinethClient

weather_schema = {
    "name": "get_weather",
    "description": "Return weather forecast.",
    "parameters": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"],
        "additionalProperties": False,
    },
}

with NinethClient(api_key="nt_live_xxx", default_model="1984-m3-0421") as client:
    step1 = client.model.request(
        "Create a daily logistics summary format and remember it.",
        cache=True,
        default_service=["browser"],
    )

    sid = step1.get("session_id")

    step2 = client.model.request(
        "Use today weather in Lagos and return JSON with fields summary and risk_level.",
        cache=True,
        session_id=sid,
        include_service=[weather_schema],
        response_format="json",
        compute=True,
        reasoning="medium",
    )

    print(step2["final_response"])
    print("tokens:", step2.get("compute"))
```

## Troubleshooting

Common issues and fixes:

1. ValueError: Authentication required
Set api_key or NINETH_API_KEY.

2. ValueError: A model is required
Pass model=... or set default_model/NINETH_DEFAULT_MODEL.

3. ValueError: session_id requires cache=True
When reusing session_id, set cache=True.

4. include_service callback conflicts
Do not define a user service named request_include_service_interlude when callback URL mode is used.

5. Stream appears to stop at awaiting_client_services
Resume manually with client_service_results, or use callback URL mode to auto-resume.

## Quick Reference: request(...) Arguments

Task identity:

- task_input
- model

Execution mode:

- stream
- max_iterations
- continuous

Generation controls:

- reasoning
- show_reasoning
- temperature
- top_p
- min_p
- top_k
- repetition_penalty
- presence_penalty
- frequency_penalty
- seed

Runtime controls:

- policy
- guardrail
- base_system
- default_service
- include_service
- callback_url
- client_service_results

Continuity:

- cache
- session_id

Inputs:

- images
- audio

Output shaping:

- response_format
- compute
- verbose
- debug (legacy alias)

Transport:

- messaging

Legacy alias compatibility:

- policy also accepts system_prompt
- verbose may be influenced by debug

## Final Note

If you are integrating nineth in production:

- Create one long-lived client per process.
- Reuse cached sessions explicitly when continuity matters.
- Keep default_service strict and intentional.
- Treat include_service schemas as request-scoped contracts.
- Prefer callback_url mode for distributed service execution.
