Skip to main content

aindy-runtime

A self-hosted execution substrate for AI systems — the layer beneath your agents, workflows and applications, running on your own database.

Work is durable. A run suspends as a first-class lifecycle state, survives a restart, and resumes where it stopped — not replayed from the beginning.

Work is governed. Approval does not merely permit a run; it mints a signed, scoped capability token bound to that run, that user and that plan. Tools are capability-gated, delegation can only narrow authority, and side effects pass a ledgered boundary you can make at-most-once.

And work accumulates. Every execution records what it knew going in and what it produced going out, into an addressable memory space that is scored and fed back into the next decision — so the tenth run is not the first run again.

The runtime is the substrate; your domain logic mounts on top as a plugin package or over the HTTP SDK. A bare install gives you the execution layer and the operator surfaces, not a finished application — see Building apps on aindy-runtime.

Deployable in minutes via Docker Compose. Extensible via a Python plugin registry. Operable via a built-in platform UI and a REST API backed by the aindy-sdk.

What it gives you:

  • Flow engine — DAG-based execution with WAIT/RESUME semantics, priority scheduling, and dead-letter recovery
  • Agent runtime — structured goal → plan → approval → execute loop with capability tokens and trigger evaluation
  • Memory system — not a retrieval library: memory is addressable (/memory/{tenant}/{namespace}/{type}/{id}), scored on impact, usage and causal depth, and joined to execution — each ExecutionUnit carries memory_context_ids and output_memory_ids, so what a run knew going in and produced going out is answerable from one row. Backed by pgvector, with an optional native scoring path (a Rust cdylib with a C++ semantic kernel) and a Python fallback pinned equal by parity tests
  • Execution units — a durable, quota-bounded unit of work with a real status machine (pending | executing | waiting | resumed | completed | failed), where waiting and resumed are distinct so an audit query can tell a fresh run from a resumption
  • Syscall contract — single SyscallDispatcher entry point with schema validation, idempotency gates, and tenant isolation
  • Platform UI — operator dashboard for flows, agents, scheduler, and observability (served at /platform)
  • Plugin registry — mount routers, flows, jobs, syscalls, connectors, and event handlers from external Python packages at boot time
  • Self-service auth — password change, forgot/reset over a signed single-use token, and email verification, with uniform responses so no endpoint becomes an account-enumeration oracle
  • Outbound connectorsregister_connector plus a capability-enforced egress boundary: recipient/domain allow-lists, per-capability rate limits, and just-in-time secrets from a broker rather than app config
  • MCP interop — call external MCP tools from agent runs, and expose runtime syscalls to external MCP clients via aindy-runtime mcp-server (opt-in, [mcp] extra)
  • Nodus script execution — embedded execution service for the Nodus DSL (.nodus / .nd), with memory builtins and WAIT/RESUME propagation back into the flow engine
  • Distributed operation — Redis-backed distributed job queue, lease-based leadership election for background schedulers, and orphan-run recovery watchdogs
  • Effect compensation — append-only effect-reversal ledger with sys.v1.agent.undo to walk back a run's recorded side effects, plus sys.v1.agent.simulate for zero-side-effect rehearsal against virtual tools
  • Sandbox certification — Docker-backed extension sandbox with an escape-test suite, posture reporting (aindy-runtime sandbox), and an append-only audit log
  • Webhooks — subscription CRUD on the platform API for pushing runtime events to external endpoints
  • Federated memory recall — cross-agent recall via POST /memory/federated/recall, dispatched through the syscall contract

Why it is shaped this way

The runtime was built as the execution layer beneath a closed-loop system — the Infinity Algorithm, whose support layer needs real behaviour observed, converted into signals, fed into scoring, and used to adjust what runs next:

observe → score → adjust → execute → observe

Building that pushed the loop's primitives down into the runtime as generic mechanisms, rather than leaving them in the application. They are ordinary runtime event types (AINDY/core/system_event_types.py) and a runtime-owned observation service, so any system mounted on the runtime inherits them without writing any of it:

Loop stage Runtime primitive
observe the watcher — platform_layer/watcher_service.py, routes/watcher_router.py
recall recall.used
score score.computed
adjust next_action.chosen
execute next_action.dispatched

The division is deliberate and holds for any consumer: the application owns the formulas; the runtime owns the loop. Scoring functions, KPI weighting and policy are domain logic and belong in your app. Observation, the signal path, the causal record, durable re-execution and the provenance that ties them together are substrate concerns.

For the full statement of what the runtime is and what a consumer inherits — including where the claims stop — see docs/runtime/WHAT_THE_RUNTIME_IS.md.

Stability: public surfaces declared under docs/runtime/ are stable. Extension and orchestration surfaces marked experimental may change between minor versions. In-process extensions require trusted code — this is not a sandboxed third-party plugin host.

Quickstart

Prerequisites: Docker Desktop (or Docker Engine + Compose plugin v2.20+).

# 1. Clone
git clone https://github.com/Masterplanner25/aindy-runtime.git
cd aindy-runtime

# 2. Configure
cp AINDY/.env.example AINDY/.env
#    Open AINDY/.env and set at minimum:
#      SECRET_KEY  — generate: python3 -c "import secrets; print(secrets.token_hex(32))"
#      OPENAI_API_KEY

# 3. Start
docker compose up -d

# 4. Run migrations + wait for ready
#    (alembic upgrade head runs automatically inside the api container on boot)
#    Watch progress:
docker compose logs -f api

# 5. Verify
curl http://localhost:8000/ready    # → {"status": "ok", ...}

# 6. Visit the platform UI
#    http://localhost:8000/platform

Production-shaped deployment (Redis + distributed worker):

docker compose --profile full up -d

With metrics (Prometheus on port 9090):

docker compose --profile full --profile monitoring up -d

Cloud / remote VM with nginx + TLS (ports locked down, HTTPS):

NGINX_CONF=nginx.tls.conf \
docker compose -f docker-compose.yml -f docker-compose.prod.yml \
  --profile full --profile proxy up -d

See Remote deployment below for the full TLS checklist.

After the server starts

Once curl http://localhost:8000/ready returns {"status": "ok"}, create your first account and API key:

# Register. Returns 202 with NO token — registration does not log you in.
# Passwords must be at least 8 characters.
curl -s -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "yourpassword", "username": "you"}' \
  | python -m json.tool

# Log in — copy access_token from the response
curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "yourpassword"}' \
  | python -m json.tool

# Promote yourself to admin (needed to create Platform API keys).
# For a compose deployment the CLI lives in the container, not on the host:
#   docker compose exec api aindy-runtime auth promote-admin you@example.com
aindy-runtime auth promote-admin you@example.com

# Create a Platform API key (save the 'key' field — shown only once).
# Unknown scopes are rejected with 422 — see the table below for the full set.
curl -s -X POST http://localhost:8000/platform/keys \
  -H "Authorization: Bearer <your-jwt>" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "scopes": ["memory.read", "memory.write", "flow.execute", "event.emit"]}' \
  | python -m json.tool

API key scopes — the complete set. Anything else is a 422:

Scope Grants
flow.read / flow.execute Read flow definitions and runs / dispatch sys.v1.flow.run
memory.read / memory.write / memory.delete Recall, write, and hard-delete memory nodes — delete is not implied by write
agent.run Create and execute agent runs
execution.read Read execution units, metrics, and observability surfaces
event.emit Emit events through the syscall contract
webhook.manage Manage webhook subscriptions
platform.admin Full platform administration

Registration returns 202 and no token. It does not log you in. If an email channel is configured it sends a verification link. This is deliberate: a response that differed between "created" and "already exists" would be an account-enumeration oracle. Log in separately for a token. Verification is not required to log in unless you set AINDY_REQUIRE_VERIFIED_LOGIN=true.

Then install the SDK and make your first call:

pip install aindy-sdk
from aindy_sdk import AINDYClient

client = AINDYClient("http://localhost:8000", api_key="aindy_your_key")
registry = client.syscalls.list()
print(registry["total_count"], "syscalls available")

Full SDK documentation and examples: aindy-sdk

Building apps on aindy-runtime

Three integration patterns, listed by increasing coupling:

1. SDK (external HTTP) — Any service that can make HTTP requests can integrate via Platform API keys. Install aindy-sdk, create a key with the scopes you need, and use AINDYClient. See After the server starts above.

2. Agent runs — Submit agent objectives via POST /apps/agent/run. The runtime executes the objective through its flow engine. No in-process code needed — just an authenticated HTTP call with a capability token. Note: the /apps/agent/run route itself is registered by the app plugin layer (pattern 3), not the bare runtime — a plugin bootstrap such as aindy-apps-monolith must be mounted for this endpoint to exist.

3. Trusted Python extensions (in-process) — Set AINDY_TRUST_EXTERNAL_PYTHON_EXTENSIONS=true to load a Python package into the runtime process at startup. Extensions register routers, flows, jobs, syscalls, and event handlers through the plugin registry. Any trusted Python package that ships a schema-valid aindy_plugins.json manifest can plug in this way — point the runtime at it with AINDY_APP_PLUGIN_MANIFEST=/path/to/aindy_plugins.json, or let it discover an aindy_plugins.json in the working directory. The plugin layer is not tied to any particular app package; aindy-apps-monolith is the first-party reference implementation, not a required dependency.

Reference implementation: aindy-apps-monolith contains 16 working domain apps built on this pattern. The canonical how-to doc is docs/architecture/PLUGIN_REGISTRY_PATTERN.md — it covers every registration category the registry exposes, boot-order dependency declarations, and a step-by-step guide for adding a new domain app.

Trust posture note: option 3 is a trusted-internal mechanism. It does not sandbox extension code. Do not use it to load untrusted third-party packages.

Note — database host inside compose: The DATABASE_URL in AINDY/.env must use the compose service name as the host, not localhost:

DATABASE_URL=postgresql://aindy:aindy@postgres:5432/aindy

postgres resolves on the compose network; localhost does not.

Note — pgvector required: The compose file uses pgvector/pgvector:pg16 instead of the stock postgres:16-alpine. The runtime stores memory embeddings as VECTOR(1536) columns, which requires the PostgreSQL pgvector extension. docker/init-pgvector.sql runs CREATE EXTENSION IF NOT EXISTS vector on first initialization. If you bring your own PostgreSQL instance, run that statement once before first boot.

Note — published database ports: postgres (5432), redis (6379), and mongo (27017) publish to the host for local development convenience. For production deployments on a cloud VM, remove the ports: blocks from those services or use a compose override file. See TECH_DEBT: COMPOSE-PROD-PORTS-1.

Install

pip install aindy-runtime

Import name: The distribution name is aindy-runtime but the importable module is AINDY (uppercase — it is an acronym). import aindy_runtime will not work.

from AINDY._version import __version__  # correct
from AINDY.platform_layer.deployment_contract import deployment_contract_summary
# import aindy_runtime  ← ImportError

For local development (editable install from source):

python -m pip install -e .

For staged release builds:

python -m pip install -e .[release]

CLI

aindy-runtime init       Scaffold AINDY/.env (with generated SECRET_KEY), Dockerfile,
                         docker-compose.yml, and docker/init-pgvector.sql for a new install
aindy-runtime serve      Start the HTTP API server (requires DATABASE_URL)
aindy-runtime sandbox    Report sandbox capabilities and exit
aindy-runtime bootstrap-schema   Create runtime-owned tables from packaged metadata
                                 and stamp the Alembic baseline (idempotent; requires DATABASE_URL)
aindy-runtime mcp-server         Serve AINDY syscalls as an MCP server over stdio for external
                                 MCP clients (needs the [mcp] extra; read-only by default)
aindy-runtime auth promote-admin <email>   Grant admin to a registered user (grant-only)
aindy-runtime --help     Show help and exit
aindy-runtime --version  Show version and exit

Run

Runtime-only API boot:

aindy-runtime serve

Minimum runtime environment:

DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DBNAME
SECRET_KEY=...
OPENAI_API_KEY=sk-...

For local smoke tests only, SQLite remains opt-in and must be declared explicitly:

DATABASE_URL=sqlite://
AINDY_ALLOW_SQLITE=1
SECRET_KEY=runtime-local-secret-key
OPENAI_API_KEY=sk-test-placeholder

Equivalent module and ASGI forms:

python -m AINDY.runtime_only serve
uvicorn AINDY.runtime_only:app

Upgrading

pip (local install)

pip install --upgrade aindy-runtime

Verify the new version:

aindy-runtime --version
# or, while the server is running:
curl http://localhost:8000/api/version

If this release includes a schema change, set AINDY_SCHEMA_RECONCILE=true before restarting. The startup log will tell you whether reconciliation is needed; if AINDY_ENFORCE_SCHEMA=true is set, the server will refuse to start rather than silently run against a mismatched schema.

AINDY_SCHEMA_RECONCILE=true aindy-runtime serve

Once the server confirms a clean startup you can unset the flag.

Docker Compose

# The api service builds locally (`build: .`) rather than pulling a published image,
# so `docker compose pull` has nothing to fetch. Rebuild instead:
docker compose build --no-cache api    # picks up the aindy-runtime pin in the Dockerfile
docker compose up -d                   # recreate containers

The Dockerfile installs a pinned aindy-runtime==X.Y.Z from PyPI, so bump that pin to the version you want before rebuilding. The Platform UI ships inside that wheel as package data rather than being built by the Dockerfile, so a container serves the UI belonging to the pinned version — not your working tree.

If the release bumps the schema, set the reconcile flag in AINDY/.env before restarting, then remove it after the first clean boot.

Rollback

pip install "aindy-runtime==<previous-version>"
# or, for Docker:
docker compose down && docker compose up -d   # after reverting the image tag in docker-compose.yml

Rolling back across a schema change requires a database restore — schema migrations are not automatically reversed on downgrade.

Remote deployment

For cloud VM or any deployment where a domain name and HTTPS are required. The proxy profile brings up an nginx container on ports 80 and 443 that forwards all traffic to the api container. The docker-compose.prod.yml overlay closes all internal port bindings so nothing is reachable from outside the host except nginx.

Plain HTTP (behind a TLS-terminating load balancer)

Suitable for AWS ALB, GCP Load Balancer, Cloudflare Proxy, etc. that terminate TLS and forward plain HTTP to the backend.

docker compose -f docker-compose.yml -f docker-compose.prod.yml \
  --profile full --profile proxy up -d

Set ALLOWED_ORIGINS=https://yourdomain.com in AINDY/.env.

HTTPS with Let's Encrypt (direct VM, no load balancer)

# 1. On the host — obtain a certificate (certbot must be installed)
certbot certonly --standalone -d yourdomain.com

# 2. Edit nginx/nginx.tls.conf — replace `server_name _;` with your domain:
#      server_name yourdomain.com;

# 3. Mount your certs — create docker-compose.override.yml:
cat > docker-compose.override.yml << 'EOF'
services:
  nginx:
    volumes:
      - /etc/letsencrypt/live/yourdomain.com/fullchain.pem:/etc/nginx/certs/fullchain.pem:ro
      - /etc/letsencrypt/live/yourdomain.com/privkey.pem:/etc/nginx/certs/privkey.pem:ro
EOF

# 4. In AINDY/.env set:
#      ALLOWED_ORIGINS=https://yourdomain.com

# 5. Start
NGINX_CONF=nginx.tls.conf \
docker compose -f docker-compose.yml -f docker-compose.prod.yml \
  --profile full --profile proxy up -d

Certificate renewal — add to host cron (crontab -e):

0 3 * * * certbot renew --quiet && docker compose exec nginx nginx -s reload

Port exposure summary

Profile combination Ports exposed to host
Default (no overlay) 8000 (api), 5432, 6379, 27017
+ docker-compose.prod.yml 8000 (api) only
+ proxy profile 8000 (api), 80, 443
+ proxy + docker-compose.prod.yml 80, 443 only

What lives here

aindy-runtime owns the execution substrate: the runtime kernel, flow engine, memory system, agent runtime, syscall registry, platform UI, and all stable operator surfaces declared under docs/runtime/.

App-layer code (apps/, aindy_plugins.json, app-profile Alembic migrations) lives in a plugin package, not this repo — any package that implements the plugin manifest can serve that role. A.I.N.D.Y.'s own apps live in aindy-apps-monolith, the first-party reference that demonstrates the plugin pattern at scale across 16 domain apps.

Full boundary definition: docs/runtime/RUNTIME_BOUNDARY.md

Branch And PR Model

Active contribution model for this repo:

  • protected branch: main
  • pull requests should target: main
  • feature work should branch from the current main

This repo does not use the archived monolith develop-targeting flow.

Verify

python -m pytest \
  tests/unit/test_runtime_only_test_fixtures.py \
  tests/unit/test_platform_only_startup.py \
  tests/unit/test_runtime_packaging.py \
  tests/unit/test_runtime_boundary.py \
  tests/unit/test_runtime_compatibility_metadata.py \
  tests/api/test_version_api.py \
  -m runtime_only -q

Runtime CI scope in .github/workflows/runtime-ci.yml now covers the runtime-owned push/PR baseline:

  • lint runtime-owned Python code with Ruff
  • validate runtime-doc frontmatter under docs/runtime/
  • install the runtime package and test extras in editable mode
  • assert runtime code does not import apps.*
  • verify the aindy-runtime console script
  • smoke GET /health and GET /api/version in runtime-only mode
  • run the full extracted runtime-owned pytest suite (tests -m runtime_only)
  • build wheel and sdist artifacts and run twine check

GitHub Actions note:

  • runtime-ci.yml is the automatic push/PR check for main
  • release-staging.yml is intentionally manual-only (workflow_dispatch) and will not appear as a normal push/PR status check until it is dispatched

Staged release flow in .github/workflows/release-staging.yml is intentionally non-publishing:

  • verify runtime version and compatibility metadata
  • build wheel and sdist artifacts
  • run twine check
  • upload artifacts for inspection

Checks intentionally left out of the runtime repo because they remain app- or monolith-owned:

  • app bootstrap and app-profile tests
  • cross-app import boundary checks
  • app-database Alembic migration execution for app-owned tables
  • frontend, Playwright, and client build checks
  • Docker image and full monolith service-matrix validation

Runtime Schema Bootstrap

The extracted runtime is self-hostable for its own database surface.

  • startup, worker boot, and readiness checks use packaged runtime ORM metadata as the schema contract
  • on a blank database, the runtime bootstraps runtime-owned tables directly from that packaged metadata
  • on an additive-safe but out-of-date schema, startup requires explicit AINDY_SCHEMA_RECONCILE=true before mutating an initialized database
  • on incompatible drift, startup fails closed when AINDY_ENFORCE_SCHEMA=true
  • app-owned tables and the monolith Alembic history remain app-repo concerns

Blessed deploy primitive — aindy-runtime bootstrap-schema. A deploy entrypoint that wants a clean ownership split (rather than replaying the full app migration history onto a fresh database) can run:

aindy-runtime bootstrap-schema      # idempotent; requires DATABASE_URL + pgvector

This does two things the server also does at startup, but as an explicit, standalone, idempotent step: (a) builds the runtime-owned tables from packaged ORM metadata — scoped to the runtime's own table set, never app tables — and (b) stamps the runtime's alembic_version_runtime table to the runtime head revision. Step (b) is the half an app-side bootstrap cannot do correctly: it gives a create_all-built database a proper Alembic baseline, so a later runtime schema upgrade migrates from a stamped line instead of replaying the whole chain onto live tables. The recommended split is:

aindy-runtime bootstrap-schema      # runtime tables + runtime Alembic baseline
# then, in the app deploy step: build only the app-owned tables

Pass --reconcile to also apply additive column/index fixes if the runtime schema is out of date. On an already-current database the command is a no-op that re-stamps the same head.

Docs

Runtime-owned documentation lives under docs/runtime/. Start with docs/runtime/QUICKSTART.md, then docs/runtime/RUNTIME_DOC_INDEX.md to find the right document by reader type.

Release staging guidance lives in docs/runtime/RELEASE_STAGING.md.

CI ownership guidance lives in docs/runtime/CI_OWNERSHIP.md.

Deployment topology guidance lives in docs/runtime/DEPLOYMENT_PROFILES.md.

Manual GitHub branch-protection and review settings guidance lives in docs/runtime/GITHUB_SETTINGS_CHECKLIST.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aindy_runtime-2.3.0.tar.gz (1.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aindy_runtime-2.3.0-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

File details

Details for the file aindy_runtime-2.3.0.tar.gz.

File metadata

  • Download URL: aindy_runtime-2.3.0.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aindy_runtime-2.3.0.tar.gz
Algorithm Hash digest
SHA256 a6852031f3ea03d42459aa1416d540d90477145779072effc0a5e6b29c63fd7a
MD5 60b5b7a656f32cb8912a38b1f648add9
BLAKE2b-256 32e54fa6f119366630c7ecdfbb7e2618abba50e502f471ee1385a6eccfc0e1f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for aindy_runtime-2.3.0.tar.gz:

Publisher: publish.yml on Masterplanner25/aindy-runtime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aindy_runtime-2.3.0-py3-none-any.whl.

File metadata

  • Download URL: aindy_runtime-2.3.0-py3-none-any.whl
  • Upload date:
  • Size: 1.2 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aindy_runtime-2.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d418f31d84d396e553d835e9f3d51ba52b592c3b3f5366e33cc971ee2efc304
MD5 dd8eefc9d69ae35cc9b7adace4457845
BLAKE2b-256 35be87d2bb36de425fd208c7b2d7a9c96c13b6aa88daec9b2e3bfbb7b68f9ff7

See more details on using hashes here.

Provenance

The following attestation bundles were made for aindy_runtime-2.3.0-py3-none-any.whl:

Publisher: publish.yml on Masterplanner25/aindy-runtime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page