Repository: newrelic/newrelic-dotnet-agent
Pull request #3100: feat: Allow incoming W3C traceparent `sampled` flag to influence .NET agent sampling decision.
Author: login: tippmar-nr
Created: 2025-04-15T16:40:36Z | Merged: 2025-04-25T13:46:57Z

## Description
- Implements .NET agent support for respecting the W3C traceparent `sampled` flag as per the [Distributed Tracing agent spec](https://source.datanerd.us/agents/agent-specs/blob/main/distributed_tracing/Trace-Context-Payload.md#sampled). 
- Adds configuration to enable customers to control how the agent responds to an incoming traceparent header in both the sampled and not sampled case. Default configuration is to use our existing adaptive sampling logic to determine whether to sample spans.
```
  <distributedTracing enabled="true" ... >
    <sampler remoteParentSampled="default|alwaysOn|alwaysOff" remoteParentNotSampled="default|alwaysOn|alwaysOff">
  </distributedTracing>
```
Environment variables can also be used:
```
NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED=alwaysOn
NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED=alwaysOff
```
- When enabled, we will always sample (or not sample, depending on the configuration) spans in the agent based on the incoming traceparent `sampled` flag and configuration applied as shown in the following table:

| traceparent Sampled | remoteParentSampled config | remoteParentNotSampled config | agent sampling decision |
|--------|--------|--------|--------|
| missing / invalid | n/a | n/a | Use existing logic |
| 01 | `default` | n/a | Use existing logic |
| 01 | `alwaysOn` | n/a | Sampled = true |
| 01 | `alwaysOff` | n/a | Sampled = false |
| 00 | n/a | `default` | Use existing logic |
| 00 | n/a | `alwaysOn` | Sampled = true |
| 00 | n/a | `alwaysOff` | Sampled = false |

Includes unit and composite agent tests but does not include integration tests.

# Author Checklist
- [x] Unit tests, Integration tests, and Unbounded tests completed
- [x] Performance testing completed with satisfactory results (if required)

# Reviewer Checklist
- [ ] Perform code review
- [ ] Pull request was adequately tested (new/existing tests, performance tests)



Diff:
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 113da76ae0..994bb8403a 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,5 +1,6 @@
-When generating new source code files, always include the copyright header.
-
-When writing unit tests, always use modern NUnit assertions.
-
-We use JustMock.Lite, so use only mock configurations that are valid for that package.
\ No newline at end of file
+- Always use the latest C# features and syntax.
+- Use the latest C# language version available in the project.
+- Ensure all files include the standard license header at the top.
+- When writing unit tests, always use modern NUnit assertions.
+- We use JustMock.Lite, so use only mock configurations that are valid for that package.
+- Always include the required usings at the top of the file.
diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs
index 06bfb02b88..3fc98141b0 100644
--- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs
+++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.cs
@@ -3862,6 +3862,8 @@ public virtual configurationInfiniteTracingSpan_events Clone()
     public partial class configurationDistributedTracing
     {
         
+        private configurationDistributedTracingSampler samplerField;
+        
         private bool enabledField;
         
         private bool excludeNewrelicHeaderField;
@@ -3879,12 +3881,25 @@ public partial class configurationDistributedTracing
         /// </summary>
         public configurationDistributedTracing()
         {
+            this.samplerField = new configurationDistributedTracingSampler();
             this.enabledField = false;
             this.excludeNewrelicHeaderField = false;
             this.enableSuccessMetricsField = true;
             this.primary_application_idField = "Unknown";
         }
         
+        public configurationDistributedTracingSampler sampler
+        {
+            get
+            {
+                return this.samplerField;
+            }
+            set
+            {
+                this.samplerField = value;
+            }
+        }
+        
         [System.Xml.Serialization.XmlAttributeAttribute()]
         [System.ComponentModel.DefaultValueAttribute(false)]
         public bool enabled
@@ -3978,6 +3993,87 @@ public virtual configurationDistributedTracing Clone()
         #endregion
     }
     
+    [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")]
+    [System.SerializableAttribute()]
+    [System.ComponentModel.DesignerCategoryAttribute("code")]
+    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="urn:newrelic-config")]
+    public partial class configurationDistributedTracingSampler
+    {
+        
+        private RemoteParentSampledBehaviorType remoteParentSampledField;
+        
+        private RemoteParentSampledBehaviorType remoteParentNotSampledField;
+        
+        /// <summary>
+        /// configurationDistributedTracingSampler class constructor
+        /// </summary>
+        public configurationDistributedTracingSampler()
+        {
+            this.remoteParentSampledField = RemoteParentSampledBehaviorType.@default;
+            this.remoteParentNotSampledField = RemoteParentSampledBehaviorType.@default;
+        }
+        
+        [System.Xml.Serialization.XmlAttributeAttribute()]
+        [System.ComponentModel.DefaultValueAttribute(RemoteParentSampledBehaviorType.@default)]
+        public RemoteParentSampledBehaviorType remoteParentSampled
+        {
+            get
+            {
+                return this.remoteParentSampledField;
+            }
+            set
+            {
+                this.remoteParentSampledField = value;
+            }
+        }
+        
+        [System.Xml.Serialization.XmlAttributeAttribute()]
+        [System.ComponentModel.DefaultValueAttribute(RemoteParentSampledBehaviorType.@default)]
+        public RemoteParentSampledBehaviorType remoteParentNotSampled
+        {
+            get
+            {
+                return this.remoteParentNotSampledField;
+            }
+            set
+            {
+                this.remoteParentNotSampledField = value;
+            }
+        }
+        
+        #region Clone method
+        /// <summary>
+        /// Create a clone of this configurationDistributedTracingSampler object
+        /// </summary>
+        public virtual configurationDistributedTracingSampler Clone()
+        {
+            return ((configurationDistributedTracingSampler)(this.MemberwiseClone()));
+        }
+        #endregion
+    }
+    
+    [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")]
+    [System.SerializableAttribute()]
+    [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:newrelic-config")]
+    public enum RemoteParentSampledBehaviorType
+    {
+        
+        /// <summary>
+        /// Default behavior. The agent will use existing sampling logic.
+        /// </summary>
+        @default,
+        
+        /// <summary>
+        /// The agent will sample spans.
+        /// </summary>
+        alwaysOn,
+        
+        /// <summary>
+        /// The agent will NOT sample spans.
+        /// </summary>
+        alwaysOff,
+    }
+    
     [System.CodeDom.Compiler.GeneratedCodeAttribute("Xsd2Code", "3.6.0.20097")]
     [System.SerializableAttribute()]
     [System.ComponentModel.DesignerCategoryAttribute("code")]
diff --git a/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd b/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd
index 0fcc34e732..b10690a862 100644
--- a/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd
+++ b/src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd
@@ -44,6 +44,32 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:simpleType name="RemoteParentSampledBehaviorType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default">
+                <xs:annotation>
+                    <xs:documentation>
+                        Default behavior. The agent will use New Relic adaptive sampling logic.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="alwaysOn">
+                <xs:annotation>
+                    <xs:documentation>
+                        The agent will sample spans.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="alwaysOff">
+                <xs:annotation>
+                    <xs:documentation>
+                        The agent will NOT sample spans.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+        </xs:restriction>
+    </xs:simpleType>
+
     <xs:element name="configuration">
         <xs:complexType>
             <xs:all>
@@ -1214,6 +1240,26 @@
                         </xs:documentation>
                     </xs:annotation>
                     <xs:complexType>
+                        <xs:sequence>
+                            <xs:element name="sampler" minOccurs="0" maxOccurs="1">
+                                <xs:complexType>
+                                    <xs:attribute name="remoteParentSampled" type="tns:RemoteParentSampledBehaviorType" default="default">
+                                        <xs:annotation>
+                                            <xs:documentation>
+                                                Controls span sampling behavior when the trace parent was sampled.
+                                            </xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                    <xs:attribute name="remoteParentNotSampled" type="tns:RemoteParentSampledBehaviorType" default="default">
+                                        <xs:annotation>
+                                            <xs:documentation>
+                                                Controls span sampling behavior when the trace parent was not sampled.
+                                            </xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                </xs:complexType>
+                            </xs:element>
+                        </xs:sequence>
                         <xs:attribute name="enabled" type="xs:boolean" default="false">
                             <xs:annotation>
                                 <xs:documentation>
@@ -1722,7 +1768,7 @@
                                     </xs:attribute>
                                 </xs:complexType>
                             </xs:element>
-                            
+
                             <xs:element name="recordContent" minOccurs="0" maxOccurs="1">
                                 <xs:annotation>
                                     <xs:documentation>
@@ -1741,7 +1787,7 @@
                                 </xs:complexType>
                             </xs:element>
                         </xs:sequence>
-                        
+
                         <xs:attribute name="enabled" type="xs:boolean" default="false">
                             <xs:annotation>
                                 <xs:documentation>
@@ -1753,7 +1799,7 @@
                         </xs:attribute>
                     </xs:complexType>
                 </xs:element>
-                
+
                 <xs:element name="applicationLogging" minOccurs="0" maxOccurs="1">
                     <xs:annotation>
                         <xs:documentation>
diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationEnumHelpers.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationEnumHelpers.cs
new file mode 100644
index 0000000000..cca93f8916
--- /dev/null
+++ b/src/Agent/NewRelic/Agent/Core/Configuration/ConfigurationEnumHelpers.cs
@@ -0,0 +1,59 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using NewRelic.Agent.Configuration;
+using NewRelic.Agent.Core.Config;
+
+namespace NewRelic.Agent.Core.Configuration
+{
+    public static class ConfigurationEnumHelpers
+    {
+        public static RemoteParentSampledBehavior ToRemoteParentSampledBehavior(
+            this RemoteParentSampledBehaviorType localConfigEnumValue)
+        {
+            switch (localConfigEnumValue)
+            {
+                case RemoteParentSampledBehaviorType.@default:
+                    return RemoteParentSampledBehavior.Default;
+                case RemoteParentSampledBehaviorType.alwaysOn:
+                    return RemoteParentSampledBehavior.AlwaysOn;
+                case RemoteParentSampledBehaviorType.alwaysOff:
+                    return RemoteParentSampledBehavior.AlwaysOff;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(localConfigEnumValue), localConfigEnumValue, null);
+            }
+        }
+
+        public static RemoteParentSampledBehavior ToRemoteParentSampledBehavior(this string remoteParentSampledBehavior)
+        {
+            switch (remoteParentSampledBehavior.ToLower())
+            {
+                case "default":
+                    return RemoteParentSampledBehavior.Default;
+                case "alwayson":
+                    return RemoteParentSampledBehavior.AlwaysOn;
+                case "alwaysoff":
+                    return RemoteParentSampledBehavior.AlwaysOff;
+                default:
+                    return RemoteParentSampledBehavior.Default;
+            }
+        }
+
+        public static RemoteParentSampledBehaviorType ToRemoteParentSampledBehaviorType(
+            this RemoteParentSampledBehavior remoteParentSampledBehavior)
+        {
+            switch (remoteParentSampledBehavior)
+            {
+                case RemoteParentSampledBehavior.Default:
+                    return RemoteParentSampledBehaviorType.@default;
+                case RemoteParentSampledBehavior.AlwaysOn:
+                    return RemoteParentSampledBehaviorType.alwaysOn;
+                case RemoteParentSampledBehavior.AlwaysOff:
+                    return RemoteParentSampledBehaviorType.alwaysOff;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(remoteParentSampledBehavior), remoteParentSampledBehavior, null);
+            }
+        }
+    }
+}
diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs
index 7a3ce7175a..7cbfa10503 100644
--- a/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs
+++ b/src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs
@@ -1015,6 +1015,10 @@ private bool IsDistributedTracingEnabled()
 
         public bool ExcludeNewrelicHeader => _localConfiguration.distributedTracing.excludeNewrelicHeader;
 
+        public RemoteParentSampledBehavior RemoteParentSampledBehavior => EnvironmentOverrides(_localConfiguration.distributedTracing.sampler.remoteParentSampled.ToString(), "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED").ToRemoteParentSampledBehavior();
+
+        public RemoteParentSampledBehavior RemoteParentNotSampledBehavior => EnvironmentOverrides(_localConfiguration.distributedTracing.sampler.remoteParentNotSampled.ToString(), "NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED").ToRemoteParentSampledBehavior();
+
         #endregion Distributed Tracing
 
         #region Infinite Tracing
diff --git a/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs b/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs
index d4d06a921f..a2f4ab1457 100644
--- a/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs
+++ b/src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs
@@ -207,6 +207,11 @@ public ReportedConfiguration(IConfiguration configuration)
         [JsonProperty("distributed_tracing.enabled")]
         public bool DistributedTracingEnabled => _configuration.DistributedTracingEnabled;
 
+        [JsonProperty("distributed_tracing.sampler.remote_parent_sampled")]
+        public RemoteParentSampledBehavior RemoteParentSampledBehavior { get; }
+        [JsonProperty("distributed_tracing.sampler.remote_parent_not_sampled")]
+        public RemoteParentSampledBehavior RemoteParentNotSampledBehavior { get; }
+
         [JsonProperty("span_events.enabled")]
         public bool SpanEventsEnabled => _configuration.SpanEventsEnabled;
 
diff --git a/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs b/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs
index a1ee72fd25..1f62a4b8f0 100644
--- a/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs
+++ b/src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs
@@ -264,7 +264,15 @@ public ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T, string,
 
             try
             {
-                var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier, getter, transportType, _configurationService.Configuration.TrustedAccountKey, transactionStartTime);
+                var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                    carrier,
+                    getter,
+                    transportType,
+                    _configurationService.Configuration.TrustedAccountKey,
+                    transactionStartTime,
+                    _configurationService.Configuration.RemoteParentSampledBehavior,
+                    _configurationService.Configuration.RemoteParentNotSampledBehavior
+                );
 
                 if (tracingState?.IngestErrors != null)
                 {
diff --git a/src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs b/src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
index c101474a70..c2dd96295d 100644
--- a/src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
+++ b/src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
@@ -6,6 +6,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using NewRelic.Agent.Configuration;
+using NewRelic.Agent.Extensions.Logging;
 
 namespace NewRelic.Agent.Core.DistributedTracing
 {
@@ -14,7 +16,6 @@ public class TracingState : ITracingState
         // not valid for this enum, but will be replaced on first call to Type
         private DistributedTracingParentType _type = (DistributedTracingParentType)(-2);
         private DateTime _timestamp;
-        private bool? _sampled;
         private bool _validTracestateWasAccepted = false;
         private DateTime _transactionStartTime;
 
@@ -148,44 +149,40 @@ public string TransactionId
             }
         }
 
+        private bool? _sampled;
         public bool? Sampled
         {
             get
             {
-                if (_sampled != null && _sampled.HasValue)
-                {
+                if (_sampled is not null)
                     return _sampled;
-                }
 
-                if (_traceContext?.Tracestate != null && _traceContext.Tracestate.Sampled.HasValue)
-                {
+                if (_traceContext?.Tracestate is { Sampled: not null })
                     return _sampled = Convert.ToBoolean(_traceContext.Tracestate.Sampled);
-                }
 
-                if (_newRelicPayload?.Sampled != null && _newRelicPayload.Sampled.HasValue)
-                {
+                if (_newRelicPayload?.Sampled is not null)
                     return _sampled = _newRelicPayload.Sampled;
-                }
 
                 return null;
             }
         }
 
+        private float? _priority;
         public float? Priority
         {
             get
             {
-                if (_traceContext?.Tracestate != null)
+                if (_priority != null)
                 {
-                    return _traceContext.Tracestate.Priority;
+                    return _priority;
                 }
 
-                if (_newRelicPayload?.Priority != null && _newRelicPayload.Priority.HasValue)
+                if (_traceContext?.Tracestate is { Priority: not null })
                 {
-                    return _newRelicPayload.Priority;
+                    return _traceContext.Tracestate.Priority;
                 }
 
-                return null;
+                return _newRelicPayload?.Priority;
             }
         }
 
@@ -204,7 +201,14 @@ public float? Priority
         private DistributedTracePayload _newRelicPayload;
         private W3CTraceContext _traceContext;
 
-        public static ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T, string, IEnumerable<string>> getter, TransportType transportType, string agentTrustKey, DateTime transactionStartTime)
+        public static ITracingState AcceptDistributedTraceHeaders<T>(
+            T carrier,
+            Func<T, string, IEnumerable<string>> getter,
+            TransportType transportType,
+            string agentTrustKey,
+            DateTime transactionStartTime,
+            RemoteParentSampledBehavior remoteParentSampledBehavior,
+            RemoteParentSampledBehavior remoteParentNotSampledBehavior)
         {
             var tracingState = new TracingState();
             var errors = new List<IngestErrorType>();
@@ -217,6 +221,7 @@ public static ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T,
 
             tracingState._validTracestateWasAccepted = tracingState._traceContext?.Tracestate?.AccountKey != null ? true : false;
 
+            tracingState.ApplyRemoteParentSampledBehavior(remoteParentSampledBehavior, remoteParentNotSampledBehavior);
 
             // newrelic 
             // if traceparent was present (regardless if valid), ignore newrelic header
@@ -254,5 +259,49 @@ public static ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T,
 
             return tracingState;
         }
+
+        /// <summary>
+        /// Use remote parent sampled behavior configuration in conjunction with the traceparent sampled flag to determine
+        /// if the transaction should be sampled.
+        /// </summary>
+        private void ApplyRemoteParentSampledBehavior(
+            RemoteParentSampledBehavior remoteParentSampledBehavior,
+            RemoteParentSampledBehavior remoteParentNotSampledBehavior)
+        {
+            // don't do anything if the traceparent is not present or if behavior is configured to default
+            if (!_traceContext.TraceparentPresent ||
+                (remoteParentNotSampledBehavior == RemoteParentSampledBehavior.Default &&
+                 remoteParentSampledBehavior == RemoteParentSampledBehavior.Default))
+            {
+                return;
+            }
+
+            var sampledBehavior = _traceContext.Traceparent.Sampled
+                ? remoteParentSampledBehavior
+                : remoteParentNotSampledBehavior;
+
+            switch (sampledBehavior)
+            {
+                case RemoteParentSampledBehavior.AlwaysOn:
+                    _priority = 2.0f; // per the spec, set priority high so that this sample is always kept
+                    _sampled = true;
+                    break;
+                case RemoteParentSampledBehavior.AlwaysOff:
+                    _priority = 0.0f; // set lowest possible priority
+                    _sampled = false;
+                    break;
+                // don't need a case for Default, as it is handled above
+                default:
+                    throw new ArgumentException($"Invalid {(_traceContext.Traceparent.Sampled ? "remoteParentSampledBehavior" : "remoteParentNotSampledBehavior")} value: {sampledBehavior}.");
+            }
+
+            Log.Finest("ApplyRemoteParentSampledBehavior:  _traceContext.Traceparent.Sampled={SampledValue}, {ParentType}SampledBehavior: {SampledBehavior} ==> Sampled: {Sampled}, Priority: {Priority}",
+                    _traceContext.Traceparent.Sampled,
+                    _sampled.Value ? "remoteParent" : "remoteParentNot",
+                    sampledBehavior,
+                    Sampled,
+                    Priority
+                );
+        }
     }
 }
diff --git a/src/Agent/NewRelic/Agent/Core/DistributedTracing/W3CTraceparent.cs b/src/Agent/NewRelic/Agent/Core/DistributedTracing/W3CTraceparent.cs
index c9349e4076..df6d508710 100644
--- a/src/Agent/NewRelic/Agent/Core/DistributedTracing/W3CTraceparent.cs
+++ b/src/Agent/NewRelic/Agent/Core/DistributedTracing/W3CTraceparent.cs
@@ -1,8 +1,10 @@
 // Copyright 2020 New Relic, Inc. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
+using System;
 using System.Collections.Generic;
 using System.Text.RegularExpressions;
+using NewRelic.Agent.Core.Utilities;
 
 namespace NewRelic.Agent.Core.DistributedTracing
 {
@@ -48,11 +50,23 @@ public class W3CTraceparent
         public string ParentId { get; } // 8 bytes
 
         /// <summary>
-        /// TraceFlags is a 2 character string that represents an 8-bit field that controls tracing flags such as sampling, trace level, etc. 
+        /// TraceFlags is a 2 character hex string that represents an 8-bit field that controls tracing flags such as sampling, trace level, etc. 
         /// 
         /// As this is a bit field, you cannot interpret flags by decoding the hex value and looking at the resulting number.
         /// </summary>
-        public string TraceFlags { get; } // 2 bytes/8 bits
+        public string TraceFlags { get; } // 2 hex characters / 1 byte / 8 bits
+
+        private const byte FLAG_SAMPLED = 1; // 0b00000001
+
+        public bool Sampled
+        {
+            get
+            {
+                // see https://www.w3.org/TR/trace-context/#trace-flags
+                var traceFlagsInt = TraceFlags.AsSpan().FromHexString();
+                return (traceFlagsInt[0] & FLAG_SAMPLED) == FLAG_SAMPLED;
+            }
+        }
 
         /// <summary>
         /// This is used to create the object and expects to only be called with validated values from the two CreateW3CTraceparent builders.
diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs b/src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
new file mode 100644
index 0000000000..c18847667c
--- /dev/null
+++ b/src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
@@ -0,0 +1,51 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace NewRelic.Agent.Core.Utilities
+{
+    public static class HexStringConverter
+    {
+        /// <summary>
+        /// Converts a string of hex bytes (2 characters per byte) to a byte array.
+        /// </summary>
+        /// <exception cref="ArgumentException">Hex string must have an even length</exception>
+        public static byte[] FromHexString(this ReadOnlySpan<char> chars)
+        {
+            if (chars.Length % 2 != 0)
+            {
+                throw new ArgumentException("Hex string must have an even length.", nameof(chars));
+            }
+
+            byte[] result = new byte[chars.Length / 2];
+
+            for (int i = 0; i < chars.Length; i += 2)
+            {
+                result[i / 2] = (byte)((GetHexValue(chars[i]) << 4) + GetHexValue(chars[i + 1]));
+            }
+
+            return result;
+        }
+
+        private static int GetHexValue(char hex)
+        {
+            if (hex is >= '0' and <= '9')
+            {
+                return hex - '0';
+            }
+            else if (hex is >= 'a' and <= 'f')
+            {
+                return 10 + (hex - 'a');
+            }
+            else if (hex is >= 'A' and <= 'F')
+            {
+                return 10 + (hex - 'A');
+            }
+            else
+            {
+                throw new ArgumentException($"Invalid hexadecimal character: {hex}");
+            }
+        }
+    }
+}
diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs
index 555d1cd4d0..717917f4b1 100644
--- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs
@@ -190,6 +190,10 @@ public interface IConfiguration
         bool ForceSynchronousTimingCalculationHttpClient { get; }
         bool EnableAspNetCore6PlusBrowserInjection { get; }
         bool ExcludeNewrelicHeader { get; }
+
+        RemoteParentSampledBehavior RemoteParentSampledBehavior { get; }
+        RemoteParentSampledBehavior RemoteParentNotSampledBehavior { get; }
+
         bool ApplicationLoggingEnabled { get; }
         bool LogMetricsCollectorEnabled { get; }
         bool LogEventCollectorEnabled { get; }
diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/RemoteParentSampledBehavior.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/RemoteParentSampledBehavior.cs
new file mode 100644
index 0000000000..12b1bc8247
--- /dev/null
+++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/RemoteParentSampledBehavior.cs
@@ -0,0 +1,22 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace NewRelic.Agent.Configuration
+{
+    [JsonConverter(typeof(StringEnumConverter))]
+    public enum RemoteParentSampledBehavior
+    {
+        [EnumMember(Value = "default")]
+        Default,
+
+        [EnumMember(Value = "always_on")]
+        AlwaysOn,
+
+        [EnumMember(Value = "always_off")]
+        AlwaysOff
+    }
+}
diff --git a/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs b/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs
index 934f18bbe5..47d5cac9dd 100644
--- a/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs
+++ b/tests/Agent/UnitTests/CompositeTests/CompositeTestAgent.cs
@@ -133,7 +133,7 @@ public CompositeTestAgent(bool enableServerlessMode = false) : this(shouldAllowT
         {
         }
 
-        public CompositeTestAgent(bool shouldAllowThreads, bool includeAsyncLocalStorage, bool enableServerlessMode = false, bool enableGCSamplerV2 = false)
+        public CompositeTestAgent(bool shouldAllowThreads, bool includeAsyncLocalStorage, bool enableServerlessMode = false, bool enableGCSamplerV2 = false, RemoteParentSampledBehavior remoteParentSampledBehavior = RemoteParentSampledBehavior.Default, RemoteParentSampledBehavior remoteParentNotSampledBehavior = RemoteParentSampledBehavior.Default)
         {
             Log.Initialize(new Logger());
 
@@ -227,7 +227,7 @@ public CompositeTestAgent(bool shouldAllowThreads, bool includeAsyncLocalStorage
             AgentApi.SetSupportabilityMetricCounters(_container.Resolve<IApiSupportabilityMetricCounters>());
 
             // Update configuration (will also start services)
-            LocalConfiguration = GetDefaultTestLocalConfiguration();
+            LocalConfiguration = GetDefaultTestLocalConfiguration(remoteParentSampledBehavior, remoteParentNotSampledBehavior);
             ServerConfiguration = GetDefaultTestServerConfiguration();
             SecurityConfiguration = GetDefaultSecurityPoliciesConfiguration();
             InstrumentationWatcher.Start();
@@ -410,12 +410,14 @@ private void EnableAggregators()
             EventBus<AgentConnectedEvent>.Publish(new AgentConnectedEvent());
         }
 
-        private static configuration GetDefaultTestLocalConfiguration()
+        private static configuration GetDefaultTestLocalConfiguration(RemoteParentSampledBehavior remoteParentSampledBehavior, RemoteParentSampledBehavior remoteParentNotSampledBehavior)
         {
             var configuration = new configuration();
 
             // Distributed tracing is disabled by default. However, we have fewer tests that need it disabled than we do that need it enabled.
             configuration.distributedTracing.enabled = true;
+            configuration.distributedTracing.sampler.remoteParentSampled = remoteParentSampledBehavior.ToRemoteParentSampledBehaviorType();
+            configuration.distributedTracing.sampler.remoteParentNotSampled = remoteParentNotSampledBehavior.ToRemoteParentSampledBehaviorType();
 
             return configuration;
         }
diff --git a/tests/Agent/UnitTests/CompositeTests/RemoteParentSampledBehaviorTests.cs b/tests/Agent/UnitTests/CompositeTests/RemoteParentSampledBehaviorTests.cs
new file mode 100644
index 0000000000..8fd5feec51
--- /dev/null
+++ b/tests/Agent/UnitTests/CompositeTests/RemoteParentSampledBehaviorTests.cs
@@ -0,0 +1,78 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using NewRelic.Agent.Api;
+using NewRelic.Agent.Configuration;
+using NewRelic.Agent.TestUtilities;
+using NUnit.Framework;
+using System.Collections.Generic;
+using NewRelic.Agent.Extensions.Providers.Wrapper;
+
+namespace CompositeTests
+{
+    public class RemoteParentSampledBehaviorTests
+    {
+        private CompositeTestAgent _compositeTestAgent;
+        private IAgent _agent;
+
+        [TearDown]
+        public void TearDown()
+        {
+            _compositeTestAgent?.Dispose();
+        }
+
+        [TestCase(RemoteParentSampledBehavior.AlwaysOn, "01", true, TestName = "AlwaysOn_WithSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.AlwaysOn, "00", true, TestName = "AlwaysOn_WithNotSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.AlwaysOff, "01", false, TestName = "AlwaysOff_WithSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.AlwaysOff, "00", false, TestName = "AlwaysOff_WithNotSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.Default, "01", true, TestName = "Default_WithSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.Default, "00", true, TestName = "Default_WithNotSampledTraceparent")] // uses existing logic
+        public void RemoteParentSampledBehavior_Combinations(RemoteParentSampledBehavior behavior, string traceparentSampledFlag, bool expectedSampled)
+        {
+            // Initialize CompositeTestAgent with the specified RemoteParentSampledBehavior
+            _compositeTestAgent = new CompositeTestAgent(false, false,
+                remoteParentSampledBehavior: behavior,
+                remoteParentNotSampledBehavior: behavior);
+            _agent = _compositeTestAgent.GetAgent();
+
+            // Arrange
+            var transaction = _agent.CreateTransaction(
+                isWeb: true,
+                category: "WebTransaction",
+                transactionDisplayName: $"{behavior}_Transaction",
+                doNotTrackAsUnitOfWork: false);
+
+            var headers = new Dictionary<string, string>
+            {
+                { "traceparent", $"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-{traceparentSampledFlag}" }
+            };
+
+            _agent.CurrentTransaction.AcceptDistributedTraceHeaders(headers, (carrier, key) => carrier.ContainsKey(key) ? new[] { carrier[key] } : null, TransportType.HTTP);
+
+            // Act
+            var segment = _agent.StartTransactionSegmentOrThrow($"{behavior}_Segment");
+            segment.End();
+            transaction.End();
+
+            _compositeTestAgent.Harvest();
+
+            // Assert
+            var spanEvents = _compositeTestAgent.SpanEvents;
+            if (behavior == RemoteParentSampledBehavior.AlwaysOff) // if we don't sample, there will be no span events
+            {
+                Assert.That(spanEvents, Is.Empty);
+                return;
+            }
+            else
+            {
+                Assert.That(spanEvents, Is.Not.Empty);
+
+                foreach (var span in spanEvents)
+                {
+                    var intrinsicAttributes = span.IntrinsicAttributes();
+                    Assert.That(intrinsicAttributes["sampled"], Is.EqualTo(expectedSampled));
+                }
+            }
+        }
+    }
+}
diff --git a/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationEnumHelpersTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationEnumHelpersTests.cs
new file mode 100644
index 0000000000..d5cfd779e8
--- /dev/null
+++ b/tests/Agent/UnitTests/Core.UnitTest/Configuration/ConfigurationEnumHelpersTests.cs
@@ -0,0 +1,51 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using NewRelic.Agent.Configuration;
+using NewRelic.Agent.Core.Config;
+using NewRelic.Agent.Core.Configuration;
+using NUnit.Framework;
+
+namespace NewRelic.Agent.Core.Configuration.Tests
+{
+    [TestFixture]
+    public class ConfigurationEnumHelpersTests
+    {
+        [Test]
+        public void ToRemoteParentSampledBehavior_ValidEnumValues_ReturnsExpectedResults()
+        {
+            Assert.Multiple(() =>
+            {
+                Assert.That(RemoteParentSampledBehaviorType.@default.ToRemoteParentSampledBehavior(), Is.EqualTo(RemoteParentSampledBehavior.Default));
+                Assert.That(RemoteParentSampledBehaviorType.alwaysOn.ToRemoteParentSampledBehavior(), Is.EqualTo(RemoteParentSampledBehavior.AlwaysOn));
+                Assert.That(RemoteParentSampledBehaviorType.alwaysOff.ToRemoteParentSampledBehavior(), Is.EqualTo(RemoteParentSampledBehavior.AlwaysOff));
+            });
+        }
+
+        [Test]
+        public void ToRemoteParentSampledBehavior_InvalidEnumValue_ThrowsArgumentOutOfRangeException()
+        {
+            var invalidValue = (RemoteParentSampledBehaviorType)999;
+            Assert.Throws<ArgumentOutOfRangeException>(() => invalidValue.ToRemoteParentSampledBehavior());
+        }
+
+        [Test]
+        public void ToRemoteParentSampledBehaviorType_ValidEnumValues_ReturnsExpectedResults()
+        {
+            Assert.Multiple(() =>
+            {
+                Assert.That(RemoteParentSampledBehavior.Default.ToRemoteParentSampledBehaviorType(), Is.EqualTo(RemoteParentSampledBehaviorType.@default));
+                Assert.That(RemoteParentSampledBehavior.AlwaysOn.ToRemoteParentSampledBehaviorType(), Is.EqualTo(RemoteParentSampledBehaviorType.alwaysOn));
+                Assert.That(RemoteParentSampledBehavior.AlwaysOff.ToRemoteParentSampledBehaviorType(), Is.EqualTo(RemoteParentSampledBehaviorType.alwaysOff));
+            });
+        }
+
+        [Test]
+        public void ToRemoteParentSampledBehaviorType_InvalidEnumValue_ThrowsArgumentOutOfRangeException()
+        {
+            var invalidValue = (RemoteParentSampledBehavior)999;
+            Assert.Throws<ArgumentOutOfRangeException>(() => invalidValue.ToRemoteParentSampledBehaviorType());
+        }
+    }
+}
diff --git a/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs
index 03002a6b7b..250cfd7b95 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs
@@ -6,6 +6,7 @@
 using System.IO;
 using System.Linq;
 using System.Xml.Serialization;
+using NewRelic.Agent.Configuration;
 using NewRelic.Agent.Core.AgentHealth;
 using NewRelic.Agent.Core.Config;
 using NewRelic.Agent.Core.DataTransport;
@@ -4604,6 +4605,40 @@ public void MissingApplicationName_SetsApplicationNameMissing_AgentControlStatus
 
         }
 
+        [TestCase("alwaysOn", RemoteParentSampledBehavior.AlwaysOn, TestName = "RemoteParentSampledBehavior_AlwaysOn_EnvironmentVariableOverride")]
+        [TestCase("AlWaYSOn", RemoteParentSampledBehavior.AlwaysOn, TestName = "RemoteParentSampledBehavior_AlwaysOnMixedCase_EnvironmentVariableOverride")]
+        [TestCase("alwaysOff", RemoteParentSampledBehavior.AlwaysOff, TestName = "RemoteParentSampledBehavior_AlwaysOff_EnvironmentVariableOverride")]
+        [TestCase("default", RemoteParentSampledBehavior.Default, TestName = "RemoteParentSampledBehavior_Default_EnvironmentVariableOverride")]
+        [TestCase("invalidValue", RemoteParentSampledBehavior.Default, TestName = "RemoteParentSampledBehavior_InvalidValueDefaultsToDefault_EnvironmentVariableOverride")]
+        public void RemoteParentSampledBehavior_UsesEnvironmentVariableOverride(string environmentVariableValue, RemoteParentSampledBehavior expectedRemoteParentSampledBehavior)
+        {
+            // Arrange
+            Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED"))
+                .Returns(environmentVariableValue);
+
+            // Act
+            var result = _defaultConfig.RemoteParentSampledBehavior;
+
+            // Assert
+            Assert.That(result, Is.EqualTo(expectedRemoteParentSampledBehavior));
+        }
+
+        [TestCase("alwaysOn", RemoteParentSampledBehavior.AlwaysOn, TestName = "RemoteParentNotSampledBehavior_AlwaysOn_EnvironmentVariableOverride")]
+        [TestCase("alwaysOff", RemoteParentSampledBehavior.AlwaysOff, TestName = "RemoteParentNotSampledBehavior_AlwaysOff_EnvironmentVariableOverride")]
+        [TestCase("default", RemoteParentSampledBehavior.Default, TestName = "RemoteParentNotSampledBehavior_Default_EnvironmentVariableOverride")]
+        [TestCase("invalidValue", RemoteParentSampledBehavior.Default, TestName = "RemoteParentNotSampledBehavior_InvalidValueDefaultsToDefault_EnvironmentVariableOverride")]
+        public void RemoteParentNotSampledBehavior_UsesEnvironmentVariableOverride(string environmentVariableValue, RemoteParentSampledBehavior expectedRemoteParentSampledBehavior)
+        {
+            // Arrange
+            Mock.Arrange(() => _environment.GetEnvironmentVariableFromList("NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED"))
+                .Returns(environmentVariableValue);
+
+            // Act
+            var result = _defaultConfig.RemoteParentNotSampledBehavior;
+
+            // Assert
+            Assert.That(result, Is.EqualTo(expectedRemoteParentSampledBehavior));
+        }
 
         private DefaultConfiguration GenerateConfigFromXml(string xml)
         {
diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs
index 989adf183a..9a6471e953 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/AgentSettingsTests.cs
@@ -5,6 +5,8 @@
 using NUnit.Framework;
 using NewRelic.Agent.Core.DataTransport;
 using NewRelic.Agent.TestUtilities;
+using Newtonsoft.Json.Converters;
+using System.Collections.Generic;
 
 namespace NewRelic.Agent.Core.Configuration
 {
@@ -76,6 +78,8 @@ public void serializes_correctly()
                     "cross_application_tracer.cross_process_id": "CrossApplicationTracingCrossProcessId",
                     "cross_application_tracer.enabled": true,
                     "distributed_tracing.enabled": true,
+                    "distributed_tracing.sampler.remote_parent_sampled": "default",
+                    "distributed_tracing.sampler.remote_parent_not_sampled": "default",
                     "span_events.enabled": true,
                     "span_events.harvest_cycle": "00:20:34",
                     "span_events.attributes_enabled": true,
diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs
index 4516c72e2f..d9cc3c0f6c 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ConnectModelTests.cs
@@ -147,6 +147,8 @@ public void serializes_correctly()
                             "cross_application_tracer.cross_process_id": "CrossApplicationTracingCrossProcessId",
                             "cross_application_tracer.enabled": true,
                             "distributed_tracing.enabled": true,
+                            "distributed_tracing.sampler.remote_parent_sampled": "default",
+                            "distributed_tracing.sampler.remote_parent_not_sampled": "default",
                             "span_events.enabled": true,
                             "span_events.harvest_cycle": "00:20:34",
                             "span_events.attributes_enabled": true,
diff --git a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs
index 07f36d857c..ee7b4586c0 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/DataTransport/ExhaustiveTestConfiguration.cs
@@ -414,6 +414,9 @@ public class ExhaustiveTestConfiguration : IConfiguration
 
         public bool ExcludeNewrelicHeader => true;
 
+        public RemoteParentSampledBehavior RemoteParentSampledBehavior => RemoteParentSampledBehavior.Default;
+        public RemoteParentSampledBehavior RemoteParentNotSampledBehavior => RemoteParentSampledBehavior.Default;
+
         public bool ApplicationLoggingEnabled => true;
 
         public bool LogMetricsCollectorEnabled => true;
diff --git a/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/TracingStateTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/TracingStateTests.cs
index bd9b45c31d..34c9e96942 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/TracingStateTests.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/TracingStateTests.cs
@@ -7,6 +7,7 @@
 using NewRelic.Agent.Core.DistributedTracing;
 using NewRelic.Agent.Extensions.Providers.Wrapper;
 using NewRelic.Agent.Core.Utilities;
+using NewRelic.Agent.Configuration;
 
 namespace NewRelic.Agent.Core.DistributedTracing
 {
@@ -26,10 +27,12 @@ public class TracingStateTests
         private static DateTime Timestamp = DateTime.UtcNow;
 
         private const string ValidTraceparent = "00-" + TraceId + "-" + ParentId + "-01";
+        private const string ValidTraceparentNotSampled = "00-" + TraceId + "-" + ParentId + "-00";
 
         //example ValidTracestate: "33@nr=0-0-33-5043-27ddd2d8890283b4-5569065a5b1313bd-1-1.23456-1518469636025,dd=YzRiMTIxODk1NmVmZTE4ZQ,44@nr=0-0-55-5043-1238890283aasdfs-4569065a5b131bbg-1-1.23456-1518469636020";
         private static readonly string ValidTracestate = TrustKey + "@nr=0-" + (int)Type + "-" + AccountId + "-" + AppId + "-" + Guid + "-" + TransactionId + "-1-" + Priority + "-" + Timestamp.ToUnixTimeMilliseconds() + ",dd=YzRiMTIxODk1NmVmZTE4ZQ,44@nr=0-0-55-5043-1238890283aasdfs-4569065a5b131bbg-1-1.23456-1518469636020";
 
+
         // v:[2,5]
         private const string NewRelicPayloadWithUnsupportedVersion = "{ \"v\":[2,5],\"d\":{\"ty\":\"HTTP\",\"ac\":\"accountId\",\"ap\":\"appId\",\"tr\":\"traceId\",\"pr\":0.65,\"sa\":true,\"ti\":0,\"tk\":\"trustKey\",\"tx\":\"transactionId\",\"id\":\"guid\"}}";
         // ti:0
@@ -51,7 +54,15 @@ public void AcceptDistributedTraceHeadersHydratesValidNewRelicPayload(string hea
                 { headerName, encodedPayload }
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -79,7 +90,15 @@ public void AcceptDistributedTraceHeadersPopulatesErrorsIfNull(string headerName
                 { headerName, null }
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -111,7 +130,15 @@ public void AcceptDistributedTraceHeadersPopulatesErrorsIfUnsupportedVersion(str
                 { headerName, encodedPayload }
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -143,7 +170,15 @@ public void AcceptDistributedTraceHeadersPopulatesErrorsIfInvalidTimestamp(strin
                 { headerName, encodedPayload }
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -175,7 +210,15 @@ public void AcceptDistributedTraceHeadersPopulatesErrorsIfNotTraceable(string he
                 { headerName, encodedPayload }
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.Other, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.Other,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -208,7 +251,15 @@ public void AcceptDistributedTraceHeadersHydratesValidW3CTraceContext()
                 { "tracestate", ValidTracestate },
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -236,7 +287,15 @@ public void AcceptDistributedTraceHeadersHydratesValidW3CTraceContext_WithoutAVa
                 { "tracestate", "aa=1,bb=2" },
             };
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.Multiple(() =>
@@ -272,11 +331,132 @@ public void AcceptDistributedTraceHeadersPopulateErrorIfTraceParentNotParsable()
                 return string.IsNullOrEmpty(value) ? null : new List<string> { value };
             });
 
-            var tracingState = TracingState.AcceptDistributedTraceHeaders(carrier: headers, getter: GetHeader, transportType: TransportType.AMQP, agentTrustKey: TrustKey, transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)));
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(1)),
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
 
             Assert.That(tracingState, Is.Not.Null);
             Assert.That(tracingState.IngestErrors, Does.Contain(IngestErrorType.TraceParentParseException), "TracingState IngestErrors should contain TraceParentParseException.");
         }
+
+        [TestCase(true, true, RemoteParentSampledBehavior.AlwaysOn, RemoteParentSampledBehavior.Default, true, 2.0f, TestName = "TraceParentSampled_AlwaysOn")]
+        [TestCase(true, true, RemoteParentSampledBehavior.AlwaysOff, RemoteParentSampledBehavior.Default, false, 0f, TestName = "TraceParentSampled_AlwaysOff")]
+        [TestCase(true, true, RemoteParentSampledBehavior.Default, RemoteParentSampledBehavior.Default, true, 0.65f, TestName = "TraceParentSampled_Default")]
+        [TestCase(true, false, RemoteParentSampledBehavior.Default, RemoteParentSampledBehavior.AlwaysOn, true, 2.0f, TestName = "TraceParentNotSampled_AlwaysOn")]
+        [TestCase(true, false, RemoteParentSampledBehavior.Default, RemoteParentSampledBehavior.AlwaysOff, false, 0f, TestName = "TraceParentNotSampled_AlwaysOff")]
+        [TestCase(true, false, RemoteParentSampledBehavior.Default, RemoteParentSampledBehavior.Default, true, 0.65f, TestName = "TraceParentNotSampled_Default")]
+        [TestCase(false, false, RemoteParentSampledBehavior.Default, RemoteParentSampledBehavior.Default, null, null, TestName = "TraceParentNotValid")]
+        public void Sampled_TestMatrix(
+            bool traceParentValid,
+            bool traceParentSampled,
+            RemoteParentSampledBehavior remoteParentSampledBehavior,
+            RemoteParentSampledBehavior remoteParentNotSampledBehavior,
+            bool? expectedSampled, float? expectedPriority)
+        {
+            // Arrange
+            var traceparent = traceParentValid ? traceParentSampled ? ValidTraceparent : ValidTraceparentNotSampled : null;
+            var tracestate = ValidTracestate;
+
+            var headers = new Dictionary<string, string>();
+            if (traceparent != null)
+            {
+                headers["traceparent"] = traceparent;
+            }
+            if (tracestate != null)
+            {
+                headers["tracestate"] = tracestate;
+            }
+
+            // Act
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow,
+                remoteParentSampledBehavior,
+                remoteParentNotSampledBehavior
+            );
+
+            // Assert
+            Assert.That(tracingState.Sampled, Is.EqualTo(expectedSampled));
+            Assert.That(tracingState.Priority, Is.EqualTo(expectedPriority));
+        }
+
+        [Test]
+        public void Sampled_ThrowsException_WhenInvalidRemoteParentSampledBehavior()
+        {
+            var headers = new Dictionary<string, string>
+            {
+                { "traceparent", ValidTraceparent },
+                { "tracestate", ValidTracestate }
+            };
+
+            Assert.Throws<ArgumentException>(() =>
+            {
+                TracingState.AcceptDistributedTraceHeaders(
+                    carrier: headers,
+                    getter: GetHeader,
+                    transportType: TransportType.AMQP,
+                    agentTrustKey: TrustKey,
+                    transactionStartTime: DateTime.UtcNow,
+                    remoteParentSampledBehavior: (RemoteParentSampledBehavior)999, // Invalid enum value
+                    remoteParentNotSampledBehavior: RemoteParentSampledBehavior.Default
+                );
+            });
+        }
+
+        [Test]
+        public void Sampled_ThrowsException_WhenInvalidRemoteParentNotSampledBehavior()
+        {
+            var headers = new Dictionary<string, string>
+            {
+                { "traceparent", ValidTraceparentNotSampled },
+                { "tracestate", ValidTracestate }
+            };
+
+            Assert.Throws<ArgumentException>(() =>
+            {
+                TracingState.AcceptDistributedTraceHeaders(
+                    carrier: headers,
+                    getter: GetHeader,
+                    transportType: TransportType.AMQP,
+                    agentTrustKey: TrustKey,
+                    transactionStartTime: DateTime.UtcNow,
+                    remoteParentSampledBehavior: RemoteParentSampledBehavior.Default,
+                    remoteParentNotSampledBehavior: (RemoteParentSampledBehavior)999 // Invalid enum value
+                );
+            });
+        }
+
+        [Test]
+        public void Sampled_UsesTraceContextSampledValue_WhenBehaviorIsDefault()
+        {
+            var headers = new Dictionary<string, string>
+            {
+                { "traceparent", ValidTraceparent },
+                { "tracestate", ValidTracestate }
+            };
+
+            var tracingState = TracingState.AcceptDistributedTraceHeaders(
+                carrier: headers,
+                getter: GetHeader,
+                transportType: TransportType.AMQP,
+                agentTrustKey: TrustKey,
+                transactionStartTime: DateTime.UtcNow,
+                RemoteParentSampledBehavior.Default,
+                RemoteParentSampledBehavior.Default
+            );
+
+            Assert.That(tracingState.Sampled, Is.EqualTo(Sampled), "Sampled should use the value from the trace context when behavior is 'default'.");
+        }
+
         #endregion
 
         #region helpers
diff --git a/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/W3CTraceparentTests.cs b/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/W3CTraceparentTests.cs
index f38751555a..d9f43852f9 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/W3CTraceparentTests.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/DistributedTracing/W3CTraceparentTests.cs
@@ -1,6 +1,7 @@
 // Copyright 2020 New Relic, Inc. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
+using System;
 using NewRelic.Agent.Core.DistributedTracing;
 using NUnit.Framework;
 
@@ -95,7 +96,7 @@ public void Header_WrongCasing(string traceparentValue)
         [TestCase("0z-0af7651916cd43dd8448ebc80319c-b7ad6b7169203331-00")]
         [TestCase("00-zaf7651916cd43dd8448ebc80319c-b7ad6b7169203331-00")]
         [TestCase("00-0af7651916cd43dd8448ebc80319c-z7ad6b7169203331-00")]
-        [TestCase("00-0af7651916cd43dd8448ebc80319c-b7ad6b7169203331-z0")]
+        [TestCase("00-0af7651916cd43dd8448ebc80319c-b7ad6b7169203331-zz")]
         [TestCase(",0-0af7651916cd43dd8448ebc80319c-b7ad6b7169203331-00")]
         [TestCase("00-,af7651916cd43dd8448ebc80319c-b7ad6b7169203331-00")]
         [TestCase("00-0af7651916cd43dd8448ebc80319c-,7ad6b7169203331-00")]
@@ -108,5 +109,22 @@ public void Header_InvalidCharacters(string traceparentValue)
         {
             Assert.That(W3CTraceparent.GetW3CTraceParentFromHeader(traceparentValue), Is.Null);
         }
+
+        [TestCase("00", false)] // TraceFlags with sampled bit not set
+        [TestCase("01", true)]  // TraceFlags with sampled bit set
+        [TestCase("ff", true)]  // TraceFlags with all bits set
+        [TestCase("ac", false)] // TraceFlags with sampled bit not set
+        public void SampledProperty_ReturnsExpectedValue(string traceFlags, bool expectedSampled)
+        {
+            // Arrange
+            var traceparentValue = $"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-{traceFlags}";
+            var traceparent = W3CTraceparent.GetW3CTraceParentFromHeader(traceparentValue);
+
+            // Act
+            var isSampled = traceparent?.Sampled;
+
+            // Assert
+            Assert.That(isSampled, Is.EqualTo(expectedSampled));
+        }
     }
 }
diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilities/HexStringConverterTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilities/HexStringConverterTests.cs
new file mode 100644
index 0000000000..3146364073
--- /dev/null
+++ b/tests/Agent/UnitTests/Core.UnitTest/Utilities/HexStringConverterTests.cs
@@ -0,0 +1,117 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using NewRelic.Agent.Core.Utilities;
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+
+namespace NewRelic.Agent.Core.UnitTest.Utilities
+{
+    [TestFixture]
+    public class HexStringConverterTests
+    {
+        [Test]
+        public void FromHexString_ValidHexString_ReturnsByteArray()
+        {
+            // Arrange
+            var hexString = "4a6f686e446f65"; // "JohnDoe" in hex
+            var expected = new byte[] { 0x4a, 0x6f, 0x68, 0x6e, 0x44, 0x6f, 0x65 };
+
+            // Act
+            var result = hexString.AsSpan().FromHexString();
+
+            // Assert
+            Assert.That(result, Is.EqualTo(expected));
+        }
+
+        [Test]
+        public void FromHexString_InvalidHexCharacter_ThrowsArgumentException()
+        {
+            // Arrange
+            var invalidHexString = "4a6f6g";
+
+            // Act & Assert
+            var ex = Assert.Throws<ArgumentException>(() => invalidHexString.AsSpan().FromHexString());
+            Assert.That(ex.Message, Does.Contain("Invalid hexadecimal character"));
+        }
+
+        [Test]
+        public void FromHexString_OddLengthHexString_ThrowsArgumentException()
+        {
+            // Arrange
+            var oddLengthHexString = "4a6f6";
+
+            // Act & Assert
+            var ex = Assert.Throws<ArgumentException>(() => oddLengthHexString.AsSpan().FromHexString());
+            Assert.That(ex.Message, Does.Contain("Hex string must have an even length"));
+        }
+
+        [Test]
+        public void FromHexString_EmptyString_ReturnsEmptyByteArray()
+        {
+            // Arrange
+            var emptyHexString = "";
+
+            // Act
+            var result = emptyHexString.AsSpan().FromHexString();
+
+            // Assert
+            Assert.That(result, Is.Empty);
+        }
+
+        [Test]
+        public void FromHexString_MixedCaseHexString_ReturnsCorrectByteArray()
+        {
+            // Arrange
+            var hexString = "aBcDeF"; // Mixed-case hex string
+            var expected = new byte[] { 0xAB, 0xCD, 0xEF };
+
+            // Act
+            var result = hexString.AsSpan().FromHexString();
+
+            // Assert
+            Assert.That(result, Is.EqualTo(expected));
+        }
+
+        [Test]
+        public void FromHexString_SpecialCharacters_ThrowsArgumentException()
+        {
+            // Arrange
+            var invalidHexString = "4a6f6@";
+
+            // Act & Assert
+            var ex = Assert.Throws<ArgumentException>(() => invalidHexString.AsSpan().FromHexString());
+            Assert.That(ex.Message, Does.Contain("Invalid hexadecimal character"));
+        }
+
+        [Test]
+        public void FromHexString_MinimumValidInput_ReturnsSingleByte()
+        {
+            // Arrange
+            var hexString = "0A"; // Single byte
+            var expected = new byte[] { 0x0A };
+
+            // Act
+            var result = hexString.AsSpan().FromHexString();
+
+            // Assert
+            Assert.That(result, Is.EqualTo(expected));
+        }
+
+
+        [Test]
+        public void FromHexString_UpperAndLowerBound_ReturnsByteArray()
+        {
+            // Arrange
+            var hexString = "0aA9fF"; // upper and lower bound hex characters
+            var expected = new byte[] { 0x0A, 0xA9, 0xFF };
+
+            // Act
+            var result = hexString.AsSpan().FromHexString();
+
+            // Assert
+            Assert.That(result, Is.EqualTo(expected));
+        }
+    }
+}


Reviews:
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:07:45Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:10:26Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:17:17Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:25:49Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:28:34Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:32:57Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:34:16Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:35:14Z
user: login: tippmar-nr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:08:39Z
user: login: tippmar-nr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:09:51Z
user: login: nrcventura
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:18:52Z
user: login: tippmar-nr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:20:34Z
user: login: tippmar-nr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:03:50Z
user: login: tippmar-nr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:36:51Z
user: login: nrcventura
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T20:46:15Z

Review comments:
diff_hunk: @@ -44,6 +44,32 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:simpleType name="RemoteParentSampledBehaviorType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default">
+                <xs:annotation>
+                    <xs:documentation>
+                        Default behavior. The agent will use existing sampling logic.
path: src/Agent/NewRelic/Agent/Core/Config/Configuration.xsd
user: login: nrcventura
body: `existing` might be confusing. Maybe we should change it to something like the following.
```suggestion
                        Default behavior. The agent will use New Relic adaptive sampling logic.
```
created_at: 2025-04-24T17:07:45Z
author_association: MEMBER
diff_hunk: @@ -1015,6 +1015,12 @@ private bool IsDistributedTracingEnabled()
 
         public bool ExcludeNewrelicHeader => _localConfiguration.distributedTracing.excludeNewrelicHeader;
 
+        // TODO: Do we need to check serverless mode? Need to support environment variable?
path: src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs
user: login: nrcventura
body: I think this setting is also applicable to serverless mode. There should probably also be an environment variable, and some of the other agents will automatically have one based on the name of the setting, so we need to ensure that our environment variable is named consistently.
created_at: 2025-04-24T17:10:26Z
author_association: MEMBER
diff_hunk: @@ -148,45 +149,30 @@ public string TransactionId
             }
         }
 
+        private bool? _sampled;
         public bool? Sampled
         {
             get
             {
-                if (_sampled != null && _sampled.HasValue)
-                {
+                if (_sampled is not null)
                     return _sampled;
-                }
 
-                if (_traceContext?.Tracestate != null && _traceContext.Tracestate.Sampled.HasValue)
-                {
+                if (_traceContext?.Tracestate is { Sampled: not null })
                     return _sampled = Convert.ToBoolean(_traceContext.Tracestate.Sampled);
-                }
 
-                if (_newRelicPayload?.Sampled != null && _newRelicPayload.Sampled.HasValue)
-                {
+                if (_newRelicPayload?.Sampled is not null)
                     return _sampled = _newRelicPayload.Sampled;
-                }
 
                 return null;
             }
         }
 
+        private float? _priority;
         public float? Priority
         {
-            get
-            {
-                if (_traceContext?.Tracestate != null)
-                {
-                    return _traceContext.Tracestate.Priority;
-                }
-
-                if (_newRelicPayload?.Priority != null && _newRelicPayload.Priority.HasValue)
-                {
-                    return _newRelicPayload.Priority;
-                }
-
-                return null;
-            }
+            get => _priority ??
path: src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
user: login: nrcventura
body: These nested conditionals seem harder to read, and might make it harder to step through with a debugger. I think that it might be better to split up this logic using multiple if blocks and return statements (similar to the previous version of this code).
created_at: 2025-04-24T17:17:17Z
author_association: MEMBER
diff_hunk: @@ -254,5 +248,43 @@ public static ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T,
 
             return tracingState;
         }
+
+        /// <summary>
+        /// Use remote parent sampled behavior configuration in conjunction with the traceparent sampled flag to determine
+        /// if the transaction should be sampled.
+        /// </summary>
+        private void ApplyRemoteParentSampledBehavior(
+            RemoteParentSampledBehavior remoteParentSampledBehavior,
+            RemoteParentSampledBehavior remoteParentNotSampledBehavior)
+        {
+            // don't do anything if the traceparent is not present or if behavior is configured to default
+            if (!_traceContext.TraceparentPresent ||
+                (remoteParentNotSampledBehavior == RemoteParentSampledBehavior.Default &&
+                 remoteParentSampledBehavior == RemoteParentSampledBehavior.Default))
+            {
+                return;
+            }
+
+            var sampledBehavior = _traceContext.Traceparent.Sampled
+                ? remoteParentSampledBehavior
+                : remoteParentNotSampledBehavior;
+
+            switch (sampledBehavior)
+            {
+                case RemoteParentSampledBehavior.AlwaysOn:
+                    _priority = 2.0f; // per the spec, set priority high so that this sample is always kept
+                    _sampled = true;
+                    break;
+                case RemoteParentSampledBehavior.AlwaysOff:
+                    _priority = 0.0f; // set lowest possible priority
+                    _sampled = false;
+                    break;
+                // don't need a case for Default, as it is handled above
+                default:
+                    throw new ArgumentException($"Invalid {(_traceContext.Traceparent.Sampled ? "remoteParentSampledBehavior" : "remoteParentNotSampledBehavior")} value: {sampledBehavior}.");
+            }
+
+            Log.Finest($"ApplyRemoteParentSampledBehavior:  _traceContext.Traceparent.Sampled={_traceContext.Traceparent.Sampled}, {(_sampled.Value ? "remoteParent" : "remoteParentNot")}SampledBehavior: {sampledBehavior} ==> Sampled: {Sampled}, Priority:{Priority}");
path: src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
user: login: nrcventura
body: The string formatting for this finest level log message can be deferred if the code is switched to the `Log.Finest("sampled={sampledValue}", sampledValue)` style of log message strings.
created_at: 2025-04-24T17:25:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace NewRelic.Agent.Core.Utilities
+{
+    public static class HexStringConverter
+    {
+        public static byte[] FromHexString(this ReadOnlySpan<char> chars)
+        {
+            if (chars.Length % 2 != 0)
path: src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
user: login: nrcventura
body: Is this a general requirement, or is this just applicable to the trace flags scenarios?
created_at: 2025-04-24T17:28:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace NewRelic.Agent.Core.Utilities
+{
+    public static class HexStringConverter
+    {
+        public static byte[] FromHexString(this ReadOnlySpan<char> chars)
+        {
+            if (chars.Length % 2 != 0)
path: src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
user: login: nrcventura
body: If it's a general requirement we may want to change the name of this class, because someone might want to use it convert `A` to an array of bytes, because we're naming this class as a "generic" HexStringConverter.
created_at: 2025-04-24T17:32:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,84 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using NewRelic.Agent.Api;
+using NewRelic.Agent.Configuration;
+using NewRelic.Agent.TestUtilities;
+using NUnit.Framework;
+using System.Collections.Generic;
+using NewRelic.Agent.Extensions.Providers.Wrapper;
+
+namespace CompositeTests
+{
+    public class RemoteParentSampledBehaviorTests
+    {
+        private CompositeTestAgent _compositeTestAgent;
+        private IAgent _agent;
+
+        [SetUp]
+        public void SetUp()
+        {
+        }
path: tests/Agent/UnitTests/CompositeTests/RemoteParentSampledBehaviorTests.cs
user: login: nrcventura
body: Do we need this?
created_at: 2025-04-24T17:34:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,84 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using NewRelic.Agent.Api;
+using NewRelic.Agent.Configuration;
+using NewRelic.Agent.TestUtilities;
+using NUnit.Framework;
+using System.Collections.Generic;
+using NewRelic.Agent.Extensions.Providers.Wrapper;
+
+namespace CompositeTests
+{
+    public class RemoteParentSampledBehaviorTests
+    {
+        private CompositeTestAgent _compositeTestAgent;
+        private IAgent _agent;
+
+        [SetUp]
+        public void SetUp()
+        {
+        }
+
+        [TearDown]
+        public void TearDown()
+        {
+            _compositeTestAgent?.Dispose();
+        }
+
+        [TestCase(RemoteParentSampledBehavior.AlwaysOn, "01", true, TestName = "AlwaysOn_WithSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.AlwaysOn, "00", true, TestName = "AlwaysOn_WithNotSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.AlwaysOff, "01", false, TestName = "AlwaysOff_WithSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.AlwaysOff, "00", false, TestName = "AlwaysOff_WithNotSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.Default, "01", true, TestName = "Default_WithSampledTraceparent")]
+        [TestCase(RemoteParentSampledBehavior.Default, "00", true, TestName = "Default_WithNotSampledTraceparent")] // uses existing logic
+        public void RemoteParentSampledBehavior_Combinations(RemoteParentSampledBehavior behavior, string traceparentSampledFlag, bool expectedSampled)
+        {
+            // Initialize CompositeTestAgent with the specified RemoteParentSampledBehavior
+            _compositeTestAgent?.Dispose();
path: tests/Agent/UnitTests/CompositeTests/RemoteParentSampledBehaviorTests.cs
user: login: nrcventura
body: This shouldn't be necessary because of the `TearDown` method.
created_at: 2025-04-24T17:35:14Z
author_association: MEMBER
diff_hunk: @@ -254,5 +248,43 @@ public static ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T,
 
             return tracingState;
         }
+
+        /// <summary>
+        /// Use remote parent sampled behavior configuration in conjunction with the traceparent sampled flag to determine
+        /// if the transaction should be sampled.
+        /// </summary>
+        private void ApplyRemoteParentSampledBehavior(
+            RemoteParentSampledBehavior remoteParentSampledBehavior,
+            RemoteParentSampledBehavior remoteParentNotSampledBehavior)
+        {
+            // don't do anything if the traceparent is not present or if behavior is configured to default
+            if (!_traceContext.TraceparentPresent ||
+                (remoteParentNotSampledBehavior == RemoteParentSampledBehavior.Default &&
+                 remoteParentSampledBehavior == RemoteParentSampledBehavior.Default))
+            {
+                return;
+            }
+
+            var sampledBehavior = _traceContext.Traceparent.Sampled
+                ? remoteParentSampledBehavior
+                : remoteParentNotSampledBehavior;
+
+            switch (sampledBehavior)
+            {
+                case RemoteParentSampledBehavior.AlwaysOn:
+                    _priority = 2.0f; // per the spec, set priority high so that this sample is always kept
+                    _sampled = true;
+                    break;
+                case RemoteParentSampledBehavior.AlwaysOff:
+                    _priority = 0.0f; // set lowest possible priority
+                    _sampled = false;
+                    break;
+                // don't need a case for Default, as it is handled above
+                default:
+                    throw new ArgumentException($"Invalid {(_traceContext.Traceparent.Sampled ? "remoteParentSampledBehavior" : "remoteParentNotSampledBehavior")} value: {sampledBehavior}.");
+            }
+
+            Log.Finest($"ApplyRemoteParentSampledBehavior:  _traceContext.Traceparent.Sampled={_traceContext.Traceparent.Sampled}, {(_sampled.Value ? "remoteParent" : "remoteParentNot")}SampledBehavior: {sampledBehavior} ==> Sampled: {Sampled}, Priority:{Priority}");
path: src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
user: login: tippmar-nr
body: I always forget that. We probably should add a ticket to the backlog to scrub "improper" log formatting - it's used in many, many places.
created_at: 2025-04-24T18:08:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace NewRelic.Agent.Core.Utilities
+{
+    public static class HexStringConverter
+    {
+        public static byte[] FromHexString(this ReadOnlySpan<char> chars)
+        {
+            if (chars.Length % 2 != 0)
path: src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
user: login: tippmar-nr
body: It takes two hex characters to (fully) represent one byte. I thought about left-padding with `0` if the length was odd, but decided against it.
created_at: 2025-04-24T18:09:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace NewRelic.Agent.Core.Utilities
+{
+    public static class HexStringConverter
+    {
+        public static byte[] FromHexString(this ReadOnlySpan<char> chars)
+        {
+            if (chars.Length % 2 != 0)
path: src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
user: login: nrcventura
body: Should we at least add some `///` comments to call out that limitation? I couldn't come up with a better name.
created_at: 2025-04-24T18:18:52Z
author_association: MEMBER
diff_hunk: @@ -148,45 +149,30 @@ public string TransactionId
             }
         }
 
+        private bool? _sampled;
         public bool? Sampled
         {
             get
             {
-                if (_sampled != null && _sampled.HasValue)
-                {
+                if (_sampled is not null)
                     return _sampled;
-                }
 
-                if (_traceContext?.Tracestate != null && _traceContext.Tracestate.Sampled.HasValue)
-                {
+                if (_traceContext?.Tracestate is { Sampled: not null })
                     return _sampled = Convert.ToBoolean(_traceContext.Tracestate.Sampled);
-                }
 
-                if (_newRelicPayload?.Sampled != null && _newRelicPayload.Sampled.HasValue)
-                {
+                if (_newRelicPayload?.Sampled is not null)
                     return _sampled = _newRelicPayload.Sampled;
-                }
 
                 return null;
             }
         }
 
+        private float? _priority;
         public float? Priority
         {
-            get
-            {
-                if (_traceContext?.Tracestate != null)
-                {
-                    return _traceContext.Tracestate.Priority;
-                }
-
-                if (_newRelicPayload?.Priority != null && _newRelicPayload.Priority.HasValue)
-                {
-                    return _newRelicPayload.Priority;
-                }
-
-                return null;
-            }
+            get => _priority ??
path: src/Agent/NewRelic/Agent/Core/DistributedTracing/TracingState.cs
user: login: tippmar-nr
body: I like it the way it is, but I'll change it since the implementation is a bit inconsistent with other properties in this class..
created_at: 2025-04-24T18:20:34Z
author_association: MEMBER
diff_hunk: @@ -1015,6 +1015,12 @@ private bool IsDistributedTracingEnabled()
 
         public bool ExcludeNewrelicHeader => _localConfiguration.distributedTracing.excludeNewrelicHeader;
 
+        // TODO: Do we need to check serverless mode? Need to support environment variable?
path: src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs
user: login: tippmar-nr
body: Added environment variables, using same names as Ruby and Node (and maybe others)
created_at: 2025-04-24T19:03:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,47 @@
+// Copyright 2020 New Relic, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+
+namespace NewRelic.Agent.Core.Utilities
+{
+    public static class HexStringConverter
+    {
+        public static byte[] FromHexString(this ReadOnlySpan<char> chars)
+        {
+            if (chars.Length % 2 != 0)
path: src/Agent/NewRelic/Agent/Core/Utilities/HexStringConverter.cs
user: login: tippmar-nr
body: Sure.
created_at: 2025-04-24T20:36:51Z
author_association: MEMBER

PR comments:
user: login: chynesNR
created_at: 2025-04-16T21:36:36Z
body: Hey! I've switched this over to Draft for the time being. The Java Agent has run into some unexpected behavior in the field, and it may require a spec change, depending on the root cause.
author_association: MEMBER
user: login: codecov-commenter
created_at: 2025-07-10T17:27:13Z
body: ## [Codecov](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) Report
Attention: Patch coverage is `96.80851%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 82.36%. Comparing base [(`1663600`)](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/commit/1663600a54fff4b1958a8e99202d3715aa6d7b21?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) to head [(`0f37e0a`)](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/commit/0f37e0ab049d718061f986aef70c80d80eafe5e0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic).
> Report is 54 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) | Patch % | Lines |
|---|---|---|
| [...elic/Agent/Core/DistributedTracing/TracingState.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FDistributedTracing%2FTracingState.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvRGlzdHJpYnV0ZWRUcmFjaW5nL1RyYWNpbmdTdGF0ZS5jcw==) | 94.11% | [0 Missing and 2 partials :warning: ](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) |
| [...ewRelic/Agent/Core/Utilities/HexStringConverter.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FUtilities%2FHexStringConverter.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvVXRpbGl0aWVzL0hleFN0cmluZ0NvbnZlcnRlci5jcw==) | 95.83% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3100      +/-   ##
==========================================
+ Coverage   82.33%   82.36%   +0.03%     
==========================================
  Files         477      479       +2     
  Lines       30636    30718      +82     
  Branches     3424     3438      +14     
==========================================
+ Hits        25225    25302      +77     
- Misses       4610     4612       +2     
- Partials      801      804       +3     
```

| [Flag](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) | Coverage Δ | |
|---|---|---|
| [Agent](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) | `83.43% <96.80%> (+0.03%)` | :arrow_up: |
| [Profiler](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) | `72.51% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files with missing lines](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic) | Coverage Δ | |
|---|---|---|
| [...ent/Core/Configuration/ConfigurationEnumHelpers.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FConfiguration%2FConfigurationEnumHelpers.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvQ29uZmlndXJhdGlvbi9Db25maWd1cmF0aW9uRW51bUhlbHBlcnMuY3M=) | `100.00% <100.00%> (ø)` | |
| [...c/Agent/Core/Configuration/DefaultConfiguration.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FConfiguration%2FDefaultConfiguration.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvQ29uZmlndXJhdGlvbi9EZWZhdWx0Q29uZmlndXJhdGlvbi5jcw==) | `89.98% <100.00%> (+0.01%)` | :arrow_up: |
| [.../Agent/Core/Configuration/ReportedConfiguration.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FConfiguration%2FReportedConfiguration.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvQ29uZmlndXJhdGlvbi9SZXBvcnRlZENvbmZpZ3VyYXRpb24uY3M=) | `95.85% <ø> (ø)` | |
| [...stributedTracing/DistributedTracePayloadHandler.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FDistributedTracing%2FDistributedTracePayloadHandler.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvRGlzdHJpYnV0ZWRUcmFjaW5nL0Rpc3RyaWJ1dGVkVHJhY2VQYXlsb2FkSGFuZGxlci5jcw==) | `81.22% <100.00%> (+0.55%)` | :arrow_up: |
| [...ic/Agent/Core/DistributedTracing/W3CTraceparent.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FDistributedTracing%2FW3CTraceparent.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvRGlzdHJpYnV0ZWRUcmFjaW5nL1czQ1RyYWNlcGFyZW50LmNz) | `92.85% <100.00%> (+0.35%)` | :arrow_up: |
| [...ewRelic/Agent/Core/Utilities/HexStringConverter.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FUtilities%2FHexStringConverter.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvVXRpbGl0aWVzL0hleFN0cmluZ0NvbnZlcnRlci5jcw==) | `95.83% <95.83%> (ø)` | |
| [...elic/Agent/Core/DistributedTracing/TracingState.cs](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100?src=pr&el=tree&filepath=src%2FAgent%2FNewRelic%2FAgent%2FCore%2FDistributedTracing%2FTracingState.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic#diff-c3JjL0FnZW50L05ld1JlbGljL0FnZW50L0NvcmUvRGlzdHJpYnV0ZWRUcmFjaW5nL1RyYWNpbmdTdGF0ZS5jcw==) | `90.27% <94.11%> (+1.84%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/newrelic/newrelic-dotnet-agent/pull/3100/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=newrelic)

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE