Deploying Loki Log Aggregation System on Ubuntu 24.04

Loki is an open-source log aggregation system from Grafana Labs that indexes only metadata labels instead of full log content, making it cost-efficient at scale. It pairs natively with Grafana and Promtail for log shipping and querying. This guide deploys Loki using Docker Compose with Traefik handling automatic HTTPS, and verifies the backend through its API endpoints. By the end, you’ll have Loki ingesting and serving logs over HTTPS at your domain.

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/loki-logging/{loki-data,loki-config}
$ cd ~/loki-logging

2. Set ownership for the Loki data directory:

$ sudo chown -R 10001:10001 loki-data

3. Create the environment file:

$ nano .env
DOMAIN=loki.example.com
LETSENCRYPT_EMAIL=admin@example.com

4. Create the Loki configuration file:

$ nano loki-config/loki-config.yaml
auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

Deploy with Docker Compose

1. Create the Docker Compose manifest:

$ nano docker-compose.yaml
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart: unless-stopped

  loki:
    image: grafana/loki:latest
    container_name: loki
    hostname: loki
    expose:
      - "3100"
    volumes:
      - "./loki-config/loki-config.yaml:/etc/loki/loki-config.yaml"
      - "./loki-data:/loki"
    command: -config.file=/etc/loki/loki-config.yaml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.loki.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.loki.entrypoints=websecure"
      - "traefik.http.routers.loki.tls.certresolver=letsencrypt"
    restart: unless-stopped

volumes:
  letsencrypt:

2. Start the services:

$ docker compose up -d

3. Verify the services are running:

$ docker compose ps

4. View the logs:

$ docker compose logs

Access Loki

1. Check the readiness endpoint:

$ curl https://loki.example.com/ready

2. List the active labels:

$ curl https://loki.example.com/loki/api/v1/labels

3. Check the build information:

$ curl https://loki.example.com/loki/api/v1/status/buildinfo

Next Steps

Loki is running and serving logs over HTTPS. From here you can:

  • Add Loki as a data source in Grafana for log exploration with LogQL
  • Ship logs from your servers using Promtail, Grafana Alloy, or Fluent Bit
  • Move chunk storage to S3-compatible object storage for long-term retention

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Mimir Scalable Metrics Storage on Ubuntu 24.04

Mimir is an open-source, horizontally scalable long-term storage backend for Prometheus metrics from Grafana Labs. It accepts metrics via Prometheus remote write and serves them back through a Prometheus-compatible query API. This guide deploys Mimir using Docker Compose with Traefik handling automatic HTTPS, and verifies the backend through its status endpoints. By the end, you’ll have Mimir storing and serving Prometheus metrics over HTTPS at your domain.

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/mimir-metrics/{mimir-data,mimir-config}
$ cd ~/mimir-metrics

2. Create the environment file:

$ nano .env
DOMAIN=mimir.example.com
LETSENCRYPT_EMAIL=admin@example.com

3. Create the Mimir configuration file:

$ nano mimir-config/mimir-config.yaml
multitenancy_enabled: false

server:
  http_listen_port: 9009
  log_level: info

blocks_storage:
  backend: filesystem
  filesystem:
    dir: /data/blocks
  tsdb:
    dir: /data/tsdb
    retention_period: 24h

compactor:
  data_dir: /data/compactor
  sharding_ring:
    kvstore:
      store: memberlist

distributor:
  ring:
    kvstore:
      store: memberlist

ingester:
  ring:
    kvstore:
      store: memberlist
    replication_factor: 1

store_gateway:
  sharding_ring:
    kvstore:
      store: memberlist

ruler_storage:
  backend: filesystem
  filesystem:
    dir: /data/rules

limits:
  max_global_series_per_user: 0
  ingestion_rate: 0
  ingestion_burst_size: 0

Deploy with Docker Compose

1. Create the Docker Compose manifest:

$ nano docker-compose.yaml
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart: unless-stopped

  mimir:
    image: grafana/mimir:latest
    container_name: mimir
    hostname: mimir
    expose:
      - "9009"
    volumes:
      - "./mimir-config/mimir-config.yaml:/etc/mimir/mimir-config.yaml"
      - "./mimir-data:/data"
    command: -config.file=/etc/mimir/mimir-config.yaml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mimir.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.mimir.entrypoints=websecure"
      - "traefik.http.routers.mimir.tls.certresolver=letsencrypt"
      - "traefik.http.services.mimir.loadbalancer.server.port=9009"
    restart: unless-stopped

volumes:
  letsencrypt:

2. Start the services:

$ docker compose up -d

3. Verify the services are running:

$ docker compose ps

4. View the logs:

$ docker compose logs

Access Mimir

1. Check the readiness endpoint:

$ curl https://mimir.example.com/ready

A ready response confirms the backend is up.

2. Check the build information:

$ curl https://mimir.example.com/api/v1/status/buildinfo

3. Query the running configuration:

$ curl https://mimir.example.com/api/v1/status/config

4. Check the ingester ring:

$ curl https://mimir.example.com/ingester/ring

Next Steps

Mimir is running and ready to accept Prometheus metrics over HTTPS. From here you can:

  • Configure Prometheus remote_write to push metrics to your Mimir domain
  • Add Mimir as a Prometheus data source in Grafana for long-term queries
  • Move block storage to S3-compatible object storage for durable retention

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Infisical Secrets Management Platform on Ubuntu 24.04

Infisical is an open-source, end-to-end encrypted secrets management platform for storing and syncing application secrets across teams and environments. It uses PostgreSQL for persistence and Redis for caching. This guide deploys Infisical using Docker Compose with Traefik handling automatic HTTPS. By the end, you’ll have a production-ready Infisical instance accessible securely at your domain.

Set Up the Directory Structure and Secrets

1. Create the project directory structure:

$ mkdir -p ~/infisical/{db,redis,letsencrypt}
$ cd ~/infisical

2. Generate the encryption key and auth secret:

$ openssl rand -hex 16
$ openssl rand -base64 32

Note both values — the first is the ENCRYPTION_KEY, the second is the AUTH_SECRET.

3. Create the environment file:

$ nano .env
INFISICAL_DOMAIN=infisical.example.com
LETSENCRYPT_EMAIL=[email protected]
ENCRYPTION_KEY=HEX_KEY_HERE
AUTH_SECRET=BASE64_SECRET_HERE
POSTGRES_USER=infisical
POSTGRES_PASSWORD=STRONG_DB_PASSWORD
POSTGRES_DB=infisicaldb
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
REDIS_URL=redis://redis:6379

Deploy with Docker Compose

1. Add your user to the Docker group:

$ sudo usermod -aG docker $USER
$ newgrp docker

2. Create the Docker Compose manifest:

$ nano docker-compose.yml
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.44"
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  db:
    image: postgres:16-alpine
    container_name: infisical-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    volumes:
      - ./db:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    container_name: infisical-redis
    restart: unless-stopped

  infisical:
    image: infisical/infisical:latest
    container_name: infisical
    restart: unless-stopped
    depends_on:
      - db
      - redis
    environment:
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
      - AUTH_SECRET=${AUTH_SECRET}
      - DB_CONNECTION_URI=${DB_CONNECTION_URI}
      - REDIS_URL=${REDIS_URL}
      - SITE_URL=https://${INFISICAL_DOMAIN}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.infisical.rule=Host(`${INFISICAL_DOMAIN}`)"
      - "traefik.http.routers.infisical.entrypoints=websecure"
      - "traefik.http.routers.infisical.tls=true"
      - "traefik.http.routers.infisical.tls.certresolver=le"
      - "traefik.http.services.infisical.loadbalancer.server.port=8080"

3. Start the services:

$ docker compose up -d

4. Verify the services are running:

$ docker compose ps

Complete the Setup

  1. Open https://infisical.example.com in a browser.
  2. Create the Super Admin account with your email and a strong password.
  3. Sign in to access the dashboard.

Next Steps

Infisical is running and served securely over HTTPS. From here you can:

  • Create projects and organize secrets by environment
  • Integrate the Infisical CLI or SDKs into your applications and CI/CD
  • Configure secret rotation, access policies, and audit logs

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Jellyfin Open-Source Media Server on Ubuntu 24.04

Jellyfin is a free and open-source media server that organizes and streams your personal collection of movies, TV shows, music, and photos to any device, with no tracking or paywalls. This guide deploys Jellyfin using Docker Compose with Traefik handling automatic HTTPS. By the end, you’ll have a Jellyfin media server streaming securely at your domain.

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/jellyfin-media-server/{cache,config,media}
$ cd ~/jellyfin-media-server

2. Find your user UID and GID:

$ id USERNAME

3. Create the environment file:

$ nano .env
UID=YOUR_USER_UID
GID=YOUR_USER_GID

DOMAIN=jellyfin.example.com
LETSENCRYPT_EMAIL=[email protected]

Deploy with Docker Compose

1. Create the Docker Compose manifest:

$ nano docker-compose.yaml
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart: unless-stopped

  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: "${UID}:${GID}"
    hostname: jellyfin
    expose:
      - "8096"
    volumes:
      - "./config:/config"
      - "./cache:/cache"
      - "./media:/media"
    environment:
      - JELLYFIN_PublishedServerUrl=https://${DOMAIN}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.jellyfin.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.jellyfin.entrypoints=websecure"
      - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
    restart: unless-stopped

volumes:
  letsencrypt:

2. Start the services:

$ docker compose up -d

3. Verify the services are running:

$ docker compose ps

4. View the logs:

$ docker compose logs

Configure Jellyfin

  1. Open https://jellyfin.example.com in a browser.
  2. Choose your display language.
  3. Create the administrator account with a username and password.
  4. Add media libraries by selecting a content type and the matching folder under /media.
  5. Set metadata language and country preferences.
  6. Review the remote access settings.
  7. Complete setup and sign in with the administrator credentials.

Next Steps

Jellyfin is running and streaming securely over HTTPS. From here you can:

  • Install Jellyfin apps on your TV, phone, and desktop devices
  • Add more libraries and enable hardware transcoding for smoother playback
  • Create additional user accounts with per-library access control

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Forgejo Lightweight Git Server on Ubuntu 24.04

Forgejo is a community-managed, lightweight Git server forked from Gitea, offering repository hosting, code review, issue tracking, and CI/CD with a strong focus on open governance. This guide deploys Forgejo with a MariaDB backend using Docker Compose, with Traefik handling automatic HTTPS. By the end, you’ll have a self-hosted Forgejo instance served securely at your domain.

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/forgejo/{data,db,letsencrypt}
$ cd ~/forgejo

2. Create the environment file:

$ nano .env
FORGEJO_DB_USER=forgejo
FORGEJO_DB_PASS=STRONG_APP_PASSWORD
MYSQL_ROOT_PASS=STRONG_MYSQL_ROOT_PASSWORD
FORGEJO_DB_NAME=forgejodb
FORGEJO_DOMAIN=forgejo.example.com
LETSENCRYPT_EMAIL=admin@example.com

Deploy with Docker Compose

1. Add your user to the Docker group:

$ sudo usermod -aG docker $USER
$ newgrp docker

2. Create the Docker Compose manifest:

$ nano docker-compose.yml
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.44"
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  db:
    image: mariadb:latest
    container_name: forgejo-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASS}
      - MYSQL_DATABASE=${FORGEJO_DB_NAME}
      - MYSQL_USER=${FORGEJO_DB_USER}
      - MYSQL_PASSWORD=${FORGEJO_DB_PASS}
    volumes:
      - ./db:/var/lib/mysql

  forgejo:
    image: codeberg.org/forgejo/forgejo:latest
    container_name: forgejo
    restart: unless-stopped
    depends_on:
      - db
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__database__DB_TYPE=mysql
      - FORGEJO__database__HOST=db:3306
      - FORGEJO__database__NAME=${FORGEJO_DB_NAME}
      - FORGEJO__database__USER=${FORGEJO_DB_USER}
      - FORGEJO__database__PASSWD=${FORGEJO_DB_PASS}
      - FORGEJO__server__ROOT_URL=https://${FORGEJO_DOMAIN}
    volumes:
      - ./data:/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.forgejo.rule=Host(`${FORGEJO_DOMAIN}`)"
      - "traefik.http.routers.forgejo.entrypoints=websecure"
      - "traefik.http.routers.forgejo.tls=true"
      - "traefik.http.routers.forgejo.tls.certresolver=le"
      - "traefik.http.services.forgejo.loadbalancer.server.port=3000"

3. Start the services:

$ docker compose up -d

4. Verify the services are running:

$ docker compose ps

Complete the Installation

  1. Open https://forgejo.example.com in a browser to load the installation page.
  2. Leave the database settings at their defaults — they match the Compose environment.
  3. Configure the administrator account with your username, password, and email.
  4. Click Install Forgejo to finalize the setup.

Next Steps

Forgejo is running and served securely over HTTPS. From here you can:

  • Create organizations and repositories, and invite collaborators
  • Enable Forgejo Actions for built-in CI/CD pipelines
  • Configure SMTP for notifications and SSH for Git over SSH

For the full guide with additional tips, visit the original article on Vultr Docs.

Google AI Mode Hits 1 Billion Users: Information Agents and the Search Overhaul at I/O 2026

Google AI Mode crossed one billion monthly users at I/O 2026 — one year after launch, with query volume doubling every quarter. The milestone arrived alongside two structural changes: Information Agents that monitor the internet around the clock and proactively surface results, and a complete redesign of the Google Search box that now accepts text, images, files, video, and open browser tabs. This is not an incremental search update. It is the biggest architectural shift in how Google connects users to the web since PageRank.

This post covers what actually changed at Google I/O 2026 on the search side: the 1 billion user milestone and what it signals, the intelligent search box redesign, how Information Agents work and when they roll out, agentic booking for local services, and what developers and businesses relying on organic discovery need to do differently starting now.

AI Mode: One Year, One Billion Users

AI Mode launched as an experimental overlay on Google Search in May 2025. Twelve months later, it serves more than one billion monthly users, with query volume more than doubling every quarter. AI Overviews — the synthesized answer block appearing above traditional results — now reaches 2.5 billion monthly users globally.

The growth rate is the data point that matters most. One billion monthly AI Mode users means that a majority of queries arriving at Google are now answered, at least in part, by an AI-generated synthesis rather than a ranked list of links. The user still gets the link list below. But the primary answer comes first. That shift changes the fundamental calculus for content discovery: ranking in position one on a keyword no longer guarantees the user sees your content before getting their answer.

For scale context: ChatGPT crossed 900 million weekly active users in February 2026. Google reached one billion monthly AI Mode users from a product that users encounter because they were already using Google Search — not a dedicated destination they chose to visit. The distribution advantage of being the default is compounding faster than any standalone AI product can match.

The Intelligent Search Box: 25 Years Rebuilt

The Google Search box has been functionally unchanged since 1998: a text input, a submit button, a list of links. I/O 2026 replaced it with an “intelligent search box” powered by Gemini 3.5 Flash — a dynamically expanding input that accepts text, images, uploaded files, video clips, and open Chrome tabs as part of a single query.

The practical shift is significant. A developer encountering a bug can screenshot the stack trace and paste it directly. An architect reviewing a contract can upload the PDF and ask for the risk clauses. A product manager tracking a competitor can hand Google the open tab and ask what changed since last week. Search moves from “describe your problem in keywords” to “here is my actual context — what should I do.”

Gemini 3.5 Flash — also announced at I/O and covered in detail in the Gemini Spark developer guide — is now the default model powering AI Mode globally. Google reports 4x faster output token throughput versus prior frontier models in the same tier, with benchmark improvements on coding and agentic tasks over Gemini 3.1 Pro. Latency was the primary complaint from early AI Mode users. The Gemini 3.5 Flash default eliminates most of it.

The multimodal input also changes the content surface that developers need to optimize for. A query that previously arrived as a text string may now include an image, a document, or a video frame as context. Content that is answerable only for text queries — and not for the full range of inputs users will now bring — is optimized for a search interface that no longer exists.

Information Agents: Search That Works While You Are Not Looking

The most structurally significant announcement from the I/O search session was not the search box redesign. It was Information Agents.

Information Agents are autonomous AI systems that run continuously in the background, monitoring the web on topics you configure and proactively surfacing information when something relevant changes. You specify a topic — a stock ticker you are tracking, a research area you follow, a competitor’s pricing page, a product you are considering — and the agent reasons across blogs, news sites, social posts, and real-time data streams on finance, shopping, and sports on your behalf, without any additional prompt.

When the agent detects a change meeting your criteria — a price drop below a threshold, a new paper in your area, a news event affecting a portfolio position — it sends a notification with a synthesized summary. The model maintains context about your prior queries and distinguishes signal from noise: a finance-tracking agent does not alert you every time a stock ticks one cent. It alerts you when the stock crosses a specified threshold or when a news event arrives that is likely to move it.

Rollout begins this summer in the US, available first to Google AI Pro and Ultra subscribers. The feature requires the Gemini 3.5 Flash backend to maintain efficiency at the scale of thousands of concurrent background agents per user cohort. Enterprise and global rollout follow once the infrastructure scales.

For developers and content publishers, Information Agents introduce a new class of automated reader that behaves differently from a search crawler. A Googlebot visit establishes what your page says at a point in time. An Information Agent reads your content continuously, evaluating updates against a user’s configured interest profile in real time, and deciding whether a change is worth surfacing. The agent is not just indexing — it is interpreting freshness against user intent.

This matters most for content categories where timeliness has high value: pricing pages, product specifications, research summaries, policy changes, API documentation. If a competitor updates their pricing and you do not update yours, an Information Agent tracking that space may surface the competitor’s update as the relevant change. Freshness becomes a competitive signal in a way it was not under link-based ranking.

Agentic Booking: From Find It to Do It

Google is expanding “agentic booking” to a wider set of tasks including local experiences, services, restaurants, and travel. The feature moves Search from producing a list of results to completing a task on your behalf: find a restaurant matching your criteria, check real-time availability, and make the reservation — all within a single Search session, without navigating to a merchant site.

Prior versions of Google booking integrations used rule-based widgets tied to a fixed partner list. The new agentic booking runs on Gemini 3.5 Flash and handles conversational back-and-forth: refining options based on follow-up constraints, comparing alternatives, and processing edge cases a fixed widget could not handle. The agent connects directly to third-party booking APIs rather than routing through a fixed partner set.

For service businesses that rely on organic discovery, this creates a concrete architectural question. If a user can book a haircut, a venue, or a local experience directly through Search without visiting your website, your website’s role in the conversion path changes. The answer is not that websites become irrelevant — it is that they become structured data sources for AI-mediated commerce. Your website is what the agentic booking system reads to populate and validate the transaction. Accurate business hours, service areas, pricing, and availability in machine-readable schema format become the direct interface between your business and the agent completing the booking.

Developer Impact: GEO Is Operational, Not Optional

Generative Engine Optimization (GEO) — structuring content to be cited by AI-generated answer systems, not just ranked by link-based search — has been discussed for eighteen months as a forward-looking strategy. At one billion AI Mode users with Information Agents rolling out this summer, it is now an operational requirement.

Three mechanics determine how AI Mode and Information Agents interact with your content:

Answer-first extraction. AI Mode synthesizes answers from multiple sources simultaneously. The sources it cites are the ones that answer the query in the first 200 words of the page. Research on AI citation behavior consistently shows that citations pull from the first 30% of document text. If your key claim is in paragraph seven, the model has moved on. Rewriting leads to answer first — then context and support — is the single highest-leverage content change available right now. It also improves human reader experience. It is not a tradeoff.

Freshness as a competitive signal. Information Agents flag content updates in near-real-time and compare them against prior state. Pages in fast-moving categories — developer tooling, pricing, API references, research summaries, compliance documentation — compete on update recency, not just keyword match. A stale page will be outcompeted by a fresher competitor’s page, independent of traditional link equity. Implementing a content freshness cadence for your highest-value pages is now a priority equivalent to link building.

Structured data as the machine interface. Agentic booking, Information Agents, and AI Overviews all consume structured data — JSON-LD schema, OpenGraph tags, Product and Service schema — to validate and populate information. Organization, LocalBusiness, Product, Article, and FAQPage schema are no longer optional metadata. They are the contract between your site and the agents reading it. If your business data is not machine-readable, it does not participate in AI-mediated commerce or agentic search. The Google ADK for TypeScript includes schema validation tooling worth reviewing if you are building on Google’s infrastructure.

Five Changes to Make This Month

Audit the first 200 words of your highest-traffic pages. Does each page answer its primary query before introducing context, navigation, or backstory? If not, rewrite the lead. One page per day on your top 20 pages is a sustainable cadence that outperforms most long-term content strategies in the current AI search environment.

Add and validate schema markup. Run your top pages through Google’s Rich Results Test. For product pages: Product schema with price, availability, and reviewCount. For service businesses: LocalBusiness or Service schema with hours, service areas, and pricing ranges. For content: Article schema with author, datePublished, and dateModified. The test shows you exactly what Google’s systems can and cannot read about your page.

Establish a freshness update cadence. Identify your pages covering fast-moving topics: pricing, feature lists, API references, legal and policy pages. Set a recurring review on your calendar. An updated timestamp with substantive changes signals to Information Agents that your content is an active maintained source, not an archive.

Treat API and technical documentation as a primary SEO surface. AI Mode receives a disproportionate share of queries from developers asking technical questions with long context. A well-structured API reference that answers questions directly, with working code examples, competes for AI citations in a way that a general marketing page does not. If your documentation has not been updated since 2025, it is losing citations to competitors who maintained theirs.

Test the Gemini 3.5 Flash API in your products. The model powering AI Mode is available to developers via the Gemini API today. If you build products using Gemini, the migration is a model string update. Google indicated pricing at approximately one-third to one-half less than prior flagship models with benchmark improvements on coding and agentic tasks over Gemini 3.1 Pro.

import { GoogleGenerativeAI } from "@google/generative-ai";

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);

// Gemini 3.5 Flash — now powering Google AI Mode, available via API
const model = genAI.getGenerativeModel({
  model: "gemini-3.5-flash",
  generationConfig: {
    temperature: 0.2,
    maxOutputTokens: 4096,
  },
});

// Multimodal input — matches what the new Search box accepts
const result = await model.generateContent({
  contents: [{
    role: "user",
    parts: [
      { text: "Summarize the key risks in this contract" },
      {
        inlineData: {
          mimeType: "application/pdf",
          data: base64PdfData   // base64-encoded file content
        }
      }
    ]
  }]
});

console.log(result.response.text());

What the Next Six Months Look Like

Information Agents roll out to US AI Pro and Ultra subscribers this summer. Agentic booking expands to a wider range of local services over the same period. Gemini 3.5 Pro — the higher-capability sibling to 3.5 Flash for complex reasoning — ships approximately one month from today. Spark’s MCP connector expansion and desktop file system access follow over the summer.

The trajectory is clear: search is becoming less of a directory and more of an agent runtime. The billion-user threshold for AI Mode is not a launch metric — it is the point where the new behavior is load-bearing for the search ecosystem. Publishers, developers, and businesses that optimize for AI agents reading their content this quarter will be better positioned than those waiting for the behavior to stabilize before adapting. It is not stabilizing. The query volume is doubling every quarter.

Developer tools, starter kits, and templates for building AI-native products that work with the current search landscape are available at wowhow.cloud.

Originally published at wowhow.cloud

Snyk scans your MCP servers by running them. Here is what that means.

Snyk’s agent-scan tool works by starting every MCP server it finds in your config and querying its tool descriptions. That is not a bug. It is the architecture. To retrieve tool descriptions from a stdio MCP server, you have to execute it. The tool does exactly what it says on the box.

The problem is the use case.

What agent-scan actually does

When you run snyk-agent-scan, it reads your local MCP configuration files:
~/.cursor/mcp.json, Claude Desktop config, Windsurf config, and others. For each server it finds, it executes the command array from the config, spins up the server, connects via the MCP protocol, retrieves tool descriptions, and ships that data to Invariant Labs’ API at invariantlabs.ai for analysis.

One developer confirmed this directly when the API returned a 429 Too Many Requests response, which proved the scanner had executed the servers, connected to them, and transmitted their data off-machine without a prompt.

Snyk has since added a consent flow that shows you the server name, command, and environment variables before execution. In CI/CD you bypass it entirely with --dangerously-run-mcp-servers.

The consent prompt is the right fix. But the architectural question is worth sitting with.

The fundamental tension

Here is the thing: Snyk’s approach is not wrong for what it is trying to do. If you want to check a server you already trust and have installed, executing it to retrieve tool descriptions is reasonable. That is not different from running a container to inspect its behavior.

The problem shows up at the edges:

Scanning an untrusted config. The entire point of a security scanner is to tell you whether something is safe before you commit to running it. If the scanner starts the server to analyze it, and the server is malicious, the scanner has just executed the malicious payload. The command array in mcp.json is attacker-controlled content.

CI/CD pipelines. The --dangerously-run-mcp-servers flag exists precisely because interactive consent prompts break automation. Any CI job that needs to scan MCP configs must bypass the consent flow entirely.

Data exfiltration. Tool names, descriptions, and partial config are sent to a third-party API for analysis. In regulated environments, that is a compliance conversation. In any environment, it is a data residency question.

What Bawbel does differently

Bawbel never starts a server. It reads the file and analyzes the text.

pip install bawbel-scanner
bawbel scan ./my-skill.md       # reads the file, never executes it
bawbel ssc https://server.io    # fetches .well-known/mcp.json, never starts it

The trade-off is real: static analysis cannot detect runtime-only behaviors. A server that looks clean but phones home during execution will pass Bawbel and fail Snyk. Both things can be true at the same time.

What static analysis can do:

  • Scan skill files, server manifests, and system prompts before they hit production
  • Run in CI/CD without executing any agent code
  • Work in air-gapped environments with no external API calls
  • Detect the 48 AVE attack classes across 121 detection rules
  • Produce SARIF output for GitHub Security tab integration

When to use which

Use Snyk agent-scan when you want runtime behavioral analysis of servers you are already running locally. It is the right tool for auditing your current setup.

Use Bawbel when you want to gate skill files and server manifests before deployment, scan in CI/CD without executing code, or work in environments where running untrusted code for analysis is not acceptable.

They cover different threat surfaces. The distinction matters.

The broader point

The MCP ecosystem is moving toward skill registries – shared repositories of agent capabilities, similar to npm or PyPI. When that happens at scale, the question of whether your scanner executes registry packages to analyze them becomes the same question the npm ecosystem has been answering since 2018.

npm had supply chain attacks where install hooks ran malicious code. The lesson was: never execute untrusted code as part of the analysis step.

Static analysis first. Dynamic analysis in a sandboxed, isolated environment with no network access. And always, explicitly, with consent.

Links

When smart is not enough — Multi-agent AI orchestration defaults

Imagine a relay race where all four runners are Olympic-level athletes. Fast, technically perfect, experienced. Now imagine nobody told them the running order. The handoff protocol. Who holds the baton first.

What happens? Someone starts. Maybe the right person, maybe not. The baton gets passed in the wrong direction. Two runners reach the same point at the same time. The team is brilliant. The race is chaos.

This is the exact problem with multi-agent AI orchestration when you skip the config.

Website: https://stack.cardor.dev/ahk

The orchestration gap

The standard setup for a structured multi-agent workflow looks like this:

  • Lead — decomposes the task, plans, delegates
  • Explorer — reads and maps the codebase, never writes
  • Builder — implements the plan
  • Reviewer — verifies, approves, or blocks

Four specialized roles. Clean separation of concerns. The kind of structure that turns “fix this bug” into a systematic, auditable workflow with full history and no duplicated effort.

The problem: most setups define these roles in markdown files with descriptions like:

“Use this agent to orchestrate a full task. Invoke when starting a new work session.”

That description tells the model what the lead is supposed to do. It does not tell the tool to start with lead by default. Those are two different things. And when your AI tool opens a project without an explicit default agent config, it either picks the first agent it finds alphabetically, falls back to its own built-in default, or just asks you — which defeats the point of having a structured workflow in the first place.

The relay baton is a config field, not an instruction

Here’s what actually controls who goes first in each tool.

Claude Code uses the agent field in .claude/settings.json:

{
  "agent": "lead"
}

This is the official, documented field for setting which subagent runs as the main session thread. A common mistake — including one we caught in our own materializer — is putting default_agent: "lead" in .claude/mcp.json. That field doesn’t exist in Claude Code’s config spec. It gets ignored silently, and your session starts wherever Claude decides.

OpenCode uses default_agent in opencode.json:

{
  "default_agent": "lead",
  "compaction": { "auto": true, "prune": true, "reserved": 10000 }
}

This maps correctly. OpenCode also exposes compaction config for managing context window pressure during long orchestrated sessions — useful when lead, explorer, builder, and reviewer are all accumulating history in the same run.

Codex CLI has no direct default_agent field. Instead, Codex ships with a built-in agent named default. You override it by creating .codex/agents/default.toml with your lead agent’s instructions and name = "default". Codex will use it as the entry point from that point on.

name = "default"
sandbox_mode = "read-only"

description = """
Use this agent to orchestrate a full task from the harness backlog: decompose it
into a plan, delegate to explorer, builder, and reviewer in sequence.
"""

developer_instructions = """
# Lead Agent

You are the lead agent. Your job is to orchestrate the workflow for one task at
a time. You coordinate — you do not implement.
...
"""

The second problem: roles without enforcement

Getting the starting agent right is half the battle. The other half is making sure each agent stays in its lane once the relay is running.

Instructions help. But instructions are text. A builder told “don’t read source files outside src/” can still read them if nothing stops it. An explorer told “never write files” can still write if it decides that’s the fastest path. One slightly off prompt, one edge case, and the contract breaks.

This is where per-role sandboxing does real work.

Claude Code exposes permissionMode as a frontmatter field on each agent .md file:

---
name: explorer
permissionMode: plan
---

plan mode puts the session into read-only planning mode — file writes and edits are blocked at the tool level. Not instruction. Enforcement.

For builder:

---
name: builder
permissionMode: acceptEdits
---

acceptEdits means file edits are auto-approved without a per-change confirmation prompt — the right tradeoff for an agent whose whole job is to implement.

Codex CLI uses sandbox_mode per agent TOML file:

# explorer.toml
name = "explorer"
sandbox_mode = "read-only"
# builder.toml
name = "builder"
sandbox_mode = "workspace-write"

The full matrix across our four roles:

Agent Claude Code permissionMode Codex sandbox_mode
lead plan read-only
explorer plan read-only
builder acceptEdits workspace-write
reviewer plan read-only

Why this saves tokens, not just sanity

There’s an economic argument here that goes beyond correctness.

When orchestration defaults are wrong, you burn tokens at the start of every session re-establishing context. Lead has to re-introduce itself. The wrong agent hands off awkwardly. The model spends turns figuring out where it is in the workflow instead of doing the work. Then you re-prompt. Then you clarify. Then you maybe restart.

When defaults are right, the first turn is already positioned correctly. Lead picks up the task, decomposes it, passes the baton to explorer. Explorer maps the codebase, passes to builder. Builder implements, passes to reviewer. Clean chain. No re-orientation. The first session draft is the good one.

The model’s intelligence is the same. The setup is what changes the output quality on the first try. More predictable behavior, fewer iterations, less token waste — not because the model got smarter, but because it didn’t have to guess its starting position.

Provider comparison

Feature Claude Code OpenCode Codex CLI
Agent files .claude/agents/*.md .opencode/agents/*.md .codex/agents/*.toml
File format Markdown + YAML frontmatter Markdown + YAML frontmatter TOML
Default agent field agent in settings.json default_agent in opencode.json Override built-in via default.toml
Per-role permissions permissionMode in frontmatter — (global only) sandbox_mode in TOML
MCP config file .claude/mcp.json opencode.json .codex/config.toml
Compaction config Not exposed compaction.{auto,prune,reserved} Not applicable

Putting it together

All of this is automated in agent-harness-kit. One command scaffolds the full structure — agents, MCP config, default agent wiring, sandbox settings — for whichever provider you choose:

npx ahk init --provider codex-cli
npx ahk init --provider claude-code
npx ahk init --provider opencode

The materializer knows which field to write to, in which file, in which format. You get correct defaults without cross-referencing three different docs pages — or discovering six months later that your default_agent key in mcp.json was doing nothing.

The relay metaphor holds

Back to the runners. The baton problem isn’t about ability — it’s about protocol. Define the protocol in config, not in prose. Each runner in their lane, with the right access, in the right order.

The relay runs itself. You just watch the race.

Built with agent-harness-kit — provider-agnostic scaffolding for structured multi-agent AI workflows.