diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # Revision history for stim-parser
 
+## 0.4.0.0 -- 2026-07-20
+
+* Add Pauli-target support for `OBSERVABLE_INCLUDE` and `DETECTOR`
+  annotations.
+  - New `AnnTarget` sum type: `AnnQ Ind | AnnRec Rec | AnnPauli Pauli Ind`.
+  - `Ann` now stores `[AnnTarget]` instead of `[Q]`.
+  - Single-qubit Pauli targets such as `X0`, `Y1`, `Z2` are accepted and
+    preserved, matching Python Stim behavior.
+  - Mixed `rec[-k]` and Pauli targets are supported.
+  - The `*` combiner remains unsupported inside annotations.
+* This is a **breaking API change**. Downstream code pattern-matching on
+  `Ann` or constructing annotation values needs to use `AnnTarget`.
+
 ## 0.3.0.0 -- 2026-07-20
 
 * Add optional tag support for DEM detector and observable declarations.
diff --git a/src/StimParser/Expr.hs b/src/StimParser/Expr.hs
--- a/src/StimParser/Expr.hs
+++ b/src/StimParser/Expr.hs
@@ -4,7 +4,7 @@
 type Ind = Int
 type Ph = Double
 newtype Rec = Rec Int
-  deriving (Show)
+  deriving (Show, Eq, Ord)
 newtype Sweep = Sweep Int
   deriving (Show)
 
@@ -17,7 +17,7 @@
   deriving (Show)
 
 data Pauli = PX | PY | PZ 
-  deriving (Show)
+  deriving (Show, Eq, Ord)
 data PauliInd = PauliInd Pauli Ind
   deriving (Show)
 
@@ -161,11 +161,29 @@
 annTyList :: [AnnTy]
 annTyList = [DETECTOR ..] 
 
+-- Targets that can appear inside annotations.
+--
+-- * 'AnnQ' is a plain qubit index (used by QUBIT_COORDS and MPAD).
+-- * 'AnnRec' is a measurement-record reference (used by DETECTOR and
+--   OBSERVABLE_INCLUDE).
+-- * 'AnnPauli' is a single-qubit Pauli operator (used by DETECTOR and
+--   OBSERVABLE_INCLUDE to declare Pauli-product detectors/observables).
+--
+-- Space-separated Pauli targets are implicitly multiplied, matching Python
+-- Stim behavior. The '*' combiner is not supported inside annotations.
+data AnnTarget
+  = AnnQ Ind
+  | AnnRec Rec
+  | AnnPauli Pauli Ind
+  deriving (Show, Eq)
+
 -- Annotations examples:
 --    DETECTOR(1, 0) rec[-3] rec[-6]
 --    DETECTOR(2, 0, 0) rec[-8]
 --    DETECTOR rec[-3] rec[-4] rec[-7]
+--    DETECTOR X0 Z1
 --    OBSERVABLE_INCLUDE(0) rec[-1] rec[-2]
+--    OBSERVABLE_INCLUDE(0) X0 Z1
 --    QUBIT_COORDS(0, 0) 0
 --    MPAD 0 0 1 0 1
 --    SHIFT_COORDS(500.5)
@@ -193,7 +211,7 @@
 
 newtype Coords = Coords [FInd]
 
-data Ann = Ann AnnTy (Maybe Tag) [FInd] [Q]
+data Ann = Ann AnnTy (Maybe Tag) [FInd] [AnnTarget]
   deriving (Show)
 
 -- repeat example:
diff --git a/src/StimParser/Parse.hs b/src/StimParser/Parse.hs
--- a/src/StimParser/Parse.hs
+++ b/src/StimParser/Parse.hs
@@ -6,6 +6,7 @@
 import StimParser.ParseUtils
 import Control.Monad
 import Data.List
+import Data.Maybe
 import Text.Megaparsec hiding (State)
 import Text.Megaparsec.Char
 import qualified Text.Megaparsec.Char.Lexer as L
@@ -241,35 +242,60 @@
   -- the order is tricky 
   try pm2 <|> pm1
 
+-- | Parse a measurement-record target inside an annotation.
+-- Requires a negative index, matching the semantics of rec[] in gates.
+parseAnnRecTarget :: Parser AnnTarget
+parseAnnRecTarget = do
+  lstring "rec"
+  lstring "["
+  i <- parseInt
+  lstring "]"
+  if i < 0
+    then return $ AnnRec (Rec i)
+    else fail "rec[] index in annotations must be negative (e.g., rec[-1])"
+
+-- | Parse a single-qubit Pauli target inside an annotation.
+-- The '*' combiner is intentionally not supported here.
+parseAnnPauliTarget :: Parser AnnTarget
+parseAnnPauliTarget = lexeme $ do
+  PauliInd p i <- parsePauliInd
+  return $ AnnPauli p i
+
+-- | Parse a target that may appear inside DETECTOR or OBSERVABLE_INCLUDE:
+-- either a measurement-record reference or a single-qubit Pauli target.
+parseAnnTargetMixed :: Parser AnnTarget
+parseAnnTargetMixed =
+  try parseAnnRecTarget <|> parseAnnPauliTarget
+
+-- | Parse a plain qubit-index target for annotations such as QUBIT_COORDS.
+parseAnnTargetQ :: Parser AnnTarget
+parseAnnTargetQ = AnnQ <$> parseInt
+
 parseAnn :: Parser Ann 
