Skip to content

Syntaxes

A syntax defines the wire format of a block: how its opening is detected, where metadata lives, and what closes it. Each Registry holds exactly one syntax instance. StreamBlocks ships three built-in syntaxes and lets you implement your own.

Every block has the same anatomy regardless of syntax: an opening marker, an optional metadata section, the content, and a closing marker. Here it is for DelimiterFrontmatterSyntax:

Delimiter frontmatter blockOpening markerMetadata section (YAML)Content sectionClosing marker!!start---id: plan01block_type: task---Refactor the parser module!!end
Delimiter frontmatter blockOpening markerMetadata section (YAML)Content sectionClosing marker!!start---id: plan01block_type: task---Refactor the parser module!!end

DelimiterPreambleSyntax

The default. Metadata is inline in the opening line, compact and cheap for an LLM to emit.

!!<id>:<type>[:param1:param2:...]
Content lines here
!!end

The opening delimiter carries the block id and block_type (both required, alphanumeric).

# Setup: registry + processor
registry = Registry()
registry.register("files_operations", FileOperations)
processor = StreamBlockProcessor(registry)

# Text with one block
text = "!!block01:files_operations\nsrc/main.py:C\n!!end"
stream = simple_text_stream(text)

# Process and extract blocks
async for event in processor.process_stream(stream):
    if isinstance(event, BlockEndEvent):
        block = event.get_block()
        if block:
            print("Extracted block:")
            print(block.model_dump_json(indent=2))
Option Default Description
delimiter "!!" Marker prefix for the opening line and the !!end closing line
Extra parameters in the opening line

Optional colon-separated parameters are stored in metadata as param_0, param_1, and so on:

!!file123:operation:create:urgent
Create new config file
!!end

produces metadata {"id": "file123", "block_type": "operation", "param_0": "create", "param_1": "urgent"}.

DelimiterFrontmatterSyntax

Delimiter markers with a YAML frontmatter section for richer metadata. The YAML is parsed into your metadata model (it must include id and block_type when using BaseMetadata); nested YAML is supported.

!!start
---
id: block_001
block_type: example
custom_field: value
---
Content lines here
!!end
# Create delimiter frontmatter syntax for tasks
# Using standard !!start/!!end delimiters
task_syntax = DelimiterFrontmatterSyntax(
    start_delimiter="!!start",
    end_delimiter="!!end",
)

# Create type-specific registry and register block
registry = Registry(syntax=task_syntax)
registry.register("task", TaskBlock)
Option Default Description
start_delimiter "!!start" Opening marker
end_delimiter "!!end" Closing marker

MarkdownFrontmatterSyntax

Markdown fenced code blocks with optional YAML frontmatter, useful when the stream is rendered as Markdown anyway. When frontmatter is absent (or has no block_type), the info string is the fallback block_type and all lines become content.

```[info_string]
---
id: block_001
block_type: example
custom_field: value
---
Content lines here
```
# Create markdown frontmatter syntax for patch blocks
# Each Registry holds exactly one syntax.
# To handle multiple info strings (patch/yaml/diff), you would need separate processors
# or a custom syntax that handles multiple patterns internally.
syntax = MarkdownFrontmatterSyntax(
    fence="```",
    info_string="patch",  # Will match ```patch blocks
)

# Create type-specific registry and register block
registry = Registry(syntax=syntax)
registry.register("patch", Patch)

# Create processor with config
config = ProcessorConfig(lines_buffer=10)
processor = StreamBlockProcessor(registry, config=config)
Option Default Description
fence "```" Fence string
info_string None Restricts detection to fences with this info string; also the fallback block_type

How a syntax is chosen

Pass either a Syntax enum member or a configured instance to the registry:

from hother.streamblocks import Registry, DelimiterFrontmatterSyntax
from hother.streamblocks.syntaxes.factory import Syntax

# Enum: built-in syntax with default options
registry = Registry(syntax=Syntax.DELIMITER_FRONTMATTER)

# Instance: full control over options
registry = Registry(syntax=DelimiterFrontmatterSyntax(start_delimiter="<<begin", end_delimiter="<<end"))
Enum member Syntax class
Syntax.DELIMITER_PREAMBLE (default) DelimiterPreambleSyntax
Syntax.DELIMITER_FRONTMATTER DelimiterFrontmatterSyntax
Syntax.MARKDOWN_FRONTMATTER MarkdownFrontmatterSyntax

Because a registry holds a single syntax, processing one stream with several wire formats requires separate processors or a custom syntax that recognizes multiple patterns.

Custom syntaxes

Subclass BaseSyntax and implement four methods:

Method Role
detect_line(line, candidate) Return a DetectionResult flagging opening, closing, or metadata-boundary lines; accumulate lines into the candidate
should_accumulate_metadata(candidate) Whether the candidate is still in its metadata section
extract_block_type(candidate) Pull the block_type string out of a candidate so the registry can find the block class
parse_block(candidate, block_class) Build the final ParseResult with parsed metadata and content

A custom syntax plugs into the registry like any built-in one:

syntax = XMLBlockSyntax()
registry = Registry(syntax=syntax)
registry.register("files_operations", FileOperations)
processor = StreamBlockProcessor(registry)

The full example implements an XML-comment wire format (<!-- block:type id="..." --><!-- /block -->); see the custom syntax example for the complete XMLBlockSyntax implementation.

Next steps