Runtimes

Go Runtime

The Go runtime is the most complete implementation of the AppTheory contract and ships with the broadest middleware and CDK surface. It is a reference implementation, not the source of truth — the 223 contract fixtures arbitrate when the three runtimes disagree.

Install

The Go toolchain resolves modules from the immutable git tag — no registry is involved beyond Go’s standard proxy.

go get github.com/theory-cloud/apptheory@v1.14.0

Pin a specific release tag from the releases page. AppTheory does not publish to the npm or PyPI registries; the Go module is the only language artifact that ships through Go’s normal toolchain path.

Module layout (see api-snapshots/go.txt for the exact exported surface):

Package Purpose
github.com/theory-cloud/apptheory/runtime Core runtime: apptheory.New, Context, Request, Response, route registration, middleware.
github.com/theory-cloud/apptheory/runtime/mcp MCP Streamable HTTP transport, sessions, resumable SSE.
github.com/theory-cloud/apptheory/runtime/oauth OAuth protected-resource metadata, PKCE, DCR, token-store helpers.
github.com/theory-cloud/apptheory/testkit Deterministic test environment (clock, ID queue, event builders).
github.com/theory-cloud/apptheory/testkit/mcp In-process MCP client for unit tests.
github.com/theory-cloud/apptheory/pkg/limited DynamoDB-backed cross-instance rate limiter.
github.com/theory-cloud/apptheory/pkg/jobs Jobs-ledger primitives.
github.com/theory-cloud/apptheory/pkg/sanitization Safe logging helpers.

Minimal app

package main

import (
    "context"
    "encoding/json"

    "github.com/aws/aws-lambda-go/lambda"
    apptheory "github.com/theory-cloud/apptheory/runtime"
)

func main() {
    app := apptheory.New()

    app.Get("/ping", func(ctx *apptheory.Context) (*apptheory.Response, error) {
        return apptheory.Text(200, "pong"), nil
    })

    lambda.Start(func(ctx context.Context, event json.RawMessage) (any, error) {
        return app.HandleLambda(ctx, event)
    })
}

HandleLambda is the only entrypoint you need for any AWS trigger. It detects the event shape and dispatches to the right adapter — see Event Shape Dispatch for the full table.

Tier selection

The default tier is P2. To opt down:

app := apptheory.New(apptheory.WithTier(apptheory.TierP0))

See HTTP Runtime for what each tier includes.

Deterministic tests

The Go testkit fixes time, request IDs, and AWS event shapes so handler tests do not depend on AWS:

func TestHello(t *testing.T) {
    env := testkit.NewWithTime(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
    env.IDs.Queue("req-1")

    app := env.App()
    app.Get("/hello", func(ctx *apptheory.Context) (*apptheory.Response, error) {
        return apptheory.MustJSON(200, map[string]any{
            "now_unix": ctx.Now().Unix(),
            "id":       ctx.NewID(),
        }), nil
    })

    event := testkit.APIGatewayV2Request("GET", "/hello", testkit.HTTPEventOptions{
        Headers: map[string]string{"x-request-id": "request-1"},
    })
    resp := env.InvokeAPIGatewayV2(context.Background(), app, event)

    if resp.StatusCode != 200 {
        t.Fatalf("expected 200, got %d", resp.StatusCode)
    }
}

env.IDs.Queue(...) pre-fills the ID generator so any ctx.NewID() call returns the queued value in order — handler tests stay deterministic across rerolls.

Route registration

Fluent route registration fails closed: invalid patterns, duplicate method/pattern pairs, and nil handlers panic during registration instead of silently producing a dead route. Strict helpers remain deprecated compatibility wrappers when a caller still needs an error-returning shape, and their errors now use canonical AppTheoryError messages where applicable:

if _, err := app.GetStrict("/users/{id}", h); err != nil {
    t.Fatal(err)
}

TypeScript and Python follow the same fail-closed registration contract.

HTTP error format

Default HTTP error envelopes are nested under error. AppTheoryError is the canonical client-safe error type for new code; AppError remains supported for code/message compatibility. Any HTTP error whose code string is EMPTY_BODY or INVALID_JSON is remapped by the default nested envelope to app.bad_request. To match Lift’s flat shape and preserve those legacy codes/messages:

app := apptheory.New(apptheory.WithHTTPErrorFormat(apptheory.HTTPErrorFormatFlatLegacy))
// or
app := apptheory.New(apptheory.WithLegacyHTTPErrorShape())

This setting applies to HTTP only. AppSync and WebSocket error payloads keep their existing shapes regardless.

MCP runtime

import (
    "context"
    "encoding/json"

    apptheory "github.com/theory-cloud/apptheory/runtime"
    "github.com/theory-cloud/apptheory/runtime/mcp"
)

srv := mcp.NewServer("example", "1.0.0")

_ = srv.Registry().RegisterTool(mcp.ToolDef{
    Name:        "echo",
    Description: "Echo back the provided message.",
    InputSchema: json.RawMessage(`{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}`),
}, func(ctx context.Context, args json.RawMessage) (*mcp.ToolResult, error) {
    var in struct{ Message string `json:"message"` }
    if err := json.Unmarshal(args, &in); err != nil {
        return nil, err
    }
    content := []mcp.ContentBlock{
        {Type: "text", Text: in.Message},
    }
    return &mcp.ToolResult{Content: content}, nil
})

app := apptheory.New()
h := srv.Handler()
app.Post("/mcp", h)
app.Get("/mcp", h)
app.Delete("/mcp", h)

See the MCP Method Surface for the full Streamable HTTP contract, and Remote MCP for OAuth-protected deployments.

What’s verified

The Go runtime passes all 223 contract fixtures on every commit. Any behavioral divergence between Go, TypeScript, and Python is treated as a contract bug — fix the implementation, or update the fixture and prove the change holds in all three runtimes.

Next reads