<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Joel Holmes</title><link>https://joelholmes.dev/</link><description>Recent content on Joel Holmes</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 14 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://joelholmes.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>About</title><link>https://joelholmes.dev/projects/grey-seal/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/grey-seal/about/</guid><description>&lt;p&gt;Grey Seal is a Retrieval-Augmented Generation (RAG) chat backend — a service for having grounded conversations with a knowledge base I actually own and control.&lt;/p&gt;
&lt;h2 id="why-i-built-it"&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;I wanted to be able to ask questions across the documents, links, and notes stored in my self-hosted platform and get answers that cite real sources rather than hallucinated ones. Commercial RAG products exist, but they require sending your data to a third party. Grey Seal runs entirely on my own infrastructure: the LLM runs locally via Ollama, vector search is handled by Shrike, and the data never leaves the network.&lt;/p&gt;</description></item><item><title>About</title><link>https://joelholmes.dev/projects/lynx/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/lynx/about/</guid><description>&lt;p&gt;Lynx is a personal link and feed management service — a place to save, organise, and enrich URLs and RSS/Atom feeds without handing that data to a commercial read-later or bookmarking platform.&lt;/p&gt;
&lt;h2 id="why-i-built-it"&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;I wanted to own my reading list. Existing bookmarking tools work well enough, but they&amp;rsquo;re third-party services with their own retention and privacy trade-offs. Lynx runs on my own infrastructure and stores everything in a PostgreSQL database I control.&lt;/p&gt;</description></item><item><title>About</title><link>https://joelholmes.dev/projects/magpie/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/magpie/about/</guid><description>&lt;p&gt;Magpie is a resource tagging and cataloguing service — the central hub that ties together the other services in my self-hosted platform by receiving resource events, persisting their metadata, and fanning them out to downstream consumers.&lt;/p&gt;
&lt;h2 id="why-i-built-it"&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;As I added more services (Lynx for links, Owl for books and papers, Weevil for reading data) each one needed to notify the rest of the platform that a new thing existed. Rather than wiring every producer directly to every consumer, I built Magpie as a single intake point: producers publish one event to Magpie, and Magpie re-publishes it on the shared Kafka bus for whatever downstream services care.&lt;/p&gt;</description></item><item><title>About</title><link>https://joelholmes.dev/projects/owl/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/owl/about/</guid><description>&lt;p&gt;Owl is a personal digital library service — a place to store and manage PDF/EPUB books and academic papers without depending on a third-party platform.&lt;/p&gt;
&lt;h2 id="why-i-built-it"&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;I wanted a single place to keep my reading material that I actually control. Commercial alternatives for books are fine, but for academic papers the existing options are either siloed into specific platforms or require manual bookkeeping across folders and browser tabs. Owl runs on my own infrastructure and stores everything in a database and object storage bucket I own.&lt;/p&gt;</description></item><item><title>About</title><link>https://joelholmes.dev/projects/shrike/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/shrike/about/</guid><description>&lt;p&gt;Shrike is a unified search and retrieval service — a single endpoint any service in my platform can query instead of each one maintaining its own search index.&lt;/p&gt;
&lt;h2 id="why-i-built-it"&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;As the number of services grew, so did the need to search across them. I didn&amp;rsquo;t want every service managing its own embeddings and full-text indexes independently. Shrike sits downstream of the Kafka bus, ingests events from all producers, and builds a shared dual-backend index: Qdrant for semantic (vector) search and PostgreSQL for keyword full-text search. Any service that needs search talks to Shrike rather than its own database.&lt;/p&gt;</description></item><item><title>About</title><link>https://joelholmes.dev/projects/weevil/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/weevil/about/</guid><description>&lt;p&gt;Weevil is a personal book tracking service — a place to keep a record of books I&amp;rsquo;ve read, want to read, or am currently reading, without depending on a third-party platform like Goodreads or Storygraph.&lt;/p&gt;
&lt;h2 id="why-i-built-it"&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;I wanted full ownership of my reading data. Existing platforms are convenient but come with trade-offs: lock-in, privacy concerns, and the ever-present risk that a service you rely on changes or disappears. Weevil runs on my own infrastructure, stores everything in a database I control, and exposes it through an API I wrote.&lt;/p&gt;</description></item><item><title>EasyPost</title><link>https://joelholmes.dev/cv/easypost/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/easypost/</guid><description>Building distributed systems for USPS-compliant label generation, transaction processing, and carrier integration at scale.</description></item><item><title>Architecture</title><link>https://joelholmes.dev/projects/grey-seal/arch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/grey-seal/arch/</guid><description>&lt;h2 id="system-context"&gt;System Context&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Context
 title System Context — Grey Seal within joel.holmes.haus

 Person(admin, "Admin", "Manages conversations and resources; chats with the assistant")

 Boundary(platform, "joel.holmes.haus Platform") {
 System(ui, "joel.holmes.haus", "Go-app WASM admin SPA")
 System(greyseal, "Grey Seal", "RAG-powered conversation and resource management service")
 System(shrike, "Shrike", "Search — provides hybrid semantic/keyword context retrieval")
 System(magpie, "Magpie", "Resource hub — receives ingested resources from Grey Seal")
 }

 SystemDb(postgres, "PostgreSQL", "Conversations, messages, roles, resources")
 SystemDb(redis, "Redis", "Per-conversation resource snippet cache (24 h TTL)")
 System_Ext(ollama, "Ollama", "Local LLM (deepseek-r1) — streaming chat completions")
 SystemQueue(kafka, "Kafka", "greyseal.v1.Resource (ingest queue) · shrike.v1.TextExtractedEvent")

 Rel(admin, ui, "Uses")
 Rel(ui, greyseal, "ConnectRPC")
 Rel(greyseal, postgres, "Reads / writes")
 Rel(greyseal, redis, "Resource snippet cache")
 Rel(greyseal, ollama, "Streaming LLM chat")
 Rel(greyseal, shrike, "ConnectRPC hybrid search for context")
 Rel(greyseal, kafka, "Publishes greyseal.v1.Resource")
 Rel(kafka, greyseal, "async ingest queue")
 Rel(kafka, magpie, "greyseal.v1.Resource")
&lt;/div&gt;
&lt;h2 id="container-diagram"&gt;Container Diagram&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Container
 title Grey Seal — Internal Containers

 Boundary(greyseal, "Grey Seal") {
 Container(api, "cmd/api", "Go / ConnectRPC h2c :9000", "ConversationService · ResourceService · RoleService")
 Container(worker, "cmd/worker", "Go / Kafka", "Async content fetch — scrapes websites/PDFs, publishes TextExtractedEvent to Shrike")

 Container(convSvc, "conversation.Service", "Go", "Chat (RAG pipeline) · CRUD · SubmitFeedback · summarisation")
 Container(resourceSvc, "resource.Service", "Go", "Ingest · CRUD — triggers async indexing via KafkaIndexer")
 Container(roleSvc, "role.Service", "Go", "CRUD for system prompt roles")
 Container(cache, "RedisResourceCache", "Go / Redis", "Per-conversation snippet cache keyed greyseal:conv:{uuid}:resources")

 ContainerDb(convRepo, "ConversationRepo + MessageRepo", "PostgreSQL / squirrel", "conversations · messages tables")
 ContainerDb(resourceRepo, "ResourceRepo", "PostgreSQL / squirrel", "resources table")
 }

 SystemDb(postgres, "PostgreSQL", "")
 SystemDb(redis, "Redis", "")
 System_Ext(ollama, "Ollama", "POST /api/chat stream")
 System_Ext(shrike, "Shrike", "ConnectRPC SearchService")
 SystemQueue(kafka, "Kafka", "")

 Rel(api, convSvc, "Chat · CRUD")
 Rel(api, resourceSvc, "Ingest · CRUD")
 Rel(api, roleSvc, "CRUD")
 Rel(convSvc, cache, "Cache-first context lookup")
 Rel(convSvc, shrike, "Hybrid search with EntityUUIDs filter")
 Rel(convSvc, ollama, "Streaming completion")
 Rel(convSvc, convRepo, "Persist messages + summary")
 Rel(resourceSvc, resourceRepo, "CRUD")
 Rel(resourceSvc, kafka, "Publishes TextExtractedEvent or greyseal.v1.Resource")
 Rel(kafka, worker, "greyseal.v1.Resource")
 Rel(worker, kafka, "Publishes shrike.v1.TextExtractedEvent")
 Rel(convRepo, postgres, "SQL")
 Rel(resourceRepo, postgres, "SQL")
 Rel(cache, redis, "GET / SET")
&lt;/div&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;grey-seal is a single-binary Go service (&lt;code&gt;cmd/api&lt;/code&gt;) that exposes a Connect-RPC API over HTTP/2 (h2c). It uses a layered architecture: a thin gRPC/Connect handler layer delegates to domain service interfaces, which are backed by PostgreSQL repositories. LLM inference is delegated to a local Ollama instance; semantic search is delegated to the external &lt;strong&gt;shrike&lt;/strong&gt; service. Resources are ingested asynchronously via &lt;strong&gt;Redpanda/Kafka&lt;/strong&gt;: the API enqueues events that the &lt;strong&gt;worker&lt;/strong&gt; process consumes to fetch content and forward it to shrike for chunking, embedding, and vector indexing.&lt;/p&gt;</description></item><item><title>Architecture</title><link>https://joelholmes.dev/projects/lynx/arch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/lynx/arch/</guid><description>&lt;h2 id="system-context"&gt;System Context&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Context
 title System Context — Lynx within joel.holmes.haus

 Person(admin, "Admin", "Uses the joel.holmes.haus UI to manage feeds and websites")

 Boundary(platform, "joel.holmes.haus Platform") {
 System(ui, "joel.holmes.haus", "Go-app WASM admin SPA")
 System(lynx, "Lynx", "Web feed and website archiving service")
 System(magpie, "Magpie", "Central resource index — receives website resources from Lynx")
 System(shrike, "Shrike", "Search — indexes enriched website text from Lynx")
 }

 SystemDb(postgres, "PostgreSQL", "Feeds, websites, and website metadata")
 SystemDb(minio, "MinIO / S3", "Archived HTML and readable text blobs")
 SystemQueue(kafka, "Kafka", "magpie.v1.Resource · shrike.v1.TextExtractedEvent")

 Rel(admin, ui, "Uses")
 Rel(ui, lynx, "ConnectRPC")
 Rel(lynx, postgres, "Reads / writes")
 Rel(lynx, minio, "Stores archived content")
 Rel(lynx, kafka, "Publishes resource events")
 Rel(kafka, magpie, "magpie.v1.Resource")
 Rel(kafka, shrike, "TextExtractedEvent")
&lt;/div&gt;
&lt;h2 id="container-diagram"&gt;Container Diagram&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Container
 title Lynx — Internal Containers

 Boundary(lynx, "Lynx") {
 Container(api, "cmd/api", "Go / ConnectRPC h2c :9000", "FeedService · WebsiteService · Enricher goroutine")
 Container(worker, "cmd/worker", "Go / Kafka", "FeedConsumer · WebsiteConsumer · Enricher pipeline goroutine")
 Container(ui, "cmd/ui", "Go-app WASM :8000", "Browser SPA — calls cmd/api via ConnectRPC HTTP client")

 Container(feedSvc, "feed.Service", "Go", "CRUD for feeds")
 Container(websiteSvc, "website.Service", "Go", "Create (publishes Kafka event) · CRUD")
 Container(enricher, "enrichment.Enricher", "Go / goroutine", "7-step pipeline: fetch → metadata → archive HTML → readable → archive → persist → magpie")

 ContainerDb(feedRepo, "FeedRepo", "PostgreSQL / squirrel", "feeds table")
 ContainerDb(websiteRepo, "WebsiteRepo + MetadataRepo", "PostgreSQL / squirrel", "websites · website_metadata tables")
 }

 SystemDb(postgres, "PostgreSQL", "")
 SystemDb(minio, "MinIO / S3", "websites/{uuid}/original.html · readable.txt")
 SystemQueue(kafka, "Kafka", "")

 Rel(api, feedSvc, "delegates")
 Rel(api, websiteSvc, "delegates")
 Rel(api, enricher, "Enqueue()")
 Rel(worker, feedSvc, "FeedConsumer")
 Rel(worker, websiteSvc, "WebsiteConsumer")
 Rel(worker, enricher, "Enqueue()")
 Rel(feedSvc, feedRepo, "CRUD")
 Rel(websiteSvc, websiteRepo, "CRUD")
 Rel(websiteSvc, kafka, "Publish magpie.v1.Resource")
 Rel(enricher, websiteRepo, "UpdateNameIfBlank · UpsertMetadata")
 Rel(enricher, minio, "PutObject")
 Rel(enricher, kafka, "TextExtractedEvent")
 Rel(feedRepo, postgres, "SQL")
 Rel(websiteRepo, postgres, "SQL")
&lt;/div&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Lynx is structured as a multi-binary Go monorepo. Three runnable binaries share business logic through the &lt;code&gt;lib/&lt;/code&gt; package tree.&lt;/p&gt;</description></item><item><title>Architecture</title><link>https://joelholmes.dev/projects/magpie/arch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/magpie/arch/</guid><description>&lt;h2 id="system-context"&gt;System Context&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Context
 title System Context — Magpie within joel.holmes.haus

 Person(admin, "Admin", "Manages resources, tags, and labels via the UI")

 Boundary(platform, "joel.holmes.haus Platform") {
 System(ui, "joel.holmes.haus", "Go-app WASM admin SPA")
 System(magpie, "Magpie", "Central resource index — stores and fans out resource metadata")
 System(lynx, "Lynx", "Web archiving — publishes website resources")
 System(owl, "Owl", "Library — publishes book and paper resources")
 System(greyseal, "Grey Seal", "RAG service — consumes resources for conversation context")
 System(shrike, "Shrike", "Search — receives resource events for keyword indexing")
 }

 SystemDb(postgres, "PostgreSQL", "Resources, tags, labels, resource_tags, resource_labels")
 SystemQueue(kafka, "Kafka", "magpie.v1.Resource (inbound + outbound) · greyseal.v1.Resource")

 Rel(admin, ui, "Uses")
 Rel(ui, magpie, "ConnectRPC")
 Rel(lynx, magpie, "Publishes magpie.v1.Resource")
 Rel(owl, magpie, "Publishes magpie.v1.Resource")
 Rel(magpie, postgres, "Reads / writes")
 Rel(kafka, magpie, "inbound magpie.v1.Resource")
 Rel(magpie, kafka, "outbound greyseal.v1.Resource")
 Rel(kafka, greyseal, "greyseal.v1.Resource")
 Rel(kafka, shrike, "magpie.v1.Resource")
&lt;/div&gt;
&lt;h2 id="container-diagram"&gt;Container Diagram&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Container
 title Magpie — Internal Containers

 Boundary(magpie, "Magpie") {
 Container(api, "cmd/api", "Go / ConnectRPC h2c :9000", "5 domain handlers: Resource · Tag · Label · ResourceTag · ResourceLabel")
 Container(worker, "cmd/worker", "Go / Kafka", "5 domain consumers; ResourceConsumer also republishes to grey-seal topic")
 Container(ui, "cmd/ui", "Go-app WASM :8000", "Browser SPA — resource list, tag/label management")

 Container(resourceSvc, "resource.Service", "Go", "Create · List · Get · Delete")
 Container(tagSvc, "tag.Service", "Go", "CRUD for tags")
 Container(labelSvc, "label.Service", "Go", "CRUD for labels")
 Container(rtSvc, "resource_tag.Service", "Go", "Manage tag associations")
 Container(rlSvc, "resource_label.Service", "Go", "Manage label associations")

 ContainerDb(resourceRepo, "ResourceRepo", "PostgreSQL / squirrel", "resources table (ON CONFLICT uuid DO NOTHING)")
 ContainerDb(tagRepo, "TagRepo + LabelRepo + join repos", "PostgreSQL / squirrel", "tags · labels · resource_tags · resource_labels")
 }

 SystemDb(postgres, "PostgreSQL", "")
 SystemQueue(kafka, "Kafka", "consumer group app-1")

 Rel(api, resourceSvc, "delegates · publishes Kafka event on Create")
 Rel(api, tagSvc, "delegates")
 Rel(api, labelSvc, "delegates")
 Rel(api, rtSvc, "delegates")
 Rel(api, rlSvc, "delegates")
 Rel(kafka, worker, "magpie.v1.Resource")
 Rel(worker, resourceSvc, "Upsert resource")
 Rel(worker, kafka, "Publishes greyseal.v1.Resource")
 Rel(resourceSvc, resourceRepo, "CRUD")
 Rel(tagSvc, tagRepo, "CRUD")
 Rel(resourceRepo, postgres, "SQL")
 Rel(tagRepo, postgres, "SQL")
&lt;/div&gt;
&lt;h2 id="system-context-1"&gt;System context&lt;/h2&gt;
&lt;p&gt;Magpie sits at the centre of a wider knowledge-management ecosystem. Upstream producers (e.g. &lt;code&gt;Owl&lt;/code&gt;, &lt;code&gt;Lynx&lt;/code&gt;, or direct API callers) submit resources; Magpie stores them and fans events out to downstream consumers including &lt;code&gt;grey-seal&lt;/code&gt; (RAG/vector DB), &lt;code&gt;shrike&lt;/code&gt; (search index), and others.&lt;/p&gt;</description></item><item><title>Architecture</title><link>https://joelholmes.dev/projects/owl/arch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/owl/arch/</guid><description>&lt;h2 id="system-context"&gt;System Context&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Context
 title System Context — Owl within joel.holmes.haus

 Person(admin, "Admin", "Manages personal library of books and academic papers")

 Boundary(platform, "joel.holmes.haus Platform") {
 System(ui, "joel.holmes.haus", "Go-app WASM admin SPA")
 System(owl, "Owl", "Personal digital library — books and academic papers")
 System(magpie, "Magpie", "Resource hub — receives book and paper resources from Owl")
 System(shrike, "Shrike", "Search — indexes paper/book text via TextExtractedEvent")
 }

 SystemDb(postgres, "PostgreSQL", "Books, papers, book metadata")
 SystemDb(minio, "MinIO / S3", "PDF and book file blobs")
 SystemQueue(kafka, "Kafka", "owl.v1.Paper · owl.v1.Book · magpie.v1.Resource · shrike.v1.TextExtractedEvent")
 System_Ext(arxiv, "arXiv / CrossRef", "Academic paper metadata APIs")
 System_Ext(googlebooks, "Google Books / Open Library", "Book metadata APIs (enrichment)")

 Rel(admin, ui, "Uses")
 Rel(ui, owl, "ConnectRPC")
 Rel(owl, postgres, "Reads / writes")
 Rel(owl, minio, "Stores PDFs and book files")
 Rel(owl, kafka, "Publishes paper and book events")
 Rel(owl, arxiv, "Resolves DOI / arXiv metadata")
 Rel(owl, googlebooks, "Enriches book metadata")
 Rel(kafka, magpie, "magpie.v1.Resource")
 Rel(kafka, shrike, "TextExtractedEvent")
&lt;/div&gt;
&lt;h2 id="container-diagram"&gt;Container Diagram&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Container
 title Owl — Internal Containers

 Boundary(owl, "Owl") {
 Container(api, "cmd/api", "Go / ConnectRPC h2c :9000", "BookService · PaperService · in-process Ingester goroutine")
 Container(worker, "cmd/worker", "Go / Kafka", "BookConsumer · PaperConsumer · Ingester pipeline · BookIngester pipeline")

 Container(bookSvc, "book.Service", "Go", "CRUD for books")
 Container(paperSvc, "paper.Service", "Go", "CRUD for papers · IngestPaperByURL · GetIngestionStatus")
 Container(ingester, "ingestion.Ingester", "Go / goroutine", "7-step paper pipeline: resolve → download → metadata → dedup → upload → persist → magpie")
 Container(bookIngester, "ingestion.BookIngester", "Go / goroutine", "4-step book pipeline: download → dedup → upload → persist")
 Container(metaClient, "metadata.CompositeClient", "Go / HTTP", "ArxivClient + CrossRefClient for DOI/arXiv resolution")

 ContainerDb(bookRepo, "BookRepo + BookMetadataRepo", "PostgreSQL / squirrel", "books · book_metadata tables")
 ContainerDb(paperRepo, "PaperRepo", "PostgreSQL / squirrel", "papers table")
 }

 SystemDb(postgres, "PostgreSQL", "")
 SystemDb(minio, "MinIO / S3", "papers/CHECKSUM · books/CHECKSUM")
 SystemQueue(kafka, "Kafka", "")
 System_Ext(arxiv, "arXiv / CrossRef", "")

 Rel(api, bookSvc, "delegates")
 Rel(api, paperSvc, "delegates")
 Rel(api, ingester, "Enqueue() / Status()")
 Rel(kafka, worker, "owl.v1.Book · owl.v1.Paper")
 Rel(worker, bookSvc, "BookConsumer")
 Rel(worker, paperSvc, "PaperConsumer")
 Rel(worker, ingester, "Enqueue()")
 Rel(worker, bookIngester, "Enqueue()")
 Rel(ingester, paperRepo, "Create")
 Rel(ingester, minio, "Upload PDF")
 Rel(ingester, kafka, "magpie.v1.Resource")
 Rel(ingester, metaClient, "Resolve DOI / arXiv")
 Rel(bookIngester, bookRepo, "Create")
 Rel(bookIngester, minio, "Upload book file")
 Rel(bookRepo, postgres, "SQL")
 Rel(paperRepo, postgres, "SQL")
&lt;/div&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Owl is a Go service (module &lt;code&gt;github.com/holmes89/owl&lt;/code&gt;) that provides a personal digital library for books and academic papers. Three runtime processes share the same Go module:&lt;/p&gt;</description></item><item><title>Architecture</title><link>https://joelholmes.dev/projects/shrike/arch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/shrike/arch/</guid><description>&lt;h2 id="system-context"&gt;System Context&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Context
 title System Context — Shrike within joel.holmes.haus

 Boundary(platform, "joel.holmes.haus Platform") {
 System(ui, "joel.holmes.haus", "Go-app WASM admin SPA — submits search queries")
 System(shrike, "Shrike", "Full-text and semantic search indexing service")
 System(lynx, "Lynx", "Web archiving — publishes TextExtractedEvent after enrichment")
 System(greyseal, "Grey Seal", "RAG conversations — queries Shrike for context retrieval")
 System(magpie, "Magpie", "Resource index — publishes Resource events for indexing")
 }

 SystemDb(postgres, "PostgreSQL", "Index records and full-text document store")
 SystemDb(qdrant, "Qdrant", "Vector store — cosine similarity search (768-dim)")
 System_Ext(ollama, "Ollama", "Local LLM — nomic-embed-text embeddings")
 SystemQueue(kafka, "Kafka", "TextExtractedEvent · EnvelopeEvent · ImportRequest · magpie.v1.Resource")

 Rel(ui, shrike, "ConnectRPC search queries")
 Rel(greyseal, shrike, "ConnectRPC hybrid search")
 Rel(lynx, kafka, "Publishes TextExtractedEvent")
 Rel(magpie, kafka, "Publishes magpie.v1.Resource")
 Rel(shrike, postgres, "Reads / writes index records")
 Rel(shrike, qdrant, "Vector upsert / search")
 Rel(shrike, ollama, "Embed text chunks")
 Rel(kafka, shrike, "TextExtractedEvent · EnvelopeEvent · ImportRequest · magpie.v1.Resource")
&lt;/div&gt;
&lt;h2 id="container-diagram"&gt;Container Diagram&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Container
 title Shrike — Internal Containers

 Boundary(shrike, "Shrike") {
 Container(api, "cmd/api", "Go / ConnectRPC h2c :9000", "IndexRecordService (CRUD) · SearchService (semantic + keyword + reindex)")
 Container(worker, "cmd/worker", "Go / Kafka", "4 consumers: TextExtracted · Envelope · Import · Resource")

 Container(indexSvc, "index_record.Service", "Go", "CRUD for index records")
 Container(chunker, "lib/chunker", "Go", "512-word overlapping windows, 64-word overlap")
 Container(embedder, "OllamaEmbedder", "Go / HTTP", "nomic-embed-text via Ollama REST API")
 Container(vectorStore, "QdrantStore", "Go / HTTP", "Upsert · Search · DeleteByEntity on shrike_context collection")

 ContainerDb(indexRepo, "IndexRecordRepo + DocumentRepo", "PostgreSQL / squirrel", "indexrecords (tsvector GIN) · documents tables")
 }

 SystemDb(postgres, "PostgreSQL", "")
 SystemDb(qdrant, "Qdrant", "shrike_context collection, 768-dim cosine")
 System_Ext(ollama, "Ollama", "http://ollama:11434")
 SystemDb(minio, "MinIO / S3", "Blob source for ImportConsumer")
 SystemQueue(kafka, "Kafka", "")

 Rel(api, indexSvc, "delegates CRUD")
 Rel(api, chunker, "Reindex path")
 Rel(api, embedder, "Embed query")
 Rel(api, vectorStore, "Search / DeleteByEntity")
 Rel(api, indexRepo, "Keyword search (tsvector)")
 Rel(kafka, worker, "TextExtractedEvent · EnvelopeEvent · ImportRequest · magpie.v1.Resource")
 Rel(worker, indexSvc, "Upsert index records")
 Rel(worker, chunker, "Chunk full text")
 Rel(worker, embedder, "Embed chunks")
 Rel(worker, vectorStore, "Upsert points")
 Rel(worker, minio, "Download blobs (ImportConsumer)")
 Rel(indexRepo, postgres, "SQL")
 Rel(embedder, ollama, "POST /api/embed")
 Rel(vectorStore, qdrant, "HTTP REST")
&lt;/div&gt;
&lt;h2 id="process-decomposition"&gt;Process Decomposition&lt;/h2&gt;
&lt;p&gt;Shrike is deployed as two containerised binaries (&lt;code&gt;api&lt;/code&gt; and &lt;code&gt;worker&lt;/code&gt;) plus an optional browser UI (&lt;code&gt;ui&lt;/code&gt;). All three share the same Go module and library code under &lt;code&gt;lib/&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Architecture</title><link>https://joelholmes.dev/projects/weevil/arch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/weevil/arch/</guid><description>&lt;h2 id="system-context"&gt;System Context&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Context
 title System Context — Weevil within joel.holmes.haus

 Person(admin, "Admin", "Tracks personal reading list, searches for and adds books")

 Boundary(platform, "joel.holmes.haus Platform") {
 System(ui, "joel.holmes.haus", "Go-app WASM admin SPA")
 System(weevil, "Weevil", "Book tracking and enrichment service")
 System(magpie, "Magpie", "Resource hub — receives book resources from Weevil")
 System(shrike, "Shrike", "Search — indexes book metadata")
 }

 SystemDb(postgres, "PostgreSQL", "Books and book metadata (enrichment results)")
 SystemDb(minio, "MinIO / S3", "Archived Google Books and Open Library JSON responses")
 SystemQueue(kafka, "Kafka", "weevil.v1.Book · magpie.v1.Resource")
 System_Ext(googlebooks, "Google Books API", "Book metadata and cover images")
 System_Ext(openlibrary, "Open Library API", "ISBN-based book metadata")

 Rel(admin, ui, "Uses")
 Rel(ui, weevil, "ConnectRPC")
 Rel(weevil, postgres, "Reads / writes")
 Rel(weevil, minio, "Archives enrichment API responses")
 Rel(weevil, kafka, "Publishes weevil.v1.Book")
 Rel(weevil, googlebooks, "Search + metadata enrichment")
 Rel(weevil, openlibrary, "ISBN metadata enrichment")
 Rel(kafka, magpie, "magpie.v1.Resource")
 Rel(kafka, shrike, "resource events")
&lt;/div&gt;
&lt;h2 id="container-diagram"&gt;Container Diagram&lt;/h2&gt;
&lt;div class="mermaid"&gt;C4Container
 title Weevil — Internal Containers

 Boundary(weevil, "Weevil") {
 Container(api, "cmd/api", "Go / ConnectRPC h2c :9000", "BookService (CRUD + deterministic UUID from ISBN/title) · SearchService (Google Books proxy)")
 Container(worker, "cmd/worker", "Go / Kafka", "BookConsumer · Enricher goroutine")
 Container(uiSrv, "cmd/ui", "Go-app WASM :8000", "Browser SPA — book list, create, enrichment view, search")

 Container(bookSvc, "book.Service", "Go", "Create · Get · List · Update · Delete")
 Container(searchSvc, "search.Service", "Go", "Stateless proxy to Google Books API")
 Container(enricher, "enrichment.Enricher", "Go / goroutine", "6-step pipeline: Google Books → Open Library → archive both → persist metadata → magpie (stub)")

 ContainerDb(bookRepo, "BookRepo + BookMetadataRepo", "PostgreSQL / squirrel", "books · book_metadata (ON CONFLICT DO UPDATE)")
 }

 SystemDb(postgres, "PostgreSQL", "")
 SystemDb(minio, "MinIO / S3", "books/&lt;uuid&gt;/google-books.json · open-library.json")
 SystemQueue(kafka, "Kafka", "consumer group weevil-service-group")
 System_Ext(googlebooks, "Google Books API", "")
 System_Ext(openlibrary, "Open Library API", "")

 Rel(api, bookSvc, "delegates · publishes weevil.v1.Book to Kafka on Create")
 Rel(api, searchSvc, "delegates")
 Rel(kafka, worker, "weevil.v1.Book")
 Rel(worker, bookSvc, "Persist book from Kafka")
 Rel(worker, kafka, "Publishes magpie.v1.Resource")
 Rel(worker, enricher, "Enqueue(EnrichBookRequest)")
 Rel(enricher, googlebooks, "Search by GoogleID / ISBN / title")
 Rel(enricher, openlibrary, "Fetch by ISBN")
 Rel(enricher, minio, "Archive JSON responses")
 Rel(enricher, bookRepo, "Upsert book_metadata")
 Rel(bookRepo, postgres, "SQL")
&lt;/div&gt;
&lt;h2 id="system-overview"&gt;System Overview&lt;/h2&gt;
&lt;p&gt;Weevil is structured around a classic event-driven backend. The API server publishes book creation events to Kafka; the worker consumes them, persists the book, and enqueues an enrichment request on a buffered channel for asynchronous processing.&lt;/p&gt;</description></item><item><title>Regrow</title><link>https://joelholmes.dev/cv/regrow/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/regrow/</guid><description>Designed and executed Kubernetes platform migration for existing microservices; built audit-driven MRV system for carbon credit programs using CQRS, Kafka, and Argo workflows.</description></item><item><title>API Reference</title><link>https://joelholmes.dev/projects/grey-seal/api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/grey-seal/api/</guid><description>&lt;h1 id="protocol-documentation"&gt;Protocol Documentation&lt;/h1&gt;
&lt;p&gt;&lt;a name="top"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_greyseal_v1_conversation-proto"&gt;schemas/greyseal/v1/conversation.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-v1-Conversation"&gt;Conversation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-v1-Message"&gt;Message&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-v1-MessageRole"&gt;MessageRole&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_greyseal_v1_resource-proto"&gt;schemas/greyseal/v1/resource.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-v1-Resource"&gt;Resource&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-v1-Source"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_greyseal_v1_role-proto"&gt;schemas/greyseal/v1/role.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-greyseal-v1-Role"&gt;Role&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_greyseal_v1_services_conversation-proto"&gt;schemas/greyseal/v1/services/conversation.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ChatRequest"&gt;ChatRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ChatResponse"&gt;ChatResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-CreateConversationRequest"&gt;CreateConversationRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-CreateConversationResponse"&gt;CreateConversationResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-DeleteConversationRequest"&gt;DeleteConversationRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-DeleteConversationResponse"&gt;DeleteConversationResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-GetConversationRequest"&gt;GetConversationRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-GetConversationResponse"&gt;GetConversationResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ListConversationsRequest"&gt;ListConversationsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ListConversationsResponse"&gt;ListConversationsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-SubmitFeedbackRequest"&gt;SubmitFeedbackRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-SubmitFeedbackResponse"&gt;SubmitFeedbackResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-UpdateConversationRequest"&gt;UpdateConversationRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-UpdateConversationResponse"&gt;UpdateConversationResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ConversationService"&gt;ConversationService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_greyseal_v1_services_resource-proto"&gt;schemas/greyseal/v1/services/resource.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-DeleteResourceRequest"&gt;DeleteResourceRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-DeleteResourceResponse"&gt;DeleteResourceResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-GetResourceRequest"&gt;GetResourceRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-GetResourceResponse"&gt;GetResourceResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-IngestResourceRequest"&gt;IngestResourceRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-IngestResourceResponse"&gt;IngestResourceResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ListResourcesRequest"&gt;ListResourcesRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ListResourcesResponse"&gt;ListResourcesResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ResourceService"&gt;ResourceService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_greyseal_v1_services_role-proto"&gt;schemas/greyseal/v1/services/role.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-CreateRoleRequest"&gt;CreateRoleRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-CreateRoleResponse"&gt;CreateRoleResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-DeleteRoleRequest"&gt;DeleteRoleRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-DeleteRoleResponse"&gt;DeleteRoleResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-GetRoleRequest"&gt;GetRoleRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-GetRoleResponse"&gt;GetRoleResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ListRolesRequest"&gt;ListRolesRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-ListRolesResponse"&gt;ListRolesResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-UpdateRoleRequest"&gt;UpdateRoleRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-UpdateRoleResponse"&gt;UpdateRoleResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-greyseal-services-v1-RoleService"&gt;RoleService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#scalar-value-types"&gt;Scalar Value Types&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="schemas_greyseal_v1_conversation-proto"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="right"&gt;&lt;a href="#top"&gt;Top&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="schemasgreysealv1conversationproto"&gt;schemas/greyseal/v1/conversation.proto&lt;/h2&gt;
&lt;p&gt;&lt;a name="schemas-greyseal-v1-Conversation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="conversation"&gt;Conversation&lt;/h3&gt;
&lt;p&gt;Conversation is a chat session that persists and can be resumed.&lt;/p&gt;</description></item><item><title>API Reference</title><link>https://joelholmes.dev/projects/lynx/api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/lynx/api/</guid><description>&lt;h1 id="protocol-documentation"&gt;Protocol Documentation&lt;/h1&gt;
&lt;p&gt;&lt;a name="top"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_lynx_v1_feed-proto"&gt;schemas/lynx/v1/feed.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-lynx-v1-Feed"&gt;Feed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#schemas-lynx-v1-FeedArticle"&gt;FeedArticle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_lynx_v1_services_feed-proto"&gt;schemas/lynx/v1/services/feed.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-CreateFeedRequest"&gt;CreateFeedRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-CreateFeedResponse"&gt;CreateFeedResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-DeleteFeedRequest"&gt;DeleteFeedRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-DeleteFeedResponse"&gt;DeleteFeedResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-FetchFeedArticlesRequest"&gt;FetchFeedArticlesRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-FetchFeedArticlesResponse"&gt;FetchFeedArticlesResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-GetFeedRequest"&gt;GetFeedRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-GetFeedResponse"&gt;GetFeedResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-ListFeedsRequest"&gt;ListFeedsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-ListFeedsResponse"&gt;ListFeedsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-UpdateFeedRequest"&gt;UpdateFeedRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-UpdateFeedResponse"&gt;UpdateFeedResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-FeedService"&gt;FeedService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_lynx_v1_website-proto"&gt;schemas/lynx/v1/website.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-lynx-v1-Website"&gt;Website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_lynx_v1_services_website-proto"&gt;schemas/lynx/v1/services/website.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-CreateWebsiteRequest"&gt;CreateWebsiteRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-CreateWebsiteResponse"&gt;CreateWebsiteResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-DeleteWebsiteRequest"&gt;DeleteWebsiteRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-DeleteWebsiteResponse"&gt;DeleteWebsiteResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-GetWebsiteRequest"&gt;GetWebsiteRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-GetWebsiteResponse"&gt;GetWebsiteResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-ListWebsitesRequest"&gt;ListWebsitesRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-ListWebsitesResponse"&gt;ListWebsitesResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-ReEnrichWebsiteRequest"&gt;ReEnrichWebsiteRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-ReEnrichWebsiteResponse"&gt;ReEnrichWebsiteResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-RegenerateSummaryRequest"&gt;RegenerateSummaryRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-RegenerateSummaryResponse"&gt;RegenerateSummaryResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-UpdateWebsiteRequest"&gt;UpdateWebsiteRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-UpdateWebsiteResponse"&gt;UpdateWebsiteResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-WebsiteMetadata"&gt;WebsiteMetadata&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-lynx-services-v1-WebsiteService"&gt;WebsiteService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#scalar-value-types"&gt;Scalar Value Types&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="schemas_lynx_v1_feed-proto"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="right"&gt;&lt;a href="#top"&gt;Top&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="schemaslynxv1feedproto"&gt;schemas/lynx/v1/feed.proto&lt;/h2&gt;
&lt;p&gt;&lt;a name="schemas-lynx-v1-Feed"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="feed"&gt;Feed&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Field&lt;/th&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Label&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;uuid&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;title&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;url&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;description&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;categories&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;repeated&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;website_id&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a name="schemas-lynx-v1-FeedArticle"&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>API Reference</title><link>https://joelholmes.dev/projects/magpie/api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/magpie/api/</guid><description>&lt;h1 id="protocol-documentation"&gt;Protocol Documentation&lt;/h1&gt;
&lt;p&gt;&lt;a name="top"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_label-proto"&gt;schemas/magpie/v1/label.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-magpie-v1-Label"&gt;Label&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_resource-proto"&gt;schemas/magpie/v1/resource.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-Resource"&gt;Resource&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-Source"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_resource_label-proto"&gt;schemas/magpie/v1/resource_label.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-magpie-v1-ResourceLabel"&gt;ResourceLabel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_resource_tag-proto"&gt;schemas/magpie/v1/resource_tag.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-magpie-v1-ResourceTag"&gt;ResourceTag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_services_label-proto"&gt;schemas/magpie/v1/services/label.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateLabelRequest"&gt;CreateLabelRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateLabelResponse"&gt;CreateLabelResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetLabelRequest"&gt;GetLabelRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetLabelResponse"&gt;GetLabelResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListLabelsRequest"&gt;ListLabelsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListLabelsResponse"&gt;ListLabelsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-LabelService"&gt;LabelService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_services_resource-proto"&gt;schemas/magpie/v1/services/resource.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateResourceRequest"&gt;CreateResourceRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateResourceResponse"&gt;CreateResourceResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetResourceRequest"&gt;GetResourceRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetResourceResponse"&gt;GetResourceResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListResourcesRequest"&gt;ListResourcesRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListResourcesResponse"&gt;ListResourcesResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ResourceService"&gt;ResourceService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_services_resource_label-proto"&gt;schemas/magpie/v1/services/resource_label.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateResourceLabelRequest"&gt;CreateResourceLabelRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateResourceLabelResponse"&gt;CreateResourceLabelResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetResourceLabelRequest"&gt;GetResourceLabelRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetResourceLabelResponse"&gt;GetResourceLabelResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListResourceLabelsRequest"&gt;ListResourceLabelsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListResourceLabelsResponse"&gt;ListResourceLabelsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ResourceLabelService"&gt;ResourceLabelService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_services_resource_tag-proto"&gt;schemas/magpie/v1/services/resource_tag.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateResourceTagRequest"&gt;CreateResourceTagRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateResourceTagResponse"&gt;CreateResourceTagResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetResourceTagRequest"&gt;GetResourceTagRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetResourceTagResponse"&gt;GetResourceTagResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListResourceTagsRequest"&gt;ListResourceTagsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListResourceTagsResponse"&gt;ListResourceTagsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ResourceTagService"&gt;ResourceTagService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_tag-proto"&gt;schemas/magpie/v1/tag.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-magpie-v1-Tag"&gt;Tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_magpie_v1_services_tag-proto"&gt;schemas/magpie/v1/services/tag.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateTagRequest"&gt;CreateTagRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-CreateTagResponse"&gt;CreateTagResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetTagRequest"&gt;GetTagRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-GetTagResponse"&gt;GetTagResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListTagsRequest"&gt;ListTagsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-ListTagsResponse"&gt;ListTagsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-magpie-v1-services-v1-TagService"&gt;TagService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#scalar-value-types"&gt;Scalar Value Types&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="schemas_magpie_v1_label-proto"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="right"&gt;&lt;a href="#top"&gt;Top&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="schemasmagpiev1labelproto"&gt;schemas/magpie/v1/label.proto&lt;/h2&gt;
&lt;p&gt;&lt;a name="schemas-magpie-v1-Label"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="label"&gt;Label&lt;/h3&gt;
&lt;p&gt;A single system-generated label on a resource.
Labels are machine-authored, key=value structured, confidence-scored, and
attached directly to a resource. They are immutable after creation.&lt;/p&gt;</description></item><item><title>API Reference</title><link>https://joelholmes.dev/projects/owl/api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/owl/api/</guid><description>&lt;h1 id="protocol-documentation"&gt;Protocol Documentation&lt;/h1&gt;
&lt;p&gt;&lt;a name="top"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_owl_v1_book-proto"&gt;schemas/owl/v1/book.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-owl-v1-Book"&gt;Book&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_owl_v1_paper-proto"&gt;schemas/owl/v1/paper.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-owl-v1-Paper"&gt;Paper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_owl_v1_services_book-proto"&gt;schemas/owl/v1/services/book.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-CreateBookRequest"&gt;CreateBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-CreateBookResponse"&gt;CreateBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-DeleteBookRequest"&gt;DeleteBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-DeleteBookResponse"&gt;DeleteBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetBookRequest"&gt;GetBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetBookResponse"&gt;GetBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-ListBooksRequest"&gt;ListBooksRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-ListBooksResponse"&gt;ListBooksResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-UpdateBookRequest"&gt;UpdateBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-UpdateBookResponse"&gt;UpdateBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-BookService"&gt;BookService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_owl_v1_services_paper-proto"&gt;schemas/owl/v1/services/paper.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-CreatePaperRequest"&gt;CreatePaperRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-CreatePaperResponse"&gt;CreatePaperResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-DeletePaperRequest"&gt;DeletePaperRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-DeletePaperResponse"&gt;DeletePaperResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetIngestionStatusRequest"&gt;GetIngestionStatusRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetIngestionStatusResponse"&gt;GetIngestionStatusResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetPaperRequest"&gt;GetPaperRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetPaperResponse"&gt;GetPaperResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-IngestPaperByURLRequest"&gt;IngestPaperByURLRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-IngestPaperByURLResponse"&gt;IngestPaperByURLResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-ListPapersRequest"&gt;ListPapersRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-ListPapersResponse"&gt;ListPapersResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-UpdatePaperRequest"&gt;UpdatePaperRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-UpdatePaperResponse"&gt;UpdatePaperResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-GetIngestionStatusResponse-Status"&gt;GetIngestionStatusResponse.Status&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-owl-services-v1-PaperService"&gt;PaperService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#scalar-value-types"&gt;Scalar Value Types&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="schemas_owl_v1_book-proto"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="right"&gt;&lt;a href="#top"&gt;Top&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="schemasowlv1bookproto"&gt;schemas/owl/v1/book.proto&lt;/h2&gt;
&lt;p&gt;&lt;a name="schemas-owl-v1-Book"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="book"&gt;Book&lt;/h3&gt;
&lt;p&gt;Book resource representing an uploaded or URL-fetched book&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Field&lt;/th&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Label&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;uuid&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Unique identifier&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;title&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Book title&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;authors&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;repeated&lt;/td&gt;
					&lt;td&gt;Authors&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;publisher&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Publisher&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;publication_year&lt;/td&gt;
					&lt;td&gt;&lt;a href="#int32"&gt;int32&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Publication year&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;isbn&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;ISBN if available&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;format&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;File format (pdf, epub, mobi, azw3, etc.)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;mime_type&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;MIME type&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;size_bytes&lt;/td&gt;
					&lt;td&gt;&lt;a href="#int64"&gt;int64&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;File size in bytes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;checksum&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Content hash for deduplication (SHA-256)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;storage_path&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Storage location (bucket path)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;original_filename&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Original filename&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;tags&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;repeated&lt;/td&gt;
					&lt;td&gt;User-provided tags&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;notes&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Notes or description&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;uploaded_at&lt;/td&gt;
					&lt;td&gt;&lt;a href="#google-protobuf-Timestamp"&gt;google.protobuf.Timestamp&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;When uploaded&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;page_count&lt;/td&gt;
					&lt;td&gt;&lt;a href="#int32"&gt;int32&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Number of pages if available&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;language&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Language (ISO 639-1 code)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;source_url&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;Source URL if fetched from the web&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a name="schemas_owl_v1_paper-proto"&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>API Reference</title><link>https://joelholmes.dev/projects/shrike/api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/shrike/api/</guid><description>&lt;h1 id="protocol-documentation"&gt;Protocol Documentation&lt;/h1&gt;
&lt;p&gt;&lt;a name="top"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_shrike_v1_envelope-proto"&gt;schemas/shrike/v1/envelope.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-shrike-v1-EntityEnvelope"&gt;EntityEnvelope&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_shrike_v1_import_request-proto"&gt;schemas/shrike/v1/import_request.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-shrike-v1-ImportRequest"&gt;ImportRequest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#schemas-shrike-v1-ImportRequest-MetadataEntry"&gt;ImportRequest.MetadataEntry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_shrike_v1_index_record-proto"&gt;schemas/shrike/v1/index_record.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-shrike-v1-IndexRecord"&gt;IndexRecord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_shrike_v1_services_index_record-proto"&gt;schemas/shrike/v1/services/index_record.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-CreateIndexRecordRequest"&gt;CreateIndexRecordRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-CreateIndexRecordResponse"&gt;CreateIndexRecordResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-DeleteIndexRecordRequest"&gt;DeleteIndexRecordRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-DeleteIndexRecordResponse"&gt;DeleteIndexRecordResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-GetIndexRecordRequest"&gt;GetIndexRecordRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-GetIndexRecordResponse"&gt;GetIndexRecordResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-ListIndexRecordsRequest"&gt;ListIndexRecordsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-ListIndexRecordsResponse"&gt;ListIndexRecordsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-UpdateIndexRecordRequest"&gt;UpdateIndexRecordRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-UpdateIndexRecordResponse"&gt;UpdateIndexRecordResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-IndexRecordService"&gt;IndexRecordService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_shrike_v1_services_search-proto"&gt;schemas/shrike/v1/services/search.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-Facet"&gt;Facet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-FacetValue"&gt;FacetValue&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-GetEntityRequest"&gt;GetEntityRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-GetEntityResponse"&gt;GetEntityResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-GetIndexStatsRequest"&gt;GetIndexStatsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-GetIndexStatsResponse"&gt;GetIndexStatsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-Highlight"&gt;Highlight&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-ReindexRequest"&gt;ReindexRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-ReindexResponse"&gt;ReindexResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SearchFilter"&gt;SearchFilter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SearchOptions"&gt;SearchOptions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SearchRequest"&gt;SearchRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SearchResponse"&gt;SearchResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SearchResult"&gt;SearchResult&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SuggestRequest"&gt;SuggestRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SuggestResponse"&gt;SuggestResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-shrike-services-v1-SearchService"&gt;SearchService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_shrike_v1_text_extracted-proto"&gt;schemas/shrike/v1/text_extracted.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-shrike-v1-TextExtractedEvent"&gt;TextExtractedEvent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#scalar-value-types"&gt;Scalar Value Types&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="schemas_shrike_v1_envelope-proto"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="right"&gt;&lt;a href="#top"&gt;Top&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="schemasshrikev1envelopeproto"&gt;schemas/shrike/v1/envelope.proto&lt;/h2&gt;
&lt;p&gt;&lt;a name="schemas-shrike-v1-EntityEnvelope"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="entityenvelope"&gt;EntityEnvelope&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Field&lt;/th&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Label&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;entity_uuid&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;app&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;entity_type&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;topic&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;event_type&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&amp;quot;created&amp;quot;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;occurred_at&lt;/td&gt;
					&lt;td&gt;&lt;a href="#google-protobuf-Timestamp"&gt;google.protobuf.Timestamp&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;payload&lt;/td&gt;
					&lt;td&gt;&lt;a href="#bytes"&gt;bytes&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a name="schemas_shrike_v1_import_request-proto"&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>API Reference</title><link>https://joelholmes.dev/projects/weevil/api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/projects/weevil/api/</guid><description>&lt;h1 id="protocol-documentation"&gt;Protocol Documentation&lt;/h1&gt;
&lt;p&gt;&lt;a name="top"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_weevil_v1_book-proto"&gt;schemas/weevil/v1/book.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-v1-Book"&gt;Book&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-v1-Source"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_weevil_v1_search-proto"&gt;schemas/weevil/v1/search.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#schemas-weevil-v1-Search"&gt;Search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_weevil_v1_services_book-proto"&gt;schemas/weevil/v1/services/book.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-BookMetadata"&gt;BookMetadata&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-CreateBookRequest"&gt;CreateBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-CreateBookResponse"&gt;CreateBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-GetBookRequest"&gt;GetBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-GetBookResponse"&gt;GetBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-ListBooksRequest"&gt;ListBooksRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-ListBooksResponse"&gt;ListBooksResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-ReEnrichBookRequest"&gt;ReEnrichBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-ReEnrichBookResponse"&gt;ReEnrichBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-UpdateBookRequest"&gt;UpdateBookRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-UpdateBookResponse"&gt;UpdateBookResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-BookService"&gt;BookService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas_weevil_v1_services_search-proto"&gt;schemas/weevil/v1/services/search.proto&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-CreateSearchRequest"&gt;CreateSearchRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-CreateSearchResponse"&gt;CreateSearchResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-GetSearchRequest"&gt;GetSearchRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-GetSearchResponse"&gt;GetSearchResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-ListSearchsRequest"&gt;ListSearchsRequest&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-ListSearchsResponse"&gt;ListSearchsResponse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#schemas-weevil-services-v1-SearchService"&gt;SearchService&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#scalar-value-types"&gt;Scalar Value Types&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a name="schemas_weevil_v1_book-proto"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align="right"&gt;&lt;a href="#top"&gt;Top&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="schemasweevilv1bookproto"&gt;schemas/weevil/v1/book.proto&lt;/h2&gt;
&lt;p&gt;&lt;a name="schemas-weevil-v1-Book"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="book"&gt;Book&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Field&lt;/th&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;Label&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;uuid&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;created_at&lt;/td&gt;
					&lt;td&gt;&lt;a href="#google-protobuf-Timestamp"&gt;google.protobuf.Timestamp&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;title&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;author&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;repeated&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;isbn_13&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;isbn_9&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;identifier&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;optional&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;source&lt;/td&gt;
					&lt;td&gt;&lt;a href="#schemas-weevil-v1-Source"&gt;Source&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;optional&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;read&lt;/td&gt;
					&lt;td&gt;&lt;a href="#google-protobuf-Timestamp"&gt;google.protobuf.Timestamp&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;repeated&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;description&lt;/td&gt;
					&lt;td&gt;&lt;a href="#string"&gt;string&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;optional&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a name="schemas-weevil-v1-Source"&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Thoro.ai</title><link>https://joelholmes.dev/cv/thoro-ai/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/thoro-ai/</guid><description>Conceptualized and built a central fleet management and reporting system for a global robotic fleet from the ground up; led OEM technical teams internationally and mentored newly hired engineers.</description></item><item><title>Carnegie Robotics</title><link>https://joelholmes.dev/cv/carnegie-robotics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/carnegie-robotics/</guid><description>Pioneered the cloud reporting and fleet management platform that was spun out to form Thoro.ai; independently built a decentralized distributed data system with Raft-based replication for U.S. Government robotics projects.</description></item><item><title>Edge Case Research</title><link>https://joelholmes.dev/cv/edge-case-research/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/edge-case-research/</guid><description>Developed production-grade Go services on Kubernetes for rapidly growing self-driving monitoring systems; led Kubernetes-integrated tooling to orchestrate perception model workloads.</description></item><item><title>Thycotic</title><link>https://joelholmes.dev/cv/thycotic/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/thycotic/</guid><description>Built a greenfield serverless Go application on AWS Lambda with CloudFormation, designed to transparently shift to EC2 once traffic thresholds were reached.</description></item><item><title>Wombat Security</title><link>https://joelholmes.dev/cv/wombat-security/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/wombat-security/</guid><description>Migrated monolithic Spring MVC platform to Spring Boot microservices; pioneered Docker containerization for production use across new products.</description></item><item><title>PNC Bank</title><link>https://joelholmes.dev/cv/pnc-bank/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/cv/pnc-bank/</guid><description>Early-career role building enterprise backend systems, establishing foundational skills in financial-grade engineering and large-scale Java development.</description></item><item><title>Being a Bottleneck</title><link>https://joelholmes.dev/blog/2026-07-14-being-a-bottleneck/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2026-07-14-being-a-bottleneck/</guid><description>&lt;p&gt;In my &lt;a href="https://joelholmes.dev/blog/2026-04-29-right-amount-of-automation/"&gt;last post&lt;/a&gt; I talked about automation and how I&amp;rsquo;ve been reconciling with how work gets done as a developer. The takeaway was that automation is helpful as long as the work is visible and chunked in a way that makes it easily digestible. With the way AI agents work, it becomes eerily similar to the brooms carrying buckets of water: task-oriented, efficient, and eager. Suddenly you&amp;rsquo;re faced with a huge amount of work in progress (WIP) slowly building up for you to verify. Again referencing &lt;em&gt;The Goal&lt;/em&gt;, we find that mountains of work build up in front of bottlenecks. Yes, unfortunately, you are the bottleneck when it comes to AI and that is okay. In fact, it&amp;rsquo;s really important. Why?&lt;/p&gt;</description></item><item><title>The Right Amount of Automation</title><link>https://joelholmes.dev/blog/2026-04-29-right-amount-of-automation/</link><pubDate>Wed, 29 Apr 2026 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2026-04-29-right-amount-of-automation/</guid><description>&lt;p&gt;Like many people, I&amp;rsquo;ve found the move to using AI in my work a little earth-shifting. Listening to a &lt;a href="https://www.nytimes.com/2026/04/14/podcasts/the-daily/ai-coders.html"&gt;recent episode&lt;/a&gt; of &lt;em&gt;The Daily&lt;/em&gt;, I realized I wasn&amp;rsquo;t alone in my journey as a developer through this new world. At first I resisted because AI felt like &amp;ldquo;too much magic,&amp;rdquo; and after using AI for a while now I know that my hunch was true. The &amp;ldquo;magic&amp;rdquo; seems to be the same as the magic used in &amp;ldquo;The Sorcerer&amp;rsquo;s Apprentice&amp;rdquo; (or the German &amp;ldquo;Der Zauberlehrling&amp;rdquo;) wherein a mindless object can do your work to possibly deterious effects.&lt;/p&gt;</description></item><item><title>Continuous Delivery Made Easy</title><link>https://joelholmes.dev/blog/2021-10-28-continuous-delivery-made-easy/</link><pubDate>Thu, 28 Oct 2021 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2021-10-28-continuous-delivery-made-easy/</guid><description>&lt;p&gt;After graduating college I wound up with a job at a large bank. There I had a great history lesson on how software used to be developed. Huge requirements documents. Six month development windows. Saturdays spent trying to meet arbitrary requirements set forth my a committee. It was painful.&lt;/p&gt;
&lt;p&gt;After the first week I sat down with a senior developer who was supposed to show me how to access the mainframe (this was 2011). He looked at me and said, &amp;ldquo;What are you even doing here? You should get out while you can.&amp;rdquo; I laughed and didn&amp;rsquo;t think anything of it for three years. For those three years I suffered through some pretty rough times including some major health issues but just thought of what I did as just a four letter word: W-O-R-K. It wasn&amp;rsquo;t fun but it was money.&lt;/p&gt;</description></item><item><title>Suggested Books</title><link>https://joelholmes.dev/blog/2021-04-12-suggested-books/</link><pubDate>Mon, 12 Apr 2021 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2021-04-12-suggested-books/</guid><description>&lt;p&gt;As I work with more and more junior developers I often find myself suggesting different books that have helped mold me into the developer I am today. For better or worse these books have changed my perspective on the industry and keep me coming back for more. I love reading technology books and so it can often feel overwhelming when I try to compile a list. Nor do I like the &amp;ldquo;Best 10 Technology Books Everyone Should Read&amp;rdquo; posts because I find them cliche and not that helpful because it lumps books together in one pile rather than by category or interest. I&amp;rsquo;m hoping this post is a living one in which I will be adding and updating content as time goes on since I&amp;rsquo;m revisiting and reading new books each day.&lt;/p&gt;</description></item><item><title>Work and Team Breakdown</title><link>https://joelholmes.dev/blog/2020-12-10-work-and-team-breakdown/</link><pubDate>Thu, 10 Dec 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-12-10-work-and-team-breakdown/</guid><description>&lt;p&gt;The breakdown of work and the relation it has within development teams has interested me for a long time. There is no question as to where the thoughts began but they have led me down a larger path of discovery. The journey began by reading &lt;a href="https://itrevolution.com/book/the-phoenix-project/"&gt;The Phoenix Project&lt;/a&gt; by Gene Kim, in it he talks about the four types of work, how work should be scheduled and broken down into easily repeatable tasks much like a manufacturing plant would. This made sense to me, what is all work but a sense of easily broken down tasks and steps? The smaller the tasks the easier it is to complete and increase throughput.&lt;/p&gt;</description></item><item><title>See One, Do One, Teach One</title><link>https://joelholmes.dev/blog/2020-09-30-see-one--do-one--teach-one/</link><pubDate>Wed, 30 Sep 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-09-30-see-one--do-one--teach-one/</guid><description>&lt;p&gt;In the medical industry there is a common practice among surgical students learning a new procedure which is &amp;ldquo;see one, do one, teach one&amp;rdquo;. Seeing one allows the students to study from another experienced surgeon, ask questions, and take notes completely focused on learning from a teacher. Doing one enforces the application of the learned material allowing for physical practice with guidance from a teacher on the best practices. Finally in teaching one you learn to explain what you did to another and relearn and reinforce the steps of a procedure.&lt;/p&gt;</description></item><item><title>Working Together While Working Separately</title><link>https://joelholmes.dev/blog/2020-09-28-working-together-while-working-separately/</link><pubDate>Mon, 28 Sep 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-09-28-working-together-while-working-separately/</guid><description>&lt;p&gt;In my current job I&amp;rsquo;ve been helping lead a small team in the development of a new product. This is a coordinated effort across the company and been a challenge to some as we quickly switch between discovering our customer&amp;rsquo;s needs with developing them. We have a road map and a plan but translating this into actual work has been difficult. All of the reading I&amp;rsquo;ve done over the years has helped me develop plans and strategies in order to help organize a group of people around a given task in order to deliver. As with anything application ends up being more difficult than theory.&lt;/p&gt;</description></item><item><title>Joy of Creating</title><link>https://joelholmes.dev/blog/2020-09-21-joy-of-creating/</link><pubDate>Mon, 21 Sep 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-09-21-joy-of-creating/</guid><description>&lt;p&gt;I enjoy my work. I find it fulfilling and the problem solving to be engaging. Yet at the end of the day it&amp;rsquo;s work. The drudgery of completing tasks, the politics, the deadlines, can take away joy. Software development isn&amp;rsquo;t unique in this area, almost all jobs have this fine balance between fulfillment and drudgery which is why people have hobbies outside of work. Softball teams and woodworking, painting and poetry, all contribute to balancing our lives and bringing us joy through leisure.&lt;/p&gt;</description></item><item><title>Why I Program</title><link>https://joelholmes.dev/blog/2020-08-13-why-i-program/</link><pubDate>Thu, 13 Aug 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-08-13-why-i-program/</guid><description>&lt;blockquote&gt;
&lt;p&gt;We believe that programming can be, and should be, an intellectually rewarding activity; that a good programming language is a power conceptual tool&amp;hellip; - The Art of Prolog&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why do I program? I like the intellectual simulation of solving a problem or designing something. So much of the time as a developer you end up building the same thing over and over. API endpoint for this or a message processor on a queue for that. Maybe this is just the pattern I&amp;rsquo;ve fallen in over the years. There is room for improvement or building better services but that isn&amp;rsquo;t enough sometimes. Where I find joy is in solving the problems or learning something new.&lt;/p&gt;</description></item><item><title>Mighty Oak</title><link>https://joelholmes.dev/blog/2020-07-13-mighty-oak/</link><pubDate>Mon, 13 Jul 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-07-13-mighty-oak/</guid><description>&lt;p&gt;There is something strange about the way we view ourselves within in jobs. On television there is always a leader who emerges from a difficult scenario or a person who saves a company single handed by making the right deal. But this isn&amp;rsquo;t how it works, or at least it shouldn&amp;rsquo;t. Throughout my career I&amp;rsquo;ve learned that anything of significance shouldn&amp;rsquo;t be done on your own. In order to build something significant you need to work as a team. Now, you may be the first person on a project or a person who comes up with the idea, but without a community to support you failure will be inevitable.&lt;/p&gt;</description></item><item><title>Technology Maven</title><link>https://joelholmes.dev/blog/2020-07-06-technology-maven/</link><pubDate>Mon, 06 Jul 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-07-06-technology-maven/</guid><description>&lt;p&gt;In my career I have found myself collecting things. I like to collect programming books, links about new databases and patterns, talks about functional programming. This collection has become somewhat obsessive in some cases and led to the disorganization I&amp;rsquo;ve described in &lt;a href="https://joelholmes.dev/posts/2020-06-25_where-i-am.html"&gt;other posts&lt;/a&gt;. When I was starting out I was told that this was not good, that you could run around your entire career chasing &amp;ldquo;shiny things&amp;rdquo;. This is true if you are chasing the newest JavaScript library or a cutting edge language, but I felt that maybe this didn&amp;rsquo;t apply to me. I collected books about LISP and functional programming which weren&amp;rsquo;t new. I watched talks about Java patterns and concurrency models. Was I chasing shiny things? Was this a waste of time?&lt;/p&gt;</description></item><item><title>Getting Started</title><link>https://joelholmes.dev/blog/2020-07-05-getting-started/</link><pubDate>Sun, 05 Jul 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-07-05-getting-started/</guid><description>&lt;p&gt;Last week I &lt;a href="https://joelholmes.dev/posts/2020-06-25_where-i-am.html"&gt;wrote about&lt;/a&gt; issues I was having getting things done and worrying about stagnating in my career. I took some time over the past week to disconnect, relax, and reflect about both this problem with &amp;ldquo;closing&amp;rdquo; and my career. Throughout my life I&amp;rsquo;ve been constantly &lt;a href="https://joelholmes.dev/posts/2020-06-11_rushing.html"&gt;rushing&lt;/a&gt; forward and in my career worrying about what will come next. This rushing and worrying always leads me into a mild depression that becomes difficult to overcome. Eventually I realize where I am and find out where I need to go. Sometimes it&amp;rsquo;s a job change, other times it&amp;rsquo;s a mentality shift, and in one case it took me flying half way around the world for work to start fresh. What I&amp;rsquo;ve learned is to do the best where you are at and things will move forward. Following the course of the river instead of paddling against it. What is more important is maintaining a feeling of exploration and hunger. In &lt;em&gt;Becoming Steve Jobs&lt;/em&gt; the authors write:&lt;/p&gt;</description></item><item><title>Where I Am</title><link>https://joelholmes.dev/blog/2020-06-25-where-i-am/</link><pubDate>Thu, 25 Jun 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-06-25-where-i-am/</guid><description>&lt;p&gt;I&amp;rsquo;m all over the place. I can see that by my notebooks. Each month I sit down and write down a list of goals I want to accomplish. This ranges from books to read to projects to complete to posts to write. All in all I typically manage to complete about 60-70% of what I set out to do. But lately I&amp;rsquo;ve not even come close. This past month my goal was to clean things up and organize and I accomplished nothing.&lt;/p&gt;</description></item><item><title>Rushing</title><link>https://joelholmes.dev/blog/2020-06-11-rushing/</link><pubDate>Thu, 11 Jun 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-06-11-rushing/</guid><description>&lt;p&gt;I&amp;rsquo;ve always rushed things. In elementary school I rushed through activities to move onto the next thing. In middle school I rushed through classes to take High School classes early. I did the same thing in High School, taking college courses early on to keep moving forward. I couldn&amp;rsquo;t wait to get through college and &amp;ldquo;into the real world&amp;rdquo;. Only to find myself there thinking about changing jobs or going back to school. It would be easy to make the excuse that my brain just is always rushing fast, but I feel like that is an easy excuse. I also don&amp;rsquo;t think I&amp;rsquo;m alone in this and hope to find a way to solve this personally.&lt;/p&gt;</description></item><item><title>Simple Site</title><link>https://joelholmes.dev/blog/2020-05-26-simple-site/</link><pubDate>Tue, 26 May 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-05-26-simple-site/</guid><description>&lt;p&gt;Over the past couple of months I have worked on trying to increase my flow of writing. So far I have failed at that. I don&amp;rsquo;t really have an excuse for it but upon reflection I thought that I could maybe make things a little easier on myself by simplifying my website.&lt;/p&gt;
&lt;p&gt;I drew inspiration from a site I found called &lt;a href="https://tinyprojects.dev/"&gt;Tiny Projects&lt;/a&gt; and another post about &lt;a href="https://tomcritchlow.com/2019/02/17/building-digital-garden/"&gt;building a digital garden&lt;/a&gt;. The problem with my sites have always been they are cumbersome and mildly complicated. This stems from the origin of having a &amp;ldquo;digital resume&amp;rdquo; or portfolio to show to others for job searches. There I tried to flex my developer muscles by writing complicated HTML, to building with Gulp.js and Pug, then moving to a Javascript framework, to using Hugo. Each step had it&amp;rsquo;s complications and were not maintainable.&lt;/p&gt;</description></item><item><title>Project Of My Own</title><link>https://joelholmes.dev/blog/2020-04-15-project-of-my-own/</link><pubDate>Wed, 15 Apr 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-04-15-project-of-my-own/</guid><description>&lt;p&gt;Michael Pollan is one of my favorite authors. Each of his works follow a similar pattern. Define an overarching theme (gardening, cooking, eating), then define some components, go into the history and practice of each sub component. For example in his work &lt;em&gt;A Place of My Own&lt;/em&gt; the overarching theme is &lt;em&gt;architecture&lt;/em&gt; and some sub-components are &lt;em&gt;location&lt;/em&gt;, &lt;em&gt;design&lt;/em&gt;, &lt;em&gt;material&lt;/em&gt;, etc. In the section on location he talks about Feng-Shui and energy flow of the earth to find the right spot for his house going into the history of Feng-Shui and providing examples of buildings like Falling Water as ideal locations. In the end its a great example of personal project exploration.&lt;/p&gt;</description></item><item><title>Language Ergonomics</title><link>https://joelholmes.dev/blog/2020-03-28-language-ergonomics/</link><pubDate>Sat, 28 Mar 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-03-28-language-ergonomics/</guid><description>&lt;p&gt;There are a ton of programming languages. Each language then holds a specific set of &lt;a href="https://www.youtube.com/watch?v=2wZ1pCpJUIM"&gt;values&lt;/a&gt; that lend itself to solving certain problems. Too much time is spent arguing over which language is best and for what reason instead of looking at which languages match can help you solve a problem or match your values.&lt;/p&gt;
&lt;p&gt;This last point is why I have spent a lot time exploring new languages.&lt;/p&gt;
&lt;p&gt;Early in my career my manager had told me to focus on a depth and breadth strategy for developing in my career; go deep on one technology but explore others to see what is out there. And so I set off to become a better Java developer while still exploring languages and technologies that I came across. Soon I learned about containers, learned how they worked, how they were made and what languages worked best to create lightweight containers. This is where I started developing in Go. Over time I grew to love developing in Go and moved on from Java.&lt;/p&gt;</description></item><item><title>Engineer Mathematician</title><link>https://joelholmes.dev/blog/2020-03-27-engineer-mathematician/</link><pubDate>Fri, 27 Mar 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-03-27-engineer-mathematician/</guid><description>&lt;p&gt;When I was in college I intented to major in Mathematics. Computer science was supposed to be my minor and support my interests in math. In my first semestor I took a formal math class and was completely lost. Meeting with my professor I discovered that my interests were in his terms &amp;ldquo;messy math&amp;rdquo; and not &amp;ldquo;pure math&amp;rdquo;. Dissalusioned with this notion of being interested in &amp;ldquo;messy math&amp;rdquo; I decided to shift my focus to Computer Science which I felt would allow me to reason through problems in the &amp;ldquo;messy way&amp;rdquo; I liked.&lt;/p&gt;</description></item><item><title>On Writing</title><link>https://joelholmes.dev/blog/2020-01-20-on-writing/</link><pubDate>Mon, 20 Jan 2020 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2020-01-20-on-writing/</guid><description>&lt;p&gt;Throughout my career I&amp;rsquo;ve always had side projects to help me learn, to practice, to sharpen my skills. Sometimes they were relate to things I had read or ideas that had crossed my mind. Whatever the reason for working on the project it typically ended up being benefitial for my personal growth. Unknowingly I was establishing a practice of a kata, doing an activity in order to refine and hone your skills.&lt;/p&gt;</description></item><item><title>Brave New World</title><link>https://joelholmes.dev/blog/2019-02-16-brave-new-world/</link><pubDate>Sat, 16 Feb 2019 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2019-02-16-brave-new-world/</guid><description>&lt;p&gt;Having just finished Aldous Huxley&amp;rsquo;s Brave New World, I was struck at the predominance of theme of desensitization. In a culture today where people have become desensitized to the world around them, I worry about its impact on our society. I&amp;rsquo;m not saying we&amp;rsquo;ve entered into a dystopian world but these types of books are meant to be hyperbole in order to draw attention to larger concerns expressed by the other or conclusions drawn from the reader.&lt;/p&gt;</description></item><item><title>Be Good to Yourself</title><link>https://joelholmes.dev/blog/2019-02-02-be-good-to-yourself/</link><pubDate>Sat, 02 Feb 2019 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2019-02-02-be-good-to-yourself/</guid><description>&lt;p&gt;Anything that gives you joy or happiness that is entirely for yourself. I have always struggled with self care because it always felt selfish&amp;hellip; which is what it is supposed to be. Taking something for yourself can feel awkward at first while you look at all the things that need to be done or the expectations others may have for you. I have always struggled with the need to be the perfect father, husband, son, and employee. With this expectation of myself I left little room for self care and became frustrated, depressed and disengaged in all aspects of my life. All from a lack of self care.&lt;/p&gt;</description></item><item><title>Lambda Terraform</title><link>https://joelholmes.dev/blog/2019-01-21-lambda-terraform/</link><pubDate>Mon, 21 Jan 2019 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2019-01-21-lambda-terraform/</guid><description>&lt;p&gt;In a previous post I wrote about how to deploy an application in a container. The gist of the post talked about how containers provide a layer of abstraction so that your applications can run on any machine. This is great for local development and large scale applications. However, in order for them to run in a production environment they more than likely are required to run on some sort of runtime or server which can provide some additional cost overhead.&lt;/p&gt;</description></item><item><title>Growing As a Developer</title><link>https://joelholmes.dev/blog/2019-01-15-growing-as-a-developer/</link><pubDate>Tue, 15 Jan 2019 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2019-01-15-growing-as-a-developer/</guid><description>&lt;p&gt;Becoming a better developer does not always mean being an expert in a programming language. Nor does it mean being the smartest person in the room. More often than not you will find that you aren&amp;rsquo;t an expert and you aren&amp;rsquo;t the smartest person. Work is more than just programming, it&amp;rsquo;s understanding and developing products that people will use and to do that it requires more than just a rock star programmer, it requires a person who is has the abilities to help their company succeed.&lt;/p&gt;</description></item><item><title>New Year</title><link>https://joelholmes.dev/blog/2019-01-04-new-year/</link><pubDate>Fri, 04 Jan 2019 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2019-01-04-new-year/</guid><description>&lt;p&gt;I doubt anyone subscribes to my blog or reads it regularly but do those who tune in may have noticed that I haven’t been updating as much recently. For a while I was on a good pattern of posting once a week or at least every other week. These posts where planned and structured and part of an on going process of learning and self discovery. But that changed. Where I was once learning and writing about technology and felt inspired each step of the way, I soon found that both the projects and writing became strained and obligatory instead of inspired.&lt;/p&gt;</description></item><item><title>Cloud Team Thoughts</title><link>https://joelholmes.dev/blog/2018-11-09-cloud-team-thoughts/</link><pubDate>Fri, 09 Nov 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-11-09-cloud-team-thoughts/</guid><description>&lt;p&gt;When I started developing Microservices I felt like I had been missing out on a huge part of the modern technology world. It turned out that I was right. Over the past few years I’ve been trying in my mind to figure out how modern development teams run effectively. What I discovered was that there are tons of people out there trying to figure out the same thing. Many of the things I’ll write about are patterns that have been around for more than a decade, yet still many companies don’t seem to adopt them. In the end building a team in the “cloud era” has changed the landscape and we can find that each change we see in infrastructure, development practices, project management, and testing perpetuates further advancement and change.&lt;/p&gt;</description></item><item><title>Go Docker</title><link>https://joelholmes.dev/blog/2018-10-26-go-docker/</link><pubDate>Fri, 26 Oct 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-10-26-go-docker/</guid><description>&lt;p&gt;So you’ve built an app that runs on your machine. Great. Now what?&lt;/p&gt;
&lt;p&gt;Obviously software is built to be used and today most of that software is being run on servers in the cloud. But sometimes it’s not. Sometimes it might be running on some old hardware in the basement of a company or maybe another developer needs to use your code as part of integration.&lt;/p&gt;
&lt;p&gt;Whatever the case may be it would be nice to package your application in such a way that it’s guaranteed to work no matter what environment it is in.&lt;/p&gt;</description></item><item><title>Agile Career</title><link>https://joelholmes.dev/blog/2018-10-10-agile-career/</link><pubDate>Wed, 10 Oct 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-10-10-agile-career/</guid><description>&lt;p&gt;There is a very important product that you have complete control over: you. Your skill set, your personality, your overall career is completely within your control. Those are all features of your product and it’s on those pieces that you need to iterate and improve.&lt;/p&gt;
&lt;p&gt;Good products are successful often because they have good product managers. It is up to you to decide how you fit into the world and what people are demanding of you. Product managers help take “market interest” and turn it into deliverable features that enhance the product. Sometimes these features aren’t in demand but will aid in the overall quality of the product.&lt;/p&gt;</description></item><item><title>Go Pipeline</title><link>https://joelholmes.dev/blog/2018-10-03-go-pipeline/</link><pubDate>Wed, 03 Oct 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-10-03-go-pipeline/</guid><description>&lt;p&gt;One of the most powerful things about Go is the ability to run parts of your application concurrently. This allows the application to run in parallel at times (depending on the number of cores) or continue to run while waiting for a blocked process. There are a number of concurrency patterns that allow you to structure your applications to get a performance boost.&lt;/p&gt;
&lt;p&gt;Recently I stumbled across the &lt;a href="https://blog.golang.org/pipelines"&gt;“Pipeline”&lt;/a&gt; pattern and was able to use it to transform incoming data and aggregate the results. Imagine a water treatment plant. Dirty water comes in, clean water comes out. The water goes through several phases before it is clean. First you may filter out large chunks of sediment, then smaller chunks, until you are screening microscopic pieces of debris. Then you want to put it through a series of chemical cleanings and finally test the water quality. Water flows through these various phases. At no point is water stopped until the first step is fully complete. Instead the water is processed as it flow ultimately becoming clean.&lt;/p&gt;</description></item><item><title>Zero Dollar Idea</title><link>https://joelholmes.dev/blog/2018-09-28-zero-dollar-idea/</link><pubDate>Fri, 28 Sep 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-09-28-zero-dollar-idea/</guid><description>&lt;p&gt;People enter into technology thinking they can pursue an idea that will make them a lot of money. If you read startup stories you will find that most start with an idea, evolve into a business, become broke, rebound and make millions…. or end up being broke and learning a lot.&lt;/p&gt;
&lt;p&gt;I’ve read these stories but I am not a risk taker. Maybe I just don’t believe in my ideas as much as others or lack the confidence to pursue them. However, I like to see things come to life. Will it work? I don’t know. I know how to build it just not how to sell it.&lt;/p&gt;</description></item><item><title>Go Mock Testing</title><link>https://joelholmes.dev/blog/2018-09-27-go-mock-testing/</link><pubDate>Thu, 27 Sep 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-09-27-go-mock-testing/</guid><description>&lt;p&gt;You should always write tests. To fully understand how to write production level applications in any language you should be able to write unit tests. Some people go to the extreme of doing Test Driven Development (TDD). TDD advocates that you should write tests before you write the code. This can be a good approach when trying to write code that can be easily tested. Yet I find that TDD is a lot like agile; a lot of people say they do it but in reality they do some sort of hybrid version. In the end it boils down to eating your veggies before your steak. Some people love veggies, just like people love writing unit tests, but most people find satisfaction in running their code and seeing it work.&lt;/p&gt;</description></item><item><title>Go Hex Arch</title><link>https://joelholmes.dev/blog/2018-09-19-go-hex-arch/</link><pubDate>Wed, 19 Sep 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-09-19-go-hex-arch/</guid><description>&lt;p&gt;In an earlier post I wrote about how to structure Go programs to be leveraged by both a FaaS and a standalone application. This simple application showed the power of what a Hex architecture can do by limiting the need to rewrite logic when an external resource has changed. In that example we looked at changing communication between an HTTP call from an AWS Lambda to a direct application call.&lt;/p&gt;</description></item><item><title>Brain Expansion</title><link>https://joelholmes.dev/blog/2018-09-10-brain-expansion/</link><pubDate>Mon, 10 Sep 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-09-10-brain-expansion/</guid><description>&lt;p&gt;I have a terrible memory. It’s not because I don’t pay attention; there is just too much on my mind. The barrage of incoming texts, work emails, news alerts, and ideas creates chaos. Disconnecting is not an option all of the time, especially for those in the technology field, so what can you do?&lt;/p&gt;
&lt;p&gt;It used to be when a computer was running slow or crashing the best solution was to add more RAM, expand its memory. You can’t just open someone’s head and add more memory. Nor do I believe that “brain exercises” can help. Instead, what I have found is the only way to handle the title wave of distraction is to create a map, establish boundaries, and think out loud.&lt;/p&gt;</description></item><item><title>Serverless to Server</title><link>https://joelholmes.dev/blog/2018-08-16-serverless-to-server/</link><pubDate>Thu, 16 Aug 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-08-16-serverless-to-server/</guid><description>&lt;p&gt;Functions as a service (FaaS) have really become a staple in most development circles. They provide little infrastructure overhead in the development of microservices and are often very lightweight. Yet FaaS face their own sets of challenges when it comes to costs, response times, and observability.&lt;/p&gt;
&lt;p&gt;I believe that architectures will evolve over time. In some cases you may never want to change your FaaS but there may be cases that you&amp;rsquo;ll want to move it to it&amp;rsquo;s own application. The problem is you may want to go back or you may not want to rewrite all of the business logic injected into that FaaS. So what do you do?&lt;/p&gt;</description></item><item><title>Catching Up</title><link>https://joelholmes.dev/blog/2018-08-15-catching-up/</link><pubDate>Wed, 15 Aug 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-08-15-catching-up/</guid><description>&lt;p&gt;I felt that I had a pretty good grasp of what the technology world was like when I graduated from college. After all I just built a series of programs for my classes (of varying quality), none of which had real world applications. I had built systems using live APIs like Twitter and used cool frameworks like Google Web Toolkit. So in the end I felt pretty confident in my abilities even though I had no exposure to the industry.&lt;/p&gt;</description></item><item><title>Understanding Serverless</title><link>https://joelholmes.dev/blog/2018-08-15-understanding-serverless/</link><pubDate>Wed, 15 Aug 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-08-15-understanding-serverless/</guid><description>&lt;p&gt;To be honest, I’ve had a rough time understanding how FaaS fit into my vision of a microservice architecture. It made sense that they were microservices themselves, especially when they are event driven: put something in a bucket, then do something, maybe do something else and so on. But what I didn’t understand was why build a serverless function if you could build an API.&lt;/p&gt;
&lt;p&gt;In the end I realize that it was how I had learned to develop microservices that was the crux of my issue. I have always been a very detail oriented person. I like to write the code, package it, provision my server, deploy my code to the server, add the routing, etc. All of this obviously takes time, overhead, and an understanding of the underlying systems.&lt;/p&gt;</description></item><item><title>Accelerating Development</title><link>https://joelholmes.dev/blog/2018-07-15-accelerating-development/</link><pubDate>Sun, 15 Jul 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-07-15-accelerating-development/</guid><description>&lt;p&gt;The main goal of every startup is to make money. Every part of the organization should be driven to create money for the company. This means creating products that users want to buy while decreasing the cost to produce the product all in a timely manner.&lt;/p&gt;
&lt;p&gt;In a technology startup the product to deliver in many cases is code. The beginning phases of this process involve a lot of late nights, rapid development, and manual processes. As the company grows and the product becomes more complex these practices becomes untenable and error prone. Consequently the speed of development slows and the ability to respond to the market diminishes.&lt;/p&gt;</description></item><item><title>Consumer Driven Contracts</title><link>https://joelholmes.dev/blog/2018-06-15-consumer-driven-contracts/</link><pubDate>Fri, 15 Jun 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-06-15-consumer-driven-contracts/</guid><description>&lt;p&gt;In the past I have written about what a &lt;a href="../what-are-microservices/"&gt;microservice architecture looks like&lt;/a&gt; and some of the tradeoffs that come with it. One of the biggest gains this architecture brings you is the ability for many people to be working on different projects at the same time. Inevitably the services you are writing will be used or you may need to consume someone else’s API to get your work done.&lt;/p&gt;
&lt;p&gt;This is easy right?&lt;/p&gt;</description></item><item><title>Clean Repositories</title><link>https://joelholmes.dev/blog/2018-05-15-clean-repositories/</link><pubDate>Tue, 15 May 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-05-15-clean-repositories/</guid><description>&lt;p&gt;When reading &lt;a href="http://a.co/h1ciBQD"&gt;Clean Architecture&lt;/a&gt; by Robert Martin, I came across a chapter of the same name.
In it the author describes this “idealized” architecture structure where business and application logic are at the
center of the diagram with outward rings surrounding it describing interactions with various frameworks and IO devices.&lt;/p&gt;
&lt;p&gt;This architecture can also be known as &lt;a href="http://alistair.cockburn.us/Hexagonal+architecture"&gt;Hex Architecture&lt;/a&gt; or Ports and Adapters Architecture, all having a similar design
where core business logic is independent of the IO interfaces surrounding it allowing for architectures that are independent
of frameworks, ui, and databases.&lt;/p&gt;</description></item><item><title>Narrative Code</title><link>https://joelholmes.dev/blog/2018-04-18-narrative-code/</link><pubDate>Wed, 18 Apr 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-04-18-narrative-code/</guid><description>&lt;h2 id="intro"&gt;Intro&lt;/h2&gt;
&lt;p&gt;In starting this series, I was confronted with the dilemma of which area of programming should I start with?
Testing? Data Modeling? Language Types? All of which are very important topics and areas that any good developer
should know. However, I felt strongly that the best area to start was not with code at all but writing.&lt;/p&gt;
&lt;p&gt;As a developer we tend to write our code for one person, ourselves. Yet most of the time our code can live
longer than the tenure at a job. Or even worse, you are forced to revisit code you wrote years ago and have
no clue what it does.&lt;/p&gt;</description></item><item><title>Programming Is a Craft</title><link>https://joelholmes.dev/blog/2018-03-11-programming-is-a-craft/</link><pubDate>Sun, 11 Mar 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-03-11-programming-is-a-craft/</guid><description>&lt;p&gt;&lt;em&gt;This is a response to the following article &lt;a href="https://techcrunch.com/2018/02/24/programming-as-craft/?ncid=rss"&gt;Programming as craft&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Programming is a craft. I will never claim to be a master of the craft but to say that programming cannot be
considered a craft overlooks many elements of the profession. Sure, groups of developers aren&amp;rsquo;t building artisan
furniture or designing magnificent cathedrals but none of these crafts were perfected overnight and relatively
speaking programming is in its infancy.&lt;/p&gt;
&lt;p&gt;The notion that programming is &lt;em&gt;not&lt;/em&gt; a craft stuck me in an odd sense and I have been wrestling internally with
its contents for a few weeks now. This response is not a critique of the authors points but rather a different
perspective reflective of a personal journey that I&amp;rsquo;ve had in my career as a developer. I hope to address a
few of his criticisms and point out some possible overlooked areas of the profession.&lt;/p&gt;</description></item><item><title>Dumb Phone Eulogy</title><link>https://joelholmes.dev/blog/2018-03-01-dumb-phone-eulogy/</link><pubDate>Thu, 01 Mar 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-03-01-dumb-phone-eulogy/</guid><description>&lt;p&gt;I am one of the few people who&amp;rsquo;ve probably traded in their smart phone for a dumb phone. By dumb phone I mean a phone that pretty much
just makes calls and texts. For over eight years now I&amp;rsquo;ve scorned the smart phone market because of the increasing negative effects I feel it
has on people. The concept of being constantly connected always presents itself as a great thing but often leads to isolated interactions
with people because they are busy checking their Facebook or Twitter feeds or playing some new game.&lt;/p&gt;</description></item><item><title>Sagas</title><link>https://joelholmes.dev/blog/2018-02-26-sagas/</link><pubDate>Mon, 26 Feb 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-02-26-sagas/</guid><description>&lt;p&gt;My last couple posts have been about developing and designing cloud based applications. In the &lt;a href="../what-are-microservices/"&gt;What Are Microservices&lt;/a&gt; blog post I wrote about the importance of having small shippable applications that can easily scale depending on demand and are decoupled from other services to allow for faster development and independent deployments. In &lt;a href="../a-tangled-mess/"&gt;A Tangled Mess&lt;/a&gt; we explored how to decouple the systems even more by publishing events as they occur and having other systems react in appropriate ways.&lt;/p&gt;</description></item><item><title>A Tangled Mess</title><link>https://joelholmes.dev/blog/2018-02-25-a-tangled-mess/</link><pubDate>Sun, 25 Feb 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-02-25-a-tangled-mess/</guid><description>&lt;p&gt;The goal of microservices is to produce independent systems that have loose coupling. This allows for the ability to manage and release upgrades without impacting other applications. However, many first implementations of microservices make the developers believe that the only way to communicate with another API is through a client. Thinking that obviously this is part of the whole process, they design many APIs that rely on other APIs and the chain goes on and on, and in some cases back to the original API for more data. As you can imagine this becomes a tangled mess.&lt;/p&gt;</description></item><item><title>What Are Microservices</title><link>https://joelholmes.dev/blog/2018-02-24-what-are-microservices/</link><pubDate>Sat, 24 Feb 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-02-24-what-are-microservices/</guid><description>&lt;p&gt;At the beginning we all start building an application. Then some part of it takes off and before you know it you are throwing new pieces on the original application until it becomes unweildy. Thus is born the Monolith.
In time your system gets large, cumbersome, and hard to maintain. Then you hear about microservices (or maybe you started that way because it was in fashion when you started development) and start building them.
But in developing microservices we can often forget about what they are and why we are doing it. It’s not because it’s the cool thing to do nor is it the easiest thing to do (quite the opposite) but it’s a smart thing to do.&lt;/p&gt;</description></item><item><title>Another Tech Blog</title><link>https://joelholmes.dev/blog/2018-02-23-another-tech-blog/</link><pubDate>Fri, 23 Feb 2018 00:00:00 +0000</pubDate><guid>https://joelholmes.dev/blog/2018-02-23-another-tech-blog/</guid><description>&lt;p&gt;Who needs another technology blog? Aren&amp;rsquo;t there enough out there? It seems like it&amp;rsquo;s a right of passage for someone to create one but to who&amp;rsquo;s benefit?
This has been the third or fourth (maybe fifth) attempt for me to start some sort of blog each with their vision.&lt;/p&gt;
&lt;p&gt;Yet in the end they sit in a corner of the internet wasting away helping no one. All blogs feel
that they need to solve the world&amp;rsquo;s problems, get one million hits, and then the authors can quit their day jobs.&lt;/p&gt;</description></item></channel></rss>