Client SDK for the A.I.N.D.Y. runtime API
Project description
aindy-sdk
Universal interface SDK for the A.I.N.D.Y. runtime API.
aindy-sdk is the local+cloud bridge: the same client code works against a
locally-installed runtime (where the operator owns the infrastructure) and a
cloud-hosted runtime (where the provider owns the infrastructure). Switching
deployment context requires only a base_url change — no application code
changes.
Part of the Masterplan Infinite Weave ecosystem by Shawn Knight. Runtime infrastructure: https://github.com/Masterplanner25/aindy-runtime
Install
pip install aindy-sdk
Requirements
- Python 3.11+
- No external dependencies (stdlib only) for the API client
- Watcher subpackage optional dependencies:
psutil(fallback window detection),pyobjc-framework-AppKit+pyobjc-framework-Quartz(macOS window titles),xdotoolsystem package (Linux window detection)
Getting started
1. Start the runtime
Install and start aindy-runtime (full instructions):
git clone https://github.com/Masterplanner25/aindy-runtime.git
cd aindy-runtime
cp AINDY/.env.example AINDY/.env # set SECRET_KEY and OPENAI_API_KEY at minimum
docker compose up -d
Wait until the server is ready:
curl http://localhost:8000/ready # → {"status": "ok", ...}
2. Register and log in
# Register
curl -s -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "yourpassword", "display_name": "You"}' \
| python -m json.tool
# Log in — copy the 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
To promote your account to admin (required to create API keys via the CLI):
# Using the CLI (no server restart needed)
aindy-runtime auth promote-admin you@example.com
# Or via env var (requires server restart)
# Add AINDY_BOOTSTRAP_ADMIN_EMAIL=you@example.com to AINDY/.env, then docker compose restart api
3. Create a Platform API key
Platform API keys start with aindy_ and are shown only once — save the key field.
JWT="<your-access_token>"
curl -s -X POST http://localhost:8000/platform/keys \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"scopes": ["memory.read", "memory.write", "flow.run", "event.emit"]
}' \
| python -m json.tool
Available scopes:
| Scope | Grants access to |
|---|---|
memory.read |
Read and search memory nodes |
memory.write |
Write and delete memory nodes |
flow.run |
Trigger flow runs |
event.emit |
Emit events to the event bus |
syscall.* |
Full syscall registry access |
execution.read |
Read execution unit status |
4. First call
from aindy_sdk import AINDYClient
client = AINDYClient(
base_url="http://localhost:8000",
api_key="aindy_your_platform_key",
)
result = client.memory.read("/memory/you/notes/**", limit=5)
print(result["data"]["nodes"])
Run the full example:
AINDY_API_KEY=aindy_... python examples/quickstart.py
Usage
from aindy_sdk import AINDYClient
# Local runtime (operator owns infrastructure)
client = AINDYClient(
base_url="http://localhost:8000",
api_key="aindy_your_platform_key",
)
# Cloud-hosted — same code, different base_url
# client = AINDYClient(base_url="https://runtime.aindy.ai", api_key="...")
# Read memory
result = client.memory.read("/memory/shawn/entities/**", limit=5)
nodes = result["data"]["nodes"]
# Run a flow
analysis = client.flow.run("analyze_entities", {"data": nodes})
# Write memory
client.memory.write(
"/memory/shawn/insights/outcome",
analysis["data"].get("summary", ""),
tags=["sdk-demo"],
)
# Emit an event
client.events.emit("analysis.completed", {"node_count": len(nodes)})
API reference
All capabilities are accessed through namespaces on AINDYClient:
| Namespace | Description |
|---|---|
client.memory |
Read, write, and search memory nodes by MAS path or vector similarity |
client.flow |
Trigger flow runs and poll their status |
client.events |
Emit events to the runtime event bus |
client.nodus |
Compile and execute Nodus scripts |
client.syscalls |
List and inspect the syscall registry |
client.execution |
Introspect in-flight or completed execution units |
client.sandbox |
Query sandbox assurance posture |
Every method returns a syscall envelope:
{
"status": "success", # "success" or "error"
"data": { ... }, # payload — varies by call
"trace_id": "abc123...", # for log correlation
"duration_ms": 12, # server-side execution time in ms
"error": None, # error description when status == "error"
}
Memory (client.memory)
# Read nodes at a MAS path (glob patterns supported)
result = client.memory.read("/memory/shawn/entities/**", limit=10)
nodes = result["data"]["nodes"]
# Write a node
result = client.memory.write(
"/memory/shawn/insights/daily-summary",
"Worked on SDK documentation today.",
tags=["insight", "daily"],
node_type="insight",
)
node_id = result["data"]["node"]["id"]
# Semantic similarity search
result = client.memory.search("recent project decisions", limit=5)
# Delete a node
client.memory.delete(node_id)
Flow (client.flow)
# Trigger a flow run and get the result
result = client.flow.run("analyze_entities", {"data": nodes})
print(result["status"]) # "success" | "waiting" | "failed"
# For long-running flows: trigger and poll
result = client.flow.trigger("long_analysis", {"input": "..."})
run_id = result["data"]["run_id"]
import time
while True:
status = client.execution.get(run_id)
if status["data"]["status"] not in ("running", "waiting"):
break
time.sleep(2)
Events (client.events)
# Emit an event (can trigger waiting flows)
result = client.events.emit(
"user.action.completed",
{"user_id": "u-123", "action": "document_saved"},
)
Nodus (client.nodus)
# Execute a Nodus script
result = client.nodus.run_script(
script="""
let greeting = "Hello from Nodus"
set_state("message", greeting)
emit("script.hello", {text: greeting})
""",
input={},
)
print(result["nodus_status"]) # "complete" | "wait" | "error"
print(result["output_state"]) # dict of set_state() calls
print(result["events_emitted"]) # count of emit() calls
Execution (client.execution)
run = client.execution.get("run-abc123")
status = run["data"]["status"] # "running" | "success" | "failed" | "waiting"
syscall_count = run["data"]["syscall_count"]
wall_time_ms = run["data"]["wall_time_ms"] # accumulated wall-clock time (includes I/O wait)
Syscalls (client.syscalls)
registry = client.syscalls.list(version="v1")
print(registry["total_count"])
for action, spec in registry["syscalls"]["v1"].items():
deprecated = " [DEPRECATED]" if spec.get("deprecated") else ""
print(f"sys.v1.{action}{deprecated}")
Error handling
The SDK maps HTTP status codes to typed exceptions so you can handle failure cases explicitly rather than inspecting status codes:
from aindy_sdk import AINDYClient
from aindy_sdk.exceptions import (
AuthenticationError, # 401 — key missing, invalid, or expired
PermissionDeniedError, # 403 — key scope doesn't include this capability
NotFoundError, # 404 — resource doesn't exist
ValidationError, # 422 — bad request shape or schema violation
ResourceLimitError, # 429 — syscall count or wall_time_ms exceeded
ServerError, # 5xx — unexpected runtime error
NetworkError, # connection refused, timeout, or DNS failure
AINDYError, # base class — catches all of the above
)
client = AINDYClient(base_url="http://localhost:8000", api_key="aindy_...")
try:
result = client.memory.read("/memory/shawn/entities/**", limit=5)
except AuthenticationError:
print("Invalid API key — check AINDY_API_KEY")
except PermissionDeniedError as exc:
print(f"Key scope missing: {exc.message}")
# exc.response contains the full server error payload
except ValidationError as exc:
print(f"Bad request: {exc.message}")
except NetworkError as exc:
print(f"Server unreachable: {exc.cause}")
except AINDYError as exc:
print(f"Runtime error [{exc.status_code}]: {exc.message}")
All typed errors carry:
.message— human-readable description.status_code— HTTP status code (NoneforNetworkError).response— raw server JSON dict
Watcher
The SDK ships the A.I.N.D.Y. Watcher client process. The watcher runs on your machine, observes the active OS window, classifies your activity, and emits structured signals to the runtime for flow triggering and session tracking.
# Start the watcher (requires AINDY_API_KEY and AINDY_WATCHER_API_URL)
python -m aindy_sdk.watcher.watcher
# Dry run — logs signals, no HTTP requests
python -m aindy_sdk.watcher.watcher --dry-run
# Custom poll interval and verbose logging
python -m aindy_sdk.watcher.watcher --poll-interval 10 --log-level DEBUG
Environment variables:
| Variable | Default | Description |
|---|---|---|
AINDY_WATCHER_API_URL |
http://localhost:8000 |
Runtime base URL |
AINDY_API_KEY |
(required) | Service API key (X-API-Key header) |
AINDY_WATCHER_DRY_RUN |
false |
Log signals only, no HTTP POST |
AINDY_WATCHER_POLL_INTERVAL |
5 |
Seconds between window samples |
AINDY_WATCHER_FLUSH_INTERVAL |
10 |
Seconds between signal batch flushes |
AINDY_WATCHER_BATCH_SIZE |
20 |
Signals per POST batch |
AINDY_WATCHER_CONFIRMATION_DELAY |
30 |
Seconds of focus before session_started fires |
AINDY_WATCHER_DISTRACTION_TIMEOUT |
60 |
Seconds of off-task time before distraction state |
AINDY_WATCHER_RECOVERY_DELAY |
30 |
Seconds of focus to recover from distraction |
AINDY_WATCHER_HEARTBEAT_INTERVAL |
300 |
Seconds between heartbeat signals |
AINDY_WATCHER_LOG_LEVEL |
INFO |
DEBUG | INFO | WARNING | ERROR |
See examples/watcher_demo.py for a dry-run demonstration.
Status
Beta. Part of the aindy-runtime 1.0.0 ecosystem.
Project details
Release history Release notifications | RSS feed
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_sdk-1.0.0.tar.gz.
File metadata
- Download URL: aindy_sdk-1.0.0.tar.gz
- Upload date:
- Size: 34.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6923047b6880558f1d68cc82a5bf1980d55abff55e9ef49e07fd837de11b085e
|
|
| MD5 |
7dec9ad7116a0a650e800669d0459155
|
|
| BLAKE2b-256 |
830773b254291e8602f80bad6bd76efaf6b0e60ad49f3a23a229de46a055bff1
|
Provenance
The following attestation bundles were made for aindy_sdk-1.0.0.tar.gz:
Publisher:
publish.yml on Masterplanner25/aindy-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aindy_sdk-1.0.0.tar.gz -
Subject digest:
6923047b6880558f1d68cc82a5bf1980d55abff55e9ef49e07fd837de11b085e - Sigstore transparency entry: 1876538614
- Sigstore integration time:
-
Permalink:
Masterplanner25/aindy-sdk@146ce143ff22b3c5a5245439feff87d8c0d5430f -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/Masterplanner25
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@146ce143ff22b3c5a5245439feff87d8c0d5430f -
Trigger Event:
push
-
Statement type:
File details
Details for the file aindy_sdk-1.0.0-py3-none-any.whl.
File metadata
- Download URL: aindy_sdk-1.0.0-py3-none-any.whl
- Upload date:
- Size: 33.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
919c817e2c9c62ea34ee8b628b6c8a49f1cffe0ccd023f88557a6244319ce218
|
|
| MD5 |
73f2a2fa05a9a3e78a6a6dfc0ca9c952
|
|
| BLAKE2b-256 |
b41e0ae54aa3658def28e5d8cad217639d725af399bd109e96e400dfd5302ba3
|
Provenance
The following attestation bundles were made for aindy_sdk-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on Masterplanner25/aindy-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aindy_sdk-1.0.0-py3-none-any.whl -
Subject digest:
919c817e2c9c62ea34ee8b628b6c8a49f1cffe0ccd023f88557a6244319ce218 - Sigstore transparency entry: 1876538783
- Sigstore integration time:
-
Permalink:
Masterplanner25/aindy-sdk@146ce143ff22b3c5a5245439feff87d8c0d5430f -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/Masterplanner25
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@146ce143ff22b3c5a5245439feff87d8c0d5430f -
Trigger Event:
push
-
Statement type: