NuCoreAI Platform
Goal
Convert natural language user queries into commands, queries, and programs for any NuCore-enabled platform (currently eisy).
Quick Start
git clone https://github.com/NuCoreAI/nucore-ai.git
cd nucore-ai
python -m venv .venv && source .venv/bin/activate
pip install -e .
Development
pip install -e . installs production dependencies only. To run the test suite, install the dev
extras instead:
pip install -e ".[dev]"
# or: pip install -r requirements-dev.txt
pytest
Running the Unified Runtime
The entry point is the unified runtime: one system prompt, one native tool-calling agentic loop, no router, no per-intent directory dispatch. It executes user queries directly against a NuCore backend.
Create a runtime profile JSON first (see src/unified/runtime_config.example.json).
Minimal (no backend)
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--query "Turn on the patio lights"
With NuCore Backend (eisy)
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--backend-api-classpath iox.IoXWrapper \
--backend-api-base-url https://192.168.6.134 \
--backend-api-username admin \
--backend-api-password yourpassword \
--json-output true
Interactive Mode
Omit --query to enter an interactive prompt loop:
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--backend-api-classpath iox.IoXWrapper \
--backend-api-base-url https://192.168.6.134 \
--backend-api-username admin \
--backend-api-password yourpassword
WebSocket Server Mode
Pass --websocket-port to run as a standalone WebSocket server instead of
--query/REPL mode -- no HTTP framework involved (uses the websockets package, already
a project dependency). Each connection gets its own session and conversation history;
every received message is treated as a query, and the response streams back over the
same connection.
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--backend-api-classpath iox.IoXWrapper \
--backend-api-base-url https://192.168.6.134 \
--backend-api-username admin \
--backend-api-password yourpassword \
--websocket-port 8765
This is a lower-level alternative to eisy_ai's FastAPI-based chat server (a separate
sibling project) -- use this mode when a raw ws:// endpoint is all you need, without
serving a browser UI.
TLS (wss://)
Add --ssl-certfile/--ssl-keyfile (a PEM cert and private key, given together) to
serve wss:// instead of ws:// -- needed for clients that always connect over TLS
(e.g. the Eisy UI). Uses Python's standard-library ssl module, no extra dependency.
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--backend-api-classpath iox.IoXWrapper \
--backend-api-base-url https://192.168.6.134 \
--backend-api-username admin \
--backend-api-password yourpassword \
--websocket-port 8765 \
--ssl-certfile secrets/cert.pem \
--ssl-keyfile secrets/key.pem
Generate a self-signed pair for local/dev use with:
openssl req -x509 -newkey rsa:2048 -keyout secrets/key.pem -out secrets/cert.pem \
-days 825 -nodes -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
secrets/ is already gitignored. A self-signed cert will trigger a browser warning
unless the client is configured to skip verification.
Secrets File
Use --secrets-file to provide API keys as key/value pairs. These values are
loaded into a dict and passed to provider dispatch as the environment source.
The file must be valid JSON with a single top-level object. Each property name is a secret name and each value is the string to use for lookup. Do not use shell syntax, comments, or nested structures.
Example secrets.json:
{
"OPENAI_API_KEY": "...",
"ANTHROPIC_API_KEY": "...",
"GEMINI_API_KEY": "...",
"XAI_API_KEY": "...",
"LLAMACPP_API_KEY": "..."
}
Format rules:
- Top level must be a JSON object.
- Keys should be environment-style secret names such as
OPENAI_API_KEY. - Values should be strings.
- Duplicate or alias keys are allowed if you want to point multiple names at the same secret value.
Usage:
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--secrets-file /path/to/secrets.json \
--query "Turn on the patio lights"
Logging
The runtime supports centralized, flexible logging for both development and production use.
Runtime Logging Flags
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--log-level DEBUG \
--log-file logs/unified-runtime.log
Use JSON logs for ingestion by external tools:
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--log-json \
--log-file logs/unified-runtime.json.log
Disable console logs (for quiet batch or service environments):
python -m unified.run_unified_runtime \
--runtime-config src/unified/runtime_config.example.json \
--no-log-console \
--log-file logs/unified-runtime.log
Logging Environment Variables
NUCORE_LOG_LEVEL(default:INFO)NUCORE_LOG_JSON(true/false, default:false)NUCORE_LOG_FILE(optional file path)NUCORE_LOG_CONSOLE(true/false, default:true)
Logger Usage in Code
from utils import configure_logging, get_logger
configure_logging(level="INFO")
logger = get_logger(__name__)
logger.info("runtime started")
Full CLI Reference
| Flag | Description |
|---|---|
--runtime-config |
Required path to JSON with top-level nucore_runtime |
--secrets-file |
Optional JSON file of secret key/value pairs passed into provider client key resolution |
--query |
Single query mode; omit for interactive loop |
--websocket-port |
Run as a native WebSocket server on this port instead of --query/REPL mode |
--ssl-certfile |
PEM cert file; with --ssl-keyfile, serves --websocket-port over wss:// |
--ssl-keyfile |
PEM private key file; with --ssl-certfile, serves --websocket-port over wss:// |
--backend-api-classpath |
Python class path for backend API (e.g. iox.IoXWrapper) |
--backend-api-base-url |
Base URL for backend API |
--backend-api-username |
Backend API username |
--backend-api-password |
Backend API password |
--json-output |
Enable JSON output mode for backend API |
--prompt_type |
Prompt variant to use (e.g. shared-features) |
--log-level |
Logging level override: DEBUG, INFO, WARNING, ERROR, CRITICAL |
--log-file |
Optional rotating log file path |
--log-json |
Emit logs in JSON format |
--no-log-console |
Disable console logging |
--stream / --no-stream |
Force LLM token streaming on/off for every nucore_runtime profile, overriding each profile's own stream setting |
--max-iterations |
Override the agentic loop's max tool-call iterations per query (defaults to runtime config's max_iterations, or 8) |
--preferences-dir |
Directory for this installation's customer preferences (aliases/events); overrides runtime config's preferences_dir -- no default, preferences are unavailable without one |
--diagnostic-step |
Bypass the LLM/agentic loop and call one diagnostic tool directly against a live backend (e.g. get_full_system_config); prints the raw result and exits. Pairs with --diagnostic-params. Manual testing only |
--diagnostic-params |
JSON object of keyword params for --diagnostic-step |
Supported Providers
| Provider | Alias | Env Var |
|---|---|---|
| Anthropic Claude | claude, anthropic |
ANTHROPIC_API_KEY |
| OpenAI | openai |
OPENAI_API_KEY |
| Google Gemini | gemini, google |
GEMINI_API_KEY |
| xAI Grok | grok, xai |
XAI_API_KEY |
| llama.cpp (local) | llama.cpp, llamacpp |
LLAMACPP_API_KEY (optional) |
Provider and model settings come from the runtime profile file passed to --runtime-config. Profiles use a provider field and do not rely on legacy llm aliases or supported_llms fallback behavior. API keys can be embedded in the profile, supplied via --secrets-file, or read from process environment variables.
Ready-to-copy example profiles: src/unified/runtime_config.example.json (Claude, the default),
src/unified/runtime_config.openai.example.json, src/unified/runtime_config.grok.example.json.
Copy whichever one you want over your live runtime_config.json (or pass it directly via
--runtime-config) and set that provider's env var.
A profile can also set "reasoning_effort" (e.g. "none"/"low"/"medium"/"high"), forwarded
to OpenAI-compatible providers only when present. Some reasoning-tier models reject function tools
on /v1/chat/completions entirely unless this is explicitly set -- see the OpenAI example profile.
Using a Local (Edge) LLM with llama.cpp
Build llama.cpp
sudo apt install build-essential cmake clang libomp-dev libcurl4-openssl-dev
CPU only
cmake -B build.cpu
cmake --build build.cpu --config release
Nvidia GPU
sudo ubuntu-drivers install
sudo apt install nvidia-cuda-toolkit
cmake -B build.cuda -DGGML_CUDA=on
cmake --build build.cuda --config release
Start the Server
build.cuda/bin/llama-server \
-m /path/to/model.gguf \
-c 64000 --port 8013 --host 0.0.0.0 \
-t 15 --n-gpu-layers 50 --batch-size 8192
Connect the Runtime to llama.cpp
python -m unified.run_unified_runtime \
--runtime-config /path/to/nucore_runtime.json \
--backend-api-classpath iox.IoXWrapper \
--backend-api-base-url https://192.168.6.134 \
--backend-api-username admin \
--backend-api-password yourpassword
Runtime profile for llama.cpp (--runtime-config target):
{
"nucore_runtime": {
"default": {
"provider": "llama.cpp",
"model": "qwen3-instruct",
"url": "http://192.168.6.113:8013/v1",
"max_turns": 20,
"temperature": 0.2,
"max_tokens": 32000
}
}
}
Capabilities
Beyond device/group/routine/variable command-and-control, the unified runtime supports:
- Diagnostics -- stateless, no session or start call:
get_diagnostics_promptfetches reference material (PLM/device link records, the DEVICE ACTIVITY LOG format and how to read it for past behavior, known fixes) on demand for the current question only -- like consulting DEVICE DATABASE/ROUTINES DATABASE, it never turns into a standing mode and doesn't carry over to a later, unrelated question in the same conversation.run_diagnostic_stepthen runs one diagnostic step directly against the backend (e.g. checking or starting/stopping/restarting core or plugin services), andrun_shell_commandruns a shell command on the backend host (e.g. to search the device activity log). - Plan --
start_plan/run_plan_stepwalk a customer through a structured multi-step task such as a new device installation, rather than a single command/response turn. - User preferences --
preference_op/list_preferencesstore per-user aliases (e.g. naming a device or routine) and event subscriptions, persisted across sessions. - Plugin management --
list_store_plugins/list_purchased_plugins/list_installed_pluginscover NuCore's plugin marketplace (browse/license/installed state);get_plugin_capabilities/call_pluginlet the model extend its own capabilities with a plugin's tools when no built-in tool covers a request.install_plugin/buy_plugin/delete_plugindon't complete anything themselves -- for security reasons, installing, purchasing, and deleting all happen on the web -- each returns a link for the customer to finish there. Starting/stopping/restarting a plugin's underlying service goes through the diagnostics flow above, not this one.
See src/unified/prompt/definitions.md for the exact tool-selection rules the model follows for
each of these, and src/unified/README.md for the tool/handler layout.
Hardware
Tested with eisy.
Further Documentation
- Unified runtime architecture and tool reference:
src/unified/README.md
Metadata
Release files for nucore-ai 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nucore_ai-2.0.0.tar.gz | 231.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nucore_ai-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 513.3 kB
Release files / nucore_ai-2.0.0.tar.gz
| Download URL | nucore_ai-2.0.0.tar.gz |
|---|---|
| Size | 231.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
22e89d5a7901b87599cb15853010e2ba64ee0c9e0a08003437cc055ae910d8b9
|
|
BLAKE2b-256 checksum How to use checksums |
aad4d4f3e1ae537ac99aa8353aff5c19c79fec3e3797190f0052d7292b669517
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / nucore_ai-2.0.0-py3-none-any.whl
| Download URL | nucore_ai-2.0.0-py3-none-any.whl |
|---|---|
| Size | 281.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
de324946f351fd140e3b2a644f80c6b4d7aeced29c20fa311b51a1607e326f88
|
|
BLAKE2b-256 checksum How to use checksums |
3ec352e98aabd53a632104f44e7f8a0c970c7099a869d17af032499534fa1325
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency log