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 — eachExecutionUnitcarriesmemory_context_idsandoutput_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 Rustcdylibwith 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), wherewaitingandresumedare distinct so an audit query can tell a fresh run from a resumption - Syscall contract — single
SyscallDispatcherentry 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 connectors —
register_connectorplus 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.undoto walk back a run's recorded side effects, plussys.v1.agent.simulatefor 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
202and 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 setAINDY_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_URLinAINDY/.envmust use the compose service name as the host, notlocalhost:DATABASE_URL=postgresql://aindy:aindy@postgres:5432/aindy
postgresresolves on the compose network;localhostdoes not.
Note — pgvector required: The compose file uses
pgvector/pgvector:pg16instead of the stockpostgres:16-alpine. The runtime stores memory embeddings asVECTOR(1536)columns, which requires the PostgreSQLpgvectorextension.docker/init-pgvector.sqlrunsCREATE EXTENSION IF NOT EXISTS vectoron first initialization. If you bring your own PostgreSQL instance, run that statement once before first boot.
Note — published database ports:
postgres(5432),redis(6379), andmongo(27017) publish to the host for local development convenience. For production deployments on a cloud VM, remove theports:blocks from those services or use a compose override file. SeeTECH_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-runtimeconsole script - smoke
GET /healthandGET /api/versionin 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.ymlis the automatic push/PR check formainrelease-staging.ymlis 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=truebefore 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aindy_runtime-2.1.0.tar.gz.
File metadata
- Download URL: aindy_runtime-2.1.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19e29d0df668bf00c0acc9d65bdd54fa16ce5ef53ed78371c6f54ade0942824a
|
|
| MD5 |
94d5fb8e019661620a7dbdeaf045977d
|
|
| BLAKE2b-256 |
f52c64a9e005a65473560cc6b1c3fc0864b643ea637a92cf35b30190be799b65
|
Provenance
The following attestation bundles were made for aindy_runtime-2.1.0.tar.gz:
Publisher:
publish.yml on Masterplanner25/aindy-runtime
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aindy_runtime-2.1.0.tar.gz -
Subject digest:
19e29d0df668bf00c0acc9d65bdd54fa16ce5ef53ed78371c6f54ade0942824a - Sigstore transparency entry: 2482468779
- Sigstore integration time:
-
Permalink:
Masterplanner25/aindy-runtime@ea988d156ba4d747fd2316ca3f9addc9417795b8 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/Masterplanner25
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ea988d156ba4d747fd2316ca3f9addc9417795b8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file aindy_runtime-2.1.0-py3-none-any.whl.
File metadata
- Download URL: aindy_runtime-2.1.0-py3-none-any.whl
- Upload date:
- Size: 1.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ffc538b8565ed5085f9e495a0b4643163af729074c3eaa161621257a40b032e
|
|
| MD5 |
57ce244e17287b7e226f2a7659e83c1f
|
|
| BLAKE2b-256 |
7496b711592df6522072316c444ba36ffc87f6067fb9a084ead40c11d972260c
|
Provenance
The following attestation bundles were made for aindy_runtime-2.1.0-py3-none-any.whl:
Publisher:
publish.yml on Masterplanner25/aindy-runtime
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aindy_runtime-2.1.0-py3-none-any.whl -
Subject digest:
7ffc538b8565ed5085f9e495a0b4643163af729074c3eaa161621257a40b032e - Sigstore transparency entry: 2482468830
- Sigstore integration time:
-
Permalink:
Masterplanner25/aindy-runtime@ea988d156ba4d747fd2316ca3f9addc9417795b8 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/Masterplanner25
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ea988d156ba4d747fd2316ca3f9addc9417795b8 -
Trigger Event:
push
-
Statement type: