lucidAGENTS
OperateDeploy

Deploy with Hono

Run the canonical Lucid route plan as a long-lived Fetch-native Bun service.

Hono is the most direct deployment for a Bun/Fetch-native Lucid API. The adapter creates a Hono app and binds every route from runtime.http.routes. It does not install its own paywall, parser, or task registry.

Prerequisites

  • Build the Next workspace set or generate a matching Hono project.
  • Configure one public origin/base path, payment provider, receiving address, network, secrets, and required durable stores.
  • Use a long-lived process when you depend on SQLite, in-process streams, owned task execution, or scheduler workers.

Production entrypoint

src/index.ts
import { app, runtime } from './lib/agent';

const port = Number(process.env.PORT ?? 3000);
const server = Bun.serve({
  port,
  fetch: app.fetch,
});

console.log(`Listening on ${server.url}`);

let shutdownPromise: Promise<void> | undefined;
function shutdown(signal: string): Promise<void> {
  shutdownPromise ??= (async () => {
    console.log(`Received ${signal}; draining`);
    await server.stop(false);
    await runtime.close();
  })();
  return shutdownPromise;
}

for (const signal of ['SIGINT', 'SIGTERM'] as const) {
  process.on(signal, () => {
    void shutdown(signal).then(
      () => process.exit(0),
      error => {
        console.error('Shutdown failed', error);
        process.exit(1);
      }
    );
  });
}

Export runtime and app from a server-only agent module:

import { createAgentApp } from '@lucid-agents/hono';

export const runtime = await buildRuntime();
const bound = await createAgentApp(runtime, {
  beforeMount(app) {
    app.use('*', securityHeadersMiddleware);
  },
  afterMount(app) {
    app.get('/ready', async context => {
      const ready = await checkRequiredDependencies();
      return context.json({ ready }, ready ? 200 : 503);
    });
  },
});

export const app = bound.app;

/health is Lucid liveness. Keep /ready private or minimally descriptive and include the database schema/atomic probe, facilitator /supported, and critical downstream dependency checks appropriate to this deployment.

Build an OCI container

Use the package's actual Bun version and pin the base image by digest in your deployment repository. A generated Hono project can use this shape:

FROM oven/bun:1.3.10 AS app
WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run type-check && bun run build

ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000

CMD ["bun", "run", "start"]

Build and smoke it locally:

docker build -t lucid-hono:canary .
docker run --rm -p 3000:3000 --env-file .env lucid-hono:canary
curl --fail http://localhost:3000/health

Do not bake .env, private keys, database passwords, or facilitator tokens into the image. Use the orchestrator's secret mount/injection and make the filesystem read-only except for explicit persistent paths.

Storage topology

  • In-memory stores are preview-only.
  • SQLite payment/SIWX files require persistent disk, one compatible writer topology, backups, and migration/restore tests.
  • Multiple replicas need shared Postgres payment/SIWX state plus custom shared HTTP idempotency, task, and scheduler stores.
  • The package ships no durable task or scheduler implementation.
  • MPP services must inject the Postgres challenge store for shared replicas; process-local defaults are for development only.

Run migrations in a separate deployment job or one elected instance before readiness. Do not let every replica race destructive migrations at startup.

Reverse proxy and streaming

Forward the original Host and scheme so generated Agent Card URLs are public and correct. For SSE routes:

  • disable proxy buffering and response caching;
  • avoid middleware that buffers/compresses the entire body;
  • flush headers immediately and preserve text/event-stream;
  • set idle/request timeouts beyond the bounded stream duration;
  • propagate disconnect/abort to the Bun Request;
  • cap concurrent streams separately from ordinary invokes.

Expose PAYMENT-REQUIRED, PAYMENT-RESPONSE, Payment-Receipt, and any SIWX headers through CORS only to browser origins that genuinely need them.

Scaling and workers

Web replicas can scale horizontally only after every shared invariant is moved out of process. Run scheduler polling and recoverable long-running task work in separate long-lived worker processes; do not start a scheduler timer in every web replica unless each occurrence is protected by a correct shared lease.

Autoscale within database connection, facilitator/provider, wallet, and chain RPC limits. A burst of replicas can otherwise turn one traffic spike into a provider outage.

Canary verification

curl --fail https://service.example/health
curl --fail https://service.example/.well-known/agent-card.json
curl -i https://service.example/entrypoints/quote/invoke \
  -H 'content-type: application/json' \
  -H 'idempotency-key: deploy-hono-canary-000001' \
  --data '{"input":{"symbol":"ETH"}}'

The third call must return 402 before the funded canary. Then execute one low-limit paid call, assert schema-valid output plus PAYMENT-RESPONSE, locate the external transaction and local payment/idempotency record, restart the container, and replay the same key.

Failure and rollback

FailureResponse
Readiness cannot reach required store/providerKeep instance out of load balancer
SSE buffers or disconnect is ignoredFix proxy/runtime before production traffic
Settlement outcome is unknown during shutdownStop paid admission and reconcile; preserve staged/idempotency state
Store migration is incompatibleRoll forward/restore with tested procedure; do not erase payment state
Canary charges without expected fulfillmentHalt rollout, retain evidence, follow refund/incident runbook

Roll back to a schema-compatible image after draining. Keep state and evidence intact. See the Hono package reference, durable storage, and deployment overview.

On this page