-parseAnn = do 
-  let 
-    -- parser for case: DETECTOR[tag](1, 0) rec[-3] rec[-6]
-    pm1 = do 
-      ty <- parseAnnTy 
-      tag <- optional parseTag
+parseAnn = do
+  ty <- parseAnnTy
+  tag <- optional parseTag
+  case ty of
+    TICK ->
+      return $ Ann TICK tag [] []
+    SHIFT_COORDS -> do
       fds <- parseTuple parseFInd
-      qs <- parseExhaust parseQ
-      return $ Ann ty tag fds qs
-    -- parser for case: DETECTOR[tag] rec[-3] rec[-4] rec[-7]
-    pm2 = do 
-      ty <- parseAnnTy
-      tag <- optional parseTag
-      qs <- parseExhaust parseQ
-      return $ Ann ty tag [] qs
-    -- parser for case: SHIFT_COORDS[tag](500.5)
-    pm3 = do 
-      ty <- parseAnnTy
-      tag <- optional parseTag
+      return $ Ann SHIFT_COORDS tag fds []
+    QUBIT_COORDS -> do
       fds <- parseTuple parseFInd
-      return $ Ann ty tag fds []
-    -- parser for case: TICK[tag]
-    pm4 = do
-      ty <- parseShowCI TICK
-      tag <- optional parseTag
-      return $ Ann ty tag [] []
-  -- the order is tricky
-  try pm1 <|> try pm2 <|> try pm3 <|> pm4 
+      qs <- parseExhaust parseAnnTargetQ
+      return $ Ann QUBIT_COORDS tag fds qs
+    MPAD -> do
+      qs <- parseExhaust parseAnnTargetQ
+      return $ Ann MPAD tag [] qs
+    DETECTOR -> do
+      mfds <- optional (parseTuple parseFInd)
+      ts <- parseExhaust parseAnnTargetMixed
+      return $ Ann DETECTOR tag (fromMaybe [] mfds) ts
+    OBSERVABLE_INCLUDE -> do
+      fds <- parseTuple parseFInd
+      ts <- parseExhaust parseAnnTargetMixed
+      return $ Ann OBSERVABLE_INCLUDE tag fds ts
 
 parseStart :: Parser ()
 parseStart = void (lstring "!!!Start") 
diff --git a/src/StimParser/Trans.hs b/src/StimParser/Trans.hs
--- a/src/StimParser/Trans.hs
+++ b/src/StimParser/Trans.hs
@@ -37,6 +37,17 @@
     nqs <- mapM flattenQ qs 
     return $ Gate gt tag nqs 
 
+-- Annotation targets behave similarly to 'Q': plain qubits and Pauli targets
+-- are preserved, while measurement-record references are resolved to absolute
+-- qubit indices using the current measurement count.
+instance FlattenQ AnnTarget where
+  type Out AnnTarget = AnnTarget
+  flattenQ (AnnQ i) = return (AnnQ i)
+  flattenQ (AnnRec (Rec r)) = do
+    (count, _) <- get
+    return $ AnnQ (count + r + 1)
+  flattenQ (AnnPauli p i) = return (AnnPauli p i)
+
 -- for each measure, we increase the count by 1
 -- currently, we assume measure does not contains (QRec Rec)
 instance FlattenQ Measure where
@@ -74,15 +85,15 @@
 
   -- TODO: type-level constraint that fs and cs should be of the same length
   -- TODO: type-level constraint that count should not be used in this case
-  flattenQ (Ann DETECTOR tag fs qs) = do
+  flattenQ (Ann DETECTOR tag fs ts) = do
     (count, Coords cs) <- get
     let nfs = zipWith (+) fs cs
-    nqs <- mapM flattenQ qs
-    return $ Just $ Ann DETECTOR tag nfs nqs
+    nts <- mapM flattenQ ts
+    return $ Just $ Ann DETECTOR tag nfs nts
     
-  flattenQ (Ann aty tag fs qs) = do 
-    nqs <- mapM flattenQ qs 
-    return $ Just $ Ann aty tag fs nqs
+  flattenQ (Ann aty tag fs ts) = do 
+    nts <- mapM flattenQ ts 
+    return $ Just $ Ann aty tag fs nts
 
 instance FlattenQ Stim where
   type Out Stim = Maybe Stim
diff --git a/stim-parser.cabal b/stim-parser.cabal
--- a/stim-parser.cabal
+++ b/stim-parser.cabal
@@ -20,7 +20,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version:            0.3.0.0
+version:            0.4.0.0
 
 -- A short (one-line) description of the package.
 synopsis:           A parser combinator library for STIM quantum circuit files
diff --git a/test/Test/Parse.hs b/test/Test/Parse.hs
--- a/test/Test/Parse.hs
+++ b/test/Test/Parse.hs
@@ -260,17 +260,26 @@
   [ assertShowEqual "parseAnn TICK" 
       (Ann TICK Nothing [] []) (run parseAnn "TICK")
   , assertShowEqual "parseAnn DETECTOR with coords and rec" 
-      (Ann DETECTOR Nothing [In 1, In 0] [QRec (Rec (-3)), QRec (Rec (-6))]) 
+      (Ann DETECTOR Nothing [In 1, In 0] [AnnRec (Rec (-3)), AnnRec (Rec (-6))]) 
       (run parseAnn "DETECTOR(1, 0) rec[-3] rec[-6]")
   , assertShowEqual "parseAnn DETECTOR without coords" 
-      (Ann DETECTOR Nothing [] [QRec (Rec (-3)), QRec (Rec (-4))]) 
+      (Ann DETECTOR Nothing [] [AnnRec (Rec (-3)), AnnRec (Rec (-4))]) 
       (run parseAnn "DETECTOR rec[-3] rec[-4]")
   , assertShowEqual "parseAnn SHIFT_COORDS" 
       (Ann SHIFT_COORDS Nothing [Fl 500.5] []) 
       (run parseAnn "SHIFT_COORDS(500.5)")
   , assertShowEqual "parseAnn OBSERVABLE_INCLUDE" 
-      (Ann OBSERVABLE_INCLUDE Nothing [In 0] [QRec (Rec (-1))]) 
+      (Ann OBSERVABLE_INCLUDE Nothing [In 0] [AnnRec (Rec (-1))]) 
       (run parseAnn "OBSERVABLE_INCLUDE(0) rec[-1]")
+  , assertShowEqual "parseAnn DETECTOR with Pauli targets" 
+      (Ann DETECTOR Nothing [] [AnnPauli PX 0, AnnPauli PZ 1]) 
+      (run parseAnn "DETECTOR X0 Z1")
+  , assertShowEqual "parseAnn OBSERVABLE_INCLUDE with Pauli targets" 
+      (Ann OBSERVABLE_INCLUDE Nothing [In 0] [AnnPauli PX 0, AnnPauli PZ 1]) 
+      (run parseAnn "OBSERVABLE_INCLUDE(0) X0 Z1")
+  , assertShowEqual "parseAnn OBSERVABLE_INCLUDE mixed targets" 
+      (Ann OBSERVABLE_INCLUDE Nothing [In 0] [AnnRec (Rec (-1)), AnnPauli PZ 0]) 
+      (run parseAnn "OBSERVABLE_INCLUDE(0) rec[-1] Z0")
   ]
 
 -- | Regression test for the noise-before-gate ordering bug.
diff --git a/test/Test/Trans.hs b/test/Test/Trans.hs
--- a/test/Test/Trans.hs
+++ b/test/Test/Trans.hs
@@ -83,8 +83,11 @@
   [ assertShowEqual "flattenQ Ann TICK" 
       (Just (Ann TICK Nothing [] [])) (evalState (flattenQ (Ann TICK Nothing [] [])) (0, Coords [In 0]))
   , assertShowEqual "flattenQ Ann DETECTOR" 
-      (Just (Ann DETECTOR Nothing [In 1, In 0] [Q 10])) 
-      (evalState (flattenQ (Ann DETECTOR Nothing [In 1, In 0] [Q 10])) (10, Coords [In 0, In 0]))
+      (Just (Ann DETECTOR Nothing [In 1, In 0] [AnnQ 10])) 
+      (evalState (flattenQ (Ann DETECTOR Nothing [In 1, In 0] [AnnQ 10])) (10, Coords [In 0, In 0]))
+  , assertShowEqual "flattenQ Ann OBSERVABLE_INCLUDE resolves rec" 
+      (Just (Ann OBSERVABLE_INCLUDE Nothing [In 0] [AnnQ 10, AnnPauli PZ 0])) 
+      (evalState (flattenQ (Ann OBSERVABLE_INCLUDE Nothing [In 0] [AnnRec (Rec (-1)), AnnPauli PZ 0])) (10, Coords [In 0]))
   ]
   -- Note: SHIFT_COORDS test moved to separate test case
 
