diff --git a/secretspec.cabal b/secretspec.cabal
--- a/secretspec.cabal
+++ b/secretspec.cabal
@@ -1,9 +1,9 @@
 cabal-version:      2.4
 name:               secretspec
-version:            0.19.1
+version:            0.20.0
 synopsis:           Haskell SDK for SecretSpec, a declarative secrets manager
 description:
-  A thin client over the @secretspec-ffi@ C ABI (linked at build time).
+  A thin client over the @libsecretspec@ C ABI (linked at build time).
   Resolution (providers, chains, profiles, generation, @as_path@) happens in the
   Rust core; this package marshals a JSON request to the native library and
   parses the response, mirroring the Rust derive crate's vocabulary.
@@ -17,8 +17,8 @@
 
 flag use-pkg-config
   description:
-    Locate secretspec_ffi and its link dependencies through pkg-config
-    (secretspec_ffi.pc) instead of command-line library and linker paths.
+    Locate secretspec and its link dependencies through pkg-config
+    (libsecretspec.pc) instead of command-line library and linker paths.
   default:            False
   manual:             True
 
@@ -35,14 +35,14 @@
   -- the static archive; pkg-config may select a static or shared install.
   --
   -- Without the flag: point --extra-lib-dirs at a directory containing ONLY
-  -- libsecretspec_ffi.a (so -lsecretspec_ffi resolves to the archive, not a
+  -- libsecretspec.a (so -lsecretspec resolves to the archive, not a
   -- co-located .so), and pass the archive's transitive native deps via
   -- --ghc-options=-optl<lib> (capture them with
-  -- `cargo rustc -p secretspec-ffi --crate-type staticlib -- --print native-static-libs`).
+  -- `cargo rustc -p libsecretspec --crate-type staticlib -- --print native-static-libs`).
   if flag(use-pkg-config)
-    pkgconfig-depends: secretspec_ffi
+    pkgconfig-depends: libsecretspec
   else
-    extra-libraries:  secretspec_ffi
+    extra-libraries:  secretspec
   -- The Darwin frameworks among the archive's native dependencies, declared
   -- so every final link (executable, test-suite, ghci) receives them through
   -- the compiler driver.
diff --git a/src/SecretSpec.hs b/src/SecretSpec.hs
--- a/src/SecretSpec.hs
+++ b/src/SecretSpec.hs
@@ -3,7 +3,7 @@
 
 -- | Haskell SDK for SecretSpec, a declarative secrets manager.
 --
--- A thin client over the @secretspec-ffi@ C ABI, linked at build time.
+-- A thin client over the @libsecretspec@ C ABI, linked at build time.
 -- Resolution (providers, fallback chains, profiles, generation, @as_path@)
 -- happens entirely in the Rust core; this module marshals a JSON request to
 -- @secretspec_resolve@, parses the response envelope, and exposes it with the
@@ -19,12 +19,15 @@
 module SecretSpec
   ( -- * Builder
     Builder
+  , CallerContext(..)
   , builder
   , withPath
+  , withInlineSpec
   , withProvider
   , withProfile
   , withScope
   , withReason
+  , withCaller
   , withNoValues
     -- * Resolve (value-carrying)
   , Resolved(..)
@@ -63,13 +66,16 @@
 import           System.Directory (doesFileExist, removeFile)
 import qualified System.Environment.Blank as Env
 
--- The three C ABI functions, linked at build time. The default build embeds the
+-- The four C ABI functions, linked at build time. The default build embeds the
 -- static archive; pkg-config builds may use a static or shared install. They
 -- are declared @safe@ because @secretspec_resolve@ may block on provider I/O
 -- (1Password, LastPass), and a @safe@ call lets other Haskell threads run.
 foreign import ccall safe "secretspec_resolve"
   c_secretspec_resolve :: CString -> IO CString
 
+foreign import ccall safe "secretspec_call"
+  c_secretspec_call :: CString -> IO CString
+
 foreign import ccall safe "secretspec_free"
   c_secretspec_free :: CString -> IO ()
 
@@ -173,18 +179,35 @@
   , bProfile  :: Maybe Text
   , bScope    :: Maybe Text
   , bReason   :: Maybe Text
+  , bCaller   :: Maybe CallerContext
   , bNoValues :: Bool
+  , bInline   :: Maybe (Value, Text)
   }
 
+-- | Caller-asserted software-integration context (SecretSpec 0.20+). It is
+-- audit metadata and never supplies the user access reason.
+data CallerContext = CallerContext
+  { callerName      :: Text
+  , callerVersion   :: Maybe Text
+  , callerOperation :: Maybe Text
+  , callerResource  :: Maybe Text
+  } deriving (Show, Eq)
+
 -- | A builder with no options set.
 builder :: Builder
-builder = Builder Nothing Nothing Nothing Nothing Nothing False
+builder = Builder Nothing Nothing Nothing Nothing Nothing Nothing False Nothing
 
 -- | Resolve from a manifest at this path instead of walking up from the working
 -- directory.
 withPath :: Text -> Builder -> Builder
-withPath v b = b { bPath = Just v }
+withPath v b = b { bPath = Just v, bInline = Nothing }
 
+-- | Resolve strict inline-spec v1 at its logical base directory (0.20+).
+-- The static linker requires @secretspec_call@, so an older native archive
+-- fails at link time instead of falling back to a filesystem manifest.
+withInlineSpec :: Value -> Text -> Builder -> Builder
+withInlineSpec spec baseDir b = b { bPath = Nothing, bInline = Just (spec, baseDir) }
+
 -- | Override the provider (a @keyring:\/\/@-style URI or a configured alias).
 withProvider :: Text -> Builder -> Builder
 withProvider v b = b { bProvider = Just v }
@@ -201,6 +224,10 @@
 withReason :: Text -> Builder -> Builder
 withReason v b = b { bReason = Just v }
 
+-- | Identify the invoking software integration (SecretSpec 0.20+).
+withCaller :: CallerContext -> Builder -> Builder
+withCaller v b = b { bCaller = Just v }
+
 -- | Omit secret values, returning only structure and provenance.
 withNoValues :: Bool -> Builder -> Builder
 withNoValues v b = b { bNoValues = v }
@@ -257,7 +284,7 @@
 -- missing, and 'SecretSpecError' for any other failure.
 load :: Builder -> IO Resolved
 load b = do
-  resp <- callNative (requestBytes b Nothing)
+  resp <- callNative (isInline b) (requestBytes b Nothing)
   value <- responseValue resp resolveSchemaVersion "resolve"
   (prov, prof, scope, secs, mreq, mopt) <- fromResult (parseEither pResolve value)
   case mreq of
@@ -279,7 +306,7 @@
 -- status @"missing_required"@.
 report :: Builder -> IO Report
 report b = do
-  resp <- callNative (requestBytes b (Just "report"))
+  resp <- callNative (isInline b) (requestBytes b (Just "report"))
   value <- responseValue resp reportSchemaVersion "report"
   (prov, prof, scope, secs) <- fromResult (parseEither pReport value)
   pure (Report prov prof scope secs)
@@ -295,16 +322,37 @@
 -- (@mode = Just "report"@), omitting unset options.
 requestBytes :: Builder -> Maybe Text -> BL.ByteString
 requestBytes b mode =
-  encode . object $
-    catMaybes
-      [ ("path" .=) <$> bPath b
-      , ("provider" .=) <$> bProvider b
-      , ("profile" .=) <$> bProfile b
-      , ("scope" .=) <$> bScope b
-      , ("reason" .=) <$> bReason b
+  case bInline b of
+    Nothing -> encode options
+    Just (spec, baseDir) -> encode $ object
+      [ "request_version" .= (1 :: Int)
+      , "operation" .= ("resolve" :: Text)
+      , "source" .= object
+          [ "kind" .= ("inline" :: Text)
+          , "spec_version" .= (1 :: Int)
+          , "base_dir" .= baseDir
+          , "spec" .= spec
+          ]
+      , "options" .= options
       ]
-      ++ ["no_values" .= True | bNoValues b]
-      ++ ["mode" .= m | Just m <- [mode]]
+  where
+    options = object $
+      catMaybes
+        [ ("path" .=) <$> bPath b
+        , ("provider" .=) <$> bProvider b
+        , ("profile" .=) <$> bProfile b
+        , ("scope" .=) <$> bScope b
+        , ("reason" .=) <$> bReason b
+        , ("caller" .=) . callerValue <$> bCaller b
+        ]
+        ++ ["no_values" .= True | bNoValues b]
+        ++ ["mode" .= m | Just m <- [mode]]
+    callerValue caller = object . catMaybes $
+      [ Just ("name" .= callerName caller)
+      , ("version" .=) <$> callerVersion caller
+      , ("operation" .=) <$> callerOperation caller
+      , ("resource" .=) <$> callerResource caller
+      ]
 
 -- Marshal a request to secretspec_resolve and copy the response out before
 -- freeing the native allocation.
@@ -314,13 +362,16 @@
 -- around 'load') from landing between the call returning and the free being
 -- installed, and @finally@ guarantees the free runs whether @packCString@
 -- succeeds, throws, or is interrupted — so the secret-bearing buffer never leaks.
-callNative :: BL.ByteString -> IO BS.ByteString
-callNative reqLazy =
+isInline :: Builder -> Bool
+isInline = maybe False (const True) . bInline
+
+callNative :: Bool -> BL.ByteString -> IO BS.ByteString
+callNative versioned reqLazy =
   BS.useAsCString (BL.toStrict reqLazy) $ \creq ->
     mask $ \restore -> do
-      cresp <- c_secretspec_resolve creq
+      cresp <- (if versioned then c_secretspec_call else c_secretspec_resolve) creq
       if cresp == nullPtr
-        then throwIO (SecretSpecError "ffi" "secretspec_resolve returned null")
+        then throwIO (SecretSpecError "ffi" (if versioned then "secretspec_call returned null" else "secretspec_resolve returned null"))
         else restore (BS.packCString cresp) `finally` c_secretspec_free cresp
 
 -- Decode the envelope, unwrap @ok@/@error@, and check the schema version,
@@ -347,7 +398,7 @@
     T.concat
       [ "unsupported ", kind, " schema version ", T.pack (show got)
       , " (expected ", T.pack (show expected)
-      , "); the secretspec-ffi library and this SDK are out of sync"
+      , "); the libsecretspec library and this SDK are out of sync"
       ]
 
 fromResult :: Either String a -> IO a
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -40,6 +40,8 @@
 
   let tests =
         [ ("abi_version_nonempty", testAbiVersion)
+        , ("caller_context_builder", testCallerContextBuilder)
+        , ("inline_spec", testInlineSpec)
         , ("missing_required_throws", testMissingRequired)
         , ("scoped_resolution", testScope)
         , ("codegen", testCodegen)
@@ -71,6 +73,37 @@
 testAbiVersion = do
   v <- S.abiVersion
   expect (not (T.null v)) "abi version was empty"
+
+testCallerContextBuilder :: IO ()
+testCallerContextBuilder = do
+  let caller = S.CallerContext "git" (Just "2.51.0")
+        (Just "credential_get") (Just "github.com")
+      configured = S.builder & S.withCaller caller
+  configured `seq` pure ()
+
+testInlineSpec :: IO ()
+testInlineSpec = do
+  tmp <- getTemporaryDirectory
+  let dir = tmp </> "secretspec-hs-inline"
+  createDirectoryIfMissing True dir
+  writeFile (dir </> "inline.env") "TOKEN=inline-haskell\n"
+  let spec = object
+        [ "project" .= object ["name" .= ("haskell-inline" :: Text)]
+        , "providers" .= object ["env" .= ("dotenv://inline.env" :: Text)]
+        , "profiles" .= object
+            [ "default" .= object
+                [ "secrets" .= object
+                    [ "TOKEN" .= object
+                        [ "description" .= ("token" :: Text)
+                        , "providers" .= (["env"] :: [Text])
+                        ]
+                    ]
+                ]
+            ]
+        ]
+  resolved <- S.load (S.builder & S.withInlineSpec spec (T.pack dir) & S.withReason "inline test")
+  let token = Map.lookup "TOKEN" (S.resolvedSecrets resolved) >>= S.get
+  expect (token == Just "inline-haskell") "inline spec did not resolve TOKEN"
 
 testMissingRequired :: IO ()
 testMissingRequired = do
