edge-proc 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,512 @@
1
+ Metadata-Version: 2.4
2
+ Name: edge-proc
3
+ Version: 0.1.5
4
+ Summary: Local execution substrate: ship signed, content-addressed data bundles to devices with fail-closed verification and delta sync, then run search and ranking locally through a deterministic router.
5
+ Project-URL: Homepage, https://github.com/hseshadr/edge-proc
6
+ Project-URL: Repository, https://github.com/hseshadr/edge-proc
7
+ Project-URL: Issues, https://github.com/hseshadr/edge-proc/issues
8
+ Author-email: Harish Seshadri <harish.seshadri@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: edgeproc-core>=0.2.1
19
+ Requires-Dist: pydantic-settings>=2.5
20
+ Requires-Dist: pydantic>=2.9
21
+ Requires-Dist: typer>=0.26.8
22
+ Provides-Extra: bundles
23
+ Requires-Dist: cryptography>=44; extra == 'bundles'
24
+ Requires-Dist: filelock>=3.20; extra == 'bundles'
25
+ Requires-Dist: httpx>=0.27; extra == 'bundles'
26
+ Requires-Dist: packaging>=24; extra == 'bundles'
27
+ Requires-Dist: structlog>=26.1.0; extra == 'bundles'
28
+ Requires-Dist: zstandard>=0.25; extra == 'bundles'
29
+ Provides-Extra: localvec
30
+ Requires-Dist: faiss-cpu>=1.14.3; extra == 'localvec'
31
+ Requires-Dist: numpy>=2.5.1; extra == 'localvec'
32
+ Requires-Dist: rank-bm25>=0.2; extra == 'localvec'
33
+ Requires-Dist: sentence-transformers>=5.6.0; extra == 'localvec'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # EdgeProc
37
+
38
+ **Ship a big file to a lot of devices — and let every one of them prove it's the real file, unmodified, before using it. Then send only the parts that changed.**
39
+
40
+ [![CI](https://github.com/hseshadr/edge-proc/actions/workflows/ci.yml/badge.svg)](https://github.com/hseshadr/edge-proc/actions/workflows/ci.yml)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
42
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)
43
+
44
+ ## The problem, as a story
45
+
46
+ When a video game ships an update, your console doesn't re-download the whole 80 GB game. It
47
+ downloads a small patch. And before it installs anything, it checks a signature to confirm the
48
+ patch really came from the studio — not from someone who slipped a modified file onto a mirror.
49
+
50
+ Plenty of software that isn't a game needs exactly that and rarely gets it. A search index. A
51
+ machine-learning model. An offline catalog or price list. These files are big, they change
52
+ often, and they have to land on phones, browsers, and small boxes you don't own or control.
53
+
54
+ Which leaves two awkward questions:
55
+
56
+ 1. **Is this actually my file?** It crossed someone else's network and sat on someone else's
57
+ CDN. If it arrived corrupted — or quietly swapped — would you find out? Or would your app
58
+ keep running and just start giving wrong answers?
59
+ 2. **Do I have to send all of it again?** If one row of a 400 MB index changed, making every
60
+ user re-download 400 MB is a real bandwidth bill and a bad experience.
61
+
62
+ **EdgeProc is a Python library that answers both.** You publish once; any number of devices
63
+ pull the update, verify it genuinely came from you and arrived intact, and fetch only the
64
+ bytes that actually changed.
65
+
66
+ ## How it answers them
67
+
68
+ - **Every file is split into chunks, and each chunk is named by a fingerprint of its own
69
+ contents.** That fingerprint (a SHA-256 hash) changes completely if even one byte changes —
70
+ so a corrupted or tampered chunk no longer matches the name it was requested under, and is
71
+ refused. The technical name for this is a *content-addressed store*, or CAS.
72
+ - **Exactly one small file is signed, and it vouches for everything else.** The publisher signs
73
+ a *version pointer*: a few bytes saying "version 1.0.1 is live, and its file list is
74
+ `<hash>`". That file list (the *manifest*) names every chunk by hash. So one signature check
75
+ covers the entire release, and there's only one secret to protect.
76
+ - **Chunk boundaries follow the content, not fixed offsets.** Edit one line in the middle of a
77
+ big file and only the chunk holding that line changes — everything after it keeps its old
78
+ fingerprint instead of shifting. That's what makes the next update a small delta.
79
+ - **If verification fails, nothing is installed.** Not "installed with a warning." The sync
80
+ exits non-zero and the previous good version stays live.
81
+
82
+ Once the data has landed, EdgeProc also runs the search and ranking **on the device** — no
83
+ embedding API, no vector database, no ranking server in the request path.
84
+
85
+ ## See it work
86
+
87
+ Everything below was run to produce the output shown. About five minutes, most of it a
88
+ one-time model download.
89
+
90
+ ```bash
91
+ git clone https://github.com/hseshadr/edge-proc.git
92
+ cd edge-proc
93
+ uv sync --all-extras # first run also downloads a ~90 MB embedding model
94
+ ```
95
+
96
+ ### 1. Make something worth shipping
97
+
98
+ A small product catalog, turned into a searchable index on disk.
99
+
100
+ ```bash
101
+ cat > save_index.py <<'PY'
102
+ import asyncio
103
+ from pathlib import Path
104
+
105
+ from edgeproc_core.vector_mgmt.core.types import IndexConfig, VectorEmbedding
106
+
107
+ from edgeproc.localvec.encoder import TextEncoder
108
+ from edgeproc.localvec.faiss_index import FaissVectorIndex
109
+
110
+ CATALOG = {
111
+ "p1": "red running shoes",
112
+ "p2": "waterproof hiking boots",
113
+ "p3": "blue denim jacket",
114
+ "p4": "trail running sneakers",
115
+ }
116
+
117
+
118
+ async def main() -> None:
119
+ ids, texts = list(CATALOG), list(CATALOG.values())
120
+ encoder = TextEncoder()
121
+ index = FaissVectorIndex("catalog_idx", IndexConfig(dimension=encoder.dim))
122
+ await index.insert(
123
+ [
124
+ VectorEmbedding(entity_id=i, embedding=v.tolist())
125
+ for i, v in zip(ids, encoder.encode_texts(texts), strict=True)
126
+ ]
127
+ )
128
+ index.save(Path("catalog_idx"))
129
+
130
+
131
+ asyncio.run(main())
132
+ PY
133
+
134
+ uv run python save_index.py
135
+ ls catalog_idx
136
+ ```
137
+
138
+ ```text
139
+ index.faiss
140
+ state.json
141
+ ```
142
+
143
+ ### 2. Mint a signing key
144
+
145
+ `private.key` stays on your build machine forever. `public.key` is what devices are given, and
146
+ it's the only thing they have to trust.
147
+
148
+ ```bash
149
+ uv run edgeproc keygen --out keys
150
+ ```
151
+
152
+ ```text
153
+ wrote keys/private.key and keys/public.key
154
+ ```
155
+
156
+ ### 3. Publish a signed release
157
+
158
+ ```bash
159
+ mkdir -p src && cp -r catalog_idx src/
160
+
161
+ uv run edgeproc publish \
162
+ --src src --origin-dir origin \
163
+ --key keys/private.key \
164
+ --bundle-id catalog --version 1.0.0 --pretty
165
+ ```
166
+
167
+ ```text
168
+ published v1.0.0 manifest=c4c28ab05da5
169
+ ```
170
+
171
+ `origin/` is now a plain directory of hash-named files. Put it behind any static web server or
172
+ CDN as-is — there is no application server to run.
173
+
174
+ ### 4. Pull it onto a device
175
+
176
+ ```bash
177
+ uv run edgeproc sync \
178
+ --base-url origin --cache-dir cache \
179
+ --key keys/public.key \
180
+ --materialize-to materialized --pretty
181
+ ```
182
+
183
+ ```text
184
+ synced v1.0.0 manifest=c4c28ab05da5 chunks_fetched=2 chunks_reused=0 bytes_fetched=5903
185
+ ```
186
+
187
+ ### 5. Search the file that just arrived
188
+
189
+ ```bash
190
+ cat > task.json <<'JSON'
191
+ {"kind": "search", "payload": {"query": "shoes for running", "k": 3}, "privacy_mode": "local_only"}
192
+ JSON
193
+
194
+ uv run edgeproc route --index-dir materialized/catalog_idx --task task.json --pretty
195
+ ```
196
+
197
+ ```text
198
+ success=True runtime=localvec latency=112.7ms
199
+ p1 0.219
200
+ p4 0.246
201
+ p2 0.556
202
+ ```
203
+
204
+ `p1` is "red running shoes" and `p4` is "trail running sneakers" — matched by meaning, on the
205
+ machine, with nothing in the request path but local code. The hashes and distances above
206
+ reproduce exactly; only `latency` varies by machine.
207
+
208
+ ### 6. Now watch it refuse to be fooled
209
+
210
+ This is the part worth trying yourself, because it's the whole point.
211
+
212
+ **Nothing changed? Then nothing is downloaded.**
213
+
214
+ ```bash
215
+ uv run edgeproc sync --base-url origin --cache-dir cache --key keys/public.key --pretty
216
+ ```
217
+
218
+ ```text
219
+ synced v1.0.0 manifest=c4c28ab05da5 chunks_fetched=0 chunks_reused=2 bytes_fetched=0
220
+ ```
221
+
222
+ **A small edit ships as a small delta.** Publish `1.0.1` with one line appended, then re-sync:
223
+
224
+ ```bash
225
+ echo "tiny edit" >> src/catalog_idx/state.json
226
+
227
+ uv run edgeproc publish --src src --origin-dir origin --key keys/private.key \
228
+ --bundle-id catalog --version 1.0.1 --pretty
229
+ uv run edgeproc sync --base-url origin --cache-dir cache --key keys/public.key \
230
+ --materialize-to materialized --pretty
231
+ ```
232
+
233
+ ```text
234
+ published v1.0.1 manifest=312b66ae9d63
235
+ synced v1.0.1 manifest=312b66ae9d63 chunks_fetched=1 chunks_reused=1 bytes_fetched=157
236
+ ```
237
+
238
+ 157 bytes instead of 5,903 — it re-fetched the one chunk that changed and reused the rest.
239
+
240
+ **No key means no sync.** There is no "just this once" mode:
241
+
242
+ ```bash
243
+ uv run edgeproc sync --base-url origin --cache-dir cache2 --pretty; echo "exit=$?"
244
+ ```
245
+
246
+ ```text
247
+ [config.missing] no trust root: pass --key or set EDGEPROC_TRUST_ROOT_PUBKEY_PATH (refusing to sync)
248
+ exit=1
249
+ ```
250
+
251
+ That `[config.missing]` prefix is a canonical error code, not decoration — every refusal
252
+ carries one, and `EDGEPROC_ERROR_FORMAT=json` turns the same refusal into a machine-readable
253
+ [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) object so a script can branch on it.
254
+
255
+ **Corrupt a chunk on the server and the device rejects the whole release.** Overwrite any file
256
+ under `origin/chunk/` and sync into a fresh cache:
257
+
258
+ ```bash
259
+ printf 'corrupted' > "origin/chunk/$(ls origin/chunk | head -1)"
260
+ uv run edgeproc sync --base-url origin --cache-dir cache3 --key keys/public.key --pretty
261
+ echo "exit=$?"
262
+ ls cache3
263
+ ```
264
+
265
+ ```text
266
+ [bundle.integrity_failed] sync failed: stored chunk failed to decompress
267
+ exit=1
268
+ chunks
269
+ manifests
270
+ ```
271
+
272
+ Compare that to the healthy `cache/`, which has an `active` directory. `cache3/` never got one:
273
+ the bad version was never promoted, and a device in this state keeps serving the last good
274
+ version instead of silently serving corrupted data.
275
+
276
+ ### Prefer to stay in Python?
277
+
278
+ The same search, in-process, without the CLI:
279
+
280
+ ```python
281
+ import asyncio
282
+
283
+ from edgeproc import EdgeProc, PrivacyMode, RuntimeRegistry, Task, TaskKind
284
+ from edgeproc.localvec.encoder import TextEncoder
285
+ from edgeproc.localvec.runtime import LocalVecRuntime
286
+
287
+ CATALOG = {"p1": "red running shoes", "p2": "waterproof hiking boots", "p3": "trail sneakers"}
288
+
289
+
290
+ async def main() -> None:
291
+ runtime = await LocalVecRuntime.from_texts(CATALOG, encoder=TextEncoder())
292
+ registry = RuntimeRegistry(); registry.register(runtime)
293
+ result = await EdgeProc(registry=registry).run(
294
+ Task(kind=TaskKind.SEARCH, payload={"query": "shoes for running"}, privacy_mode=PrivacyMode.LOCAL_ONLY)
295
+ )
296
+ for entity_id, distance in result.payload["results"]:
297
+ print(f" {entity_id} {CATALOG[entity_id]:<24} distance={distance:.3f}")
298
+
299
+
300
+ asyncio.run(main())
301
+ ```
302
+
303
+ ```text
304
+ p1 red running shoes distance=0.219
305
+ p3 trail sneakers distance=0.374
306
+ p2 waterproof hiking boots distance=0.556
307
+ ```
308
+
309
+ Swap `TaskKind.SEARCH` for `TaskKind.EMBED` (raw vectors) or `TaskKind.RANK` (keyword +
310
+ meaning-based ranking combined).
311
+
312
+ ## Why you'd reach for this
313
+
314
+ Moving both the data and the compute to the device buys four things at once:
315
+
316
+ - **The Nth query costs nothing.** Per-request cloud bills for embeddings, vector search, and
317
+ reranking collapse into a one-time bundle build.
318
+ - **Traffic spikes land on clients, not your servers.** A launch or a front-page link is
319
+ absorbed by users' own devices. Nothing to autoscale, nothing to fall over.
320
+ - **It survives weak or absent connectivity.** After one sync the device needs no network at
321
+ all to keep answering queries.
322
+ - **Tampering is caught, not tolerated.** Verification is fail-closed, and because the data is
323
+ searched locally, it never leaves the device to begin with.
324
+
325
+ Typical shapes: an app that recommends the next item with no per-search cloud bill however many
326
+ users you have; a privacy-sensitive tool where user data must never leave the machine; an edge
327
+ or IoT box that needs fast local search without standing up a backend.
328
+
329
+ ---
330
+
331
+ ## Under the hood (for developers)
332
+
333
+ Everything above is the friendly surface. Here is what actually happens, in the real terms.
334
+
335
+ ### Install
336
+
337
+ edge-proc isn't on PyPI yet, so the working install is the clone-and-go setup above — one
338
+ command, no sibling checkouts:
339
+
340
+ ```bash
341
+ git clone https://github.com/hseshadr/edge-proc.git
342
+ cd edge-proc
343
+ uv sync --all-extras # core + extras + dev tooling
344
+ ```
345
+
346
+ That Just Works — `edgeproc-core` resolves from
347
+ [PyPI](https://pypi.org/project/edgeproc-core/) (`edgeproc-core>=0.2.1`, the first
348
+ release shipping the `edgeproc_core` import package), so `uv sync` fetches
349
+ everything; nothing else to clone. Co-developing `edgeproc-core` alongside
350
+ EdgeProc? Clone it next to this repo and add the path override commented in
351
+ `pyproject.toml`.
352
+
353
+ Or install from PyPI — the extras install directly:
354
+
355
+ ```bash
356
+ pip install edge-proc # core + CLI (pure router, contracts)
357
+ pip install edge-proc[localvec] # + FAISS vector runtime (EMBED / SEARCH / RANK)
358
+ pip install edge-proc[bundles] # + manifest + checksum sync substrate
359
+ pip install edge-proc[localvec,bundles] # full local substrate
360
+ ```
361
+
362
+ EdgeProc is **purely a dependency** — a library an application embeds, not a service you sign
363
+ up for. The core is tiny; the heavy machinery (FAISS, sync) is opt-in behind extras. It builds
364
+ on [`edgeproc-core`](https://github.com/hseshadr/edgeproc-core): the FAISS index here
365
+ is a concrete implementation of that library's `VectorIndex` Protocol.
366
+
367
+ ### The deterministic router
368
+
369
+ You hand EdgeProc a `Task` and a router picks which engine (a "runtime") serves it. **That
370
+ router is a plain rulebook, never an AI** — it asks each registered runtime "do you accept this
371
+ task?" and picks the first that says yes. Because it's a pure function, the same `Task` against
372
+ the same runtimes always routes the same way, so a trace is replayable and you can prove which
373
+ runtime touched a request.
374
+
375
+ ### The typed result and the Task/budget model
376
+
377
+ A `Task` carries its `kind` (`EMBED` / `SEARCH` / `RANK`), a `payload`, a `privacy_mode`, and a
378
+ latency/memory **budget declaration**. `EdgeProc` admits work through a thread-safe
379
+ `MemoryManager`: the sum of declared in-flight reservations cannot exceed
380
+ `max_in_flight_memory_mb`, and every reservation releases in a `finally`-safe context. This is
381
+ deterministic admission control, **not** a native-RSS limit: the budget remains a declaration,
382
+ not an enforcement boundary for allocations inside FAISS, NumPy, or another native runtime. The
383
+ host or container owns RSS, CPU, and process-level termination. Share one `MemoryManager` across
384
+ facades when they share a process.
385
+
386
+ Every run returns a typed `ResultEnvelope` — a structured object with `success`, the serving
387
+ `runtime`, `latency`, and the `payload` — not a loose dict. Typed in, typed out.
388
+
389
+ ### The verification chain, precisely
390
+
391
+ `sync` verifies the pointer signature against the pinned trust-root pubkey **before trusting
392
+ anything**, diffs the manifest against the local cache, fetches only missing chunks, re-checks
393
+ every chunk against its content address, and only then atomically promotes the new version. A
394
+ tampered chunk fails its content-address check; a forged pointer fails its signature check —
395
+ both exit non-zero with no traceback, and neither promotes into the cache.
396
+
397
+ Chunking is content-defined (GearCDC) and chunks are zstd-compressed. Add `--http` to `sync`,
398
+ serving `origin/` over any static HTTP server or CDN, to go over the wire instead of the
399
+ filesystem; the contract is identical and only the transport changes.
400
+
401
+ ### Configuration: `EdgeProcSettings` + `EDGEPROC_`-prefixed env vars
402
+
403
+ Deploy-time config is read lazily from the environment / `.env` via `EdgeProcSettings`
404
+ (`edgeproc/core/settings.py`). It validates documented settings but
405
+ ignores unrelated host variables, so an embedded library coexists with the application's own
406
+ environment. Env vars use the `EDGEPROC_` prefix (except the HF token, which uses the
407
+ ecosystem-standard `HF_TOKEN`):
408
+
409
+ | Setting | Env var | Default | Purpose |
410
+ | --- | --- | --- | --- |
411
+ | `model_name` | `EDGEPROC_MODEL_NAME` | `sentence-transformers/all-MiniLM-L6-v2` | Embedding model. |
412
+ | `hf_token` | `HF_TOKEN` | `None` | Hugging Face auth token. |
413
+ | `default_k` | `EDGEPROC_DEFAULT_K` | `10` | Default top-k results. |
414
+ | `http_timeout` | `EDGEPROC_HTTP_TIMEOUT` | `30.0` | Bundle HTTP fetch timeout (s). |
415
+ | `mutation_lock_timeout` | `EDGEPROC_MUTATION_LOCK_TIMEOUT` | `30.0` | Bounded cross-process publish/sync/promote/GC lock wait (s). |
416
+ | `task_budget_ms` | `EDGEPROC_TASK_BUDGET_MS` | `5000` | Default per-task latency budget. |
417
+ | `task_budget_memory_mb` | `EDGEPROC_TASK_BUDGET_MEMORY_MB` | `256` | Default per-task memory budget. |
418
+ | `max_in_flight_memory_mb` | `EDGEPROC_MAX_IN_FLIGHT_MEMORY_MB` | `512` | Sum of declared task reservations admitted concurrently by one `EdgeProc` instance. |
419
+ | `max_materialize_bytes` | `EDGEPROC_MAX_MATERIALIZE_BYTES` | `256 MiB` | Maximum one file materialized into a returned `bytes` value. |
420
+ | `max_decompressed_bytes` | `EDGEPROC_MAX_DECOMPRESSED_BYTES` | `64 MiB` | Ceiling on one chunk's decompressed plaintext — refuses a zstd bomb before it exhausts memory. A legitimate chunk is ≤256 KiB, so this never rejects real data. |
421
+ | `max_fetch_bytes` | `EDGEPROC_MAX_FETCH_BYTES` | `256 MiB` | Ceiling on a single HTTP fetch body (pointer, manifest, or chunk) — bounds a hostile origin. |
422
+ | `max_sync_total_bytes` | `EDGEPROC_MAX_SYNC_TOTAL_BYTES` | `4 GiB` | Aggregate bytes one `sync` will pull before refusing — disk-exhaustion defense against a runaway manifest. |
423
+ | `max_sync_files` | `EDGEPROC_MAX_SYNC_FILES` | `100000` | Aggregate file count one `sync` will pull before refusing, for the same reason. |
424
+ | `rrf_k_window` | `EDGEPROC_RRF_K_WINDOW` | `60` | RRF rank-window constant for hybrid fusion. |
425
+ | `trust_root_pubkey_path` | `EDGEPROC_TRUST_ROOT_PUBKEY_PATH` | `None` | Pinned sync trust-root pubkey (no key ⇒ `sync` refused). |
426
+
427
+ That is the complete set — all 15 fields of `EdgeProcSettings`. A test asserts this table
428
+ matches the settings object field-for-field, so a new setting cannot ship undocumented.
429
+
430
+ One more environment variable exists that is deliberately **not** an `EdgeProcSettings`
431
+ field, because it steers CLI output rather than library behavior:
432
+
433
+ | Env var | Default | Purpose |
434
+ | --- | --- | --- |
435
+ | `EDGEPROC_ERROR_FORMAT` | `text` | Set to `json` and every fail-closed exit prints one [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) object on stderr instead of a text line — so a CI step or supervising process branches on `type` rather than pattern-matching prose. |
436
+
437
+ ```console
438
+ $ EDGEPROC_ERROR_FORMAT=json edgeproc sync --base-url ./origin --cache-dir ./cache --key absent.key
439
+ {"detail": "could not read trust-root key absent.key: [Errno 2] No such file or directory: 'absent.key'", "field": "--key", "title": "A required setting is missing: --key.", "type": "config.missing"}
440
+ ```
441
+
442
+ It is read straight from the environment rather than through `EdgeProcSettings` on purpose:
443
+ this runs on the failure path, and building the settings object there would let an unrelated
444
+ malformed variable raise *while reporting another error*, swallowing the refusal being reported.
445
+
446
+ For the full picture — system context, bundle lifecycle, the verification chain, and the module
447
+ map — see [**docs/ARCHITECTURE.md**](docs/ARCHITECTURE.md) (with d2 diagrams).
448
+
449
+ ### Measured numbers
450
+
451
+ ```bash
452
+ uv sync --all-extras
453
+ uv run poe gate
454
+ uv run python benchmarks/benchmark.py
455
+ ```
456
+
457
+ Both commands check themselves. The benchmark runs a fixed, offline fixture and prints JSON
458
+ with the latency it measured, peak memory, the machine it ran on, and pass/fail against each
459
+ budget — so you get *your* numbers rather than having to trust someone else's.
460
+
461
+ The figures measured here, and the hardware they were measured on, are recorded in
462
+ [**docs/OPERATIONS.md**](docs/OPERATIONS.md#measured-evidence). That is the one place they
463
+ live: this README deliberately does not restate them, because a number copied into two
464
+ documents is a number that will eventually disagree with itself.
465
+
466
+ ## Status & roadmap
467
+
468
+ **Shipped (v0):** a deterministic non-AI router, a FAISS-backed local-vector runtime (`EMBED` /
469
+ `SEARCH` / `RANK`), and a content-addressed, signed-bundle sync substrate (pinned ed25519 +
470
+ content-defined chunking), all behind opt-in extras.
471
+
472
+ **Roadmap — not built yet** (kept as Protocol seams, not in v0):
473
+
474
+ - **First-party WASM kernel v0** — one deterministic hot path (chunk hash/verify, BM25, or
475
+ rerank math), Rust→wasm32, running identically in the browser and in Python via wasmtime,
476
+ filling the `CUSTOM_WASM` seam — *roadmap, not built yet; full definition of done in
477
+ [ROADMAP.md](ROADMAP.md).*
478
+ - Biscuit capability tokens for fine-grained, attenuable authorization — *roadmap, not built yet.*
479
+ - Sigstore keyless bundle signing as an alternative to pinned ed25519 keys — *roadmap, not built yet.*
480
+
481
+ ## Docs
482
+
483
+ - [docs/QUICKSTART.md](docs/QUICKSTART.md) — the `keygen → publish → sync → route` loop as a
484
+ standalone five-minute walkthrough.
485
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — system context, bundle lifecycle, CAS +
486
+ manifest, module boundaries, seams.
487
+ - [docs/OPERATIONS.md](docs/OPERATIONS.md) — threat model, privacy flow, recovery/SLA ownership,
488
+ resource ceilings, and the measured performance gate.
489
+ - [docs/diagrams/](docs/diagrams/) — d2 sources + rendered SVGs.
490
+
491
+ ## Develop
492
+
493
+ ```bash
494
+ uv sync --all-extras # core + extras + dev tooling
495
+ uv run poe gate # lint + format-check + mypy strict + Radon Grade A + pytest (≥90% statement+branch cov)
496
+ ```
497
+
498
+ `poe gate` mirrors CI exactly — if it passes locally, CI passes.
499
+
500
+ ## About
501
+
502
+ **EdgeProc** — also written `edge-proc` and `edgeproc`; canonical repo
503
+ [`hseshadr/edge-proc`](https://github.com/hseshadr/edge-proc) — is the open-source, local-first
504
+ delivery-and-search substrate described above. It builds on
505
+ [**edgeproc-core**](https://github.com/hseshadr/edgeproc-core), the vector-partitioning
506
+ protocol its FAISS runtime implements. Canonical entity page:
507
+ [edge-reco.com/edgeproc](https://edge-reco.com/edgeproc), on a domain we control. It is **not
508
+ affiliated with any other product or company named "EdgeProc"**.
509
+
510
+ ## License
511
+
512
+ MIT
@@ -0,0 +1,36 @@
1
+ edgeproc/__init__.py,sha256=_s3PVEdWgM6uGvOILIiY8qFi53GhLWSA3VCyu9RAAY4,1370
2
+ edgeproc/_version.py,sha256=JAwQso-mFo9_4Gbeuq9N3CfGWxTSTvNrWNZCsobN2M4,109
3
+ edgeproc/errors.py,sha256=9kYjX6EgrDP_6sfAJe3FALPLfwJIAakVzsUEQZLGej4,3492
4
+ edgeproc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ edgeproc/bundles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ edgeproc/bundles/adapters.py,sha256=u5QKzNF8wqflTR1FiSl5ZTE3QVqhz-RYLceoEmzUOv8,3567
7
+ edgeproc/bundles/cas.py,sha256=ZhyPW2GBBbE_kHa3MtPeNXE_snSKQO3vZKKrHVBUeGI,14546
8
+ edgeproc/bundles/chunking.py,sha256=DWs5Nj7uknbmNJcJTJH1acwKZeTvg2O7nbyG_AxLedw,4301
9
+ edgeproc/bundles/containment.py,sha256=yKQON-llZWVu0KgKRqHKWF2z7CHy0wunuIt0bBHjZZY,2489
10
+ edgeproc/bundles/manifest.py,sha256=SsSMikjbyObHGHxsbMHWGWZDjUsTCip4C8rL1MWTyhM,6060
11
+ edgeproc/bundles/publish.py,sha256=a__QG9XOVQtzaTJ1l1pvdvDMKfz0MfnOQYghojKII9U,6070
12
+ edgeproc/bundles/signing.py,sha256=JF68SgXxxb5QW6jStANxxyXso3p79JrlvnH_7wLwGxU,4023
13
+ edgeproc/bundles/sync.py,sha256=I8CC4Wt7jY1k2ih16vYvXdZbK-fk219bNjD1di5h4jc,10365
14
+ edgeproc/cli/__init__.py,sha256=9AXZqAQZF_ZZCfjeGvOph5MpJMqVsF2quz1ynQyCEl0,174
15
+ edgeproc/cli/app.py,sha256=P1wg1E9dDcsUCS_DPKl4t1lEUxBdKiHrX3IlfNkoSig,23028
16
+ edgeproc/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ edgeproc/core/facade.py,sha256=kyppCKH4ZNjCKAZgYaKCINJvIjKLRXgPlNCwRQ8NcRc,4995
18
+ edgeproc/core/memory.py,sha256=ROAmjA_Rhs85kZcN_MIcxIYJ9ylXB_a0gQabepcHMn0,2641
19
+ edgeproc/core/models.py,sha256=5QylFUiQQoQhkLpnnOsXKra3-JPdeKCWSJFtpWcMkd4,3543
20
+ edgeproc/core/protocols.py,sha256=sut_zf5b2Kiwocugbelhj7ZqIlVE5ke-CchmI_WoBpo,1913
21
+ edgeproc/core/registry.py,sha256=x6CzGbBF2eWwbMvCAXuZ0ItU0GSAGO9ix83mt8vBmis,721
22
+ edgeproc/core/router.py,sha256=4P1rutQeKYCitj6-AgA8DT0zSF3l824oiQrgarQTB2o,840
23
+ edgeproc/core/settings.py,sha256=vof_y8PtdHDyMj38fzSiViSWCmbyHGUcXVx_NrHb3Sk,3753
24
+ edgeproc/core/telemetry.py,sha256=IsO4CaXuas6L_V0hIIyG8rZ53sEV4Hvnur4FZoKaFfA,1233
25
+ edgeproc/localvec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ edgeproc/localvec/encoder.py,sha256=JJQY6RPEKEENNCi2v0kS2YOAwcAjjaqVLrUukg9mbYM,3308
27
+ edgeproc/localvec/faiss_index.py,sha256=7Q5pZDd2NpecOiiMGPgVeaeMV_A4x-oMyeRRq9DpdAc,10170
28
+ edgeproc/localvec/fusion.py,sha256=ldtHVwaqyOA2W5x8-a24PbrHlVWdTmHMKthk8kVKTi0,1290
29
+ edgeproc/localvec/loader.py,sha256=t3fQF2zyUdxa1jXgPepjqY0eW-C8bGf61VoxYmLTBQU,1444
30
+ edgeproc/localvec/runtime.py,sha256=3fe2ojr_LDjaWAEsQ4WQ0_eOTHS0zVw0cHn7rFRcRRI,6236
31
+ edgeproc/localvec/searcher.py,sha256=2EHNyDnmOJyk62da0d8bn5N3QsB8dtgSEnZuRjszhtA,1578
32
+ edge_proc-0.1.5.dist-info/METADATA,sha256=kOctExDVQVANVC59E_amiR910GdfUWZYhfDqgyKKbWk,22325
33
+ edge_proc-0.1.5.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
34
+ edge_proc-0.1.5.dist-info/entry_points.txt,sha256=sh8IGYooeSGlEIwhcDP00P2h4qo_qh-ezAh7oU1u_5U,46
35
+ edge_proc-0.1.5.dist-info/licenses/LICENSE,sha256=DKAeGDB_tj9gKaHjTZRtKQTnm6jf2sgCNl5o9H09baE,1072
36
+ edge_proc-0.1.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ edgeproc = edgeproc.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harish Seshadri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
edgeproc/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """EdgeProc — AI-native local execution substrate.
2
+
3
+ The whole core surface is importable from here: the facade, the contract models,
4
+ the Protocol seams, and the concrete defaults (``DefaultRouter``, ``NullSink``,
5
+ ``BufferedSink``, ``RuntimeRegistry``) a consumer actually wires together.
6
+ Runtimes (e.g. ``LocalVecRuntime`` under the ``[localvec]`` extra) and the
7
+ bundle substrate (``[bundles]``) live in their own subpackages so the core stays
8
+ dependency-light.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from edgeproc._version import __version__
14
+ from edgeproc.core.facade import EdgeProc
15
+ from edgeproc.core.memory import MemoryBudgetExceededError, MemoryManager
16
+ from edgeproc.core.models import (
17
+ CapabilityVerdict,
18
+ PrivacyMode,
19
+ Provenance,
20
+ ResultEnvelope,
21
+ Task,
22
+ TaskKind,
23
+ )
24
+ from edgeproc.core.protocols import Router, Runtime, TelemetrySink
25
+ from edgeproc.core.registry import RuntimeRegistry
26
+ from edgeproc.core.router import DefaultRouter
27
+ from edgeproc.core.telemetry import BufferedSink, NullSink
28
+
29
+ __all__ = [
30
+ "BufferedSink",
31
+ "CapabilityVerdict",
32
+ "DefaultRouter",
33
+ "EdgeProc",
34
+ "MemoryBudgetExceededError",
35
+ "MemoryManager",
36
+ "NullSink",
37
+ "PrivacyMode",
38
+ "Provenance",
39
+ "ResultEnvelope",
40
+ "Router",
41
+ "Runtime",
42
+ "RuntimeRegistry",
43
+ "Task",
44
+ "TaskKind",
45
+ "TelemetrySink",
46
+ "__version__",
47
+ ]
edgeproc/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Single source of the package version (leaf module — safe to import anywhere)."""
2
+
3
+ __version__ = "0.1.5"
File without changes