argus-cortex 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ """argus-cortex — Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract"""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ # Written by hatch-vcs at build time (see pyproject [tool.hatch.build.hooks.vcs]).
7
+ from argus_cortex._version import __version__
8
+ except ImportError: # running from a source checkout that hasn't been built
9
+ from importlib.metadata import PackageNotFoundError, version
10
+
11
+ try:
12
+ __version__ = version("argus-cortex")
13
+ except PackageNotFoundError:
14
+ __version__ = "0.0.0+unknown"
15
+
16
+ from argus_cortex.backends import (
17
+ Backend,
18
+ BackendError,
19
+ BackendKind,
20
+ LocalBackend,
21
+ RemoteBackend,
22
+ RemoteProvider,
23
+ resolve_device,
24
+ with_retries,
25
+ )
26
+ from argus_cortex.taxonomy import TargetCategory, TargetProfile, TargetStyle
27
+ from argus_cortex.wire import (
28
+ VersionError,
29
+ check_version,
30
+ make_versioned_base,
31
+ render_schema,
32
+ schema_major,
33
+ wire_schema,
34
+ )
35
+
36
+ __all__ = [
37
+ "__version__",
38
+ # taxonomy
39
+ "TargetProfile",
40
+ "TargetCategory",
41
+ "TargetStyle",
42
+ # wire
43
+ "VersionError",
44
+ "check_version",
45
+ "make_versioned_base",
46
+ "render_schema",
47
+ "schema_major",
48
+ "wire_schema",
49
+ # backends
50
+ "Backend",
51
+ "BackendError",
52
+ "BackendKind",
53
+ "LocalBackend",
54
+ "RemoteBackend",
55
+ "RemoteProvider",
56
+ "resolve_device",
57
+ "with_retries",
58
+ ]
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,286 @@
1
+ """The local / remote AI-backend contract, shared across the suite.
2
+
3
+ Generalised from argus-lens's captioner backends (``CaptionBackend`` →
4
+ ``LocalBackend`` / ``CloudBackend``) so every stage that calls a model — lens
5
+ captioning, proof scoring — shares one lifecycle, one retry policy, and one way
6
+ to point at a **hosted service by IP/port** instead of loading weights locally.
7
+
8
+ * :class:`Backend` — lifecycle + capability attributes (``load`` / ``unload`` /
9
+ ``is_available``); the domain call (caption an image, score an image) is added
10
+ by the subclass.
11
+ * :class:`LocalBackend` — runs inference in-process on a resolved device.
12
+ * :class:`RemoteBackend` — calls a hosted HTTP endpoint (``base_url`` or
13
+ ``host``/``port``, or a known :class:`RemoteProvider`); httpx lives behind the
14
+ ``argus-cortex[remote]`` extra and is imported lazily.
15
+
16
+ Concrete, domain-specific adapters (a CLIP scorer, an OpenAI captioner) subclass
17
+ these in the consuming package; this module stays domain-agnostic.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import time
23
+ from collections.abc import Callable
24
+ from enum import StrEnum
25
+ from typing import TypeVar
26
+
27
+ T = TypeVar("T")
28
+
29
+
30
+ class BackendError(RuntimeError):
31
+ """A backend failure: model unavailable, remote endpoint unreachable."""
32
+
33
+
34
+ class BackendKind(StrEnum):
35
+ """Where a backend runs — locally in-process, or a remote hosted service."""
36
+
37
+ LOCAL = "local"
38
+ REMOTE = "remote"
39
+
40
+
41
+ def with_retries(
42
+ fn: Callable[[], T],
43
+ *,
44
+ attempts: int = 3,
45
+ base_delay: float = 0.5,
46
+ max_delay: float = 8.0,
47
+ retry_on: tuple[type[BaseException], ...] = (Exception,),
48
+ sleep: Callable[[float], None] = time.sleep,
49
+ ) -> T:
50
+ """Call *fn*, retrying on *retry_on* with exponential backoff.
51
+
52
+ Re-raises the last error after *attempts* tries. ``sleep`` is injectable so
53
+ tests exercise the backoff without wall-clock delay. Narrow ``retry_on`` to
54
+ the transient failures you actually want retried — the default of
55
+ ``Exception`` also retries deterministic programming errors.
56
+ """
57
+ if attempts < 1:
58
+ raise ValueError(f"attempts must be >= 1, got {attempts}")
59
+ last: BaseException | None = None
60
+ for attempt in range(attempts):
61
+ try:
62
+ return fn()
63
+ except retry_on as exc:
64
+ last = exc
65
+ if attempt == attempts - 1:
66
+ break
67
+ sleep(min(max_delay, base_delay * (2**attempt)))
68
+ raise last # type: ignore[misc] # attempts >= 1, so a caught error was assigned
69
+
70
+
71
+ def resolve_device(device: str = "auto") -> str:
72
+ """Resolve ``"auto"`` to ``"cuda"``/``"mps"``/``"cpu"`` (or pass a fixed device through).
73
+
74
+ Uses torch only if it's importable; with no torch present, ``"auto"`` becomes
75
+ ``"cpu"`` so a device-free environment still works.
76
+ """
77
+ if device and device != "auto":
78
+ return device
79
+ try:
80
+ import torch
81
+ except ImportError:
82
+ return "cpu" # no torch -> cpu; a real GPU-detection error below must surface
83
+ if torch.cuda.is_available():
84
+ return "cuda"
85
+ mps = getattr(torch.backends, "mps", None)
86
+ if mps is not None and mps.is_available():
87
+ return "mps"
88
+ return "cpu"
89
+
90
+
91
+ class Backend:
92
+ """Base for any model-backed backend: lifecycle + capability descriptor.
93
+
94
+ Subclasses set the class attributes and add the domain call. ``load`` /
95
+ ``unload`` default to no-ops so a trivial backend need not override them;
96
+ ``is_available`` / ``availability_reason`` let a registry skip a backend that
97
+ can't run right now (missing weights, unset API key) with a clear reason.
98
+ """
99
+
100
+ name: str = "base"
101
+ kind: BackendKind = BackendKind.LOCAL
102
+ requires_gpu: bool = False
103
+
104
+ def load(self, device: str = "auto") -> None:
105
+ """Load weights / init the client. Called lazily on first use."""
106
+
107
+ def unload(self) -> None:
108
+ """Release resources (GPU memory, HTTP connections)."""
109
+
110
+ def is_available(self) -> bool:
111
+ """Whether this backend can be used right now."""
112
+ return True
113
+
114
+ def availability_reason(self) -> str | None:
115
+ """Human-readable reason this backend is unavailable, or ``None``."""
116
+ return None
117
+
118
+
119
+ class LocalBackend(Backend):
120
+ """Base for backends that run inference in-process on a device.
121
+
122
+ The target device is supplied once via :meth:`load` and remembered, so the
123
+ domain call stays device-free. Subclasses read ``self.device``.
124
+ """
125
+
126
+ kind = BackendKind.LOCAL
127
+ requires_gpu = True
128
+
129
+ def __init__(self) -> None:
130
+ self.device: str | None = None
131
+
132
+ def load(self, device: str = "auto") -> None:
133
+ self.device = resolve_device(device)
134
+
135
+
136
+ class RemoteProvider(StrEnum):
137
+ """Known hosted inference providers (base URLs in :data:`REMOTE_PROVIDER_URLS`)."""
138
+
139
+ HF_INFERENCE = "hf_inference"
140
+ NVIDIA_NIM = "nvidia_nim"
141
+ OPENAI_COMPAT = "openai_compat"
142
+ REPLICATE = "replicate"
143
+
144
+
145
+ # Default base URLs per provider. OPENAI_COMPAT has none — a local/self-hosted
146
+ # OpenAI-compatible server is addressed by an explicit base_url / host+port.
147
+ REMOTE_PROVIDER_URLS: dict[RemoteProvider, str | None] = {
148
+ RemoteProvider.HF_INFERENCE: "https://api-inference.huggingface.co",
149
+ RemoteProvider.NVIDIA_NIM: "https://integrate.api.nvidia.com/v1",
150
+ RemoteProvider.OPENAI_COMPAT: None,
151
+ RemoteProvider.REPLICATE: "https://api.replicate.com/v1",
152
+ }
153
+
154
+
155
+ class RemoteBackend(Backend):
156
+ """Base for backends that call a hosted HTTP endpoint.
157
+
158
+ Point it at a service by ``base_url``, by ``host``/``port`` via
159
+ :meth:`from_host`, or at a known provider via :meth:`from_provider`. An
160
+ ``api_key`` becomes a bearer ``Authorization`` header unless one is already
161
+ supplied. httpx is imported lazily (``argus-cortex[remote]``); an injected
162
+ ``_http`` client (tests) bypasses the import.
163
+ """
164
+
165
+ kind = BackendKind.REMOTE
166
+ requires_gpu = False
167
+
168
+ def __init__(
169
+ self,
170
+ base_url: str | None = None,
171
+ *,
172
+ api_key: str | None = None,
173
+ timeout: float = 30.0,
174
+ headers: dict[str, str] | None = None,
175
+ provider: RemoteProvider | None = None,
176
+ ) -> None:
177
+ self.base_url = base_url.rstrip("/") if base_url else None
178
+ self.api_key = api_key
179
+ self.timeout = timeout
180
+ self.provider = provider
181
+ self.headers = dict(headers or {})
182
+ if api_key and not any(k.lower() == "authorization" for k in self.headers):
183
+ self.headers["Authorization"] = f"Bearer {api_key}"
184
+ self._http = None # lazily-created httpx.Client (or an injected fake)
185
+
186
+ @classmethod
187
+ def from_host(cls, host: str, port: int, *, scheme: str = "http", **kwargs: object) -> RemoteBackend:
188
+ """Address a service by host + port, e.g. a self-hosted model server."""
189
+ return cls(f"{scheme}://{host}:{port}", **kwargs) # type: ignore[arg-type]
190
+
191
+ @classmethod
192
+ def from_provider(cls, provider: RemoteProvider | str, **kwargs: object) -> RemoteBackend:
193
+ """Address a known hosted provider by its default base URL.
194
+
195
+ This wires up only the URL (and auth) — it does NOT adapt to a provider's
196
+ request/response protocol. Replicate's poll-based predictions, an
197
+ OpenAI-compatible ``/chat/completions`` shape, etc. are the concrete
198
+ subclass's job; the plain :meth:`post_json` / :meth:`get_json` here assume
199
+ a single synchronous JSON round-trip.
200
+ """
201
+ provider = RemoteProvider(provider)
202
+ base = REMOTE_PROVIDER_URLS[provider]
203
+ if base is None and "base_url" not in kwargs:
204
+ raise BackendError(f"provider {provider.value} needs an explicit base_url (host+port of your server)")
205
+ return cls(kwargs.pop("base_url", base), provider=provider, **kwargs) # type: ignore[arg-type]
206
+
207
+ def is_available(self) -> bool:
208
+ return bool(self.base_url)
209
+
210
+ def availability_reason(self) -> str | None:
211
+ return None if self.base_url else "no base_url configured (set base_url or host/port)"
212
+
213
+ def _client(self): # noqa: ANN202 (httpx is optional; avoid importing at module load)
214
+ if self._http is None:
215
+ try:
216
+ import httpx
217
+ except ImportError as exc: # pragma: no cover - exercised only without the extra
218
+ raise BackendError("remote backends need: pip install argus-cortex[remote]") from exc
219
+ if not self.base_url:
220
+ raise BackendError("remote backend has no base_url")
221
+ self._http = httpx.Client(base_url=self.base_url, timeout=self.timeout, headers=self.headers)
222
+ return self._http
223
+
224
+ @staticmethod
225
+ def _transient_errors() -> tuple[type[BaseException], ...]:
226
+ """Errors worth retrying: network/timeout blips, NOT 4xx/5xx responses.
227
+
228
+ ``httpx.TransportError`` covers connect/read/write/pool timeouts and
229
+ connection failures. A ``raise_for_status()`` 4xx/5xx (``HTTPStatusError``)
230
+ is deliberately excluded — a 401/404 never succeeds on retry, and a POST
231
+ that reached the server must not be blindly re-sent.
232
+ """
233
+ try:
234
+ import httpx
235
+
236
+ return (httpx.TransportError,)
237
+ except ImportError: # pragma: no cover
238
+ return (OSError,)
239
+
240
+ @staticmethod
241
+ def _wrap_errors() -> tuple[type[BaseException], ...]:
242
+ """Exception types to wrap as :class:`BackendError` (bad status, bad JSON)."""
243
+ try:
244
+ import httpx
245
+
246
+ return (httpx.HTTPError, ValueError)
247
+ except ImportError: # pragma: no cover
248
+ return (ValueError, OSError)
249
+
250
+ def _send(self, call: Callable[[], dict], *, label: str, attempts: int) -> dict:
251
+ """Retry *call* on transient failures; wrap any failure in BackendError.
252
+
253
+ So callers catch one error type whether the endpoint was unreachable,
254
+ returned a bad status, or sent a non-JSON body.
255
+ """
256
+ try:
257
+ return with_retries(call, attempts=attempts, retry_on=self._transient_errors())
258
+ except self._wrap_errors() as exc:
259
+ raise BackendError(f"{label} failed: {exc}") from exc
260
+
261
+ def post_json(self, path: str, payload: dict, *, attempts: int = 3) -> dict:
262
+ """POST JSON and return the JSON response; retries transient failures only."""
263
+ client = self._client()
264
+
265
+ def call() -> dict:
266
+ resp = client.post(path, json=payload)
267
+ resp.raise_for_status()
268
+ return resp.json()
269
+
270
+ return self._send(call, label=f"POST {path}", attempts=attempts)
271
+
272
+ def get_json(self, path: str, *, attempts: int = 3) -> dict:
273
+ """GET and return the JSON response; retries transient failures only."""
274
+ client = self._client()
275
+
276
+ def call() -> dict:
277
+ resp = client.get(path)
278
+ resp.raise_for_status()
279
+ return resp.json()
280
+
281
+ return self._send(call, label=f"GET {path}", attempts=attempts)
282
+
283
+ def unload(self) -> None:
284
+ if self._http is not None:
285
+ self._http.close()
286
+ self._http = None
argus_cortex/py.typed ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,41 @@
1
+ """The shared target taxonomy — what a dataset is curated *for*.
2
+
3
+ This is the "moat" the whole Argus suite pivots on: the curator scores and
4
+ labels exports against it, the captioner picks caption variants from it, the
5
+ forge renders trainer configs for it, and proof evaluates a LoRA against it. It
6
+ lived, copied verbatim, in argus-curator and argus-forge (whose comment noted it
7
+ should "eventually hoist into a shared argus-cortex package") — this is that hoist.
8
+
9
+ Keep this module dependency-light (pydantic only) and change it deliberately: a
10
+ change here ripples through every stage of the suite.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Literal
16
+
17
+ from pydantic import BaseModel
18
+
19
+ TargetStyle = Literal["photo", "anime"]
20
+
21
+ # What the dataset is curated for. Note this is distinct from argus-lens's
22
+ # caption *buckets* (identity/action/pose_composition/wardrobe/lighting/setting):
23
+ # those describe caption fragments, this describes the training objective.
24
+ TargetCategory = Literal["identity", "wardrobe", "pose_composition", "setting"]
25
+
26
+ TARGET_STYLES: tuple[TargetStyle, ...] = ("photo", "anime")
27
+ TARGET_CATEGORIES: tuple[TargetCategory, ...] = ("identity", "wardrobe", "pose_composition", "setting")
28
+
29
+
30
+ class TargetProfile(BaseModel):
31
+ """What a dataset was curated for — shared verbatim across the suite.
32
+
33
+ Inherited unchanged from stage to stage (curator → lens → forge → proof) so
34
+ a decision made once at curation time is honoured everywhere downstream; no
35
+ stage remaps the taxonomy.
36
+ """
37
+
38
+ target_style: TargetStyle = "photo"
39
+ target_backend: str | None = "sdxl"
40
+ checkpoint: str | None = None
41
+ target_category: TargetCategory = "identity"
argus_cortex/wire.py ADDED
@@ -0,0 +1,107 @@
1
+ """Versioned wire-schema tooling shared across the suite.
2
+
3
+ Every Argus service defines a wire contract (Pydantic models exchanged over
4
+ HTTP/CLI and codegen'd against by argus-studio) and stamps it with a version so
5
+ a consumer can refuse an incompatible major instead of misreading it — the
6
+ ``MANIFEST_VERSION`` / ``PROOF_VERSION`` discipline. That machinery was
7
+ re-implemented per repo; this module is the single copy.
8
+
9
+ A package supplies its own version constant and field name (``proof_version``,
10
+ ``manifest_version`` — kept per-package for readable payloads) and gets:
11
+
12
+ * :func:`check_version` — the major-compatibility gate;
13
+ * :func:`make_versioned_base` — a Pydantic base that stamps + checks the field;
14
+ * :func:`render_schema` — the canonical committed-schema string, so the
15
+ ``schema`` / ``schema --check`` CLI is identical everywhere.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from typing import TYPE_CHECKING
22
+
23
+ from pydantic import BaseModel, model_validator
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Iterable, Sequence
27
+
28
+
29
+ class VersionError(RuntimeError):
30
+ """A wire payload's version is incompatible with this build."""
31
+
32
+
33
+ def schema_major(version: str) -> str:
34
+ """The major component of a version string (``'2.7' -> '2'``)."""
35
+ return version.split(".", 1)[0]
36
+
37
+
38
+ def check_version(
39
+ version: str,
40
+ supported_majors: Sequence[str],
41
+ *,
42
+ label: str = "wire schema",
43
+ error: type[Exception] = VersionError,
44
+ ) -> None:
45
+ """Raise if *version*'s major is not supported.
46
+
47
+ The single gate every deserialization path runs through, so an incompatible
48
+ payload is refused with a clear, consistent message across the suite. Pass
49
+ ``error`` to raise a package's own exception type (e.g. ``ProofError``) so
50
+ version failures fold into that package's error hierarchy.
51
+ """
52
+ if schema_major(version) not in tuple(supported_majors):
53
+ understood = ", ".join(f"{m}.x" for m in supported_majors)
54
+ raise error(f"{label} {version} is not supported (this build understands {understood}) — upgrade or regenerate")
55
+
56
+
57
+ def make_versioned_base(
58
+ field_name: str,
59
+ current: str,
60
+ supported_majors: Sequence[str],
61
+ *,
62
+ label: str | None = None,
63
+ class_name: str = "VersionedBase",
64
+ error: type[Exception] = VersionError,
65
+ ) -> type[BaseModel]:
66
+ """Build a Pydantic base that stamps and version-checks *field_name*.
67
+
68
+ Subclasses of the returned base carry ``field_name`` (defaulting to
69
+ *current*) and, on construction or ``model_validate``, refuse a payload
70
+ whose major is not in *supported_majors*. Keeping the field name a parameter
71
+ lets each package keep its readable name (``proof_version``, …) while sharing
72
+ the logic; ``error`` selects the exception type raised on a mismatch.
73
+ """
74
+ majors = tuple(supported_majors)
75
+ lbl = label or field_name
76
+
77
+ def _check_wire_version(self: BaseModel) -> BaseModel:
78
+ check_version(getattr(self, field_name), majors, label=lbl, error=error)
79
+ return self
80
+
81
+ namespace = {
82
+ "__annotations__": {field_name: str},
83
+ field_name: current,
84
+ "_check_wire_version": model_validator(mode="after")(_check_wire_version),
85
+ }
86
+ return type(class_name, (BaseModel,), namespace)
87
+
88
+
89
+ def wire_schema(models: Iterable[type[BaseModel]], *, title: str) -> dict:
90
+ """Combined JSON Schema for *models* (serialization view, ``$defs`` refs)."""
91
+ from pydantic.json_schema import models_json_schema
92
+
93
+ _, schema = models_json_schema(
94
+ [(m, "serialization") for m in models],
95
+ title=title,
96
+ ref_template="#/$defs/{model}",
97
+ )
98
+ return schema
99
+
100
+
101
+ def render_schema(models: Iterable[type[BaseModel]], *, title: str) -> str:
102
+ """The canonical committed-schema string: sorted, indented, newline-terminated.
103
+
104
+ Used by both ``schema`` (write) and ``schema --check`` (compare) so the two
105
+ can never disagree about formatting.
106
+ """
107
+ return json.dumps(wire_schema(models, title=title), indent=2, sort_keys=True) + "\n"
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: argus-cortex
3
+ Version: 0.1.0
4
+ Summary: Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract
5
+ Project-URL: Repository, https://github.com/smk762/argus-cortex
6
+ Author: smk762
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: pydantic>=2.7
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
20
+ Requires-Dist: pytest>=8.0; extra == 'dev'
21
+ Requires-Dist: ruff==0.15.16; extra == 'dev'
22
+ Provides-Extra: remote
23
+ Requires-Dist: httpx>=0.27; extra == 'remote'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # argus-cortex
27
+
28
+ Shared foundation for the Argus suite: taxonomy, versioned wire-schema tooling, and the local/remote AI backend contract.
29
+
30
+ Part of the [Argus suite](https://github.com/smk762?tab=repositories&q=argus) (quarry → curator → lens → forge → proof). This is the library the stages import so the code they *share* — the target taxonomy, the wire-schema versioning discipline, and the way they call local or hosted models — lives in one place instead of being copy-pasted per repo.
31
+
32
+ ## What's here
33
+
34
+ - **`argus_cortex.taxonomy`** — `TargetProfile` / `TargetStyle` / `TargetCategory`, the "moat" types every stage inherits verbatim.
35
+ - **`argus_cortex.wire`** — the versioned wire-schema toolkit: `check_version()` (major-compatibility gate), `make_versioned_base()` (a Pydantic base that stamps + checks a version field like `proof_version` / `manifest_version`), and `wire_schema()` / `render_schema()` for the committed-schema `schema --check` CLI pattern.
36
+ - **`argus_cortex.backends`** — the AI-backend contract generalised from argus-lens: `Backend` / `LocalBackend` / `RemoteBackend` (point at a hosted service by `base_url`, `host`/`port`, or a known `RemoteProvider`), plus `with_retries()` and `resolve_device()`. httpx lives behind the `[remote]` extra.
37
+
38
+ ```python
39
+ from argus_cortex.wire import make_versioned_base, render_schema
40
+ from argus_cortex.backends import RemoteBackend, RemoteProvider
41
+
42
+ # per-package versioned base (readable field name kept local, logic shared)
43
+ _Versioned = make_versioned_base("proof_version", "1.0", ("1",))
44
+
45
+ # reach a hosted scorer by IP/port, or a known provider
46
+ scorer = RemoteBackend.from_host("192.168.1.20", 9000, api_key="…")
47
+ nim = RemoteBackend.from_provider(RemoteProvider.NVIDIA_NIM, api_key="…")
48
+ ```
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ uv pip install argus-cortex # core (pydantic only)
54
+ uv pip install "argus-cortex[remote]" # + httpx for RemoteBackend
55
+ ```
56
+
57
+ ## Develop
58
+
59
+ ```bash
60
+ make install # venv + editable install with the "dev" extras
61
+ make test
62
+ make lint
63
+ ```
64
+
65
+ ## CI / Release
66
+
67
+ - **CI** runs via the shared [`argus-ci`](https://github.com/smk762/argus-ci) reusable workflow.
68
+ - **Release** publishes to PyPI (OIDC trusted publishing) on `v*` tags. No container image — this is a library.
69
+ - Versioning is derived from git tags via `hatch-vcs` — tag `vX.Y.Z` to cut a release.
70
+
71
+ This repo was scaffolded from [`argus-pkg-template`](https://github.com/smk762/argus-pkg-template).
72
+ Run `copier update` to pull template changes (CI, release, tooling).
@@ -0,0 +1,10 @@
1
+ argus_cortex/__init__.py,sha256=HVTi86pMTX9wHH49wspw80zxeQNxKdu6iPYSq6rd3IE,1425
2
+ argus_cortex/_version.py,sha256=n_5vdJsPNu7wZ57LGuRL585uvll-hiuvZUBWzdG0RQU,520
3
+ argus_cortex/backends.py,sha256=J0EeSOTsjO2eyXjBu3E3SmUpPxPq7F9CWyuQCM_hGdM,11061
4
+ argus_cortex/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ argus_cortex/taxonomy.py,sha256=4fa9oSwb0E2rmLheClUmWl14Dj5GuU-FUvhv921jWyI,1701
6
+ argus_cortex/wire.py,sha256=4P16st77HtrwkjoCGQYs6Js58TLsN0RTcnorldgHp7Q,4036
7
+ argus_cortex-0.1.0.dist-info/METADATA,sha256=Mboy9IHfnExU8AtpapKy8xyTmvWc7dCpov9jHkr1eEE,3477
8
+ argus_cortex-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
9
+ argus_cortex-0.1.0.dist-info/licenses/LICENSE,sha256=rVItjFwYlGlMC6dPK1mp0iIQE6ku9HMneX4mNbkuU54,1063
10
+ argus_cortex-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smk762
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.