Metadata-Version: 2.4
Name: ashr-manifold
Version: 0.5.0
Summary: Task-agnostic client for Manifold observations, artifacts, experiments, and monitoring
License-Expression: LicenseRef-Proprietary
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx<1,>=0.27

# ashr-manifold

```bash
pip install ashr-manifold        # published releases (PyPI)
pip install ./sdk                # from a checkout
```

The import name stays `ashr_sdk` either way. Releases publish via
`.github/workflows/publishing.yaml` (PyPI Trusted Publisher): bump `version`
in `sdk/pyproject.toml`, then `gh release create sdk-v<version>`.

```python
from ashr_sdk import AshrClient

ashr = AshrClient()  # reads ASHR_BASE_URL and ASHR_API_KEY
experiment = ashr.experiments.run(
    name="invoice-v5",
    task="invoice_extraction",
    dataset="ds_...",
    models=["org/model-a", "org/model-b"],
    training_config={"epochs": 1, "eval_batch_size": 8},
    gates={"field_f1": 0.8},
)
```

See `../docs/sdk.md` for task-agnostic observation capture, optional artifact
links, evaluation, promotion, monitoring, errors, and model configuration.

## Continuous learning and progressive rollout

```python
# validator outcomes label observations like machine reviewers
ashr.checks([{"interaction_id": iid,
              "checks": {"compile": True, "scene_load": True}}],
            reviewer="game-engine")

# one call: freeze -> environment -> simultaneous runs -> halving ->
# gates -> incumbent-protected promotion to the alias
report = ashr.learning_loops.run(
    task="game_generation",
    candidates=[{"model_source_id": src["id"], "recipe": "sft_lora"}],
    halving={"metric": "proxy_score", "at_progress": 0.5, "kill_fraction": 0.5},
)

environment = ashr.environments.build(
    task="game_generation",
    dataset_id="ds_games_v1",
    reward_spec={"compile": 1, "scene_load": 1, "runtime_error": -5},
)

ashr.learning_loops.configure(
    task="game_generation",
    environment_id=environment["id"],
    config={
        "trigger_after_reviewed": 500,
        "trigger_every_days": 7,
        "benchmark_incumbent": True,
        "auto_train": True,
        "auto_rollout": False,
    },
)

ashr.router.configure(
    task="game_generation",
    incumbent_model_id="m_current",
    challenger_model_id="m_candidate",
    traffic_percent=5,
    gates={"compile_rate_delta": .03, "runtime_success": .99},
)

assignment = ashr.router.decide(
    task="game_generation", request_key=request_id)
# Serve assignment["model_id"], then attach the delayed business result:
ashr.router.outcome(assignment["id"], {"compiled": True, "reward": 1})
```

Routing is deterministic per policy and request key. Setting traffic to zero
is an immediate control-plane rollback to the incumbent:

```python
ashr.router.set_traffic("game_generation", 0)
```
