Skip to content
Merged
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
36 changes: 33 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ must be labeled as roadmap on the pages.
- by default, changes in this repository do not preserve backward compatibility
- do not keep legacy aliases, deprecated field names, old route shapes, old module IDs, old CLI flags, or old API-visible IDs unless the change request explicitly requires compatibility
- when renaming or reshaping behavior, update the code, tests, `README.md`, `docs/architecture/architecture-overview.md`, `examples/Caddyfile.example`, this file, and the nearest nested `AGENTS.md` to describe only the current behavior unless compatibility is explicitly required
- Reusable command implementation rules and the exact F1 surface list live in
`pkg/distribution/AGENTS.md`.
- protected Enterprise SPI and reusable distribution entry points follow the
compatibility policy in
[`docs/design/enterprise-extension-contract.md`](docs/design/enterprise-extension-contract.md)
Expand Down Expand Up @@ -88,6 +90,8 @@ go test ./path/to/package -run TestName -v
Notes:

- `make build` builds `agw` from `cmd/agw/main.go`, `agwd` from `cmd/agwd/main.go`, and `agwctl` from `cmd/agwctl/main.go`.
- The three mains own explicit linked-module imports and delegate to `pkg/distribution/caddycmd`, `daemoncmd`, and `ctlcmd`. Command construction must keep flag state local; dotenv, signals, and process exit belong to execution.
- Both server mains pass `distribution.CommunityRuntime()`; CLI passes `CommunityValidation()`. These supply validated manifest metadata, with explicitly injected attribution producers/allowlists and per-lifecycle public sink factories excluded from serialization. Keep declarations aligned with main imports.
- The resulting binary is a standard Caddy binary with custom modules compiled in, so normal Caddy subcommands such as `run`, `reload`, `validate`, and `hash-password` work.

## Core Modules
Expand Down Expand Up @@ -168,18 +172,22 @@ authentication is delegated to the HTTP mount layer such as Caddy
Deep per-subsystem rules live in nested `AGENTS.md` files next to the code.
Read the applicable subsystem file before changing either its runtime package
or a cross-cutting adapter for it (for example `caddy/admin`,
`caddy/dispatcher`, `standalone/server`, `pkg/gatewaybundle`, or `cmd/agwctl`):
`caddy/dispatcher`, `standalone/server`, `pkg/gatewaybundle`, or `pkg/distribution/ctlcmd`):

- `pkg/gateway/` — runtime route/VirtualKey/provider resolution (`AgentGateway`) and the public route-model packages (`llmroute`, `modelcatalog`, `mcproute`, `agentroute`, `virtualkey`) → `pkg/gateway/AGENTS.md`
- `pkg/acp/` — native ACP runtime (`codex`, `opencode`): pooling, sessions, permissions, transcripts → `pkg/acp/AGENTS.md`
- `pkg/credential/` — cross-cutting credential model, persistence, scheduling, expiry detection, and external refresh transport → `pkg/credential/AGENTS.md`
- `pkg/llm/` — provider interface/registry, built-in providers, and the `einomodel` eino bridge → `pkg/llm/AGENTS.md`
- `pkg/mcp/` — MCP service runtime and the `einotool` eino bridge → `pkg/mcp/AGENTS.md`
- `pkg/a2a/` — A2A Protocol 1.0 JSON-RPC (Path B translating client/backend and Path A governed native proxy are implemented) → `pkg/a2a/AGENTS.md`
- `pkg/configstore/` — generic config store/backends (persisted backend: `sqlite`; stores `providers`, `credentials`, `routes`, `mcp_services`, `agents`, `virtual_keys`, `managed_models`) → `pkg/configstore/AGENTS.md`
- `pkg/configstore/` — generic config store/backends (persisted backend: `sqlite`; stores `providers`, `credentials`, `routes`, `mcp_services`, `agents`, `virtual_keys`, `managed_models`, `extensions`) → `pkg/configstore/AGENTS.md`
- `pkg/agent/` — agent control plane (`pkg/agent/runtime` contracts, `Agent` model, route/service → agent index) → `pkg/agent/AGENTS.md`; builtin eino ADK host → `pkg/agent/builtin/AGENTS.md`
- `internal/observability/` — usage events, event pipeline, OTLP export, `einotap` → `internal/observability/AGENTS.md`
- `pkg/dispatcher/llmapi/` — LLM protocol handler runtimes; see "Protocol handler modules" above
- `pkg/gateway/virtualkey/limiter/` — candidate protected backend SPI, generation
fences, strict selection and reusable suites → its nested `AGENTS.md`
- `pkg/extension/` — linked registry, bounded envelopes and prepared generations → `pkg/extension/AGENTS.md`
- `pkg/attribution/` and `pkg/event/` — trusted bounded context and sanitized public sink transport → their nested `AGENTS.md` files
- `caddy/gateway/` — Caddy app wiring and global `agent_gateway` Caddyfile parsing; see "Caddy app" above

Cross-cutting invariants:
Expand Down Expand Up @@ -221,6 +229,16 @@ The main config lives in the global `agent_gateway` block.

Important current directives:

- internal extensions are independent of Caddyfile and use ConfigStore as their sole configuration source; manage them through Admin API or Bundle apply. Caddyfile/Caddy JSON and standalone static Bundles reject extension configuration
- `online.agentguide.community.limiter` schema 1 selects a linked backend using
`backend_module_id`, `backend_type`, `timeout_ms` (1–1000, default 100) and
`failure_mode` (default `closed`). Defaults use memory; alternate providers must
be configured dependencies. Exhaustion is 429, failure is bounded 503, explicit
open mode is degraded; cancellation and retired-policy errors never fail open,
and consumes are not retried
- limiter generations preserve unchanged dimensions and unrelated refresh state;
key policy/backend changes retire state. Quota storage survives connection-handle
refresh; request admission always uses the current prepared handle
- `provider_types` is startup-only provider type availability; when omitted all registered provider types are enabled
- providers use `provider_type <name>`
- LLM routes use `protocol <openai|anthropic|cc>`, MCP routes use `protocol mcp`, and Agent ingress routes use `protocol agent` or `protocol a2a` (HTTP Agent native Path A; see `docs/design/http-agent-runtime.md`)
Expand All @@ -233,11 +251,19 @@ Implemented families:

- LLM: `/admin/llm/providers/...`, `/admin/llm/provider_types` (read-only listing), `/admin/llm/api_handler_types`, `/admin/llm/routes/...`, `/admin/llm/models/providers/{provider_id}/discovered`, `/admin/llm/models/providers/{provider_id}/refresh`, `/admin/llm/models/managed/...`
- `/admin/virtual_keys/...`, `/admin/credentials/...`
- Contributed Admin subroutes are declared by `Factory.AdminRoutes` and handled
by prepared `AdminContributor` resources only under `/admin/extensions/<module_id>/...`;
module roots and all other Community families are reserved. All Admin requests
share a full-body 4 MiB boundary (413), reject compression (415), and inherit
mount authentication, CORS, logging and generic JSON panic recovery. Module
event publishers capture trusted request identity; Publish contexts cannot
replace the event actor or correlation.
- `GET /admin/manifest` and `GET /admin/extensions`, `GET|PUT|DELETE /admin/extensions/{module_id}` (reference-preserving module configuration)
- MCP: `/admin/mcp/services/...`, `/admin/mcp/routes/...`, `/admin/mcp/runtime/...` (discovery, execution, dispatcher runtime inspection)
- ACP: `/admin/acp/runtime/...` (native diagnostics keyed by `agent_id`)
- builtin: `/admin/builtin/runtime/...`
- agents: `/admin/agents/...` plus unified route CRUD under `/admin/agents/routes`
- metrics: summaries (with pipeline health counters), per-protocol LLM/MCP/ACP timeseries and breakdowns, recent interaction events, and the Prometheus exposition endpoint `GET /admin/metrics/prometheus`
- metrics: summaries (with combined drop/failure, rejected-event and public sink health counters), per-protocol LLM/MCP/ACP timeseries and breakdowns, recent interaction events, and the Prometheus exposition endpoint `GET /admin/metrics/prometheus`

Stubbed families currently return `501 Not Implemented`:

Expand All @@ -249,6 +275,10 @@ Stubbed families currently return `501 Not Implemented`:
- `docs/architecture/architecture-overview.md`: current broader architecture
- `docs/design/enterprise-extension-contract.md`: protected external SPI,
distribution assembly, and cross-repository compatibility rules
- `docs/guides/custom-distribution.md`: external assembly and validation workflow
- `docs/releases/v0.7.0.md`: exact designated surfaces and release gates
- `docs/plans/v0.7.0-enterprise-extension-foundation.md`: active paired plan
for reusable distribution entry points and the first protected proof SPI
- `examples/Caddyfile.example`: working reference config
- `cmd/agw/main.go`: the definitive list of linked modules

Expand Down
16 changes: 15 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Makefile for Caddy LLM Router

.PHONY: all build build-agw build-agwd build-agwctl build-xcaddy clean deps fmt verify-release smoke-release
.PHONY: all build build-agw build-agwd build-agwctl build-xcaddy clean deps fmt verify-release smoke-release test-extension-consumer test-extension-consumer-candidate test-extension-consumer-release

# Binary names
BINARY_NAME=agw
Expand Down Expand Up @@ -65,3 +65,17 @@ verify-release:
# Exercise the built binaries and both HTTP Agent ingress paths as processes.
smoke-release: build
AGW_RELEASE_SMOKE=1 $(GOTEST) ./tests/release -run TestReleaseProcesses -v

# Build the independent Go-module entries against this checkout using a temporary
# replacement only; no go.work or replace directive is checked in.
test-extension-consumer:
./scripts/test-extension-consumer

# Immutable candidate proof; commits must be recorded before invoking this gate.
test-extension-consumer-candidate:
@test -n "$(COMMUNITY_COMMIT)" || (echo 'set COMMUNITY_COMMIT to an exact commit' >&2; exit 2)
./scripts/test-extension-consumer candidate "$(COMMUNITY_COMMIT)"

# Fresh caches, published dependency, no checkout replacement or workspace.
test-extension-consumer-release:
./scripts/test-extension-consumer release
129 changes: 127 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,11 @@ virtualKeys:
```

`rate_limits` is optional. It independently limits LLM, MCP, and agent ingress
for a VirtualKey with in-memory token buckets; `agent` covers common Agent
turns and native A2A POSTs. Exceeded requests return `429` with `Retry-After`.
for a VirtualKey through the configured limiter backend (process-local memory
by default); `agent` covers common Agent
turns and native A2A POSTs. Exceeded requests return `429` with `Retry-After`. Backend failures/timeouts
default to `503 limiter_unavailable`; explicit fail-open is reported as degraded.
Admission is never retried automatically.

Set the admin Basic Auth credentials for agwctl as an environment variable, then apply the bundle:

Expand Down Expand Up @@ -593,3 +596,125 @@ go test ./pkg/llm/provider/...
./agw adapt --config ./Caddyfile
./agw run --config ./Caddyfile
```

Reusable distribution entries live in `pkg/distribution/caddycmd`,
`pkg/distribution/daemoncmd`, and `pkg/distribution/ctlcmd`. Separate binaries
keep their own explicit factory/module imports and supply `distribution.Assembly`
metadata. `daemoncmd.NewCommand` and `ctlcmd.NewCommand` create independent Cobra
commands; their `Run` helpers load dotenv and return an exit code for main.
`caddycmd.New(...).Run()` delegates process behavior to upstream Caddy.
Public metrics options use `distribution.MetricsConfig` and `OTLPConfig`.
`Assembly.Registry()` validates explicitly linked declarations; metadata never
dynamically loads module code.

See the [custom distribution guide](docs/guides/custom-distribution.md) for assembly,
configuration, Admin, attribution and backend integration. The
[v0.7 release notes](docs/releases/v0.7.0.md) enumerate designated compatibility
surfaces and the remaining published-tag gates.

Run `make test-extension-consumer` to build and test the separate-module consumer
against this checkout; the same gate is included in `make verify-release`.

All Admin routes share a 4 MiB received-body limit. The gateway checks the full
body before dispatching a write, including chunked requests. Larger bodies return
JSON HTTP 413; compressed bodies return HTTP 415. Bundle apply uses this limit
per serialized resource, not per local Bundle file. Panic responses use generic
JSON HTTP 500 diagnostics.

### Linked extensions

`agw manifest`, `agwd manifest`, and `agwctl manifest` show deterministic linked
metadata. The CLI reports validation capabilities; `agwctl manifest --remote`
queries the server runtime manifest through the authenticated Admin mount.

Separately compiled distributions declare descriptors and configuration
factories through `distribution.Assembly`. Gateway Bundle `extensions` use
`module_id`, `schema_version`, and module-owned JSON `config`. Unknown modules
fail validation/startup. Community binaries have no commercial factories.
`GET /admin/extensions` and `GET|PUT|DELETE /admin/extensions/{module_id}` manage
persisted envelopes in the existing ConfigStore. Caddyfile and standalone static
Bundles do not configure internal extensions; use Admin API or Bundle apply.
Apply handles extensions in dependency order before other resources and reports
partial results when an extension fails. Export retains configuration and secret references.

Secret fields declared by a factory accept only `{"$secret_ref":"env:NAME"}`.
Local validation never resolves these tokens. Server preparation resolves them;
persisted/exported JSON retains the token. Preparation/persistence failure leaves
the current generation active. See the
[extension contract](docs/design/enterprise-extension-contract.md#113-configuration-ownership-mutation-and-secrets).


### Contributed Admin routes

Runtime factories may declare `extension.Factory.AdminRoutes`. Their prepared
resources implement `extension.AdminContributor`; modules need the `admin` role.
Routes belong exclusively to `/admin/extensions/<module_id>/...`; the module root
remains Community envelope CRUD. Patterns accept literals and single-segment
`{name}` wildcards. Duplicate or overlapping method/path declarations fail
assembly with `route_conflict`; GET also owns HEAD. OPTIONS is shared preflight.

Handlers receive an owned reference-preserving config, logger, attribution accessor
and owner-scoped public mutation publisher through `AdminDependencies`. The
publisher captures trusted request identity; a module-supplied context cannot
replace event attribution. Handlers use
`WriteAdminError` for bounded JSON errors. No gateway, store or credentials are
injected. Both server modes keep the outer Admin authentication, CORS, logging,
panic recovery and full-body boundary. Module health appears in `/admin/health`;
generation leases drain handlers before resource retirement, with bounded request
cancellation and reverse dependency shutdown. Unconfigured modules return 503.

### Limiter backend extension

`pkg/gateway/virtualkey/limiter` is the candidate protected domain SPI.
A separately linked module implements `LimiterProvider` on its prepared resource
and returns a `LimiterBackend` for a declared type. The Community selection
extension has schema 1 and defaults to local memory, 100 ms, and fail-closed:

```yaml
extensions:
- module_id: online.agentguide.community.limiter
schema_version: 1
config:
backend_module_id: example.organization.limiter
backend_type: redis
timeout_ms: 100
failure_mode: closed
```

This example requires the named backend module to be linked and configured in
the same Bundle; Community does not ship a production Redis backend. Unknown,
unconfigured or unavailable selections fail startup/apply. `timeout_ms` accepts
1–1000 and is capped by the caller deadline. With no limit for a dimension,
admission is unlimited and never calls the backend. Caller cancellation and
shutdown and retired-policy errors never fail open. Set `failure_mode: open` only for intentional degraded
admission on backend failure or timeout.

Policies have opaque per-key/dimension generations. Unchanged policies preserve
tokens across metadata writes and unrelated extension refreshes; changed dimensions,
key deletion/reset and backend switches retire state. A switch resets full buckets
without transferring tokens. Quota state must survive replacement of prepared
connection handles; retired generations must stay fenced against late operations.
Public mutation events report `state_reset`; admission events distinguish allow,
deny and degraded. Prometheus exposes `agentgateway_limiter_admissions_total`
with only dimension, outcome and bounded backend type labels.

See the [limiter contract](docs/design/enterprise-extension-contract.md#116-limiter-backend-semantics)
and reusable `limiter/contracttest.Limiter` and `LimiterRefill` suites. Released-tag
protection and paired repository evidence remain release gates.

### Trusted attribution and public event sinks

Custom distributions can inject `Assembly.AttributionProducer`, exact namespaced
`AttributeKeys`, and linked-module `EventSinks`. A validated VirtualKey supplies
the default subject; a trusted producer can supply subject/tenant. Client tenant
headers are ignored. `pkg/attribution` exposes bounded context values with defensive
copies, while runtime route/Agent/run/trace correlation comes from the gateway.

Public sinks receive only typed `limiter.admission` and `extension.mutation`
envelopes from the existing pipeline. Config, secret references, resolved values,
prompts and arbitrary error messages are excluded. Delivery uses isolated bounded
queues, FIFO flush and deadline-aware shutdown. Overflow drops the newest event;
delivery is best-effort without retry or replay. `GET /admin/metrics` includes
`pipeline.rejected_events` and per-sink `pipeline.public_sinks` health/counters.
See the [attribution/event contract](docs/design/enterprise-extension-contract.md#114-attribution-and-public-events)
for bounds, lifecycle semantics and candidate protected symbols.
Loading
Loading