diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4b88379 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +.git +.gitignore +.dockerignore +.pytest_cache +.mypy_cache +.ruff_cache +__pycache__ +node_modules +dist +.next +.vinext +.wrangler +lecore-site +*.py[cod] +*.pyo +.venv +venv +env +.env +.env.* +metrics +holostuff.zip +current backlogs +*_backlog.md +PANEL_*_backlog.md +RENDER_PIPELINE_BACKLOG.md diff --git a/API_QUICKREF.md b/API_QUICKREF.md index d227e5f..712815b 100644 --- a/API_QUICKREF.md +++ b/API_QUICKREF.md @@ -1,6 +1,48 @@ # leCore API Quick Reference -*A scannable, one-line-per-symbol map of the app-building surface -- auto-generated by `apiquickref.py` on 2026-07-12. For the full engine (every module), see REFERENCE.md.* +*A scannable, one-line-per-symbol map of the app-building surface -- auto-generated by `apiquickref.py` on 2026-07-14. For the full engine (every module), see REFERENCE.md.* + +## Product wedge + +### `holographic_product` +*holographic_product.py -- the small product-facing leCore facade.* + +- **class `MemoryEntry`** -- One stored memory item. + - `to_dict(self)` -- Return a JSON-safe representation of this memory entry. + - `from_dict(cls, data)` -- Build a memory entry from `to_dict` data. +- **class `LocalAgentCore`** -- Product facade for local agent memory, skill routing, and evidence. + - `entries(self)` -- A copy of the stored entries, in insertion order. + - `remember(self, text, label=None, metadata=None, id=None)` -- Store one local memory. + - `remember_many(self, items)` -- Store several memories. + - `recall(self, query, k=3, abstain=None)` -- Return the nearest stored memories for `query`, best first. + - `suggest(self, task, k=5)` -- Suggest capabilities for a plain-English task. + - `route(self, task)` -- Route a task to one capability when confident, otherwise return options. + - `evidence(self)` -- Return a machine-readable product readiness snapshot. + - `dashboard(self, html=False)` -- Return the evidence dashboard as a dict, or static HTML with `html=True`. + - `dashboard_html(data)` -- Render an evidence snapshot as a dependency-free static HTML dashboard. + - `to_state(self)` -- Serialize configuration and entries. + - `from_state(cls, state)` -- Rebuild a core from `to_state` data. + - `save(self, path)` -- Write the product state to JSON and return the path. + - `load(cls, path)` -- Load a product state saved by `save`. +- `demo()` -- Build a tiny ready-to-query product demo. + +### `holographic_x402_api` +*holographic_x402_api.py -- publish LocalAgentCore as an x402-paid API.* + +- **class `PaidRoute`** -- One x402-protected route. + - `key(self)` -- The route key shape expected by x402 middleware, e.g. +- **class `X402Config`** -- Seller configuration for the x402-paid API. + - `from_env(cls, require_pay_to=True)` -- Build config from LECORE_X402_* environment variables. + - `to_public_dict(self)` -- Public, JSON-safe view of the payment configuration. +- `optional_dependency_help()` -- Install hint for the optional paid API dependencies. +- `leos_token_offer()` -- Public metadata for the leOS CA-only offer. +- `landing_page_html(config)` -- Render the buyer-facing landing page served from `/`. +- `payment_manifest(config)` -- Plain JSON route manifest, useful for docs, `/pricing`, and tests. +- `x402_route_configs(config)` -- Build x402 SDK RouteConfig objects for the protected routes. +- `x402_resource_server(config)` -- Create an x402 resource server wired to the configured facilitator. +- `create_app(core=None, config=None, paid=True, admin_token=None, state_path=None)` -- Create the FastAPI app. +- `load_core(path)` -- Load a persisted core if present, otherwise return the demo core. +- `main(argv=None)` -- CLI entry point for local x402 API serving. ## Scene authoring diff --git a/AWS_X402_DEPLOY.md b/AWS_X402_DEPLOY.md new file mode 100644 index 0000000..42702be --- /dev/null +++ b/AWS_X402_DEPLOY.md @@ -0,0 +1,161 @@ +# AWS x402 Deployment + +> **Note:** the default deploy path is Fly.io — see +> [`FLY_X402_DEPLOY.md`](FLY_X402_DEPLOY.md). This doc remains as the AWS +> alternative; its wallet-storage guidance is provider-agnostic. + +This is the production shape for serving `LocalAgentCore` as an x402-paid API +on AWS. + +## Short Answer + +Yes, we can launch this on AWS. For the **seller** side of x402, the service +does **not** need a wallet private key in the container. It only needs: + +- the public receiving wallet address (`LECORE_X402_PAY_TO`) +- x402/facilitator configuration +- an admin token for seller-only memory writes + +The receiving wallet should be a cold wallet, hardware wallet, Safe/multisig, +or a custody wallet. The API simply tells x402 where funds should go. + +Only build an AWS-hosted signing wallet if the app itself must **spend** funds +or pay upstream APIs as a buyer. + +## Recommended AWS Architecture + +- **ECS Fargate** runs the `Dockerfile.x402` container. +- **Application Load Balancer** terminates HTTPS and forwards to port `4021`. +- **ECR** stores the container image. +- **Secrets Manager** stores `LECORE_X402_ADMIN_TOKEN` and production + facilitator credentials. +- **SSM Parameter Store or plain task env** stores non-secret config like + `LECORE_X402_PAY_TO`, `LECORE_X402_PRICE`, and `LECORE_X402_NETWORK`. +- **CloudWatch Logs** captures service logs. +- **AWS WAF** can rate-limit and block bad traffic at the ALB. + +Protected paid routes: + +- `POST /v1/recall` +- `POST /v1/route` +- `GET /v1/dashboard` + +Free routes: + +- `GET /health` +- `GET /pricing` + +Seller-only route: + +- `POST /admin/remember`, guarded by `X-Admin-Token` + +## Build And Push + +```bash +aws ecr create-repository --repository-name lecore-x402 + +ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)" +REGION="${AWS_REGION:-us-west-2}" +IMAGE="$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/lecore-x402:latest" + +aws ecr get-login-password --region "$REGION" \ + | docker login --username AWS --password-stdin "$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com" + +docker build -f Dockerfile.x402 -t "$IMAGE" . +docker push "$IMAGE" +``` + +## Runtime Environment + +Non-secret environment variables: + +```text +LECORE_X402_PAY_TO=0xYourReceivingWallet +LECORE_X402_PRICE=$0.001 +LECORE_X402_NETWORK=eip155:8453 +LECORE_X402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402 +``` + +Secrets Manager values: + +```text +LECORE_X402_ADMIN_TOKEN= +CDP_API_KEY_ID= +CDP_API_KEY_SECRET= +``` + +Use ECS task definition `secrets` entries for secrets, not literal environment +variables in the task definition. + +## Wallet Storage Decision + +### Seller API, Recommended + +Do **not** store a private key in AWS. + +The API receives payments; it does not spend. x402 payment verification and +settlement happen through the facilitator. The service only advertises +`payTo`. + +Best receiving wallet options: + +- Safe/multisig +- hardware wallet +- cold wallet +- custodial account dedicated to receipts + +### Buyer/Spender API, If Needed Later + +If the leCore agent itself needs to pay other x402 APIs, use a separate signer +service: + +1. Create an AWS KMS asymmetric signing key with `ECC_SECG_P256K1`. +2. Derive the public Ethereum address from `kms:GetPublicKey`. +3. Allow only a narrow IAM role to call `kms:Sign`. +4. Sign EIP-712/EIP-3009 payload digests through KMS. +5. Enforce spend limits in application logic before every signing request. +6. Log every signing request with CloudTrail and app-level audit records. + +This keeps the private key non-exportable: it never appears in the container. + +### High-Assurance Signer + +For larger balances or stronger isolation, put the signing service in **AWS +Nitro Enclaves** and allow KMS decrypt/sign only when enclave attestation +matches the expected image measurement. + +### Last Resort + +Storing a raw private key in Secrets Manager is acceptable only for testnet or +very small hot-wallet balances. If used, wrap it with strict IAM, rotation +plans, spend limits, CloudTrail alarms, and a tiny blast radius. + +## First Production Checklist + +- Use mainnet network id and production facilitator URL. +- Put the ALB behind HTTPS only. +- Keep `/admin/remember` private or blocked from the public ALB path. +- Keep paid route configs explicit; avoid wildcard paid routes at first. +- Add WAF rate limits. +- Add CloudWatch alarms on 5xx, 402 spikes, and admin write attempts. +- Keep customer memory isolated before offering paid writes. +- Do not put secrets or PII in x402 route descriptions or payment metadata. + +## Local Smoke Before AWS + +```bash +pip install ".[x402]" +export LECORE_X402_PAY_TO="0xYourReceivingWallet" +export LECORE_X402_ADMIN_TOKEN="local-admin-secret" +python holographic_x402_api.py --unpaid-dev --host 127.0.0.1 --port 4021 +``` + +Then: + +```bash +curl http://127.0.0.1:4021/health +curl http://127.0.0.1:4021/pricing +curl -X POST http://127.0.0.1:4021/v1/route \ + -H "Content-Type: application/json" \ + -d '{"task":"search local agent memory"}' +``` diff --git a/CAPABILITIES.md b/CAPABILITIES.md index f428028..0e3e0f1 100644 --- a/CAPABILITIES.md +++ b/CAPABILITIES.md @@ -288,6 +288,14 @@ import lecore; m=lecore.UnifiedMind(dim=256,seed=0); print(m.suggest_pipeline('t ``` *Find it by:* how do I get from points to a mesh, chain capabilities, build a pipeline, route between datatypes, what steps turn X into Y +### x402 paid API publisher +publish the LocalAgentCore product wedge as a paid HTTP API: FastAPI routes for recall, task routing, and the evidence dashboard protected by x402 middleware, with free health/pricing routes and admin-token-gated memory writes.. + +```python +from holographic_x402_api import create_app, X402Config; app = create_app(config=X402Config(pay_to='0x...')) +``` +*Find it by:* x402, paid api, payment required, 402, monetize api, micropayment, agent payments, pay per request + ## Memory, search & recall *store things and get them back by CONTENT, not by exact key.* @@ -390,6 +398,14 @@ mind.navigator_benchmark() # recall + the fixed-beam baseline ``` *Find it by:* navigator, adaptive search, learned search, search a tree, nearest neighbour search, beam search, spend less effort on easy queries, reflex cache +### Local agent core (memory + routing) +the PRODUCT-FACING wedge: LocalAgentCore gives a local agent deterministic text memory (remember/recall), skill routing over the live capability catalog, JSON persistence, and a readiness dashboard with C-kernel status. This is the small stable door for embedding leCore without learning the whole research surface first.. + +```python +from holographic_product import LocalAgentCore; core = LocalAgentCore(); core.remember('local agent memory'); core.recall('agent memory') +``` +*Find it by:* product, productization, agent memory, local memory, durable memory, recall, skill routing, dashboard + ### Memoize a pure function (the purity gate is the point) skip re-execution of PURE work whose inputs repeat. mind.memoize_pure(fn) keys on (the function's EXACT canonical source, its arguments) and REFUSES a function that is not pure -- is_pure rejects the clock, RNG, IO, global writes, and transitive impurity through a call-graph fixpoint, while accepting a locally-allocated container. A cache over an impure function returns a stale answer silently, so the gate raises instead. MEASURED: 36x on a repeated 256x256 SVD, bit-identical. THE BACKLOG CALLS THIS 'shape-keyed memoization', AND THAT NAME IS A BUG: a canonical shape erases identifiers and constants, so `def f(x): return x + 1` and `def g(x): return x + 2` have the SAME shape and would share a cache entry. mind.canonical_shape(fn) exists, and is a COMPRESSION primitive, never a cache key. KEPT NEGATIVE: the key costs O(input bytes) -- fingerprinting a 512x512 array costs 1.747 ms while A.sum() costs 0.084 ms, so a cheap function of a large array loses 21x; ask mind.machine_place with the function's own cost as the baseline. TWO BACKLOG NUMBERS DID NOT REPRODUCE: shape reuse is 1.13x (node type + depth) or 1.87x (control flow), not 2.36x -- it is a property of the equivalence relation, not the code; and tree purity is 35.4% (781 of 2,188 module-level functions), not 76%. HONEST SCOPE: the gate resolves callees within ONE module, so a function that calls an IMPORTED helper is refused as unresolved (sound, and why tucker.rank_gate is rejected -- it reaches fix_eigvec_signs from another module). Cross-module resolution wants types.. @@ -2461,4 +2477,4 @@ import numpy as np; import lecore; m=lecore.UnifiedMind(dim=256,seed=0); x=np.li --- -*308 capability homes. Regenerate this file with `python capdoc.py` (it reads the live catalog, so it stays in step with the engine).* +*310 capability homes. Regenerate this file with `python capdoc.py` (it reads the live catalog, so it stays in step with the engine).* diff --git a/Dockerfile.x402 b/Dockerfile.x402 new file mode 100644 index 0000000..e533289 --- /dev/null +++ b/Dockerfile.x402 @@ -0,0 +1,15 @@ +FROM python:3.12-slim + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +WORKDIR /app + +COPY . /app + +RUN python -m pip install --no-cache-dir --upgrade pip \ + && python -m pip install --no-cache-dir ".[x402]" + +EXPOSE 4021 + +CMD ["python", "holographic_x402_api.py", "--host", "0.0.0.0", "--port", "4021"] diff --git a/FLY_X402_DEPLOY.md b/FLY_X402_DEPLOY.md new file mode 100644 index 0000000..8e82145 --- /dev/null +++ b/FLY_X402_DEPLOY.md @@ -0,0 +1,85 @@ +# Fly.io x402 Deployment + +This is the default production shape for serving `LocalAgentCore` as an +x402-paid API. It uses `Dockerfile.x402` and `fly.x402.toml`; no cloud-specific +code changes are needed. (For the AWS variant and the full wallet-storage +discussion, see [`AWS_X402_DEPLOY.md`](AWS_X402_DEPLOY.md) — the wallet +guidance there is provider-agnostic and applies here too.) + +## Seller Security Model + +Same as the AWS doc's short answer: the seller side needs **no private key** +in the container. It only needs: + +- the public receiving wallet address (`LECORE_X402_PAY_TO`) +- x402/facilitator configuration +- an admin token for seller-only memory writes + +Use a cold wallet, hardware wallet, or Safe/multisig as the receiving address. + +## First Deploy (testnet) + +```bash +fly launch --config fly.x402.toml --no-deploy # creates the app, keeps our config +fly volumes create lecore_data --config fly.x402.toml --size 1 + +# Secrets first: the app fails loud on boot without a pay-to address. +fly secrets set --config fly.x402.toml \ + LECORE_X402_PAY_TO="0xYourReceivingWallet" \ + LECORE_X402_ADMIN_TOKEN="$(openssl rand -hex 24)" + +fly deploy --config fly.x402.toml +``` + +Verify: + +```bash +curl https://lecore-x402.fly.dev/health +curl https://lecore-x402.fly.dev/pricing +# Paid route must answer 402 with a `payment-required` challenge header: +curl -si https://lecore-x402.fly.dev/v1/dashboard | head -5 +``` + +Seed seller memory (writes persist to the volume via `LECORE_X402_STATE`): + +```bash +curl -X POST https://lecore-x402.fly.dev/admin/remember \ + -H "Content-Type: application/json" \ + -H "X-Admin-Token: " \ + -d '{"text":"local agents need deterministic durable memory","label":"memory"}' +``` + +## Custom Domain + +```bash +fly certs add lecore.rati.foundation --config fly.x402.toml +``` + +Point DNS at the app, then set `LECORE_X402_PUBLIC_URL` in `fly.x402.toml` to +the custom domain so the landing page and `/pricing` advertise the right +endpoint. + +## Mainnet Flip + +The defaults are Base Sepolia + the signup-free x402.org testnet facilitator, +which does **not** settle real funds. To charge real USDC on Base: + +```toml +LECORE_X402_NETWORK = "eip155:8453" +LECORE_X402_FACILITATOR_URL = "https://api.cdp.coinbase.com/platform/v2/x402" +``` + +The CDP facilitator requires Coinbase Developer Platform credentials — check +the current x402/CDP docs for the auth shape and set any required keys with +`fly secrets set`, never in `fly.x402.toml`. Re-verify the `payment-required` +challenge advertises `eip155:8453` before announcing the endpoint. + +## Production Checklist + +- Mainnet network id + production facilitator before announcing. +- Receiving address is cold/multisig, never a hot key in the container. +- `LECORE_X402_ADMIN_TOKEN` set via `fly secrets`, rotated if shared. +- Volume mounted and `LECORE_X402_STATE` set, or accept that admin writes + reset to the demo core on every restart. +- Keep paid route configs explicit; no wildcard paid routes. +- No secrets or PII in route descriptions or payment metadata. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..a53d96e --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,50 @@ +# leCore Product Wedge + +The first product surface is **LocalAgentCore**: a small facade for local agent +memory, capability routing, and readiness evidence. + +It deliberately narrows the promise. The full repo is a broad research engine; +this surface is the five-minute path for a builder who wants a deterministic, +inspectable local substrate. + +```python +from holographic_product import LocalAgentCore + +core = LocalAgentCore(dim=512, seed=0) +core.remember("local agents need deterministic durable memory", label="memory") +core.remember("capability routing should act when confident", label="routing") + +print(core.recall("deterministic local memory")[0]) +print(core.route("render a scene with global illumination")) +print(core.dashboard()) +``` + +The same object is available from the friendly import surface: + +```python +import lecore + +core = lecore.product.LocalAgentCore() +``` + +## What It Productizes + +- **Memory:** local text memories encoded through `UniversalEncoder` and recalled + through the shared `Index` home. +- **Routing:** plain-English tasks routed through the existing skill catalog. +- **Evidence:** a JSON/static-HTML dashboard with memory counts, capability + counts, determinism checks, and optional C-kernel availability. +- **Persistence:** `save(path)` and `LocalAgentCore.load(path)` round-trip the + stable state as JSON. Vectors are rebuilt from seed, text context, and entries. +- **Paid API publishing:** `holographic_x402_api.py` serves the product wedge as + an optional x402-paid FastAPI service. See [`X402_API.md`](X402_API.md). + +## Honest Scope + +This is not a neural database, a hosted service, or a general semantic model. +Out of the box it matches by the deterministic holographic text geometry it is +given. Better domain recall comes from adding domain memories, teaching text +context, or layering a specialized encoder on the same facade. + +The product rule is simple: the public wedge stays small, auditable, local, and +measured. The research garden remains available behind it. diff --git a/README.md b/README.md index fd50b92..313836a 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ pip install "leos-core[symbolic]" # pip install .[symbolic] design-ti pip install "leos-core[zig]" # pip install .[zig] native batch kernels, 2-5x (ziglang -- whole # toolchain in one wheel, bit-identical in safe mode) pip install "leos-core[images]" # pip install .[images] jpg/webp/... image I/O (Pillow, no Flask) +pip install "leos-core[x402]" # pip install .[x402] paid API publishing (x402, FastAPI) pip install "leos-core[dev]" # pip install .[dev] run the tests and make plots (pytest, matplotlib) pip install "leos-core[all]" # pip install .[all] everything portable, one shot pip install "leos-core[ui,jit]" # pip install .[ui,jit] ...or combine whichever you want @@ -162,6 +163,14 @@ Like leOS, leCore is **free and open source**, and the work that keeps it free i `find_capability` first, wire every capability to a mind faculty (so it is `/invoke`-able), register it in the catalog so it is discoverable, and run the reachability/gap audits — the discipline that keeps the codebase from growing gaps or isolating code in tests. Read this before making code changes. +- **[`PRODUCT.md`](PRODUCT.md)** — the **narrow product wedge**: `LocalAgentCore`, a small stable facade for local + agent memory, capability routing, persistence, and the readiness dashboard. Start here if you want the five-minute + "use it in an agent" path rather than the whole research surface. +- **[`X402_API.md`](X402_API.md)** — the **paid API publishing guide**: serve the product wedge over FastAPI with + optional x402 middleware, per-route pricing, and admin-gated memory writes. +- **[`FLY_X402_DEPLOY.md`](FLY_X402_DEPLOY.md)** — the **launch guide** for the paid API on Fly.io (default), with + [`AWS_X402_DEPLOY.md`](AWS_X402_DEPLOY.md) as the AWS alternative and the wallet-storage discussion (KMS, Nitro + Enclaves, cold/multisig receiving wallets). - **[`CAPABILITIES.md`](CAPABILITIES.md)** — the **front-door menu**: a plain-language, grouped list of what leCore can do and the one call that starts each job. The friendliest place to begin if you're deciding whether the engine already does the thing you need. Generated from the live capability catalog by `capdoc.py` and kept in sync by CI. diff --git a/X402_API.md b/X402_API.md new file mode 100644 index 0000000..a20a4c0 --- /dev/null +++ b/X402_API.md @@ -0,0 +1,97 @@ +# x402 API Publishing + +Yes: leCore can be published as a paid API with x402. + +The implementation lives in `holographic_x402_api.py`. It wraps +`LocalAgentCore` with a small FastAPI app and applies x402 middleware only to +the public read/compute routes: + +- `POST /v1/recall` +- `POST /v1/route` +- `GET /v1/dashboard` + +Free routes: + +- `GET /health` +- `GET /pricing` + +Admin route: + +- `POST /admin/remember`, guarded by `X-Admin-Token` + +This split is deliberate. Paid customers can use the memory/router/dashboard, +but they cannot mutate the shared memory store unless they also hold the admin +token. + +## Install + +```bash +pip install ".[x402]" +``` + +The core package still needs only NumPy. The `x402` extra pulls in the optional +FastAPI/x402/uvicorn stack. + +## Testnet Run + +The default network is Base Sepolia (`eip155:84532`) and the default facilitator +is the signup-free x402.org testnet facilitator. + +```bash +export LECORE_X402_PAY_TO="0xYourReceivingWallet" +export LECORE_X402_PRICE="$0.001" +export LECORE_X402_ADMIN_TOKEN="local-admin-secret" + +python holographic_x402_api.py --host 127.0.0.1 --port 4021 +``` + +Inspect pricing: + +```bash +curl http://127.0.0.1:4021/pricing +``` + +Add memories locally as the seller: + +```bash +curl -X POST http://127.0.0.1:4021/admin/remember \ + -H "Content-Type: application/json" \ + -H "X-Admin-Token: local-admin-secret" \ + -d '{"text":"local agents need deterministic durable memory","label":"memory"}' +``` + +Requests to paid routes return `402 Payment Required` unless the client retries +with a valid x402 payment payload: + +```bash +curl -X POST http://127.0.0.1:4021/v1/recall \ + -H "Content-Type: application/json" \ + -d '{"query":"deterministic local memory"}' +``` + +## Local Unpaid Smoke Test + +Use this only for development: + +```bash +python holographic_x402_api.py --unpaid-dev --host 127.0.0.1 --port 4021 +``` + +## Production Notes + +- Use a real receiving wallet and a production facilitator. +- Put the API behind HTTPS. +- Keep route prices explicit; avoid wildcard paid route configs for this first + product surface. +- Keep writes admin-only, or move customer writes into isolated per-customer + stores before charging for them. +- Treat x402 payment metadata as public enough to avoid putting secrets or PII + in route descriptions. + +The implementation follows the current x402 seller shape: FastAPI middleware, +`RouteConfig`, `PaymentOption`, an `exact` EVM scheme, and a facilitator-backed +resource server. + +For hosting, see [`FLY_X402_DEPLOY.md`](FLY_X402_DEPLOY.md) (default) or +[`AWS_X402_DEPLOY.md`](AWS_X402_DEPLOY.md) (alternative; also holds the full +wallet-storage discussion). diff --git a/apiquickref.py b/apiquickref.py index ec2e2db..93abd04 100644 --- a/apiquickref.py +++ b/apiquickref.py @@ -22,11 +22,12 @@ # ---------------------------------------------------------------------------------------------------------- # THE CURATED SURFACE. Edit this list to change what the quick reference covers. Grouped by the job a builder -# is doing, in the order they meet it: author a scene -> model geometry -> aim a camera -> render -> ship. +# is doing, in the order they meet it: product wedge -> author a scene -> model geometry -> aim a camera -> render -> ship. # Kept deliberately SHORT -- the point is a page you can scan, not a full index (that is REFERENCE.md). # ---------------------------------------------------------------------------------------------------------- CURATED = [ + ("Product wedge", ["holographic_product", "holographic_x402_api"]), ("Scene authoring", ["holographic_scene_doc", "holographic_modifier"]), ("Geometry / SDF", ["holographic_sdf", "holographic_sdfscene", "holographic_mesh"]), ("Transforms", ["holographic_transform"]), diff --git a/capabilities.json b/capabilities.json index 9025ad1..6ec67ee 100644 --- a/capabilities.json +++ b/capabilities.json @@ -2278,6 +2278,29 @@ "semantic": null, "theme": "Geometry, modeling & rendering" }, + { + "aliases": [ + "product", + "productization", + "agent memory", + "local memory", + "durable memory", + "recall", + "skill routing", + "dashboard", + "first user", + "facade", + "local agent core" + ], + "consumes": [], + "does": "the PRODUCT-FACING wedge: LocalAgentCore gives a local agent deterministic text memory (remember/recall), skill routing over the live capability catalog, JSON persistence, and a readiness dashboard with C-kernel status. This is the small stable door for embedding leCore without learning the whole research surface first.", + "example": "from holographic_product import LocalAgentCore; core = LocalAgentCore(); core.remember('local agent memory'); core.recall('agent memory')", + "name": "Local agent core (memory + routing)", + "native": true, + "produces": [], + "semantic": null, + "theme": "Memory, search & recall" + }, { "aliases": [ "pack images", @@ -7210,8 +7233,32 @@ "produces": [], "semantic": null, "theme": "Scenes you can describe & adjust" + }, + { + "aliases": [ + "x402", + "paid api", + "payment required", + "402", + "monetize api", + "micropayment", + "agent payments", + "pay per request", + "fastapi", + "api publishing", + "sell api", + "paid route" + ], + "consumes": [], + "does": "publish the LocalAgentCore product wedge as a paid HTTP API: FastAPI routes for recall, task routing, and the evidence dashboard protected by x402 middleware, with free health/pricing routes and admin-token-gated memory writes.", + "example": "from holographic_x402_api import create_app, X402Config; app = create_app(config=X402Config(pay_to='0x...'))", + "name": "x402 paid API publisher", + "native": false, + "produces": [], + "semantic": null, + "theme": "Discover & drive it (for agents)" } ], - "count": 308, + "count": 310, "schema_version": "1.0" } diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index 44a5595..82a1782 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -129,6 +129,7 @@ The core requires **only NumPy**. Everything else is declared as a named "extra" | `jit` | `numba` | numba-compiled fast paths (`holographic_jit`, `sdf_render`, `codegen`) | | `symbolic` | `sympy` | design-time symbolic gradients (`holographic_codegen`, `sdf_render`) | | `gpu` | `cupy` | the GPU backend (`holographic_backend`) — see the CuPy note | +| `x402` | `x402[fastapi,evm]`, `uvicorn` | paid API publishing (`holographic_x402_api`) | | `ui` | `flask`, `pillow` | the browser UI (`app.py`) and image load/save | | `dev` | `pytest`, `matplotlib` | running the test suite and generating plots | | `all` | numba, sympy, flask, pillow, pytest, matplotlib | everything portable, in one shot | diff --git a/fly.x402.toml b/fly.x402.toml new file mode 100644 index 0000000..4976b82 --- /dev/null +++ b/fly.x402.toml @@ -0,0 +1,37 @@ +# Fly.io config for the x402-paid leCore API. +# +# Deploy with: +# fly deploy --config fly.x402.toml +# +# See FLY_X402_DEPLOY.md for the full runbook (secrets, volume, mainnet flip). + +app = "lecore-x402" +primary_region = "sea" + +[build] + dockerfile = "Dockerfile.x402" + +[env] + # Non-secret config. LECORE_X402_PAY_TO is a public receiving address and can + # live here; LECORE_X402_ADMIN_TOKEN must be set with `fly secrets set`. + LECORE_X402_NETWORK = "eip155:84532" + LECORE_X402_FACILITATOR_URL = "https://x402.org/facilitator" + LECORE_X402_PRICE = "$0.0011" + LECORE_X402_PUBLIC_URL = "https://lecore-x402.fly.dev" + # Persist admin memory writes across restarts (requires the volume below). + LECORE_X402_STATE = "/data/core.json" + +[[mounts]] + source = "lecore_data" + destination = "/data" + +[http_service] + internal_port = 4021 + force_https = true + auto_stop_machines = "stop" + auto_start_machines = true + min_machines_running = 0 + +[[vm]] + size = "shared-cpu-1x" + memory = "512mb" diff --git a/holographic/caching_and_storage/holographic_catalog.py b/holographic/caching_and_storage/holographic_catalog.py index e3fce53..6eb9899 100644 --- a/holographic/caching_and_storage/holographic_catalog.py +++ b/holographic/caching_and_storage/holographic_catalog.py @@ -4331,6 +4331,22 @@ def default_catalog(): c.register_capability("Mantis-shrimp vision (12-band + polarization)", "see as a MANTIS SHRIMP does: 12 spectral receptors from deep UV to far red PLUS linear and CIRCULAR polarization (holographic_observer.mantis_view). The circular channels use a quarter-wave retarder (the R8 rhabdomere, Chiou 2008) before linear detectors -- the sense mantis shrimp uniquely have. Composes the observer (O1) and Mueller elements (P2). Field-native. KEPT NEGATIVE (Thoen 2014): a DIRECT per-receptor readout, NOT colour-opponent -- mantis colour discrimination is measured coarse. mantis_receptors / polarization_readout / mantis_view", example="import numpy as np; import lecore; m=lecore.UnifiedMind(dim=256,seed=0); L=np.linspace(300,720,140); b=np.exp(-0.5*((L-500)/60)**2); S=np.zeros(L.shape+(4,)); S[...,0]=b; S[...,3]=b; print(m.mantis_view(S,L)['handedness_sign'])", native=True, aliases=("mantis shrimp vision", "mantis shrimp eye", "see ultraviolet and polarization", "circular polarization vision", "twelve band eye", "twelve photoreceptors", "see what a mantis shrimp sees", "UV plus polarization sensor", "handedness of light detector", "stomatopod vision", "many band eye readings"), semantic="transform/warp", consumes=("spectrum",), produces=("image",)) c.register_capability("See what the mantis sees (false colour)", "FALSE COLOUR: show a human what a non-human sensor sees (holographic_falsecolor). Map invisible channels onto R/G/B -- ULTRAVIOLET becomes a chosen hue, e-vector ANGLE becomes hue (strength = saturation), circular HANDEDNESS becomes a red/blue diverging map. mantis_falsecolor turns a mantis_view into three images (colour, polarization, handedness). Field-native. EVERY map is a CHOICE (Eno), not true colour. wavelength_to_rgb / hsv_to_rgb / falsecolor_spectral / falsecolor_polarization / falsecolor_handedness / mantis_falsecolor", example="import numpy as np; import lecore; m=lecore.UnifiedMind(dim=256,seed=0); L=np.linspace(300,720,140); S=np.zeros(L.shape+(4,)); S[...,0]=np.exp(-0.5*((L-330)/20)**2); S[...,3]=S[...,0]; fc=m.mantis_falsecolor(m.mantis_view(S,L)); print(float(fc['color'].max())>0)", native=True, aliases=("false color", "false colour", "see what the mantis sees", "visualize polarization as color", "map invisible channels to rgb", "see ultraviolet as visible color", "polarization angle to hue", "handedness color map", "wavelength to rgb", "make UV visible", "visualize a non-human sensor", "hsv to rgb"), semantic="convert/emit", consumes=("image",), produces=("image",)) c.register_capability("Doppler velocity & drift acceleration", "read VELOCITY and ACCELERATION out of a spectral shift or drift (holographic_dedoppler). doppler_velocity turns an observed vs rest wavelength into a line-of-sight velocity (classical v=c*z, or relativistic, which stays below c); redshift gives z; doppler_shift is the forward model (velocity -> observed wavelength). drift_acceleration turns a narrowband frequency drift rate (Hz/s -- what detect_drifting finds) into the emitter's acceleration a=-c*(df/dt)/f: the SETI reading of a drifting tone. Field-native. doppler_velocity / redshift / doppler_shift / drift_acceleration", example="import lecore; m=lecore.UnifiedMind(dim=256,seed=0); lr=656.28e-9; print(round(float(m.doppler_velocity(m.doppler_shift(lr,3e5),lr))/1e3,1))", native=True, aliases=("doppler velocity", "redshift to velocity", "radial velocity from wavelength", "relativistic doppler", "doppler shift", "wavelength shift to speed", "drift rate to acceleration", "how fast is it moving", "recession velocity", "line of sight velocity", "SETI drift acceleration", "how fast is a star moving", "speed of a source from its spectrum", "velocity from a spectral line"), semantic="analyze/measure", consumes=("timeseries",), produces=("scalar",)) + c.register_capability("Local agent core (memory + routing)", "the PRODUCT-FACING wedge: LocalAgentCore gives a " + "local agent deterministic text memory (remember/recall), skill routing over the live " + "capability catalog, JSON persistence, and a readiness dashboard with C-kernel status. " + "This is the small stable door for embedding leCore without learning the whole research " + "surface first.", + example="from holographic_product import LocalAgentCore; core = LocalAgentCore(); core.remember('local agent memory'); core.recall('agent memory')", + native=True, aliases=("product", "productization", "agent memory", "local memory", + "durable memory", "recall", "skill routing", "dashboard", + "first user", "facade", "local agent core")) + c.register_capability("x402 paid API publisher", "publish the LocalAgentCore product wedge as a paid HTTP API: " + "FastAPI routes for recall, task routing, and the evidence dashboard protected by x402 " + "middleware, with free health/pricing routes and admin-token-gated memory writes.", + example="from holographic_x402_api import create_app, X402Config; app = create_app(config=X402Config(pay_to='0x...'))", + native=False, aliases=("x402", "paid api", "payment required", "402", "monetize api", + "micropayment", "agent payments", "pay per request", "fastapi", + "api publishing", "sell api", "paid route")) return c diff --git a/holographic_product.py b/holographic_product.py new file mode 100644 index 0000000..ae85e16 --- /dev/null +++ b/holographic_product.py @@ -0,0 +1,356 @@ +"""holographic_product.py -- the small product-facing leCore facade. + +WHY THIS EXISTS +--------------- +The research engine is intentionally broad: memory, geometry, rendering, +simulation, jobs, skills, and more all share the same holographic substrate. +That is useful for research, but a first-time product user needs one narrow, +reliable door. + +`LocalAgentCore` is that door. It packages the current production wedge: + + * local deterministic text memory (`remember` / `recall`) + * agent skill routing through the existing capability catalog (`route`) + * an evidence snapshot and static HTML dashboard (`dashboard`) + +It does not replace `UnifiedMind` or hide the research surface. It is a small, +boring facade over the stable pieces, meant to be easy to install, test, demo, +and embed. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +import html +import json +from pathlib import Path +import re +from typing import Any, Dict, Iterable, List, Optional + +import numpy as np + +from holographic.caching_and_storage.holographic_index import Index +from holographic.agents_and_reasoning.holographic_mind import UniversalEncoder + + +_WORD_RE = re.compile(r"[a-z0-9_]+") + + +def _tokens(text: Any) -> List[str]: + """Deterministic product tokenization: lower-case content tokens, no hidden NLP dependency.""" + if isinstance(text, (list, tuple)): + return [str(t).lower() for t in text if str(t).strip()] + return _WORD_RE.findall(str(text).lower()) + + +@dataclass +class MemoryEntry: + """One stored memory item.""" + + id: str + text: str + label: Optional[str] = None + metadata: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + """Return a JSON-safe representation of this memory entry.""" + return { + "id": self.id, + "text": self.text, + "label": self.label, + "metadata": dict(self.metadata), + } + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "MemoryEntry": + """Build a memory entry from `to_dict` data.""" + return cls( + id=str(data["id"]), + text=str(data.get("text", "")), + label=data.get("label"), + metadata=dict(data.get("metadata") or {}), + ) + + +class LocalAgentCore: + """Product facade for local agent memory, skill routing, and evidence. + + The API is deliberately small: + + core = LocalAgentCore() + core.remember("local agents need deterministic memory", label="memory") + core.recall("deterministic local memory") + core.route("render a scene") + core.dashboard() + + Text memory uses the existing `UniversalEncoder` and `Index` homes. It is + deterministic, local-only, and query-safe: `recall()` does not mutate the + stored corpus or teach the encoder new query words. + """ + + def __init__(self, dim: int = 512, seed: int = 0, route_threshold: float = 0.6): + self.dim = int(dim) + self.seed = int(seed) + self.route_threshold = float(route_threshold) + self._entries: List[MemoryEntry] = [] + self._encoder = UniversalEncoder(self.dim, seed=self.seed) + self._vectors: Optional[np.ndarray] = None + self._index: Optional[Index] = None + self._next_id = 1 + + # ---- memory --------------------------------------------------------------------------------------- + @property + def entries(self) -> List[MemoryEntry]: + """A copy of the stored entries, in insertion order.""" + return list(self._entries) + + def remember( + self, + text: Any, + label: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, + id: Optional[str] = None, + ) -> Dict[str, Any]: + """Store one local memory. Returns the stored entry as a plain dict.""" + entry_id = str(id) if id is not None else self._allocate_id() + if any(e.id == entry_id for e in self._entries): + raise ValueError("memory id already exists: %s" % entry_id) + entry = MemoryEntry(entry_id, str(text), label, dict(metadata or {})) + self._entries.append(entry) + self._rebuild_index() + return entry.to_dict() + + def remember_many(self, items: Iterable[Any]) -> List[Dict[str, Any]]: + """Store several memories. Each item may be text or a dict with text/label/metadata/id.""" + stored = [] + for item in items: + if isinstance(item, dict): + stored.append(self.remember( + item.get("text", ""), + label=item.get("label"), + metadata=item.get("metadata"), + id=item.get("id"), + )) + else: + stored.append(self.remember(item)) + return stored + + def recall(self, query: Any, k: int = 3, abstain: Optional[float] = None) -> List[Dict[str, Any]]: + """Return the nearest stored memories for `query`, best first. + + `abstain` is passed to `Index.nearest`; when set, noisy matches can + return an empty list instead of a guess. + """ + if not self._entries or self._index is None: + return [] + q = self._encode_text(query) + hits = self._index.nearest(q, k=min(int(k), len(self._entries)), abstain=abstain) + by_id = {entry.id: entry for entry in self._entries} + out = [] + for entry_id, score in hits: + entry = by_id[str(entry_id)] + row = entry.to_dict() + row["score"] = float(score) + out.append(row) + return out + + # ---- agent routing -------------------------------------------------------------------------------- + def suggest(self, task: str, k: int = 5) -> List[Dict[str, Any]]: + """Suggest capabilities for a plain-English task.""" + from holographic.misc import holographic_skills as skills + + return skills.suggest(task, k=k) + + def route(self, task: str) -> Dict[str, Any]: + """Route a task to one capability when confident, otherwise return options.""" + from holographic.misc import holographic_skills as skills + + routed = skills.route(task, act_threshold=self.route_threshold) + out = {"task": str(task)} + out.update(routed) + return out + + # ---- evidence / dashboard ------------------------------------------------------------------------- + def evidence(self) -> Dict[str, Any]: + """Return a machine-readable product readiness snapshot.""" + from holographic.caching_and_storage.holographic_catalog import default_catalog + + c_kernel = self._c_kernel_status() + route_probe = self.route("search a big pile of vectors") + return { + "name": "leCore LocalAgentCore", + "status": "ready" if self._deterministic_probe() else "check", + "memory": { + "entries": len(self._entries), + "dim": self.dim, + "index_method": self._index.method if self._index is not None else None, + "query_mutates_store": False, + }, + "routing": { + "capabilities": len(default_catalog()), + "probe_decision": route_probe.get("decision"), + "probe_skill": (route_probe.get("skill") or {}).get("name"), + }, + "c_kernel": c_kernel, + "checks": { + "deterministic_encoding": self._deterministic_probe(), + "local_only": True, + "no_model_weights": True, + }, + } + + def dashboard(self, html: bool = False) -> Any: + """Return the evidence dashboard as a dict, or static HTML with `html=True`.""" + data = self.evidence() + return self.dashboard_html(data) if html else data + + @staticmethod + def dashboard_html(data: Dict[str, Any]) -> str: + """Render an evidence snapshot as a dependency-free static HTML dashboard.""" + memory = data.get("memory", {}) + routing = data.get("routing", {}) + c_kernel = data.get("c_kernel", {}) + checks = data.get("checks", {}) + + def esc(value: Any) -> str: + return html.escape("" if value is None else str(value)) + + rows = [ + ("Status", data.get("status")), + ("Memories", memory.get("entries")), + ("Dimension", memory.get("dim")), + ("Index", memory.get("index_method") or "empty"), + ("Capabilities", routing.get("capabilities")), + ("Route Probe", "%s: %s" % (routing.get("probe_decision"), routing.get("probe_skill"))), + ("C Kernel", "available" if c_kernel.get("available") else "not built"), + ("C Path", c_kernel.get("path") or ""), + ("Deterministic", checks.get("deterministic_encoding")), + ("Local Only", checks.get("local_only")), + ("No Model Weights", checks.get("no_model_weights")), + ] + body = "\n".join( + "%s%s" % (esc(k), esc(v)) + for k, v in rows + ) + return """ + + + + leCore LocalAgentCore Dashboard + + + +
+

leCore LocalAgentCore

+

Local deterministic memory, skill routing, and readiness evidence.

+ + %s +
+
+ +""" % body + + # ---- persistence ---------------------------------------------------------------------------------- + def to_state(self) -> Dict[str, Any]: + """Serialize configuration and entries. Vectors are seed/context-derived and rebuilt on load.""" + return { + "dim": self.dim, + "seed": self.seed, + "route_threshold": self.route_threshold, + "next_id": self._next_id, + "entries": [entry.to_dict() for entry in self._entries], + } + + @classmethod + def from_state(cls, state: Dict[str, Any]) -> "LocalAgentCore": + """Rebuild a core from `to_state` data.""" + core = cls( + dim=int(state.get("dim", 512)), + seed=int(state.get("seed", 0)), + route_threshold=float(state.get("route_threshold", 0.6)), + ) + core._entries = [MemoryEntry.from_dict(row) for row in state.get("entries", [])] + core._next_id = int(state.get("next_id", len(core._entries) + 1)) + core._rebuild_index() + return core + + def save(self, path: Any) -> str: + """Write the product state to JSON and return the path.""" + p = Path(path) + p.write_text(json.dumps(self.to_state(), indent=2, sort_keys=True), encoding="utf-8") + return str(p) + + @classmethod + def load(cls, path: Any) -> "LocalAgentCore": + """Load a product state saved by `save`.""" + data = json.loads(Path(path).read_text(encoding="utf-8")) + return cls.from_state(data) + + # ---- internals ------------------------------------------------------------------------------------ + def _allocate_id(self) -> str: + entry_id = "m%d" % self._next_id + self._next_id += 1 + return entry_id + + def _encode_text(self, text: Any) -> np.ndarray: + toks = _tokens(text) + return self._encoder.encode(toks, modality="text") + + def _rebuild_index(self) -> None: + self._encoder = UniversalEncoder(self.dim, seed=self.seed) + for entry in self._entries: + toks = _tokens(entry.text) + if toks: + self._encoder.learn_text([toks]) + if not self._entries: + self._vectors = None + self._index = None + return + self._vectors = np.stack([self._encode_text(entry.text) for entry in self._entries]) + self._index = Index(self._vectors, labels=[entry.id for entry in self._entries], method="exact", seed=self.seed) + + def _deterministic_probe(self) -> bool: + a = self._encode_text("deterministic local memory") + b = self._encode_text("deterministic local memory") + return bool(np.allclose(a, b)) + + @staticmethod + def _c_kernel_status() -> Dict[str, Any]: + try: + import holographic_c + + return { + "available": bool(holographic_c.available()), + "path": holographic_c.backend_path(), + } + except Exception as exc: # pragma: no cover - defensive dashboard reporting + return {"available": False, "path": None, "error": "%s: %s" % (type(exc).__name__, exc)} + + +def demo() -> LocalAgentCore: + """Build a tiny ready-to-query product demo.""" + core = LocalAgentCore(dim=512, seed=0) + core.remember("local agents need deterministic durable memory", label="agent-memory") + core.remember("capability routing should act when confident and choose when ambiguous", label="routing") + core.remember("the C kernel accelerates the audited vector algebra hot path", label="c-kernel") + return core + + +def _selftest() -> None: + core = demo() + assert core.recall("deterministic local memory")[0]["label"] == "agent-memory" + assert core.route("start pause resume cancel a job")["decision"] == "act" + assert core.dashboard()["checks"]["deterministic_encoding"] + print("OK: holographic_product self-test passed") + + +if __name__ == "__main__": + _selftest() diff --git a/holographic_x402_api.py b/holographic_x402_api.py new file mode 100644 index 0000000..e5fde37 --- /dev/null +++ b/holographic_x402_api.py @@ -0,0 +1,429 @@ +"""holographic_x402_api.py -- publish LocalAgentCore as an x402-paid API. + +WHY THIS EXISTS +--------------- +`LocalAgentCore` is the narrow product wedge. This module makes it sellable as +an HTTP API without making x402, FastAPI, or uvicorn core dependencies. + +The boundary is intentionally conservative: + + * public read/compute routes are x402-paid + * health/pricing routes are free + * memory writes are admin-token gated, not pay-to-write + +That keeps the paid surface useful while preventing customers from poisoning a +shared memory store just because they paid for one request. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from html import escape +import argparse +import os +from pathlib import Path +from string import Template +from typing import Any, Dict, Iterable, List, Optional, Tuple + +from holographic_product import LocalAgentCore, demo + + +DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator" +DEFAULT_NETWORK = "eip155:84532" # Base Sepolia, safe default for testnet publishing. +DEFAULT_PRICE = "$0.0011" +DEFAULT_PUBLIC_URL = "https://lecore.rati.foundation" +LEOS_SITE_URL = "https://discoverleos.com/" +LEOS_TOKEN_CA = "5xgsnby6P9zqGK71J7H4yJLxzqPvNbC7rDZxNzjHmj7e" +LEOS_TOKEN_PRICE = "$0.0010" + + +@dataclass(frozen=True) +class PaidRoute: + """One x402-protected route.""" + + method: str + path: str + description: str + price: Optional[str] = None + mime_type: str = "application/json" + + @property + def key(self) -> str: + """The route key shape expected by x402 middleware, e.g. `POST /v1/recall`.""" + return "%s %s" % (self.method.upper(), self.path) + + +DEFAULT_PAID_ROUTES: Tuple[PaidRoute, ...] = ( + PaidRoute("POST", "/v1/recall", "Recall nearest memories from a LocalAgentCore instance"), + PaidRoute("POST", "/v1/route", "Route a plain-English task to a leCore capability"), + PaidRoute("GET", "/v1/dashboard", "Read the LocalAgentCore evidence dashboard"), +) + + +LANDING_PAGE_TEMPLATE = Template(""" + + + + +leCore x402 API + + + + +
+
+ + +

x402 seller live$network_label$price per callleOS CA $leos_token_price

leCore x402 API

Buy the small, useful surface of leCore: local agent memory, capability routing, and a readiness dashboard, sold as paid HTTP primitives instead of another subscription dashboard.

+ +
+
Endpoint$public_url
Paymentx402 exact scheme
Buyer shapeinspect, pay, call
+

Why you would buy it

Because most agents do not need a platform. They need a few reliable cognitive calls.

You pay for an answerable primitive, not a monthly seat.

The API is narrow enough to trust: read/compute routes are paid, memory writes stay admin-gated.

It exposes the useful part of leCore first: local agent memory plus capability routing.

The implementation is deployed, health-checked, and already returning x402 payment challenges.

+

leOS token offer

A slightly cheaper CA-only price for the leOS token.

Token price$leos_token_price
CA$leos_token_ca
leOS website
+

What the payment unlocks

Three paid routes, each small enough to understand.

POST

Recall

/v1/recall

Pull nearest memories from a compact local agent core without shipping a whole application stack.

POST

Route

/v1/route

Send a plain-language task and get the leCore capability it should use, with evidence attached.

GET

Dashboard

/v1/dashboard

Read the readiness surface: memory counts, capability map, abstention behavior, and route coverage.

+

Good first buyers

Teams who want the leCore idea without adopting the whole repo.

Agent memory for prototypes that should remember without a database rollout.

Capability routing for tools that need to pick the right leCore subsystem before doing work.

Evidence dashboards for teams deciding whether a local vector system is ready to productize.

A working x402 seller endpoint to copy when you want pay-per-call APIs instead of subscriptions.

+

Proof it is real

It is already deployed, priced, and protected.

The free endpoints show health and pricing. Paid endpoints return a real x402 payment challenge. The receiving address is public, while admin writes stay out of the paid customer path.

Price
$price
Network
$network_name
Receiver
$pay_to_short
Status
Healthy
+

The pitch

Buy it when you want a local-memory agent primitive that can pay for itself one request at a time.

+
+ +""") + + +@dataclass(frozen=True) +class X402Config: + """Seller configuration for the x402-paid API.""" + + pay_to: str + price: str = DEFAULT_PRICE + network: str = DEFAULT_NETWORK + facilitator_url: str = DEFAULT_FACILITATOR_URL + scheme: str = "exact" + routes: Tuple[PaidRoute, ...] = DEFAULT_PAID_ROUTES + public_url: str = DEFAULT_PUBLIC_URL + + def __post_init__(self) -> None: + if not self.pay_to: + raise ValueError("pay_to is required") + if not self.price.startswith("$"): + raise ValueError("x402 price must include a dollar prefix, e.g. '$0.001'") + if not self.network: + raise ValueError("network is required") + if not self.facilitator_url: + raise ValueError("facilitator_url is required") + + @classmethod + def from_env(cls, require_pay_to: bool = True) -> "X402Config": + """Build config from LECORE_X402_* environment variables.""" + pay_to = os.environ.get("LECORE_X402_PAY_TO", "") + if require_pay_to and not pay_to: + raise ValueError("set LECORE_X402_PAY_TO to the receiving wallet address") + return cls( + pay_to=pay_to or "0xYourAddress", + price=os.environ.get("LECORE_X402_PRICE", DEFAULT_PRICE), + network=os.environ.get("LECORE_X402_NETWORK", DEFAULT_NETWORK), + facilitator_url=os.environ.get("LECORE_X402_FACILITATOR_URL", DEFAULT_FACILITATOR_URL), + public_url=os.environ.get("LECORE_X402_PUBLIC_URL", DEFAULT_PUBLIC_URL), + ) + + def to_public_dict(self) -> Dict[str, Any]: + """Public, JSON-safe view of the payment configuration.""" + return { + "pay_to": self.pay_to, + "price": self.price, + "network": self.network, + "facilitator_url": self.facilitator_url, + "scheme": self.scheme, + "public_url": self.public_url, + } + + +def optional_dependency_help() -> str: + """Install hint for the optional paid API dependencies.""" + return 'Install the optional API dependencies with: pip install ".[x402]" (includes FastAPI and EVM x402 support)' + + +def _landing_nodes() -> str: + """CSS-positioned visual nodes for the marketing page hero.""" + nodes = [] + for index in range(34): + size = 9 if index % 5 == 0 else 7 if index % 3 == 0 else 5 + nodes.append( + '' + % ((index * 29) % 100, (index * 47 + 11) % 100, (index % 9) * -0.45, size) + ) + return "".join(nodes) + + +def _short_address(address: str) -> str: + """Compact public wallet display.""" + if len(address) <= 12: + return address + return "%s...%s" % (address[:6], address[-4:]) + + +def _network_name(network: str) -> str: + """Human label for known x402 network ids.""" + return {"eip155:84532": "Base Sepolia", "eip155:8453": "Base"}.get(network, network) + + +def leos_token_offer() -> Dict[str, str]: + """Public metadata for the leOS CA-only offer.""" + return { + "name": "leOS CA offer", + "site": LEOS_SITE_URL, + "ca": LEOS_TOKEN_CA, + "price": LEOS_TOKEN_PRICE, + "note": "Only the CA is needed for this token offer.", + } + + +def landing_page_html(config: X402Config) -> str: + """Render the buyer-facing landing page served from `/`.""" + network_name = _network_name(config.network) + offer = leos_token_offer() + return LANDING_PAGE_TEMPLATE.substitute( + nodes=_landing_nodes(), + public_url=escape(config.public_url), + price=escape(config.price), + network=escape(config.network), + network_label=escape("%s x402" % network_name), + network_name=escape(network_name), + pay_to_short=escape(_short_address(config.pay_to)), + leos_site_url=escape(offer["site"], quote=True), + leos_token_ca=escape(offer["ca"]), + leos_token_price=escape(offer["price"]), + ) + + +def payment_manifest(config: X402Config) -> List[Dict[str, Any]]: + """Plain JSON route manifest, useful for docs, `/pricing`, and tests.""" + out = [] + for route in config.routes: + price = route.price or config.price + out.append({ + "route": route.key, + "description": route.description, + "mime_type": route.mime_type, + "accepts": [{ + "scheme": config.scheme, + "price": price, + "network": config.network, + "pay_to": config.pay_to, + }], + }) + return out + + +def x402_route_configs(config: X402Config) -> Dict[str, Any]: + """Build x402 SDK RouteConfig objects for the protected routes.""" + try: + from x402.http import PaymentOption + from x402.http.types import RouteConfig + except ImportError as exc: + raise RuntimeError(optional_dependency_help()) from exc + + routes = {} + for route in config.routes: + routes[route.key] = RouteConfig( + accepts=[ + PaymentOption( + scheme=config.scheme, + pay_to=config.pay_to, + price=route.price or config.price, + network=config.network, + ) + ], + mime_type=route.mime_type, + description=route.description, + ) + return routes + + +def x402_resource_server(config: X402Config) -> Any: + """Create an x402 resource server wired to the configured facilitator.""" + try: + from x402.http import FacilitatorConfig, HTTPFacilitatorClient + from x402.mechanisms.evm.exact import ExactEvmServerScheme + from x402.server import x402ResourceServer + except ImportError as exc: + raise RuntimeError(optional_dependency_help()) from exc + + facilitator = HTTPFacilitatorClient(FacilitatorConfig(url=config.facilitator_url)) + server = x402ResourceServer(facilitator) + server.register(config.network, ExactEvmServerScheme()) + return server + + +def create_app( + core: Optional[LocalAgentCore] = None, + config: Optional[X402Config] = None, + paid: bool = True, + admin_token: Optional[str] = None, + state_path: Optional[str] = None, +) -> Any: + """Create the FastAPI app. + + With `paid=True`, the public `/v1/*` read/compute routes are protected by + x402 middleware. Set `paid=False` for local development smoke tests. + """ + try: + from fastapi import FastAPI, Header, HTTPException + from fastapi.responses import HTMLResponse + except ImportError as exc: + raise RuntimeError(optional_dependency_help()) from exc + + app = FastAPI(title="leCore x402 API", version="0.1.0") + core = core or demo() + config = config or (X402Config.from_env(require_pay_to=paid) if paid else X402Config.from_env(require_pay_to=False)) + + if paid: + try: + from x402.http.middleware.fastapi import PaymentMiddlewareASGI + except ImportError as exc: + raise RuntimeError(optional_dependency_help()) from exc + app.add_middleware( + PaymentMiddlewareASGI, + routes=x402_route_configs(config), + server=x402_resource_server(config), + ) + + def require_admin(header_value: Optional[str]) -> None: + if not admin_token: + raise HTTPException(status_code=403, detail="admin writes are disabled") + if header_value != admin_token: + raise HTTPException(status_code=401, detail="invalid admin token") + + @app.get("/", response_class=HTMLResponse, include_in_schema=False) + async def landing() -> str: + return landing_page_html(config) + + @app.get("/health") + async def health() -> Dict[str, Any]: + return { + "ok": True, + "name": "leCore x402 API", + "paid": bool(paid), + "memory": core.evidence()["memory"], + } + + @app.get("/pricing") + async def pricing() -> Dict[str, Any]: + return { + "ok": True, + "x402": config.to_public_dict(), + "token_offer": leos_token_offer(), + "routes": payment_manifest(config), + } + + @app.post("/v1/recall") + async def recall(payload: Dict[str, Any]) -> Dict[str, Any]: + query = payload.get("query") + if query is None: + raise HTTPException(status_code=400, detail="POST /v1/recall needs {query}") + return { + "ok": True, + "query": query, + "hits": core.recall(query, k=int(payload.get("k", 3)), abstain=payload.get("abstain")), + } + + @app.post("/v1/route") + async def route(payload: Dict[str, Any]) -> Dict[str, Any]: + task = payload.get("task") + if task is None: + raise HTTPException(status_code=400, detail="POST /v1/route needs {task}") + return {"ok": True, "route": core.route(str(task))} + + @app.get("/v1/dashboard") + async def dashboard() -> Dict[str, Any]: + return {"ok": True, "dashboard": core.dashboard()} + + @app.post("/admin/remember") + async def remember(payload: Dict[str, Any], x_admin_token: Optional[str] = Header(default=None)) -> Dict[str, Any]: + require_admin(x_admin_token) + text = payload.get("text") + if text is None: + raise HTTPException(status_code=400, detail="POST /admin/remember needs {text}") + memory = core.remember(text, label=payload.get("label"), metadata=payload.get("metadata")) + # Without this, admin writes live only in-process and vanish on restart. + if state_path: + core.save(state_path) + return {"ok": True, "memory": memory} + + return app + + +def load_core(path: Optional[str]) -> LocalAgentCore: + """Load a persisted core if present, otherwise return the demo core.""" + if path and Path(path).exists(): + return LocalAgentCore.load(path) + return demo() + + +def main(argv: Optional[Iterable[str]] = None) -> None: + """CLI entry point for local x402 API serving.""" + p = argparse.ArgumentParser(description="Serve LocalAgentCore as an x402-paid API") + p.add_argument("--host", default=os.environ.get("LECORE_X402_HOST", "127.0.0.1")) + p.add_argument("--port", type=int, default=int(os.environ.get("LECORE_X402_PORT", "4021"))) + p.add_argument("--state", default=os.environ.get("LECORE_X402_STATE")) + p.add_argument("--pay-to", default=os.environ.get("LECORE_X402_PAY_TO", "")) + p.add_argument("--price", default=os.environ.get("LECORE_X402_PRICE", DEFAULT_PRICE)) + p.add_argument("--network", default=os.environ.get("LECORE_X402_NETWORK", DEFAULT_NETWORK)) + p.add_argument("--facilitator-url", default=os.environ.get("LECORE_X402_FACILITATOR_URL", DEFAULT_FACILITATOR_URL)) + p.add_argument("--public-url", default=os.environ.get("LECORE_X402_PUBLIC_URL", DEFAULT_PUBLIC_URL)) + p.add_argument("--admin-token", default=os.environ.get("LECORE_X402_ADMIN_TOKEN")) + p.add_argument("--unpaid-dev", action="store_true", help="Disable x402 middleware for local development only") + args = p.parse_args(list(argv) if argv is not None else None) + + paid = not args.unpaid_dev + config = X402Config( + pay_to=args.pay_to or ("0xYourAddress" if not paid else ""), + price=args.price, + network=args.network, + facilitator_url=args.facilitator_url, + public_url=args.public_url, + ) + app = create_app( + load_core(args.state), + config=config, + paid=paid, + admin_token=args.admin_token, + state_path=args.state, + ) + try: + import uvicorn + except ImportError as exc: + raise RuntimeError(optional_dependency_help()) from exc + uvicorn.run(app, host=args.host, port=args.port) + + +if __name__ == "__main__": + main() diff --git a/lecore.py b/lecore.py index a8455bf..e52cdc0 100644 --- a/lecore.py +++ b/lecore.py @@ -2,9 +2,10 @@ # # The engine is ~436 `holographic_*.py` modules organized into family packages. A newcomer shouldn't need to know which # one holds `Scene` versus `RenderSession` versus `look_at`. This module gathers the handful of things -# most callers actually want into five plain-English areas, so that after `pip install lecore` you can: +# most callers actually want into plain-English areas, so that after `pip install lecore` you can: # # import lecore +# core = lecore.product.LocalAgentCore() # local memory + routing + dashboard # doc = lecore.scene.Scene(dim=1024, seed=0) # build a scene # img = lecore.render.path_trace(sdf, camera) # render it # M = lecore.transform.look_at(eye, target) # aim a camera @@ -29,7 +30,7 @@ # --------------------------------------------------------------------------------------------------- -# The five curated areas. +# The curated areas. # # Each area is imported here and packed into a SimpleNamespace below. We keep the imports grouped by # area (not alphabetised) so it reads as "here is everything the `scene` builder needs", etc. If any @@ -40,6 +41,9 @@ # scene -- author and store a scene document (objects, handles, transforms, undo snapshots). from holographic.scene_and_pipeline.holographic_scene_doc import Scene, SceneObject +# product -- the narrowed first-user surface: local agent memory, skill routing, and readiness evidence. +from holographic_product import LocalAgentCore + # model -- edit geometry: the modifier stack, object description, SDF primitives, key mesh verbs. from holographic.misc.holographic_modifier import ModifierStack, describe_object from holographic.mesh_and_geometry.holographic_sdf import sphere, box # SDF primitives more live in holographic_sdf @@ -80,8 +84,10 @@ def _area(**members): return types.SimpleNamespace(**members) -# The five areas. These are the ONLY place a member is listed; areas() reads its map straight off these +# The areas. These are the ONLY place a member is listed; areas() reads its map straight off these # namespaces (see below) so the docs and the objects can never drift out of sync. +product = _area(LocalAgentCore=LocalAgentCore) + scene = _area(Scene=Scene, SceneObject=SceneObject) model = _area( @@ -111,9 +117,10 @@ def _area(**members): ) -# The names of the five areas, in the order a builder meets them (author -> model -> render -> sim -> -# aim). Kept as a tuple so `areas()` and any future __all__ share one source of truth. -_AREA_NAMES = ("scene", "model", "render", "sim", "transform") +# The names of the areas, in the order a product user tends to meet them (product wedge first, then +# author -> model -> render -> sim -> aim). Kept as a tuple so `areas()` and any future __all__ share +# one source of truth. +_AREA_NAMES = ("product", "scene", "model", "render", "sim", "transform") def areas(): diff --git a/requirements-x402.txt b/requirements-x402.txt new file mode 100644 index 0000000..45aae28 --- /dev/null +++ b/requirements-x402.txt @@ -0,0 +1,2 @@ +x402[fastapi,evm] +uvicorn diff --git a/setup.py b/setup.py index 995524e..ad8c042 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ def read(name): "gpu": ["cupy"], # GPU backend (holographic_backend). NOTE: CuPy is tied to your CUDA # version -- you often need a specific wheel like `cupy-cuda12x` # instead, so it is best installed by hand (and left out of `all`). + "x402": ["x402[fastapi,evm]", "uvicorn"], # paid API publishing (holographic_x402_api) # -- optional tooling -- "ui": ["flask", "pillow"], # the browser UI (app.py) + image load/save "images": ["pillow"], # image I/O beyond stdlib PNG (jpg/webp/... via mind.save_render) -- diff --git a/tests/test_holographic_product.py b/tests/test_holographic_product.py new file mode 100644 index 0000000..e525ec8 --- /dev/null +++ b/tests/test_holographic_product.py @@ -0,0 +1,66 @@ +"""Tests for the product-facing LocalAgentCore facade.""" + +from holographic_product import LocalAgentCore, demo + + +def test_local_agent_core_remembers_and_recalls(): + core = LocalAgentCore(dim=256, seed=0) + core.remember("render scenes with global illumination and light caches", label="render") + core.remember("local agents need deterministic durable memory", label="memory") + + hits = core.recall("deterministic local memory", k=2) + assert hits[0]["label"] == "memory" + assert hits[0]["score"] >= hits[1]["score"] + + +def test_recall_is_query_safe_and_deterministic(): + core = demo() + before = core.to_state() + a = core.recall("deterministic local memory") + b = core.recall("deterministic local memory") + after = core.to_state() + + assert a == b + assert before == after + + +def test_route_uses_existing_skill_catalog(): + core = LocalAgentCore(dim=128, seed=0) + routed = core.route("start pause resume cancel a job") + + assert routed["task"] == "start pause resume cancel a job" + assert routed["decision"] == "act" + assert "call" in routed["skill"] + + +def test_dashboard_reports_product_evidence(): + core = demo() + data = core.dashboard() + page = core.dashboard(html=True) + + assert data["name"] == "leCore LocalAgentCore" + assert data["memory"]["entries"] == 3 + assert data["checks"]["deterministic_encoding"] is True + assert "c_kernel" in data + assert "leCore LocalAgentCore" in page + assert "No Model Weights" in page + + +def test_save_load_roundtrip(tmp_path): + path = tmp_path / "agent-core.json" + core = demo() + core.save(path) + + loaded = LocalAgentCore.load(path) + + assert loaded.to_state() == core.to_state() + assert loaded.recall("audited c kernel hot path")[0]["label"] == "c-kernel" + + +def test_lecore_exports_product_area(): + import lecore + + assert "product" in lecore.areas() + core = lecore.product.LocalAgentCore(dim=128, seed=1) + core.remember("agent memory product facade", label="product") + assert core.recall("agent memory")[0]["label"] == "product" diff --git a/tests/test_holographic_x402_api.py b/tests/test_holographic_x402_api.py new file mode 100644 index 0000000..764950b --- /dev/null +++ b/tests/test_holographic_x402_api.py @@ -0,0 +1,145 @@ +"""Tests for the optional x402-paid API publisher.""" + +import pytest + +from holographic_x402_api import ( + DEFAULT_NETWORK, + DEFAULT_PRICE, + DEFAULT_PUBLIC_URL, + LEOS_SITE_URL, + LEOS_TOKEN_CA, + LEOS_TOKEN_PRICE, + X402Config, + create_app, + landing_page_html, + leos_token_offer, + optional_dependency_help, + payment_manifest, +) + + +def test_default_x402_config_uses_testnet_price_shape(): + cfg = X402Config(pay_to="0xabc") + + assert cfg.network == DEFAULT_NETWORK + assert cfg.price == DEFAULT_PRICE and cfg.price.startswith("$") + assert cfg.facilitator_url == "https://x402.org/facilitator" + + +def test_payment_manifest_protects_specific_read_routes_only(): + manifest = payment_manifest(X402Config(pay_to="0xabc")) + routes = {row["route"] for row in manifest} + + assert routes == {"POST /v1/recall", "POST /v1/route", "GET /v1/dashboard"} + assert all("*" not in route for route in routes) + assert "POST /admin/remember" not in routes + assert "GET /health" not in routes + assert all(row["accepts"][0]["pay_to"] == "0xabc" for row in manifest) + + +def test_price_validation_keeps_x402_format_honest(): + with pytest.raises(ValueError, match="dollar prefix"): + X402Config(pay_to="0xabc", price="0.001") + + +def test_env_config_requires_pay_to_for_paid_mode(monkeypatch): + monkeypatch.delenv("LECORE_X402_PAY_TO", raising=False) + + with pytest.raises(ValueError, match="LECORE_X402_PAY_TO"): + X402Config.from_env(require_pay_to=True) + + assert X402Config.from_env(require_pay_to=False).pay_to == "0xYourAddress" + + +def test_optional_dependency_help_points_to_extra(): + assert 'pip install ".[x402]"' in optional_dependency_help() + + +def test_landing_page_explains_why_to_buy_the_api(): + html = landing_page_html(X402Config(pay_to="0x96e1604E92A8A1edD0701be3E67Bd4366e87BB84")) + + assert "leCore x402 API" in html + assert "Buy the small, useful surface of leCore" in html + assert "%s per call" % DEFAULT_PRICE in html + assert LEOS_TOKEN_PRICE in html + assert LEOS_TOKEN_CA in html + assert LEOS_SITE_URL in html + assert "Base Sepolia x402" in html + assert "/pricing" in html + assert "/v1/dashboard" in html + assert "0x96e1...BB84" in html + assert DEFAULT_PUBLIC_URL in html + assert "Live on AWS" not in html + + +def test_landing_page_endpoint_follows_public_url_config(monkeypatch): + custom = landing_page_html(X402Config(pay_to="0xabc", public_url="https://lecore-x402.fly.dev")) + assert "https://lecore-x402.fly.dev" in custom + assert DEFAULT_PUBLIC_URL not in custom + + monkeypatch.setenv("LECORE_X402_PUBLIC_URL", "https://example.test") + assert X402Config.from_env(require_pay_to=False).public_url == "https://example.test" + assert "public_url" in X402Config(pay_to="0xabc").to_public_dict() + + +def test_leos_token_offer_is_ca_only_metadata(): + offer = leos_token_offer() + + assert offer["site"] == LEOS_SITE_URL + assert offer["ca"] == LEOS_TOKEN_CA + assert offer["price"] == LEOS_TOKEN_PRICE + assert "Only the CA is needed" in offer["note"] + + +def test_unpaid_dev_app_serves_landing_page_and_keeps_api_routes_free(): + fastapi_testclient = pytest.importorskip("fastapi.testclient") + client = fastapi_testclient.TestClient( + create_app(config=X402Config(pay_to="0xabc"), paid=False) + ) + + landing = client.get("/") + assert landing.status_code == 200 + assert landing.headers["content-type"].startswith("text/html") + assert "leCore x402 API" in landing.text + + health = client.get("/health") + assert health.status_code == 200 + assert health.json()["ok"] is True + + pricing = client.get("/pricing") + assert pricing.status_code == 200 + assert pricing.json()["x402"]["price"] == DEFAULT_PRICE + assert pricing.json()["token_offer"]["ca"] == LEOS_TOKEN_CA + assert pricing.json()["token_offer"]["price"] == LEOS_TOKEN_PRICE + + +def test_admin_remember_persists_state_when_state_path_is_set(tmp_path): + fastapi_testclient = pytest.importorskip("fastapi.testclient") + from holographic_product import LocalAgentCore + + state = tmp_path / "core.json" + client = fastapi_testclient.TestClient( + create_app( + core=LocalAgentCore(dim=256, seed=0), + config=X402Config(pay_to="0xabc"), + paid=False, + admin_token="secret", + state_path=str(state), + ) + ) + + denied = client.post("/admin/remember", json={"text": "x"}) + assert denied.status_code == 401 + assert not state.exists() + + written = client.post( + "/admin/remember", + json={"text": "durable memory survives restarts", "label": "memory"}, + headers={"X-Admin-Token": "secret"}, + ) + assert written.status_code == 200 + assert state.exists() + + reloaded = LocalAgentCore.load(str(state)) + hits = reloaded.recall("durable memory survives restarts", k=1) + assert hits and "durable" in hits[0]["text"] diff --git a/tests/test_lecore.py b/tests/test_lecore.py index df56784..80bd6ba 100644 --- a/tests/test_lecore.py +++ b/tests/test_lecore.py @@ -4,8 +4,9 @@ def test_facade_namespaces_exist(): - for area in ("scene", "model", "render", "sim", "transform"): + for area in ("product", "scene", "model", "render", "sim", "transform"): assert hasattr(lecore, area) + assert hasattr(lecore.product, "LocalAgentCore") assert hasattr(lecore.scene, "Scene") and hasattr(lecore.model, "ModifierStack") assert hasattr(lecore.render, "CancelToken") and hasattr(lecore.sim, "MPMSnow") assert hasattr(lecore.transform, "look_at") @@ -22,4 +23,4 @@ def test_facade_operations_work_end_to_end(): def test_areas_map(): a = lecore.areas() - assert set(a) == {"scene", "model", "render", "sim", "transform"} and all(len(v) for v in a.values()) + assert set(a) == {"product", "scene", "model", "render", "sim", "transform"} and all(len(v) for v in a.values())