Inspiration

Most "AI writes your social posts" tools fake personalization with a fixed window and just stuff the last few edits into a prompt and hope the model imitates your voice. That's not memory, it's a recency hack. We wanted to build the real version; a persistent, transactionally-consistent memory layer that actually compounds over time, applied to a genuinely time-sensitive use case: drafting social posts about breaking cybersecurity news while the story is still hot.

What it does

Ghostwire is an agent that triages live cybersecurity news personally for each user and drafts platform-matched social posts in their actual voice — not a generic AI voice.

  1. Live personalized triage Pulls real, current entries from CISA's Known Exploited Vulnerabilities catalog and The Hacker News, then ranks them against this specific user's engagement history, not a global trending score. A user with no history yet gets an honest fallback (severity + recency). The UI visibly distinguishes "personalized" from "cold start".
  2. Voice-matched drafting Generates platform-specific drafts (LinkedIn vs. X) by retrieving the user's persistent style vector plus the nearest historical edits, via Amazon Bedrock.
  3. Memory consolidationEvery accepted edit is written to CockroachDB in the same transaction that updates the running style-profile average, so a concurrent draft-generation request can never read a profile mid-update.
  4. Resilience A scripted, reproducible demo runs a real 3-node CockroachDB cluster, kills a node mid-write, and shows both a read and a write against style_profiles succeeding while the node is down, then confirms the write survived after the node rejoins.

How we built it

CockroachDB tools

Distributed Vector Indexing is the actual mechanism the whole product depends on. style_vector (voice memory) and embedding (news relevance) are VECTOR columns with vector indexes; personalized triage ranks live news by embedding <-> interest_vector, and draft generation retrieves the nearest historical edits by edit_vector <-> style_vector. The vectors live in the same transactional store as the relational state (users, drafts, engagement), so an edit and its embedding update land atomically, with no split-brain between a vector DB and a separate source of truth.

Agent Skills Repo (cockroachlabs/cockroachdb-skills, installed via npx skills add) We used the designing-application-transactions skill to review our own transaction code (backend/src/lib/memory.ts, budget.ts) against CockroachDB's own best practices, and it surfaced a real gap: neither of our two explicit transactions retried on 40001 serialization failures, which CockroachDB's SERIALIZABLE isolation makes a normal, expected outcome under contention, not an error condition. We added a shared withSerializableRetry helper (exponential backoff + jitter, backend/src/lib/db.ts) and applied it to both.

AWS services

  • Amazon Bedrock Titan Text Embeddings V2 (all vector embeddings) and Claude Sonnet 4.5 (draft generation), called through the AWS SDK with the execution role's credentials, not static keys.
  • AWS Lambda The entire backend runs as a Lambda behind a public Function URL, IAM role scoped.
  • Amazon S3 + CloudFront The frontend is a private S3 bucket served over HTTPS via CloudFront with Origin Access Control, so the bucket itself is never publicly reachable.

Challenges we ran into

  • CockroachDB's INT is 64-bit by default (unlike Postgres, where INT is 32-bit) — node-postgres silently returns those columns as strings, which broke a running-average calculation via string concatenation instead of arithmetic ("4" + 1 → "41", not 5) before we caught and fixed it with a type parser.
  • Amazon Titan Text Embeddings V2 outputs 1024 dimensions, not 1536 (that's V1) — we'd sized the schema for the wrong model version and had to migrate.
  • Lambda Function URL CORS: both the Function URL's own CORS config and our app code were adding Access-Control-Allow-Origin headers, producing a duplicate/combined value browsers reject outright. Fixed by letting AWS own CORS entirely.

Accomplishments that we're proud of

The voice-memory claim is the one I am most proud of, because we didn't plan the proof. During voice training, a draft eventually came out in UK English. Instead of taking that at face value, I ran the actual retrieval query the app uses in production — edit_vector <-> style_vector — live against the database, and the three nearest edits by real distance turned out to be the three with the clearest UK spelling markers.

What we learned

The voice-memory claim is the one we're most proud of, because we didn't plan the proof — we found it by accident. During voice training, a draft came out in UK English we never typed, generated automatically from 8 accumulated exemplars. Instead of taking that at face value, we ran the actual retrieval query the app uses in production — edit_vector <-> style_vector — live against the database, and the three nearest edits by real distance turned out to be the three with the clearest UK spelling markers. That's the entire memory thesis validated with a number, not an anecdote.

We're also proud of catching a real bug before it became a demo-day surprise: running CockroachDB's own transaction-design skill against our code surfaced a missing retry on 40001 serialization failures, which we fixed rather than shipped. And we chose the harder, more honest UX over the easy one — a cold-start user sees a visibly unranked fallback instead of a fabricated confidence score.

What we learned

I learned about the limitations of the current method I'm using to replicate user tone in LLM outputs, but also, unexpectedly, how much a handful of retrieved exemplars can steer it. That's the actual mechanism behind the UK-spelling draft: it's not that the model "understood" a preference, it's that vector retrieval surfaced the right examples at generation time. Once we saw that pattern hold up under a real distance query, it changed how much we trust retrieval-based personalization versus prompt-stuffing.

What's next for Ghostwire

Ghostwire started as a prototype for an improved feature planned for Cyber Pulse, an existing cybersecurity intelligence app. The persistent voice-memory architecture built here can fold into that product.

Built With

Share this project:

Updates

Submission history