oumigo 0.1.0__tar.gz

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.
Files changed (89) hide show
  1. oumigo-0.1.0/.env.example +58 -0
  2. oumigo-0.1.0/.gitignore +22 -0
  3. oumigo-0.1.0/LICENSE +21 -0
  4. oumigo-0.1.0/PKG-INFO +91 -0
  5. oumigo-0.1.0/README.md +47 -0
  6. oumigo-0.1.0/docs/api.md +372 -0
  7. oumigo-0.1.0/docs/metrics.md +507 -0
  8. oumigo-0.1.0/docs/worker-node-states.md +145 -0
  9. oumigo-0.1.0/pyproject.toml +96 -0
  10. oumigo-0.1.0/src/oumigo/__about__.py +3 -0
  11. oumigo-0.1.0/src/oumigo/__init__.py +31 -0
  12. oumigo-0.1.0/src/oumigo/api/__init__.py +42 -0
  13. oumigo-0.1.0/src/oumigo/api/agent/__init__.py +22 -0
  14. oumigo-0.1.0/src/oumigo/api/agent/agent.py +86 -0
  15. oumigo-0.1.0/src/oumigo/api/agent/chat.py +539 -0
  16. oumigo-0.1.0/src/oumigo/api/agent/response.py +144 -0
  17. oumigo-0.1.0/src/oumigo/api/agent/tool.py +540 -0
  18. oumigo-0.1.0/src/oumigo/api/api.py +453 -0
  19. oumigo-0.1.0/src/oumigo/api/manager/__init__.py +7 -0
  20. oumigo-0.1.0/src/oumigo/api/manager/manager.py +188 -0
  21. oumigo-0.1.0/src/oumigo/api/worker/__init__.py +7 -0
  22. oumigo-0.1.0/src/oumigo/api/worker/worker.py +73 -0
  23. oumigo-0.1.0/src/oumigo/cli/__init__.py +1 -0
  24. oumigo-0.1.0/src/oumigo/cli/main.py +287 -0
  25. oumigo-0.1.0/src/oumigo/common/__init__.py +1 -0
  26. oumigo-0.1.0/src/oumigo/common/env.py +57 -0
  27. oumigo-0.1.0/src/oumigo/common/http.py +8 -0
  28. oumigo-0.1.0/src/oumigo/common/identity.py +27 -0
  29. oumigo-0.1.0/src/oumigo/common/logging.py +24 -0
  30. oumigo-0.1.0/src/oumigo/common/proc.py +60 -0
  31. oumigo-0.1.0/src/oumigo/config/__init__.py +9 -0
  32. oumigo-0.1.0/src/oumigo/config/locate.py +43 -0
  33. oumigo-0.1.0/src/oumigo/config/resolve.py +10 -0
  34. oumigo-0.1.0/src/oumigo/config/spec.py +59 -0
  35. oumigo-0.1.0/src/oumigo/discovery.py +109 -0
  36. oumigo-0.1.0/src/oumigo/guard/__init__.py +33 -0
  37. oumigo-0.1.0/src/oumigo/guard/chain.py +124 -0
  38. oumigo-0.1.0/src/oumigo/guard/guard.py +59 -0
  39. oumigo-0.1.0/src/oumigo/guard/points.py +37 -0
  40. oumigo-0.1.0/src/oumigo/guard/profile.py +42 -0
  41. oumigo-0.1.0/src/oumigo/guard/verdict.py +119 -0
  42. oumigo-0.1.0/src/oumigo/protocol/__init__.py +9 -0
  43. oumigo-0.1.0/src/oumigo/protocol/messages.py +109 -0
  44. oumigo-0.1.0/src/oumigo/protocol/states.py +32 -0
  45. oumigo-0.1.0/src/oumigo/providers/__init__.py +12 -0
  46. oumigo-0.1.0/src/oumigo/providers/base.py +33 -0
  47. oumigo-0.1.0/src/oumigo/providers/factory.py +20 -0
  48. oumigo-0.1.0/src/oumigo/providers/static.py +38 -0
  49. oumigo-0.1.0/src/oumigo/service/__init__.py +6 -0
  50. oumigo-0.1.0/src/oumigo/service/manager/__init__.py +13 -0
  51. oumigo-0.1.0/src/oumigo/service/manager/auth.py +45 -0
  52. oumigo-0.1.0/src/oumigo/service/manager/console.py +231 -0
  53. oumigo-0.1.0/src/oumigo/service/manager/control/__init__.py +5 -0
  54. oumigo-0.1.0/src/oumigo/service/manager/control/registry.py +151 -0
  55. oumigo-0.1.0/src/oumigo/service/manager/control/server.py +361 -0
  56. oumigo-0.1.0/src/oumigo/service/manager/control/store.py +141 -0
  57. oumigo-0.1.0/src/oumigo/service/manager/dashboard/__init__.py +17 -0
  58. oumigo-0.1.0/src/oumigo/service/manager/dashboard/__main__.py +44 -0
  59. oumigo-0.1.0/src/oumigo/service/manager/dashboard/aggregate.py +106 -0
  60. oumigo-0.1.0/src/oumigo/service/manager/dashboard/server.py +107 -0
  61. oumigo-0.1.0/src/oumigo/service/manager/dashboard/source.py +115 -0
  62. oumigo-0.1.0/src/oumigo/service/manager/dashboard/web.py +185 -0
  63. oumigo-0.1.0/src/oumigo/service/manager/launcher.py +65 -0
  64. oumigo-0.1.0/src/oumigo/service/manager/router/__init__.py +6 -0
  65. oumigo-0.1.0/src/oumigo/service/manager/router/server.py +342 -0
  66. oumigo-0.1.0/src/oumigo/service/manager/settings.py +86 -0
  67. oumigo-0.1.0/src/oumigo/service/worker/__init__.py +11 -0
  68. oumigo-0.1.0/src/oumigo/service/worker/client.py +72 -0
  69. oumigo-0.1.0/src/oumigo/service/worker/coordinator.py +439 -0
  70. oumigo-0.1.0/src/oumigo/service/worker/hf_server.py +541 -0
  71. oumigo-0.1.0/src/oumigo/service/worker/identity.py +70 -0
  72. oumigo-0.1.0/src/oumigo/service/worker/metrics.py +807 -0
  73. oumigo-0.1.0/src/oumigo/service/worker/supervisor.py +271 -0
  74. oumigo-0.1.0/tests/__init__.py +0 -0
  75. oumigo-0.1.0/tests/pressure_test.py +804 -0
  76. oumigo-0.1.0/tests/test_api.py +311 -0
  77. oumigo-0.1.0/tests/test_chat.py +556 -0
  78. oumigo-0.1.0/tests/test_dashboard_aggregate.py +118 -0
  79. oumigo-0.1.0/tests/test_env.py +69 -0
  80. oumigo-0.1.0/tests/test_guard.py +348 -0
  81. oumigo-0.1.0/tests/test_metrics.py +304 -0
  82. oumigo-0.1.0/tests/test_registry_naming.py +53 -0
  83. oumigo-0.1.0/tests/test_router.py +218 -0
  84. oumigo-0.1.0/tests/test_smoke.py +38 -0
  85. oumigo-0.1.0/tests/test_store.py +65 -0
  86. oumigo-0.1.0/tests/test_supervisor.py +160 -0
  87. oumigo-0.1.0/tests/test_tool.py +315 -0
  88. oumigo-0.1.0/tests/worker_hf_test.py +871 -0
  89. oumigo-0.1.0/tests/worker_stub_test.py +557 -0
