Commit graph

196 commits

Author SHA1 Message Date
2fd4e18e34 merge: feat/67-dispatch-task into freeze/0.22.0 2026-07-10 18:40:25 -07:00
2f6d5e5d87 merge: feat/66-task-bridge into freeze/0.22.0
# Conflicts:
#	CHANGELOG.md
#	README.md
#	pyproject.toml
2026-07-10 18:38:55 -07:00
2f5391554c merge: feat/65-retry-wrapper into freeze/0.22.0
# Conflicts:
#	CHANGELOG.md
#	README.md
2026-07-10 18:37:49 -07:00
8d70783896 merge: feat/64-vram-estimate into freeze/0.22.0
# Conflicts:
#	CHANGELOG.md
2026-07-10 18:36:39 -07:00
0010c472ab merge: feat/58-signal-bus into freeze/0.22.0 2026-07-10 18:35:47 -07:00
43f9e9a54c feat(tasks): implement dispatch_task/get_task_status generic caller/args dispatch
Some checks failed
CI / test (pull_request) Has been cancelled
Pagepiper (and possibly other products copying its pattern) imported
dispatch_task(caller, args) -> task_id / get_task_status(task_id) -> dict
from circuitforge_core.tasks, expecting a "product/task_name" + kwargs-dict
interface. Neither function existed, so every call silently hit an
except-Exception fallback with no visible error.

This is a new module, not a TaskScheduler wrapper — TaskScheduler is keyed
by task_id/job_id/params against a specific SQLite background_tasks table
(VRAM-budgeted LLM queue), a different shape from the generic named-runnable
dispatch pagepiper actually needed.

Scope: free-tier, in-process, single-node only. Routing through the
circuitforge-orch coordinator would need a new generic task-dispatch
endpoint on that separate BSL package (CFOrchClient only exposes model/
service allocation today) — tracked as follow-up, not attempted here.
Consuming products additionally need to call register_task_runner() at
startup to benefit; that's product-side work in a separate repo.

Bump to 0.22.0.

Closes: #67
2026-07-10 18:08:48 -07:00
e0d6fb78b4 feat(task-bridge): add shared data contract for external task schedulers
Some checks failed
CI / test (pull_request) Has been cancelled
New circuitforge_core.task_bridge module: ExternalTask (frozen dataclass,
schema v1) plus push_tasks() httpx wrapper. Pilot consumer is Kiwi, pushing
pantry expiry alerts into Focus Flow (AGPL-3.0, external project). The
contract lives in cf-core (MIT) so AGPL and BSL code never share a process
or artifact — each product implements its own exporter/receiver.

No transport server, no auth/token generation, no product-specific
behavior — pure data contract + reusable push helper, same spirit as the
existing sync and tasks modules.

Bump to 0.22.0.

Closes: #66
2026-07-10 18:02:22 -07:00
ed1ee6a489 docs(retry): add CHANGELOG entry and README module row
Some checks failed
CI / test (pull_request) Has been cancelled
Follow-up to b812943 — these were authored alongside the retry module
but not staged in the original commit.
2026-07-10 18:00:33 -07:00
b812943ed1 feat(retry): add circuitforge_core.retry — standard backoff wrapper over backon
Some checks failed
CI / test (pull_request) Has been cancelled
Standardizes retry/backoff behavior for cf-core modules and products that
make external calls, replacing ad-hoc per-product retry loops. Wraps
backon (MIT, zero stdlib deps) — chosen over tenacity/backoff per the
eval in #65 for native async support, circuit breaker/hedging primitives,
and a process-wide enable/disable toggle ideal for tests.

Ships the standalone wrapper only; wiring it into llm/affiliates/reranker/
activitypub is left as follow-up work per module rather than retrofitting
LLMRouter's existing fallback-chain error handling in the same change.

Bump to 0.22.0.

Closes: #65
2026-07-10 17:55:55 -07:00
5d5c02ff84 feat(hardware): add model_vram_estimate for model-to-hardware VRAM fit checks
Some checks failed
CI / test (pull_request) Has been cancelled
Answers "can this hardware run model X at quantization level Y?" by
querying the HuggingFace Hub API for parameter count (safetensors) and
architecture (config.json), then applying the standard VRAM formula:
weights + KV cache (GQA-aware) + overhead. Reference algorithm from
LLMcalc (no code copied, unlicensed upstream).

Bump to 0.22.0.

Closes: #64
2026-07-10 17:44:36 -07:00
f32faae7be feat(signal-bus): add SSE event publisher; finish video upload + docs theme WIP
Some checks failed
CI / test (pull_request) Has been cancelled
- circuitforge_core.signal_bus: SignalBus/SignalEvent generic pub-sub over
  Server-Sent Events for real-time signal streams (thread-safe publish,
  bounded per-subscriber queues with drop-oldest overflow, keepalive).
  New `signal-bus` extra.
- circuitforge_core.video.app: POST /caption/upload accepts a multipart
  video file for callers without filesystem access to the video-service
  node; added test coverage. video-service extra now pulls in
  python-multipart, required by UploadFile parsing.
- docs: mkdocs.yml blue-grey/cyan palette now wired to a central
  docs/stylesheets/theme.css for consistent theme-aware styling.
- Bump to 0.22.0; update README module table/install line and CHANGELOG.

Closes: #58
2026-07-10 16:10:34 -07:00
11e49067a8 feat(sync): cross-device profile sync module (consent-gated, last-write-wins)
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Add circuitforge_core.sync:
- SyncStore: SQLite-backed blob store with per-user, per-data-class consent gating.
  Push rejected if user hasn't opted in to that data class (SyncPref). Pull returns
  only consented classes. Last-write-wins by updated_at timestamp (equal ts
  does not regress existing data).
- SyncConfig: env-based config factory; reads CF_SYNC_* env vars namespaced per
  product (e.g. CF_SYNC_PEREGRINE_DB).
- make_sync_router(): returns a FastAPI APIRouter pre-wired with:
    GET  /status               — last sync timestamps per data class (any tier)
    GET  /prefs                — list consent settings (any tier)
    PATCH /prefs               — opt in/out of a data class (any tier)
    POST  /push                — upload blob (Paid+ only)
    GET   /pull                — download blobs (Paid+ only)
    DELETE /blob/{data_class}  — delete one blob (any tier)
    DELETE /wipe               — clear all blobs + prefs (any tier)
- 45 tests covering consent gating, last-write-wins, blob opacity, tier checks,
  and all HTTP endpoints.

Also: feedback.py bugbot token fallback (FORGEJO_BOT_TOKEN → FORGEJO_TOKEN);
pyproject.toml: add sync optional extra.
2026-06-14 12:55:05 -07:00
e1793bde12 docs(readme): add video, mqtt, memory extras; update module table
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
- video and mqtt rows added to module table
- text row clarified: utilities + LLM inference service with backend list
- Install section: video-service, mqtt, meshtastic-service, memory extras
- Version badge bumped to 0.21.0
- Tiers description: drop Ultra mention
2026-06-05 12:01:34 -07:00
5eab4c43a4 docs: self-hoster service docs for text, video, and mqtt modules
Some checks are pending
CI / test (push) Waiting to run
Mirror / mirror (push) Waiting to run
text.md: add LLM inference service section with three-path decision
table (GGUF/transformers/VLM mmproj/classifier), multimodal content-
block API, mock mode, CF_TEXT_URL wiring.

video.md: new file covering Marlin-2B service, server-local video_path
callout, CUDA 13 nightly path, trust_remote_code note, MIT/BSL boundary
(current wrapper is MIT; special sauce pipelines go in separate BSL
module, not cf-core).

mqtt.md: new file covering broker vs serial decision tree, MQTTClient
usage, TopicRouter.matches() NotImplementedError with workaround, install
extras.
2026-06-05 11:59:48 -07:00
656e1e9816 fix(text): remove erroneous BSL comments from filter and classifier
PIIFilter and ClassifierBackend are privacy infrastructure, not
commercial AI features. Gating privacy controls behind a commercial
licence contradicts CF's privacy pillar. MIT applies per the repo root
LICENSE.
2026-06-05 11:59:28 -07:00
a92a83db4b ci: publish to Forgejo Packages on release; update install docs
Some checks are pending
CI / test (push) Waiting to run
Mirror / mirror (push) Waiting to run
Add twine upload step to release workflow so circuitforge-core lands on
both public PyPI and the Circuit-Forge Forgejo Packages index
(--extra-index-url for cf-orch installs). Reuses FORGEJO_PYPI_TOKEN for
the release creation step. Update installation.md to document editable
install pattern and optional extras.
2026-06-05 10:19:40 -07:00
cdeb410f45 fix: assorted reliability fixes
- cloud_session: bypass IPs now honour valid JWT tokens so logged-in devs
  land on their own account DB; invalid/expired JWTs soft-fail to guest
  instead of hard 401 (public endpoints stay accessible with stale cookies)
- tasks/scheduler: log unhandled exceptions that escape run_task_fn to
  prevent silent task stalls in the batch worker
- reranker: add module-level logger for structured log output
- text/transformers: use BitsAndBytesConfig for quantization (deprecated
  load_in_4bit/load_in_8bit kwargs removed in transformers 4.40+)
- __init__: derive __version__ from installed package metadata so editable
  installs always report the correct version string
2026-06-05 10:19:31 -07:00
24c75925ee feat(mqtt): meshtastic mesh networking module
Add circuitforge_core.mqtt — MQTT client, router, and meshtastic integration
(serial + MQTT backends, protobuf message models). Enables mesh radio messaging
for Raven / Link / field-use products without a cloud broker dependency.
2026-06-05 10:19:19 -07:00
cca4c54a62 feat(memory): persistent knowledge graph via mnemo sidecar
Add circuitforge_core.memory module: MemoryClient wraps the mnemo HTTP sidecar
for entity / relation storage. All operations no-op gracefully when sidecar is
unavailable so products can import unconditionally. Adds optional [memory]
extras entry in pyproject.toml (mnemo-sdk>=0.1.0).
2026-06-05 10:19:11 -07:00
0c43e95991 feat(text): classifier backend + PII filter
Add ClassifierBackend (NER/PII via transformers token-classification pipeline)
and TextFilter (redact / detect / spans modes). MockClassifierBackend provides
deterministic PII spans for tests and CI without GPU. Enables privacy-safe
pre-screening before LLM inference.
2026-06-05 10:19:03 -07:00
93ab528261 feat(text): multimodal content-block support + VLM mmproj passthrough
Add OpenAI-style content block models (ContentBlockText, ContentBlockImageURL)
to cf-text FastAPI app; update ChatMessage.content to accept str | list.
LlamaCppBackend gains mmproj_path + chat_format args for external projector
VLMs; embedded VLMs (Qwen2-VL, MiniCPM-V) detected via GGUF metadata.
Text-only backends raise ValueError on image input rather than silently
dropping them. Adds --mmproj CLI arg wired through create_app().

Closes: #66
2026-06-05 10:18:55 -07:00
5a363f3b6c fix(video): add torchvision to video-marlin extras
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Missing from initial extras list — required by QwenVLVideoProcessor
at inference time. On CUDA 13 nodes must be installed from the PyTorch
nightly cu130 index to avoid a torch version downgrade:
  pip install --index-url https://download.pytorch.org/whl/nightly/cu130 torch torchvision

Discovered during Muninn deployment (2026-05-26).
2026-06-02 20:32:03 -07:00
a7d916f630 docs: add LLM development disclosure to README
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Humans own design, architecture, code review, testing, and
verification. LLMs are part of our development workflow.
Links to circuitforge.tech/positions for our full position.
2026-05-28 08:20:17 -07:00
c2ac55259d fix(video): enforce PCI_BUS_ID order + force CUDA_VISIBLE_DEVICES assignment
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
CUDA defaults to FASTEST_FIRST device ordering, which does not match
nvidia-smi's PCI bus order on multi-GPU nodes. On Muninn, the RTX 3090
is cuda:0 and the Quadro RTX 4000 is cuda:1 — the opposite of nvidia-smi.

Two fixes:
1. Set CUDA_DEVICE_ORDER=PCI_BUS_ID so --gpu-id always matches nvidia-smi
   and the muninn.yaml profile GPU index assignments.
2. Use direct assignment (os.environ[...] = ...) instead of setdefault —
   setdefault silently no-ops if CUDA_VISIBLE_DEVICES is already present
   in the environment (conda activation, prior run, system default).
2026-05-26 15:07:30 -07:00
9f7fb45071 feat(video): add cf-video module — Marlin-2B FastAPI service + mock backend + tests
Some checks are pending
CI / test (push) Waiting to run
Mirror / mirror (push) Waiting to run
Add the circuitforge_core.video package implementing the cf-video inference
service managed by cf-orch.

Service endpoints:
  GET  /health     — liveness check; model name + VRAM
  POST /caption    — dense scene description + timestamped event list
  POST /find       — temporal grounding of a natural-language event query

Backend hierarchy:
  VideoBackend (Protocol)
    MarlinBackend  — NemoStation/Marlin-2B via transformers>=5.7.0
    MockVideoBackend — deterministic stub; no GPU required

Pydantic request/response models enforce parameter bounds at the API
boundary (max_new_tokens ge/le, event min_length=1).  Span is serialized
as list[float] | None for JSON compatibility.

MarlinBackend loads eagerly in __init__ so cf-orch's 2-second liveness
poll catches load failures immediately.  FORCE_QWENVL_VIDEO_READER env var
defaults to torchcodec (faster than av path) before transformers import.

pyproject.toml extras:
  video-marlin   — torch, transformers, torchcodec, qwen-vl-utils, av, Pillow
  video-service  — video-marlin + fastapi + uvicorn

Test coverage: 46 tests across test_mock_backend.py and test_app.py.
All passing without GPU or real video file.

Closes: #71
2026-05-25 20:00:37 -07:00
93d36346c1 feat(llm): task-based cf-orch allocation in LLMRouter (v0.21.0)
_try_cf_orch_alloc now checks for cf_orch.task + cf_orch.product keys.
When present, uses client.task_allocate(product, task) instead of
service-based allocate(). Supports peregrine#115 task-model routing.
Existing service-based configs are unaffected.
2026-05-17 19:59:48 -07:00
af66877b51 feat(community): recipe dedup support — similar_to_ref FK, search_similar_posts, migration 006
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
Adds three-layer dedup infrastructure for community recipe posts:
- Migration 006: similar_to_ref self-FK, title lower() index, recipe_id index
- CommunityPost.similar_to_ref optional field (frozen dataclass, defaults None)
- SharedStore.search_similar_posts(): title ILIKE + recipe_id match, ordered by relevance
- insert_post() wires similar_to_ref into the INSERT
2026-05-11 17:09:18 -07:00
41c9830281 docs(readme): landing page rewrite — v0.20.0, all 28 modules, LLM router + DB usage examples, full extras install table, used-by table
Some checks failed
CI / test (push) Has been cancelled
Mirror / mirror (push) Has been cancelled
2026-05-06 08:51:54 -07:00
fb3a4c697d feat(llm): v0.20.0 — LLMRouter dict init + Ollama embed preflight (closes #59, #60)
Some checks failed
CI / test (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release — PyPI / release (push) Has been cancelled
- LLMRouter.__init__ now accepts a Path | dict; pagepiper ingest scripts
  pass a runtime-constructed config dict instead of a temp file
- _check_ollama_model_pulled() preflight on embed(): checks /api/tags once
  per backend URL and raises RuntimeError("...Fix: ollama pull <model>")
  when the configured embedding model is not pulled; silently skips for
  non-Ollama backends (vLLM, etc.) that don't expose /api/tags
- 6 new tests: dict init paths (x2) + preflight scenarios (x4)
- Existing embed tests updated to mock requests.get to avoid live Ollama calls
2026-05-05 14:59:49 -07:00
ccc6a15d94 feat: cf-core v0.19.0 — add PDF extraction, VectorStore, LLMRouter.embed()
Some checks failed
CI / test (push) Waiting to run
Mirror / mirror (push) Has been cancelled
Release — PyPI / release (push) Has been cancelled
2026-05-04 16:11:57 -07:00
0ddb3cbf07 chore: bump cf-core to v0.19.0 (add pdf, vector, llm.embed) 2026-05-04 16:04:48 -07:00
7526092481 fix(llm): strengthen embed skip-verification test; add DEMO_MODE check to embed() 2026-05-04 16:02:26 -07:00
8e2d15bcd4 feat(llm): add LLMRouter.embed() for batch embedding generation
Adds embed(texts, model_override, fallback_order) to LLMRouter. Only
openai_compat backends are tried (Ollama/vLLM expose /v1/embeddings;
anthropic and vision_service do not). Uses embedding_model from backend
config when present, falls back to the chat model otherwise. Supports
cf-orch allocation and raises RuntimeError when all backends are exhausted.

4 tests added (TDD: RED → GREEN), 763 total passing, no regressions.
2026-05-04 15:58:44 -07:00
a6d906bcbb fix(vector): explicit rollback, table identifier guard, query scope fix 2026-05-04 15:55:05 -07:00
0489f1111c feat(vector): add LocalSQLiteVecStore backed by sqlite-vec
Implements the VectorStore ABC using sqlite-vec virtual tables.
Two-table design (vec0 virtual + companion meta) supports upsert,
top-k ANN query with optional metadata post-filter, delete by ID,
and bulk delete_where. Also renames VectorMatch.id → entry_id to
avoid shadowing the Python builtin, updating base.py and all tests.

Installed: sqlite-vec 0.1.9
Tests: 16 passed (7 base + 9 integration)
2026-05-04 15:41:39 -07:00
e6c69f25ae fix(vector): rename VectorMatch.entry_id to id per downstream contract
VectorMatch.entry_id renamed to VectorMatch.id to match the API contract
expected by downstream consumers (pagepiper T7). The dataclass remains frozen
to prevent field reassignment; metadata is kept as plain dict for JSON
deserialization compatibility.

- Renamed VectorMatch.entry_id field to id
- Updated all test references to use .id accessor
- Simplified metadata to plain dict (removed MappingProxyType wrapping)
- All 7 tests passing
2026-05-04 14:19:14 -07:00
9492942623 fix(vector): make VectorMatch.metadata immutable; rename id to entry_id 2026-05-04 11:46:24 -07:00
fe51914902 feat(vector): add VectorStore ABC and VectorMatch dataclass 2026-05-04 11:42:03 -07:00
ac45067ae7 test(documents): add OCR fallback and edge case tests for PDFExtractor 2026-05-04 08:45:53 -07:00
408ab64c55 test(documents): add OCR and ImportError coverage for PDFExtractor
- Add module-level guards for pytesseract and PIL.Image (enables patching in tests)
- Move `import io` from inside _ocr_page to module-level stdlib imports
- Extract _ensure_pil_image() helper with TypeError guard so isinstance check
  does not blow up when Image is patched to a MagicMock in tests
- Add 3 new tests: pdfplumber=None ImportError, sparse-page OCR fallback,
  OCR render failure returns empty chunk
- Coverage: 96% (up from 64%)
2026-05-04 08:39:31 -07:00
bbb146b361 feat(documents): add PDFExtractor text-layer extraction and PageChunk
Adds circuitforge_core/documents/pdf.py with:
- PageChunk frozen dataclass (page_number, text, source, word_count)
- PDFExtractor.chunk_pages() — pdfplumber text-layer per page, OCR fallback via pytesseract for sparse pages
- Module-level graceful ImportError guard on pdfplumber (patchable, follows cf-core optional-extra pattern)
- pdf and pdf-ocr optional extras declared in pyproject.toml

3 tests, all passing.
2026-05-04 08:33:10 -07:00
3be21ce452 chore: gitignore .worktrees directory 2026-05-04 08:23:39 -07:00
73f694ed3a fix(input/gestures): restore Iterator[np.ndarray] return type on frames() 2026-04-26 20:48:50 -07:00
0f5ea86ab0 fix(input/gestures): enforce numpy array immutability in HandLandmarks; add CameraCapture tests
- Set points.flags.writeable = False in HandsDetector.detect() so in-place
  mutation of HandLandmarks.points raises ValueError (frozen=True alone does not
  protect numpy array contents)
- Extend test_handlandmarks_is_immutable to assert ValueError on array mutation
- Add test_camera.py with 3 tests covering is_open, frames() yield/break
  behaviour, and context manager release (was at 0% coverage)
- Remove unused `import numpy as np` from camera.py; fix frames() return
  annotation to Iterator (np.ndarray ref removed with the import)
2026-04-26 20:48:02 -07:00
cb3d186a58 chore: bump cf-core to v0.18.0 — adds cf_input.gestures module
Some checks failed
Mirror / mirror (push) Has been cancelled
Release — PyPI / release (push) Has been cancelled
2026-04-26 20:20:28 -07:00
a62bff5f1e test(input/gestures): add full pipeline smoke test 2026-04-26 20:18:40 -07:00
524cc62812 feat(input/gestures): add CameraCapture and public __init__ exports 2026-04-26 20:16:18 -07:00
a31e6099c6 feat(input/gestures): implement HandsDetector wrapping mediapipe Hands 2026-04-26 20:08:05 -07:00
5a4917d455 style: black format normalizer.py and test_normalizer.py 2026-04-26 20:05:54 -07:00
460530bb03 feat(input/gestures): implement normalize_hand() with scale/translation invariance 2026-04-26 19:58:00 -07:00