Skip to main content

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.

By default the server binds TCP on 0.0.0.0 (all interfaces); pass --websocket-host to bind a specific interface instead, e.g. --websocket-host 127.0.0.1.

Unix domain socket

Give --websocket-host a unix://<path> URI instead of an IP address to serve over a Unix domain socket at <path> instead of TCP -- useful when a local reverse proxy or supervisor should reach this process without exposing a TCP port. For example, --websocket-host unix:///tmp/ai-listener serves on the socket file /tmp/ai-listener. Any value that is neither a valid IP address nor a unix://-prefixed path is rejected at startup -- there is no bare-path fallback, so a typo'd IP can't silently be treated as a socket path. The unix:// form is sufficient on its own to enter WebSocket server mode -- --websocket-port is not required (and is ignored) in this mode. A stale socket file already at that path is removed before binding, so a restart after a crash doesn't fail with "address already in use".

The socket file is always created (and forced, regardless of umask or the parent directory's group) owned by this process's own uid/primary gid with mode 0660 (rw-rw----) -- readable/writable only by that same user or group, never by anyone else.

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-host unix:///var/run/nucore/unified.sock

Add --websocket-client-id <uid> to require that every connecting client's real effective UID match <uid>; connections from any other UID are closed immediately (WebSocket close code 1008), before any query is processed. The UID is read from the kernel via getpeereid() (BSD/POSIX; this project targets FreeBSD/eisy, not Linux, so SO_PEERCRED doesn't apply here) -- it can't be spoofed by the connecting client. --websocket-client-id only has an effect in Unix socket mode; it's silently ignored when --websocket-host is a TCP host/IP.

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-host unix:///var/run/nucore/unified.sock \
  --websocket-client-id 1002

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. When console logging is on (the default), DEBUG/INFO records go to stdout and WARNING/ERROR records go to stderr -- so 2>/dev/null silences errors/warnings while keeping normal output, and piping just stdout to a log aggregator won't miss error-level records mixed in. The optional log file (--log-file/NUCORE_LOG_FILE) always receives every level, regardless of the console split.

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. Ignored when --websocket-host is a Unix socket path
--websocket-host IP address to bind the WebSocket server to over TCP (default 0.0.0.0), or a unix://<path> URI to serve over a Unix domain socket at <path> instead -- on its own (without --websocket-port) it's enough to enter WebSocket server mode. Any other value (including a bare filesystem path with no unix:// prefix) is rejected
--websocket-client-id Unix socket mode only: required effective UID (checked via getpeereid()) of the connecting client; other UIDs are rejected. Ignored when --websocket-host is a TCP host/IP
--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 (http(s)://host:port, or unix:///path/to/socket for iox.IoXWrapper to connect over a Unix domain socket instead of TCP)
--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_prompt fetches 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_step then runs one diagnostic step directly against the backend (e.g. checking or starting/stopping/restarting core services), and run_shell_command runs a shell command on the backend host (e.g. to search the device activity log).
  • Plan -- start_plan/run_plan_step walk 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_preferences store 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_plugins cover NuCore's plugin marketplace (browse/license/installed state); get_plugin_capabilities/ call_plugin let the model extend its own capabilities with a plugin's tools when no built-in tool covers a request. install_plugin/buy_plugin/delete_plugin don'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. plugin_ops starts, stops, or restarts an installed plugin's own service -- the only way to do that; core services go through the diagnostics flow above instead.

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.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nucore-ai 2.2.1
File Size Uploaded
nucore_ai-2.2.1.tar.gz 245.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nucore-ai 2.2.1
File Interpreter ABI Platform
nucore_ai-2.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 539.6 kB

Release files / nucore_ai-2.2.1.tar.gz

Download URL nucore_ai-2.2.1.tar.gz
Size 245.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9820bb99fe06b262173c2212cb6ff18edf1d0052cb8c75776f1e1c20fb6a1ad6
BLAKE2b-256 checksum
How to use checksums
ed190ae111a24bc1688ed0822a9ae2553cecfeab15f668fa67db14f2864a38f6
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 Sep 8, 2026.

Transparency log

Release files / nucore_ai-2.2.1-py3-none-any.whl

Download URL nucore_ai-2.2.1-py3-none-any.whl
Size 294.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cbe4fc1619e7be80d0878ea198bc2fc3e5ae0935c8a4bdcb8f095ddc9ff264bf
BLAKE2b-256 checksum
How to use checksums
c100255da48b143a77e8e6ded8036780b9d362991d89911d642602eea3292fd5
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 Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

3.0.2

2 release files

3.0.1

2 release files

3.0.0

2 release files

This release

2.2.1 This release

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page