Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Local build artifacts. Without this, `docker build` sends rust/target
# and ./artf-agent (hundreds of MB) as context.
artf-agent
artf-agent.exe
rust/target
coverage.out
coverage.html
.git
.DS_Store
*.log
tmp/
temp/
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ USER nobody
# Expose ports (gRPC: 50051, Web/MCP: 8081, Health: 8080)
EXPOSE 50051 8081 8080

# Health check
# Health check uses the same binary (the image has no curl/wget).
HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \
CMD ["/artf-agent", "-health-check"] || exit 1
CMD ["/artf-agent", "-health-check"]

# Set entrypoint
ENTRYPOINT ["/artf-agent"]
Expand Down
89 changes: 85 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
# Agentic RTB Framework Makefile

BINARY=artf-agent
IMAGE ?= artf-agent
LANGUAGES=go # cpp go csharp objc python ruby js
RUST_BINARY=rust/target/release/agentic-rtb-framework-service
GRPC_ADDR ?= localhost:50051
HEALTH_URL ?= http://localhost:8080
SAMPLE_SERVICE := com.iabtechlab.bidstream.mutation.services.v1.RTBExtensionPoint/GetMutations

.DEFAULT_GOAL := build

.PHONY: help deps generate build run run-dev run-all run-grpc run-mcp run-web \
test test-coverage lint \
build-rust run-rust build-all \
docker-build docker-run docker-run-all docker-compose-up docker-compose-down \
health-check grpc-test sample-banner sample-video sample-bidshade \
bindings check clean docs watch

help:
@echo "Reference agents (checked-in pkg/pb/; protoc not required):"
@echo " make deps # go mod download"
@echo " make build # Go agent -> ./$(BINARY)"
@echo " make build-rust # Rust reference service"
@echo " make test # go test ./..."
@echo " make docker-build # container image $(IMAGE)"
@echo ""
@echo "Regenerate protobufs (requires protoc; not needed to build the agents):"
@echo " make generate # Go: scripts/generate.sh"
@echo " make bindings # spec language bindings (repo-root protos)"
@echo ""
@echo "Run (requires a built binary):"
@echo " make run-all # gRPC + MCP + web + health"
@echo " make health-check # curl $(HEALTH_URL)/health/{live,ready}"
@echo " make grpc-test # grpcurl sample against $(GRPC_ADDR)"

# Go build and run targets
.PHONY: build run-all run-grpc run-mcp run-web test

deps:
go mod download

# Go protobuf regen. Not required to build; pkg/pb/ is checked in.
generate:
scripts/generate.sh

build:
go build -o $(BINARY) ./cmd/agent

run: run-all

run-dev: run-all

run-all: build
./$(BINARY) --enable-grpc --enable-mcp --enable-web

Expand All @@ -24,10 +65,14 @@ run-web: build
test:
go test ./...

# Rust build and run targets
RUST_BINARY=rust/target/release/agentic-rtb-framework-service
test-coverage:
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out

lint:
go vet ./...

.PHONY: build-rust run-rust build-all
# Rust build and run targets

build-rust:
cd rust && cargo build --release
Expand All @@ -37,6 +82,41 @@ run-rust: build-rust

build-all: build build-rust

# Docker

docker-build:
docker build -t $(IMAGE) .

docker-run: docker-run-all

docker-run-all: docker-build
docker run --rm -p 50051:50051 -p 8081:8081 -p 8080:8080 $(IMAGE)

docker-compose-up:
docker compose up --build -d

docker-compose-down:
docker compose down

# Live checks (server must already be running)

health-check:
curl -fsS $(HEALTH_URL)/health/live
@echo
curl -fsS $(HEALTH_URL)/health/ready
@echo

grpc-test: sample-banner

sample-banner:
grpcurl -plaintext -d @ $(GRPC_ADDR) $(SAMPLE_SERVICE) < samples/banner-basic.json

sample-video:
grpcurl -plaintext -d @ $(GRPC_ADDR) $(SAMPLE_SERVICE) < samples/video-deals.json

sample-bidshade:
grpcurl -plaintext -d @ $(GRPC_ADDR) $(SAMPLE_SERVICE) < samples/bid-shading.json

# Protobuf targets

