Initial commit
diff --git a/skribe/skribe.go b/skribe/skribe.go
new file mode 100644
index 0000000..d113bf0
--- /dev/null
+++ b/skribe/skribe.go
@@ -0,0 +1,100 @@
+// Package skribe defines sketch-wide logging types and functions.
+//
+// Logging happens via slog.
+package skribe
+
+import (
+	"context"
+	"io"
+	"log/slog"
+	"slices"
+	"strings"
+)
+
+type attrsKey struct{}
+
+func Redact(arr []string) []string {
+	ret := []string{}
+	for _, s := range arr {
+		if strings.HasPrefix(s, "ANTHROPIC_API_KEY=") {
+			ret = append(ret, "ANTHROPIC_API_KEY=[REDACTED]")
+		} else {
+			ret = append(ret, s)
+		}
+	}
+	return ret
+}
+
+func ContextWithAttr(ctx context.Context, add ...slog.Attr) context.Context {
+	attrs := slices.Clone(Attrs(ctx))
+	attrs = append(attrs, add...)
+	return context.WithValue(ctx, attrsKey{}, attrs)
+}
+
+func Attrs(ctx context.Context) []slog.Attr {
+	attrs, _ := ctx.Value(attrsKey{}).([]slog.Attr)
+	return attrs
+}
+
+func AttrsWrap(h slog.Handler) slog.Handler {
+	return &augmentHandler{Handler: h}
+}
+
+type augmentHandler struct {
+	slog.Handler
+}
+
+func (h *augmentHandler) Handle(ctx context.Context, r slog.Record) error {
+	attrs := Attrs(ctx)
+	r.AddAttrs(attrs...)
+	return h.Handler.Handle(ctx, r)
+}
+
+type multiHandler struct {
+	AllHandler slog.Handler
+}
+
+// Enabled implements slog.Handler. Ignores slog.Level - if there's a logger, this returns true.
+func (mh *multiHandler) Enabled(ctx context.Context, l slog.Level) bool {
+	_, ok := ctx.Value(skribeCtxHandlerKey).(slog.Handler)
+	return ok
+}
+
+// WithAttrs implements slog.Handler.
+func (mh *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+	panic("unimplemented")
+}
+
+// WithGroup implements slog.Handler.
+func (mh *multiHandler) WithGroup(name string) slog.Handler {
+	panic("unimplemented")
+}
+
+func NewMultiHandler() *multiHandler {
+	return &multiHandler{}
+}
+
+type scribeCtxKeyType string
+
+const skribeCtxHandlerKey scribeCtxKeyType = "skribe-handlerKey"
+
+func (mh *multiHandler) NewSlogHandlerCtx(ctx context.Context, logFile io.Writer) context.Context {
+	h := slog.NewJSONHandler(logFile, &slog.HandlerOptions{Level: slog.LevelDebug})
+	w := AttrsWrap(h)
+	return context.WithValue(ctx, skribeCtxHandlerKey, w)
+}
+
+func (mh *multiHandler) Handle(ctx context.Context, r slog.Record) error {
+	if mh.AllHandler != nil {
+		if err := mh.AllHandler.Handle(ctx, r); err != nil {
+			return err
+		}
+	}
+	attrs := Attrs(ctx)
+	r.AddAttrs(attrs...)
+	handler, ok := ctx.Value(skribeCtxHandlerKey).(slog.Handler)
+	if !ok {
+		panic("no skribeCtxHandlerKey value in ctx")
+	}
+	return handler.Handle(ctx, r)
+}