Skip to content

Context ​

Sema provides an ambient context system — a key-value store that flows through your entire execution without explicit parameter passing. Inspired by Laravel's Context, it's designed for tracing, metadata propagation, and sharing configuration across deeply nested calls.

Context data is automatically appended as metadata to log output (log/info, log/warn, log/error, log/debug).

Core Functions ​

context/set ​

Set a key-value pair in the current context frame.

sema
(context/set :trace-id "abc-123")
(context/set :user-id 42)

context/get ​

Retrieve a value by key. Returns nil if the key doesn't exist.

sema
(context/get :trace-id)   ; => "abc-123"
(context/get :missing)    ; => nil

context/has? ​

Check if a key exists in the context.

sema
(context/has? :trace-id)  ; => #t
(context/has? :missing)   ; => #f

context/remove ​

Remove a key from all context frames. Returns the removed value, or nil.

sema
(context/set :temp "data")
(context/remove :temp)    ; => "data"
(context/remove :temp)    ; => nil (already gone)

context/pull ​

Get a value and remove it in one step (identical to context/remove).

sema
(context/set :token "abc")
(context/pull :token)     ; => "abc"
(context/has? :token)     ; => #f

context/all ​

Get all context as a merged map.

sema
(context/set :a 1)
(context/set :b 2)
(context/all)  ; => {:a 1 :b 2}

context/merge ​

Merge a map of key-value pairs into the current context.

sema
(context/merge {:trace-id "abc" :env "production" :version "1.0"})
(context/get :env)  ; => "production"

context/clear ​

Clear all context, resetting to an empty state.

sema
(context/clear)
(context/all)  ; => {}

Scoped Overrides ​

context/with ​

Push a temporary context frame for the duration of a thunk. The frame is automatically popped when the thunk completes — even if it raises an error.

sema
(context/set :env "production")

(context/with {:env "staging" :debug #t}
  (lambda ()
    (context/get :env)      ; => "staging"
    (context/get :debug)))  ; => #t

(context/get :env)    ; => "production" (restored)
(context/get :debug)  ; => nil (gone)

Scopes nest naturally — inner values shadow outer ones:

sema
(context/set :a 1)
(context/with {:b 2}
  (lambda ()
    (context/with {:c 3}
      (lambda ()
        (list (context/get :a) (context/get :b) (context/get :c))))))
; => (1 2 3)

WARNING

Values set with context/set inside a context/with block are written to the inner frame and discarded when the scope exits. If you need a value to persist, set it before entering context/with.

Stacks ​

Context stacks are ordered lists of values that you can push to and pop from. Unlike key-value context, stacks are not scoped by context/with — pushes persist across scope boundaries.

context/push ​

Append a value to a named stack.

sema
(context/push :breadcrumbs "login")
(context/push :breadcrumbs "dashboard")
(context/push :breadcrumbs "settings")

context/stack ​

Get all values in a named stack as a list.

sema
(context/stack :breadcrumbs)
; => ("login" "dashboard" "settings")

context/pop ​

Remove and return the last value from a stack. Returns nil if the stack is empty.

sema
(context/pop :breadcrumbs)  ; => "settings"
(context/stack :breadcrumbs)
; => ("login" "dashboard")

Hidden Context ​

Hidden context stores values that are not visible via context/get, context/all, or log metadata. Use it for sensitive data like API keys or internal state.

context/set-hidden ​

sema
(context/set-hidden :api-key "sk-secret-123")

context/get-hidden ​

sema
(context/get-hidden :api-key)  ; => "sk-secret-123"
(context/get :api-key)         ; => nil (not visible in regular context)

context/has-hidden? ​

sema
(context/has-hidden? :api-key)  ; => #t

Log Integration ​

When context is non-empty, log/info, log/warn, log/error, and log/debug automatically append the context map as metadata:

sema
(context/set :trace-id "abc-123")
(context/set :user-id 42)
(log/info "Request processed")

Output:

[INFO] Request processed {:trace-id "abc-123" :user-id 42}

Hidden context is not included in log output.

Examples ​

Request tracing ​

sema
(context/set :request-id (uuid/v4))
(context/set :method "GET")
(context/set :path "/api/users")

(log/info "Request started")
; [INFO] Request started {:method "GET" :path "/api/users" :request-id "a1b2c3..."}

;; All downstream functions automatically include this context in their logs
(process-request)

Pipeline breadcrumbs ​

sema
(define (process-document doc)
  (context/push :steps "parse")
  (let ((parsed (parse doc)))
    (context/push :steps "validate")
    (let ((valid (validate parsed)))
      (context/push :steps "transform")
      (transform valid))))

(process-document input)
(context/stack :steps)
; => ("parse" "validate" "transform")

Scoped configuration ​

sema
;; Set default model
(context/set :model "claude-sonnet")

;; Override for a specific block
(context/with {:model "gpt-5.5" :temperature 0.9}
  (lambda ()
    ;; Code here sees the overridden values
    (context/get :model)))       ; => "gpt-5.5"

(context/get :model)             ; => "claude-sonnet"

Function Reference ​

FunctionArgsDescription
context/setkey valueSet a context value
context/getkeyGet a value (or nil)
context/has?keyCheck if key exists
context/removekeyRemove and return value
context/pullkeyGet and remove (alias for remove)
context/allGet all context as a map
context/mergemapMerge map into context
context/clearClear all context
context/withmap thunkScoped override
context/pushkey valuePush to named stack
context/stackkeyGet stack as list
context/popkeyPop from named stack
context/set-hiddenkey valueSet hidden value
context/get-hiddenkeyGet hidden value
context/has-hidden?keyCheck hidden key exists