bindings:
Expand All @@ -58,6 +138,7 @@ clean:
for x in ${LANGUAGES}; do \
rm -fr $${x}/*; \
done
rm -f $(BINARY) coverage.out coverage.html

docs:
podman run --rm \
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ This project implements a multi-protocol server that conforms to the ARTF specif

#### Prerequisites

- Go 1.23+
- Protocol Buffers compiler (`protoc`) v3.21+
- Go 1.23+ (required to build the Go agent)
- Rust toolchain (optional, for `make build-rust`)
- Docker (optional, for containerized deployment)

#### Critical Dependencies
Checked-in generated Go in `pkg/pb/` is enough to `make build`. You do not need to download OpenRTB or run `protoc` to build the agent.

The following tools must be installed to generate protobuf code:
#### Optional: regenerating protobuf code

`make generate` runs `scripts/generate.sh`. It is not required to build. The tools below are only needed for that regen:

| Tool | Version | Installation |
|------|---------|--------------|
Expand All @@ -79,19 +81,19 @@ Go module dependencies (managed via `go.mod`):
#### Build and Run

```bash
# Install dependencies
# Install Go module dependencies
make deps

# Generate protobuf code
make generate

# Build the server
# Build the Go server (uses checked-in pkg/pb/; protoc not required)
make build

# Optional: build the Rust reference service
make build-rust

# Run with all services enabled
make run-all

# Run in development mode (verbose)
# Same as make run-all
make run-dev
```

Expand Down Expand Up @@ -169,6 +171,7 @@ The MCP server exposes an `extend_rtb` tool that accepts OpenRTB bid requests an
| `--mcp-port` | 50052 | MCP server port (ignored when both Web and MCP enabled) |
| `--web-port` | 8081 | Web interface port |
| `--health-port` | 8080 | Health check HTTP port |
| `--health-check` | false | Probe `http://127.0.0.1:<health-port>/health/ready` and exit (Docker HEALTHCHECK) |

#### Load Balancer Configuration

Expand All @@ -193,16 +196,13 @@ make test
# Run with coverage
make test-coverage

# Test gRPC endpoint (requires grpcurl)
# Test gRPC endpoint (requires a running agent and grpcurl)
make grpc-test

# Test MCP endpoint
make mcp-test

# Check health endpoints
# Check health endpoints (requires a running agent)
make health-check

# Send sample requests via MCP
# Send bundled samples over gRPC (requires a running agent and grpcurl)
make sample-banner
make sample-video
make sample-bidshade
Expand Down
14 changes: 14 additions & 0 deletions cmd/agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ var (

// Version flag
showVersion = flag.Bool("version", false, "Show version information")

// One-shot probe for Docker HEALTHCHECK / compose. Does not start the server.
healthCheck = flag.Bool("health-check", false, "Probe local /health/ready and exit 0/1")
)

func main() {
Expand All @@ -83,6 +86,17 @@ func main() {
os.Exit(0)
}

if *healthCheck {
origin := fmt.Sprintf("http://127.0.0.1:%d", *healthPort)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := health.Probe(ctx, origin); err != nil {
log.Printf("health-check: %v", err)
os.Exit(1)
}
os.Exit(0)
}

log.Printf("Starting ARTF Agent v%s", Version)
log.Printf("Features: gRPC=%v, MCP=%v, Web=%v", *enableGRPC, *enableMCP, *enableWeb)

Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ services:
- "--web-port=8081"
- "--health-port=8080"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health/ready"]
test: ["CMD", "/artf-agent", "-health-check"]
interval: 10s
timeout: 5s
retries: 3
Expand Down Expand Up @@ -61,7 +61,7 @@ services:
- "--enable-mcp=false"
- "--enable-web=false"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health/ready"]
test: ["CMD", "/artf-agent", "-health-check"]
interval: 10s
timeout: 5s
retries: 3
Expand All @@ -87,7 +87,7 @@ services:
- "--enable-mcp"
- "--enable-web=false"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health/ready"]
test: ["CMD", "/artf-agent", "-health-check"]
interval: 10s
timeout: 5s
retries: 3
Expand Down
20 changes: 11 additions & 9 deletions docs/00-EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,28 +315,29 @@ readinessProbe:

### Prerequisites

- Go 1.22+
- Protocol Buffers compiler (`protoc`)
- protoc-gen-go and protoc-gen-go-grpc plugins
- Docker (for containerized deployment)
- Go 1.23+ (required to `make build`)
- Protocol Buffers compiler (`protoc`) and Go plugins (optional: `make generate`)
- Docker (optional, for containerized deployment)

### Build Commands

| Command | Description |
|---------|-------------|
| `make deps` | Download Go dependencies |
| `make generate` | Generate protobuf Go code |
| `make build` | Build server binary |
| `make generate` | Regenerate protobuf Go via `scripts/generate.sh` (requires protoc; not required to `make build`) |
| `make build` | Build the Go agent binary from checked-in `pkg/pb/` |
| `make build-rust` | Build the Rust reference service (`rust/Cargo.toml`) |
| `make test` | Run unit tests |
| `make test-coverage` | Run tests with coverage report |
| `make lint` | Run linter |
| `make clean` | Remove build artifacts |
| `make lint` | `go vet ./...` |
| `make clean` | Remove spec language output dirs, `artf-agent`, and coverage files |

### Run Commands

| Command | Description |
|---------|-------------|
| `make run` | Run server locally |
| `make run` | Run server locally (gRPC + MCP + web) |
| `make run-all` | Same as `make run` |
| `make docker-build` | Build Docker image |
| `make docker-run` | Run Docker container |
| `make docker-compose-up` | Start with docker-compose |
Expand All @@ -359,6 +360,7 @@ readinessProbe:
|------|---------|-------------|
| `-grpc-port` | 50051 | gRPC server listening port |
| `-health-port` | 8080 | Health check HTTP server port |
| `-health-check` | false | Probe local `/health/ready` and exit 0/1 (container HEALTHCHECK) |

### Environment Variables

Expand Down
24 changes: 24 additions & 0 deletions internal/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
package health

import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"time"
)

// Checker implements liveness and readiness probes
Expand Down Expand Up @@ -93,3 +97,23 @@ func (c *Checker) ReadinessHandler(w http.ResponseWriter, r *http.Request) {

json.NewEncoder(w).Encode(response)
}

// Probe GETs origin/health/ready. Used by the process-local -health-check flag
// so Docker HEALTHCHECK does not need curl or wget in the image.
func Probe(ctx context.Context, origin string) error {
u := strings.TrimRight(origin, "/") + "/health/ready"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return err
}
client := &http.Client{Timeout: 2 * time.Second}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%s returned %s", u, resp.Status)
}
return nil
}
Loading