@@ -0,0 +1,58 @@
1
+ # Copy to `.env` and fill in. Your real `.env` is gitignored — never commit it.
2
+ # In production, supply these via systemd EnvironmentFile= or the orchestrator.
3
+
4
+
5
+ #----------------------------------------------------------------------------
6
+ # oumigo manager — development / sandbox environment.
7
+ #----------------------------------------------------------------------------
8
+
9
+ # Shared bearer token workers must present. If unset, worker authentication is
10
+ # DISABLED (v1 dev behavior) and the manager warns at startup.
11
+ # Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"
12
+ OUMIGO_MANAGER_TOKEN=
13
+
14
+ # Optional: manager config file (else the search path is used). Overridden by
15
+ # --config-file on the command line.
16
+ # OUMIGO_CONFIG_FILE=./manager.yaml
17
+
18
+
19
+ #----------------------------------------------------------------------------
20
+ # oumigo worker — development / sandbox environment.
21
+ #----------------------------------------------------------------------------
22
+
23
+ # Negotiable model config: these override what the manager hands the worker. Set
24
+ # MODEL_NAME to serve a different model than the manager's node_spec (or to serve
25
+ # at all when the manager has no model configured). MAX_MODEL_LEN caps context.
26
+ # Applies to both --backend=vllm and --backend=transformer.
27
+ #
28
+ # MODEL_NAME=google/gemma-4-E2B-it
29
+ # MAX_MODEL_LEN=8192
30
+
31
+ # Optional: helps with HF download rate limits (in the case model is not gated).
32
+ HF_TOKEN=
33
+
34
+ # Hugging Face cache. Point at a SHARED cache to avoid re-downloading the GBs models.
35
+ # Must be set BEFORE transformers is imported — the app loads this .env first.
36
+ # HF_HOME=~/.hf_cache
37
+
38
+ # Optional: vLLM's own cache (torch.compile / CUDA-graph artifacts, NOT model
39
+ # weights — those live under HF_HOME). Defaults to ~/.cache/vllm if unset. Point
40
+ # at a shared/persistent volume to avoid recompiling on every start.
41
+ #
42
+ # VLLM_CACHE_ROOT=~/.vllm_cache
43
+
44
+ # --- FlashInfer JIT compile failures (EngineCore fails to start) ------------
45
+ # vLLM JIT-compiles some FlashInfer CUDA kernels at startup, which is fragile if
46
+ # the box's CUDA toolchain is inconsistent. Two levers, most reliable first:
47
+ #
48
+ # 1) Reliable: skip FlashInfer's JIT sampler. vLLM uses its native top-k/top-p
49
+ # path; if attention already uses Triton, FlashInfer isn't otherwise needed.
50
+ # VLLM_USE_FLASHINFER_SAMPLER=0
51
+ #
52
+ # 2) Root-cause (only if the compiler, not the toolkit, is wrong): point CUDA_HOME
53
+ # at a *self-consistent* CUDA toolkit so nvcc + headers agree. The pip CUDA
54
+ # wheels ship one in the venv (find .venv -type d -path '*/nvidia/cu13') — but
55
+ # note those wheels can be minor-skewed (e.g. nvcc 13.2 vs headers 13.0), which
56
+ # FlashInfer's CCCL guard rejects; then only lever 1 helps.
57
+ #
58
+ # CUDA_HOME=/abs/path/to/.venv/lib/pythonX.Y/site-packages/nvidia/cu13
@@ -0,0 +1,22 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # Virtual envs / tooling
11
+ .venv/
12
+ venv/
13
+ .env
14
+ .uv/
15
+ .ruff_cache/
16
+ .mypy_cache/
17
+ .pytest_cache/
18
+
19
+ # Editors / OS
20
+ .vscode/
21
+ .idea/
22
+ .DS_Store
oumigo-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GotoAI Inc.
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.
oumigo-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: oumigo
3
+ Version: 0.1.0
4
+ Summary: Vertical-integration toolkit for running and managing GPU fleets with vLLM and Transformer backends.
5
+ Project-URL: Homepage, https://github.com/gotoai/oumigo
6
+ Project-URL: Repository, https://github.com/gotoai/oumigo
7
+ Project-URL: Issues, https://github.com/gotoai/oumigo/issues
8
+ Author: GOTOAI
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cluster,inference,llm,serving,vllm
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: fastapi>=0.110
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pydantic-settings>=2
24
+ Requires-Dist: pydantic>=2
25
+ Requires-Dist: pyyaml>=6
26
+ Requires-Dist: typer>=0.12
27
+ Requires-Dist: uvicorn>=0.29
28
+ Requires-Dist: zeroconf>=0.130
29
+ Provides-Extra: conoha
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.10; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
33
+ Requires-Dist: pytest>=8; extra == 'dev'
34
+ Requires-Dist: ruff>=0.5; extra == 'dev'
35
+ Provides-Extra: manager
36
+ Provides-Extra: worker
37
+ Requires-Dist: accelerate; extra == 'worker'
38
+ Requires-Dist: bitsandbytes; extra == 'worker'
39
+ Requires-Dist: pillow; extra == 'worker'
40
+ Requires-Dist: timm; extra == 'worker'
41
+ Requires-Dist: transformers>=5.10.1; extra == 'worker'
42
+ Requires-Dist: vllm>=0.5; extra == 'worker'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # oumigo
46
+
47
+ A vertical-integration toolkit for running and managing **vLLM replica fleets** — a
48
+ manager/driver node coordinating many independent vLLM worker replicas behind a
49
+ health-aware router, with a pluggable cloud-provisioning layer.
50
+
51
+ > Status: early development. The CLI, manager console, config resolution, and the
52
+ > LAN provider exist; core worker↔manager registration, the router, and vLLM
53
+ > supervision are not built yet.
54
+
55
+ ## Architecture
56
+
57
+ Two roles:
58
+
59
+ - **Worker** (`oumigo.service.worker`): a long-lived *coordinator* supervises a vLLM server
60
+ as a child process, monitors health, executes start/stop/restart from the manager,
61
+ and owns the node state machine + restart-with-give-up policy. Workers self-register
62
+ with the manager and heartbeat.
63
+ - **Manager** (`oumigo.service.manager`): coordinates the fleet, split into sub-layers:
64
+ - **control plane** (`manager.control`): tracks worker registrations and state,
65
+ drives worker lifecycle, reconciles desired vs. actual. Low-frequency,
66
+ correctness-critical.
67
+ - **data plane / router** (`manager.router`): forwards client inference calls to
68
+ healthy workers — on the hot path of every request.
69
+ - **provisioning** (`oumigo.providers`): how workers come into existence — a
70
+ minimal, lifecycle-shaped `Provider` protocol used by the control plane. Ships
71
+ with `StaticProvider` (LAN: workers are hand-started and self-register, no
72
+ provisioning); cloud backends (e.g. ConoHa, OpenStack-based) are future
73
+ implementations of the same protocol.
74
+ - **dashboard** (`manager.dashboard`): performance & diagnostics — later.
75
+
76
+ Shared foundations: `oumigo.config` (typed settings + precedence resolution) and
77
+ `oumigo.protocol` (the wire contract both roles import so it can't drift).
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ # from oumigo/
83
+ python -m venv .venv
84
+ source .venv/bin/activate
85
+ pip install -e ".[worker,dev]" # on a GPU worker box
86
+ pip install -e ".[manager,dev]" # on the manager box
87
+ oumigo version
88
+ ```
89
+
90
+ To consume this in-development package from a sibling project, editable-install it
91
+ into that project's environment (e.g. `pip install -e ../oumigo`).
oumigo-0.1.0/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # oumigo
2
+
3
+ A vertical-integration toolkit for running and managing **vLLM replica fleets** — a
4
+ manager/driver node coordinating many independent vLLM worker replicas behind a
5
+ health-aware router, with a pluggable cloud-provisioning layer.
6
+
7
+ > Status: early development. The CLI, manager console, config resolution, and the
8
+ > LAN provider exist; core worker↔manager registration, the router, and vLLM
9
+ > supervision are not built yet.
10
+
11
+ ## Architecture
12
+
13
+ Two roles:
14
+
15
+ - **Worker** (`oumigo.service.worker`): a long-lived *coordinator* supervises a vLLM server
16
+ as a child process, monitors health, executes start/stop/restart from the manager,
17
+ and owns the node state machine + restart-with-give-up policy. Workers self-register
18
+ with the manager and heartbeat.
19
+ - **Manager** (`oumigo.service.manager`): coordinates the fleet, split into sub-layers:
20
+ - **control plane** (`manager.control`): tracks worker registrations and state,
21
+ drives worker lifecycle, reconciles desired vs. actual. Low-frequency,
22
+ correctness-critical.
23
+ - **data plane / router** (`manager.router`): forwards client inference calls to
24
+ healthy workers — on the hot path of every request.
25
+ - **provisioning** (`oumigo.providers`): how workers come into existence — a
26
+ minimal, lifecycle-shaped `Provider` protocol used by the control plane. Ships
27
+ with `StaticProvider` (LAN: workers are hand-started and self-register, no
28
+ provisioning); cloud backends (e.g. ConoHa, OpenStack-based) are future
29
+ implementations of the same protocol.
30
+ - **dashboard** (`manager.dashboard`): performance & diagnostics — later.
31
+
32
+ Shared foundations: `oumigo.config` (typed settings + precedence resolution) and
33
+ `oumigo.protocol` (the wire contract both roles import so it can't drift).
34
+
35
+ ## Development
36
+
37
+ ```bash
38
+ # from oumigo/
39
+ python -m venv .venv
40
+ source .venv/bin/activate
41
+ pip install -e ".[worker,dev]" # on a GPU worker box
42
+ pip install -e ".[manager,dev]" # on the manager box
43
+ oumigo version
44
+ ```
45
+
46
+ To consume this in-development package from a sibling project, editable-install it
47
+ into that project's environment (e.g. `pip install -e ../oumigo`).
@@ -0,0 +1,372 @@
1
+ # oumigo Python API — Reference
2
+
3
+ > Status: implemented. The client library lives under `oumigo.api` (the manager/worker
4
+ > *handles*, the spawn/attach functions, and the `agent` inference layer). The *services*
5
+ > those handles talk to live under `oumigo.service`. Everything in this document is
6
+ > importable from the top-level `oumigo` package.
7
+
8
+ The API has two layers:
9
+
10
+ 1. **Fleet control** — spawn or attach a manager and workers from Python:
11
+ `oumigo_get_or_create_manager`, `oumigo_create_worker`, and the `OumigoManager` /
12
+ `OumigoWorker` handles.
13
+ 2. **Inference** — run chats and tools against the fleet's OpenAI-compatible data plane:
14
+ `OumigoManager.create_agent(...)` → `OumigoAgent` → `OumigoChat` → `OumigoResponse`,
15
+ plus the `@tool` decorator.
16
+
17
+ ```python
18
+ from oumigo import (
19
+ oumigo_get_or_create_manager, oumigo_create_worker, # fleet control
20
+ OumigoManager, OumigoWorker, # handles
21
+ tool, Tool, ToolDefinitionError, # tools
22
+ OumigoAgent, OumigoChat, OumigoResponse, # inference
23
+ )
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Quickstart
29
+
30
+ ```python
31
+ from oumigo import oumigo_get_or_create_manager, tool
32
+
33
+ # 1. Attach to a LAN manager, or spawn one (dies with this process).
34
+ manager = oumigo_get_or_create_manager(config_file="./manager.yaml")
35
+
36
+ # 2. Declare a tool — its JSON schema is inferred from the signature + docstring.
37
+ @tool
38
+ def get_current_date(utc_offset_hours: int = 9) -> str:
39
+ """Get the current date at a given UTC offset.
40
+
41
+ Args:
42
+ utc_offset_hours: Timezone offset from UTC, in hours (e.g. 9 for JST).
43
+ """
44
+ from datetime import datetime, timedelta, timezone
45
+ return datetime.now(timezone(timedelta(hours=utc_offset_hours))).strftime("%Y-%m-%d")
46
+
47
+ # 3. Make an agent (tools + sampling defaults) and a stateful chat.
48
+ agent = manager.create_agent(tools=[get_current_date])
49
+ chat = agent.create_chat(system="You are concise.")
50
+
51
+ # 4. Ask. The tool loop runs automatically.
52
+ print(chat.request("What's the date?").text)
53
+
54
+ # Streaming:
55
+ for piece in chat.request("And tomorrow?", stream=True):
56
+ print(piece, end="")
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Fleet control
62
+
63
+ ### `oumigo_get_or_create_manager(...) -> OumigoManager`
64
+
65
+ Return a manager handle, **reusing** a live manager discovered on the LAN (mDNS) or
66
+ **spawning** one as a child of this process (armed with `PR_SET_PDEATHSIG`, so it exits
67
+ when this process does). Blocks until the control plane is healthy.
68
+
69
+ ```python
70
+ oumigo_get_or_create_manager(
71
+ bearer_token=None, config_file=None,
72
+ data_host=..., data_port=..., control_host=..., control_port=...,
73
+ provider=..., model=...,
74
+ *, discover_timeout=3.0, startup_timeout=20.0,
75
+ ) -> OumigoManager
76
+ ```
77
+
78
+ Settings resolve **explicit arg > `config_file` (YAML) > built-in default**. A missing or
79
+ unparseable `config_file` is ignored (a warning is logged).
80
+
81
+ > **Discovery caveat:** if a manager is already advertising on the LAN, it is reused and
82
+ > your `config_file` is **ignored** (the returned handle has `owned=False`). To guarantee
83
+ > your config is used, stop any other manager first, or edit the config of the manager
84
+ > that is actually running.
85
+
86
+ ### `oumigo_create_worker(...) -> OumigoWorker`
87
+
88
+ Spawn a worker child on this host and block until its vLLM/HF replica is `SERVING`
89
+ (includes the model load/download — potentially many minutes).
90
+
91
+ ```python
92
+ oumigo_create_worker(
93
+ bearer_token=None, hf_home="~/.hf_cache", vllm_cache_root="~/.vllm_cache",
94
+ hf_token=None, model_name=None,
95
+ *, manager=None, backend="vllm", manager_url=None,
96
+ discover_timeout=10.0, serving_timeout=None, poll_interval=2.0,
97
+ ) -> OumigoWorker
98
+ ```
99
+
100
+ The manager is resolved as: `manager` handle > `manager_url` > the last manager created in
101
+ this process > mDNS. `serving_timeout=None` waits **indefinitely** (you own the give-up
102
+ decision); the wait still ends early on a definitive failure (child exits or node reaches
103
+ `FAILED`).
104
+
105
+ ### `OumigoManager`
106
+
107
+ A handle to a running manager (spawned or discovered). Context manager; `stop()` is a
108
+ no-op for a discovered (`owned=False`) manager.
109
+
110
+ | Member | Description |
111
+ |---|---|
112
+ | `control_url` / `data_url` | Control-plane and data-plane base URLs. |
113
+ | `token` | Shared bearer token (or `None`). |
114
+ | `owned` | `True` only if this process spawned the child. |
115
+ | `is_healthy(timeout_s=2.0) -> bool` | `True` once `/healthz` answers 200. |
116
+ | `workers(timeout_s=5.0) -> list[dict]` | Current worker registry records. |
117
+ | `metrics(*, since=None, prefixes=None, timeout_s=5.0) -> list[dict]` | Latest slot per node, or raw historical points when `since` is set. |
118
+ | `create_agent(...) -> OumigoAgent` | Mint an inference agent (see below). |
119
+ | `stop()` | Terminate the spawned control-plane child (owned only). |
120
+
121
+ ### `OumigoWorker`
122
+
123
+ A handle to a worker child this process spawned. Context manager.
124
+
125
+ | Member | Description |
126
+ |---|---|
127
+ | `address` / `port` / `model` / `backend` / `node_id` | Replica identity. |
128
+ | `state() -> str \| None` | Node state the manager last saw (e.g. `"serving"`). |
129
+ | `is_serving() -> bool` | `True` when the replica is `SERVING`. |
130
+ | `is_alive() -> bool` | `True` while the child process runs. |
131
+ | `stop()` | Drain and shut down the replica (35 s grace before SIGKILL). |
132
+
133
+ ---
134
+
135
+ ## Inference
136
+
137
+ ### `OumigoManager.create_agent(...) -> OumigoAgent`
138
+
139
+ ```python
140
+ manager.create_agent(
141
+ tools=None,
142
+ *, max_iterations=5,
143
+ temperature=None, max_tokens=None, top_p=None, stop=None,
144
+ ) -> OumigoAgent
145
+ ```
146
+
147
+ An **agent** is a capability bundle bound to the manager's data plane: the tools and
148
+ sampling defaults shared by every chat it spawns.
149
+
150
+ - `tools` — a sequence of `Tool` (or plain functions, which are wrapped via the strict
151
+ `@tool` validator). Duplicate tool names raise `ValueError`.
152
+ - `max_iterations` — cap on model round-trips per request (the runaway tool-loop guard;
153
+ default 5). On the cap, the response's `finish_reason` is `"max_iterations"`.
154
+ - Sampling defaults apply to every chat/request; override individually later as needed.
155
+
156
+ The system prompt and tools are **server-owned** — they come from your code here, never
157
+ from a client. (See [Security & the trust boundary](#security--the-trust-boundary).)
158
+
159
+ ### `OumigoAgent.create_chat(...) -> OumigoChat`
160
+
161
+ ```python
162
+ agent.create_chat(system=None, max_history_turns=3, history=None) -> OumigoChat
163
+ ```
164
+
165
+ Start a **stateful** conversation.
166
+
167
+ - `system` — system-role content, prepended to every request in this chat.
168
+ - `max_history_turns` — how many prior `(user, assistant)` exchanges to carry into each
169
+ request. `0` disables memory. Default 3.
170
+ - `history` — prior conversation to seed (for stateless servers; see
171
+ [Stateless servers](#stateless-servers-history-rehydration)). Only `user`/`assistant`
172
+ turns are accepted.
173
+
174
+ > `OumigoChat` is **not thread-safe** — use one chat per session.
175
+
176
+ ### `OumigoChat`
177
+
178
+ | Member | Description |
179
+ |---|---|
180
+ | `request(contents: str, stream=False) -> OumigoResponse` | Run one user turn to completion (executes the tool loop). |
181
+ | `history -> list[dict]` | A copy of the carried `{"role","content"}` turns — persist this to rehydrate later. Never includes system/tool/reasoning. |
182
+
183
+ `request()` assembles `[system?] + recent history + {"role":"user","content":contents}`,
184
+ posts to the data plane, and runs the **agent loop**: while the model asks for tools,
185
+ execute the matching Python callbacks, feed their results back, and repeat — until the
186
+ model returns prose or `max_iterations` is reached. The `(user, final-answer)` exchange is
187
+ appended to `history`.
188
+
189
+ ### `OumigoResponse`
190
+
191
+ The result of one `request()` — the same type whether or not you streamed.
192
+
193
+ | Attribute | Description |
194
+ |---|---|
195
+ | `text: str` | The final answer (complete immediately for non-stream; complete after iteration for stream). |
196
+ | `reasoning: str` | The model's thinking (`reasoning_content`), kept **out of** `text`. `""` unless the worker runs a `--reasoning-parser`. Output-only. |
197
+ | `finish_reason: str \| None` | `"stop"`, `"length"`, or `"max_iterations"`. |
198
+ | `tool_calls_made: list[dict]` | One `{"name","arguments","result"}` per executed tool, in order. |
199
+ | `raw: dict \| None` | The last raw completion payload (escape hatch). |
200
+
201
+ **Iteration** (streaming):
202
+
203
+ ```python
204
+ resp = chat.request("hi", stream=True)
205
+
206
+ for piece in resp: # answer text deltas (str) — the default
207
+ ...
208
+
209
+ for piece in resp.stream(): # identical to `for piece in resp`
210
+ ...
211
+
212
+ for text, reasoning in resp.stream(get_reasoning=True): # dual channel
213
+ if reasoning:
214
+ ... # live "thinking" (reasoning_content deltas)
215
+ if text:
216
+ ... # the answer (content deltas)
217
+ ```
218
+
219
+ In `stream(get_reasoning=True)`, exactly one side of each pair is non-empty (the other is
220
+ `""`, never `None`). Iterating a response drives its one-shot request, so use `for piece
221
+ in resp` **or** one `stream(...)` call — not both. Either way `text` and `reasoning`
222
+ accumulate; after consumption, re-iterating yields the accumulated value once.
223
+
224
+ ---
225
+
226
+ ## Tools
227
+
228
+ Decorate a plain function with `@tool`; its JSON schema is inferred from the signature
229
+ (types → required/optional) and the Google-style docstring (descriptions).
230
+
231
+ ```python
232
+ from typing import Literal
233
+ from oumigo import tool
234
+
235
+ @tool
236
+ def get_weather(city: str, units: Literal["celsius", "fahrenheit"] = "celsius") -> str:
237
+ """Get the current weather for a city.
238
+
239
+ Args:
240
+ city: City name, e.g. Tokyo.
241
+ units: Temperature unit.
242
+ """
243
+ ...
244
+ ```
245
+
246
+ A decorated function is a `Tool` that **remains callable** (`get_weather("Tokyo")` still
247
+ works) and carries `.name`, `.description`, `.parameters`, plus `.to_openai()` and
248
+ `.invoke(**kwargs)`.
249
+
250
+ ### Strict validation (fail fast at import)
251
+
252
+ `@tool` validates the declaration **at decoration time** and raises a single, aggregated
253
+ `ToolDefinitionError` (listing every problem, with `file:line`) if anything is wrong.
254
+ Hard errors include:
255
+
256
+ - a parameter with no type annotation, or an unsupported type (`Any`, bare `list`/`dict`,
257
+ multi-type unions, arbitrary classes);
258
+ - supported types are `str`, `int`, `float`, `bool`, `list[T]`, `Literal[...]` (→ enum),
259
+ and `Optional[X]` / `X | None`;
260
+ - `*args` / `**kwargs` or positional-only parameters (the loop calls tools by keyword);
261
+ - a default whose type doesn't match its annotation;
262
+ - a missing docstring, an **undocumented** parameter, or a docstring `Args:` that drifts
263
+ from the signature;
264
+ - a missing (or non-serializable) return annotation.
265
+
266
+ **Escape hatches:**
267
+ - `@tool(strict=False)` demotes *documentation* checks (docstring/param descriptions) to
268
+ warnings; structural checks stay hard errors.
269
+ - `@tool(parameters={...})` supplies an explicit JSON Schema instead of inferring — still
270
+ validated against the signature (property names must match; each needs a type +
271
+ description).
272
+
273
+ ### Runtime tool errors
274
+
275
+ If a tool raises when the model calls it (bad args, exception, unknown tool), the loop
276
+ **catches it and feeds the error back to the model** as the tool result (`"Error: ..."`),
277
+ so the model can recover — `request()` never crashes on a tool failure.
278
+
279
+ ---
280
+
281
+ ## Stateless servers (history rehydration)
282
+
283
+ The XBCOM app (and any web service) is stateless per request, while `OumigoChat` is
284
+ stateful. The rule: **store the history (data), not the Chat (object).** Persist a
285
+ session's `chat.history` in your own store (dict / Redis / DB), and rehydrate a fresh,
286
+ ephemeral chat per request:
287
+
288
+ ```python
289
+ SESSIONS: dict[str, list] = {} # your session store
290
+ AGENT = manager.create_agent(tools=[...]) # server-owned tools
291
+ SYSTEM = "You are the XBCOM recommender."
292
+
293
+ def handle(session_id: str, user_msg: str) -> str:
294
+ chat = AGENT.create_chat(system=SYSTEM, history=SESSIONS.get(session_id)) # rehydrate
295
+ resp = chat.request(user_msg) # run one turn
296
+ SESSIONS[session_id] = chat.history # persist
297
+ return resp.text
298
+ ```
299
+
300
+ This is stateless-scalable (any process can serve any session), restart-safe, and
301
+ sidesteps `OumigoChat`'s single-thread constraint (each request gets its own chat).
302
+
303
+ > `max_history_turns` currently bounds **both** what the model sees and what `chat.history`
304
+ > retains (the most recent N exchanges). Set it to how much context you want to keep.
305
+
306
+ ---
307
+
308
+ ## Security & the trust boundary
309
+
310
+ Rehydrating history from a client-held or stored blob is a hardening point. `create_chat(
311
+ history=...)` enforces a **trust boundary** (`_normalize_history`):
312
+
313
+ - **Only `user`/`assistant` turns are accepted.** A `system` or `tool` role raises
314
+ `ValueError` — so a blob cannot inject a fake system prompt or a forged tool result.
315
+ - **Smuggled keys are stripped** (`tool_calls`, `reasoning_content`, …); each turn is
316
+ rebuilt as a clean `{"role","content"}` dict — so spoofed tool calls can't sneak in.
317
+ - **System prompt and tools stay server-owned** — supplied by your code via `create_agent`
318
+ / `create_chat(system=...)`, never from the request payload.
319
+
320
+ Two caveats:
321
+
322
+ 1. This protects the *history*, not the *current user turn*, which an attacker always
323
+ controls. Prompt injection via the live message ("ignore your instructions…") is a
324
+ separate concern — screen it with a guardrail layer, and never let model output
325
+ directly authorize a privileged action (tool calls touching real systems must be
326
+ independently authorized server-side).
327
+ 2. `reasoning_content` is **output-only**: surface it for display/debugging, but it is
328
+ never stored in history or fed back to the model.
329
+
330
+ ---
331
+
332
+ ## Fleet configuration notes (vLLM)
333
+
334
+ Behavior of the inference layer depends on how each worker's vLLM was launched. Pass extra
335
+ flags through the `model.extra_args` list in `manager.yaml`; they are appended verbatim to
336
+ `vllm serve`.
337
+
338
+ **Tool calling** requires vLLM to be started with an auto tool-choice parser, or any
339
+ request carrying `tools` fails with HTTP 400:
340
+
341
+ ```yaml
342
+ model:
343
+ name: google/gemma-4-12B-it-qat-w4a16-ct
344
+ extra_args:
345
+ - --enable-auto-tool-choice
346
+ - --tool-call-parser
347
+ - gemma4 # pick the parser matching your model
348
+ ```
349
+
350
+ **Reasoning** must be separated by a matching parser, or the model's channel/thinking
351
+ control tokens leak into `content`. Add:
352
+
353
+ ```yaml
354
+ - --reasoning-parser
355
+ - gemma4
356
+ ```
357
+
358
+ With the reasoning parser on, `resp.text` is the clean answer and `resp.reasoning` holds
359
+ the thinking; without it, `resp.reasoning` is `""`.
360
+
361
+ > `extra_args` flows `manager.yaml` → `NodeSpec` → the worker's `vllm serve`. The worker
362
+ > fetches its spec from the manager at startup, so a change requires **restarting the
363
+ > manager and the worker**.
364
+
365
+ ---
366
+
367
+ ## Notes & limits
368
+
369
+ - The inference layer is synchronous/blocking; streaming is a synchronous generator.
370
+ - Every model call funnels through one internal seam (`OumigoChat._payload`), reserved for
371
+ a future guardrail interceptor chain — not yet wired.
372
+ - Sampling defaults currently cover `temperature` / `max_tokens` / `top_p` / `stop`.