Skip to content

feat(local-deploy): Artifactory as single source of truth — capture writes to RT first, pgvector via sync only#3

Merged
safixdev merged 10 commits into
safixdev:local-deployfrom
KainanYuval:feat/direct-postgres-autosync
Jun 18, 2026
Merged

feat(local-deploy): Artifactory as single source of truth — capture writes to RT first, pgvector via sync only#3
safixdev merged 10 commits into
safixdev:local-deployfrom
KainanYuval:feat/direct-postgres-autosync

Conversation

@KainanYuval

Copy link
Copy Markdown
Contributor

Summary

  • capture_thought now pushes the artifact to Artifactory (RT) before any local write — RT is the SOT
  • pgvector is populated exclusively through runAutoSync; no direct DB writes from the capture path
  • artifactory.ts: adds probeRtRoot for startup diagnostics, resolves JF_PATH / ENRICHED_PATH so jf CLI works correctly inside the Docker container
  • docker-compose.yml: passes JF_PATH env var to the server container

Motivation

Previously, capture_thought wrote directly to pgvector and never touched Artifactory. This meant memories captured via MCP were invisible to any other memory system (simple-brain, mem0, etc.) sharing the same RT repo. With this change, RT is the authoritative store — a thought that doesn't exist in RT doesn't exist.

Write flow (after this PR)

capture_thought(content)
  ├─ extractMetadata(content)
  ├─ pushArtifact → Artifactory (RT)   ← single write gate
  └─ runAutoSync()                     → pgvector + embeddings

Test plan

  • capture_thought via MCP → verify artifact appears in open-brain-memories/thoughts/ in RT
  • list_thoughts returns the captured thought after sync
  • trigger_sync with reset_cursor: true re-populates pgvector from RT from scratch
  • If RT push fails, nothing lands in pgvector

Made with Cursor

KainanYuval and others added 10 commits June 11, 2026 14:39
…pgvector via sync only

- capture_thought pushes artifact to RT before any local write
- pgvector is populated exclusively through runAutoSync (no direct DB writes from capture path)
- artifactory.ts: add probeRtRoot, JF_PATH/ENRICHED_PATH env resolution for Docker
- docker-compose: pass JF_PATH env var to server container

Co-authored-by: Cursor <cursoragent@cursor.com>
…e deletes

Memories now carry full provenance and are deletable with an audit trail, and
metadata is supplied by the calling agent instead of a local chat model.

- capture_thought accepts structured metadata (type, topics, people,
  action_items, dates_mentioned, source) plus git_user/repo/context; the server
  no longer runs an LLM to infer it, falling back to minimal defaults.
- pushArtifact writes provenance as RT properties (git_user, user_id, repo,
  context, created_at, type, topics, content) via --target-props, so memories
  are listable/queryable from Artifactory without downloads.
- delete_thought tool: writes a tombstone (<id>.deleted.json) recording who
  deleted what and when, removes the live artifact, and deletes from pgvector.
- autosync reconciles tombstones every run (removes tombstoned memories from
  pgvector, never re-indexes them); re-capturing content resurrects it.
- db: add deleteByArtifactPath (postgres + supabase drivers).
- llm: remove extractMetadata/chat-model path; embedding is the only model.

Co-authored-by: Cursor <cursoragent@cursor.com>
- ollama-pull pulls only the embedding model (no gemma3:4b).
- .env.example: remove CHAT_MODEL; add JF_SERVER_ID, RT_REPO, GIT_USER and
  Artifactory guidance; document embedding-only design.
- README: reflect agent-owned metadata, Artifactory-as-SOT, no chat model, and
  updated resource expectations; point operators at the open-brain-up skill.
- add docker/.gitignore (jfrog-config/, openbrain-data/, .env.secrets).

Co-authored-by: Cursor <cursoragent@cursor.com>
Production runbook packaged as an agent skill: collects the team's Artifactory
inputs (URL, token, server-id, repo), generates the jf config the server mounts,
templates env/secrets, builds with corporate-CA support for TLS-intercepting
proxies, starts the stack, and verifies with a capture -> search round trip.
Bundles list_rt_memories.sh for inspecting live and tombstoned memories.

Co-authored-by: Cursor <cursoragent@cursor.com>
search_thoughts now takes optional `repo` (scope to the agent's current
project) and `all_repos` (global override), filtering pgvector via
metadata @> {repo}. Reduce the tool surface to the agent-facing core —
capture_thought, search_thoughts, list_thoughts, delete_thought — dropping
the ChatGPT-compat search/fetch tools, thought_stats, and trigger_sync
(autosync runs on capture and on a timer). Remove the now-dead citation
helpers and reset_cursor path.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the ~4 GB Ollama image with HuggingFace Text Embeddings Inference
(~343 MB Rust CPU server, OpenAI-compatible /v1/embeddings), keeping
mxbai-embed-large-v1 so vectors stay 1024-dim and no re-embedding is needed.
This cuts the embedding stack footprint from ~4.8 GB to ~1 GB.

Add a CDN-blocked / air-gapped path: pre-fetch the model on the host into
tei-model/ and load it via EMBED_MODEL=/model (corp proxies often reach
huggingface.co but block its CDN). Document the arm64 TEI tag.

Add option-B distribution: server now carries an image: ref
(${OPENBRAIN_IMAGE:-openbrain-mcp-server:local}) alongside build:, so teams
can pull a prebuilt image instead of building from source. Update the
open-brain-up skill with a Publishing section, the TEI model steps, the
4-tool client list, and corrected resource sizing.

Co-authored-by: Cursor <cursoragent@cursor.com>
…d auth

The MCP endpoint is a private per-developer service (sharing is via Artifactory,
the SOT), so harden it for that model:

- Bind the published port to 127.0.0.1 only — closes the prior 0.0.0.0 LAN
  exposure. Networked use must add auth + TLS at an ingress.
- Remove the dead MCP_ACCESS_KEY (it was read but never enforced) and the
  misleading "Auth" labeling; document that access control lives in RT
  (repo permissions + access token), not this port.
- Add a GET /health route and a server container healthcheck so Docker can
  detect and restart a hung server.
- Persist the autosync cursor in Postgres (new sync_state table, lazily created
  for existing deploys) and restore it on startup, so a restart resumes
  incremental sync instead of re-scanning/re-downloading the whole RT repo.
- Update the up-skill, docker README, and secrets template to match (no auth
  header, loopback URL, POSTGRES_PASSWORD only).

Co-authored-by: Cursor <cursoragent@cursor.com>
… delivery

Storage layout is now foldered per git repo:
  RT_REPO/<repo>/thoughts/<sha>.json   (+ .deleted.json tombstones)
`repo` is required on capture_memory and delete_memory so each project's
memories are physically separated and recall stays repo-scoped. Search/list
and the tombstone reconcile scan RT_REPO/*/thoughts/*.

Also finalizes the local delivery in this batch:
- MCP tools renamed to *_memory (capture/search/list/delete)
- Postgres trust auth (no password); db.ts tolerates empty password
- self-contained docker-compose (hardcoded plumbing, no .env templates)
- offline bundle (build_bundle.sh) + RT installer (install_from_bundle.sh)
- docs/skill updated to the new layout and bootstrap flow

Co-authored-by: Cursor <cursoragent@cursor.com>
Two production gaps in multi-user sync:

1. Repo-scoped cache. Sync no longer mirrors every repo's memories onto
   every client. The scope is a set seeded from SYNC_REPOS and grown
   lazily whenever a repo is captured-to or searched; each client only
   pulls RT_REPO/<repo>/thoughts/* for repos it actually uses. A newly
   scoped repo is backfilled immediately so the triggering request sees
   its data. listArtifacts/listTombstones now take an optional repo.

2. Skew-safe cursors. Per-repo cursor (sync_state key "cursor:<repo>")
   advances to the max RT `created` timestamp actually observed instead
   of the client wall clock, so local clock skew can't silently skip a
   teammate's new memory. Fetch uses `>= cursor` (idempotent upsert) to
   avoid boundary misses. getSyncCursor/setSyncCursor take a key.

compose: optional SYNC_REPOS + AUTOSYNC_INTERVAL_MS passthroughs.
Co-authored-by: Cursor <cursoragent@cursor.com>
The installer no longer assumes the friend's default jf server points at the
right Artifactory. Connection precedence:
  1. JF_URL + JF_ACCESS_TOKEN  → register a server on the fly (optional JF_SERVER_ID)
  2. JF_SERVER_ID              → use an already-configured server
  3. default jf server         → fallback
Validates a specified server exists; clear error otherwise. Docs updated with a
connection table and a repo21 (entplus) example.

Co-authored-by: Cursor <cursoragent@cursor.com>
@safixdev safixdev merged commit e315b55 into safixdev:local-deploy Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants