billet

module
v0.0.0-...-8c9b6e8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: Apache-2.0

README

Billet

Billet is the memory sidecar of the Equestrianism suite — the strap/buckle hardware that connects the girth to the saddle. It exposes two tools, save_memory and search_memory, over two transports — MCP Streamable HTTP for direct agent access, and Connect RPC (billet.v1.MemoryService, speaking Connect/gRPC/gRPC-Web) for control-plane-proxied deployments — backed by a pluggable storage backend: an in-process ephemeral default, a local persistent bolt file, or AWS Bedrock AgentCore Memory. It is written in Go, ships as a single static binary, and is deliberately thin: it owns the protocols, namespace binding, and cost governance, and delegates extraction and consolidation entirely to the configured backend. Where Chiron investigates and Stirrup changes code, Billet remembers.

Deployment models
  • Direct (default): the MCP endpoint is reachable from the agent environment; Stirrup's harness calls Billet itself. Minimal moving parts.
  • Proxied: only the RPC endpoint is enabled (--rpc --mcp=false), reachable solely by the control plane, which proxies tool calls to Billet — the agent environment gets no network path to the knowledge system, preserving Stirrup's no-direct-egress security posture while staying compatible with the rest of the Equestrianism toolset.
  • Both transports can run at once (separate listeners, one shared backend and budget).

Building

just build   # go build -o bin/billet ./cmd/billet
just test    # go test ./...
just vet     # go vet ./...
just lint    # golangci-lint if installed, else go vet
just ci      # everything CI runs
just image   # podman build -t localhost/billet:dev -f Containerfile .

Requires Go 1.27. Without just: go build -o bin/billet ./cmd/billet.

Container image

Containerfile produces a distroless image that runs as uid 65532 and works on a read-only root filesystem. .github/workflows/image.yml publishes it to ghcr.io/rxbynerd/billet:latest (plus a sha-<commit> tag) on every push to main. The default command serves MCP on :8140; the RPC-only, control-plane-proxied model needs a writable mount for the bolt database:

podman run --read-only -v billet-data:/var/lib/billet -p 8141:8141 \
  ghcr.io/rxbynerd/billet:latest \
  serve --rpc --mcp=false --rpc-listen=:8141 \
  --backend=bolt --db-path=/var/lib/billet/billet.db --namespace=<ns>

The image pre-creates /var/lib/billet owned by uid 65532, so a fresh named volume inherits that ownership. A tmpfs or bind mount over it must be writable by that uid itself (Kubernetes emptyDir is; podman --tmpfs defaults to root-only, pass mode=1777).

Usage

The two commands
# Start the server (MCP only, loopback-only default; see the --listen note below).
billet serve

# Control-plane-proxied model: RPC only, no direct MCP surface.
billet serve --rpc --mcp=false

# Emit the resolved BilletConfig JSON without starting a server.
billet config --backend agentcore-memory --region eu-west-2 --memory-id mem-abc123
Config
{
  "backend": {
    "type": "agentcore-memory",
    "region": "eu-west-2",
    "memoryId": "mem-abc123",
    "credentialsRef": "secret://AWS_PROFILE"
  },
  "namespace": "prod",
  "mcp": { "enabled": true, "listen": "127.0.0.1:8140" },
  "rpc": { "enabled": false, "listen": "127.0.0.1:8141" },
  "budget": { "monthlyGbp": 50 }
}

Resolution order (lowest to highest precedence): documented defaults → base config (--config <path>, --config -, or piped stdin) → explicit flags. backend.type defaults to "memory" — an in-process, ephemeral store with no external dependencies — so a fresh deployment never talks to a billable cloud service by accident. namespace is required when backend.type is "agentcore-memory" or "bolt", and must match ^[A-Za-z0-9][A-Za-z0-9_-]{2,63}$. backend.path is required when backend.type is "bolt": the bbolt database file to use, created if absent (its parent directory must already exist). A bolt config:

{
  "backend": { "type": "bolt", "path": "/var/lib/billet/billet.db" },
  "namespace": "prod",
  "mcp": { "enabled": true, "listen": "127.0.0.1:8140" },
  "rpc": { "enabled": false, "listen": "127.0.0.1:8141" }
}
Flag Config field Default Notes
--mcp mcp.enabled true Serve the MCP Streamable HTTP endpoint (direct agent-environment access).
--listen mcp.listen 127.0.0.1:8140 MCP bind address. Loopback-only by default — the endpoint is unauthenticated, so exposing it further (--listen :8140 or a non-loopback address) is an explicit operator choice.
--rpc rpc.enabled false Serve the billet.v1.MemoryService Connect RPC endpoint (control-plane-proxied access).
--rpc-listen rpc.listen 127.0.0.1:8141 RPC bind address; same loopback-only reasoning as --listen.
--namespace namespace default Long-term recall scope (AgentCore actorId, or a bolt database's per-namespace bucket); required for agentcore-memory and bolt.
--backend backend.type memory memory, agentcore-memory, or bolt.
--region backend.region AWS region (agentcore-memory).
--memory-id backend.memoryId AgentCore Memory resource ID (agentcore-memory).
--credentials-ref backend.credentialsRef secret:// reference selecting AWS credentials (agentcore-memory).
--db-path backend.path Database file path (bolt).
--budget budget.monthlyGbp uncapped Rough, call-count-based cost estimate cap in GBP (not real billing — see docs/DECISIONS.md).

billet config --validate checks the resolved config with BilletConfig.Validate and exits non-zero on failure; without it, a partial or chained config is emitted as-is so a pipeline stage can complete it downstream. billet config redacts backend.credentialsRef to secret://[REDACTED] by default; pass --redact=false when a pipeline stage genuinely needs the real value to flow through to the next stage or to billet serve.

At least one transport must be enabled; when both are, they must bind distinct addresses.

The tool surface
Tool Input Output
save_memory content: string, kind?: "event"|"fact" {memory_id, accepted}
search_memory query: string, limit?: int (default 5) {records: [{memory_id, content, score, created_at}]}

The RPC transport exposes the same operations as billet.v1.MemoryService.SaveMemory/SearchMemory (proto/billet/v1/memory.proto); Go clients import the generated stubs from github.com/rxbynerd/billet/gen/billet/v1/billetv1connect. Semantics are identical on both transports — validation, limits, budget gating, and error policy live in one shared core (internal/service).

No tool or RPC takes a namespace or session_id parameter — both are bound once at server startup from BilletConfig, not chosen per call by the calling LLM (see docs/DECISIONS.md).

Wiring into Stirrup

Add an MCPServerConfig entry to a Stirrup RunConfig:

{
  "tools": {
    "mcpServers": [
      {
        "name": "billet",
        "uri": "https://billet.internal:8140/",
        "allowedTools": ["save_memory", "search_memory"]
      }
    ]
  }
}

Documentation

Directories

Path Synopsis
cmd
billet command
Command billet is the Equestrianism suite's MCP memory sidecar: it exposes save_memory and search_memory over Streamable HTTP, backed by a pluggable storage backend.
Command billet is the Equestrianism suite's MCP memory sidecar: it exposes save_memory and search_memory over Streamable HTTP, backed by a pluggable storage backend.
gen
internal
backend
Package backend defines the Backend seam: where Billet actually stores and recalls memory.
Package backend defines the Backend seam: where Billet actually stores and recalls memory.
cli
Package cli defines the billet command tree and binds flags onto the declarative BilletConfig.
Package cli defines the billet command tree and binds flags onto the declarative BilletConfig.
config
Package config defines BilletConfig: the single declarative configuration a Billet server is built from.
Package config defines BilletConfig: the single declarative configuration a Billet server is built from.
cost
Package cost implements Billet's v1 cost governance: a rough, rolling call-count-based estimate of AgentCore spend, checked before every save_memory/search_memory call.
Package cost implements Billet's v1 cost governance: a rough, rolling call-count-based estimate of AgentCore spend, checked before every save_memory/search_memory call.
mcpserver
Package mcpserver exposes Billet's tool surface as an MCP server over Streamable HTTP: the save_memory and search_memory tools Stirrup's harness calls directly (docs/DECISIONS.md, "two transports").
Package mcpserver exposes Billet's tool surface as an MCP server over Streamable HTTP: the save_memory and search_memory tools Stirrup's harness calls directly (docs/DECISIONS.md, "two transports").
rpcserver
Package rpcserver exposes Billet's tool surface as a Connect RPC service (billet.v1.MemoryService) speaking the Connect, gRPC, and gRPC-Web protocols, for control-plane-proxied deployments where the agent environment has no direct network access to Billet (docs/DECISIONS.md, "two transports").
Package rpcserver exposes Billet's tool surface as a Connect RPC service (billet.v1.MemoryService) speaking the Connect, gRPC, and gRPC-Web protocols, for control-plane-proxied deployments where the agent environment has no direct network access to Billet (docs/DECISIONS.md, "two transports").
secret
Package secret defines the Resolver seam for secret:// references, the suite-wide convention for keeping credentials out of configuration.
Package secret defines the Resolver seam for secret:// references, the suite-wide convention for keeping credentials out of configuration.
service
Package service implements the transport-neutral core of Billet's two tool operations: input validation, the cost-guard gate, limit clamping, and the generic caller-facing error policy.
Package service implements the transport-neutral core of Billet's two tool operations: input validation, the cost-guard gate, limit clamping, and the generic caller-facing error policy.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL