Beyond tracking execution, Strikes provides a powerful data flow system that allows you to log, store, and analyze data generated during your runs. Data can serve as parameters to your tasks and runs as well as input and output objects.

One of Strikes’ most powerful features is its ability to store and organize different types of data within your runs and tasks. Understanding these capabilities helps you capture the right information to evaluate and improve your agents and systems.

Parameters

Parameters are lightweight key-value pairs typically used for configuration values, settings, or hyperparameters:

import dreadnode as dn

@dreadnode.task(log_params=["learning_rate", "batch_size"])
async def train_model(learning_rate: float, batch_size: int) -> None:
    # Training logic here
    pass

with dn.run("my-experiment"):
    # Log individual parameters
    dn.log_param("learning_rate", 0.01)
    
    # Or log multiple parameters at once
    dn.log_params(
        batch_size=32,
        epochs=100,
        model="transformer"
    )

    # Call the task with parameters
    await train_model(learning_rate=0.01, batch_size=32)

Parameters are ideal for:

  • Tracking experiment configurations
  • Recording hyperparameters
  • Setting environment variables
  • Storing metadata about your run

Parameters are stored efficiently, making it easy to filter and compare runs quickly. They’re primarily intended for scalar values (strings, numbers, booleans) that define your experiment’s setup.

Parameters do not store multiple values over time. If you need to track changes to a parameter over the lifetime of a run, consider using the parameter inside a task and call it multiple times.

Inputs and Outputs

For rich data that you have available during execution, Strikes provides input and output storage:

with dn.run("text-generation"):
    # Log a complex input object
    prompt = {
        "text": "Write a story about a robot learning to paint.",
        "temperature": 0.7,
        "max_tokens": 1024
    }
    dn.log_input("prompt", prompt)
    
    # Generate response and log the output
    response = generate_text(prompt)
    dn.log_output("response", response)

Strikes maintains a rich serialization layer to support many different kinds of Python objects:

  • Dictionaries, lists, and other JSON-serializable objects
  • NumPy arrays and Pandas DataFrames
  • Custom objects (serialized with pickle)
  • Large datasets (automatically stored efficiently)

This capability allows you to capture the complete data flow through your system, creating a comprehensive record of what went in and what came out.

Task Input and Output Tracking

Tasks automatically track their function arguments as inputs and return values as an output:

@dn.task()
async def classify_image(image_data: dict) -> dict:
    # 'image_data' is automatically logged as input
    result = run_classifier(image_data)
    # return value is automatically logged as output
    return result

with dn.run():
    # Run the task
    result = await classify_image({"url": "https://example.com/cat.jpg"})

You can control this behavior with task options:

@dn.task(
    log_inputs=["image_url"], # Only log specific arguments
    log_output=False          # Don't log the return value
)
async def process_image(image_url: str, settings: dict) -> dict:
    # Only 'image_url' is logged, 'settings' is not
    result = process(image_url, settings)
    
    # Manually log what we want
    dn.log_output("processed_result", result)
    
    return result

Artifacts

For files and directories, Strikes provides artifact storage:

with dn.run("model-training"):
    # Train a model
    model = train_model()
    
    # Save to disk
    model.save("./model_checkpoint")
    
    # Log the entire directory as an artifact
    dn.log_artifact("./model_checkpoint")

Artifacts are ideal for:

  • Model checkpoints
  • Generated images or media
  • Log files
  • Datasets
  • Source code snapshots

When you log an artifact, Strikes:

  1. Preserves the directory structure
  2. Handles large files efficiently
  3. Deduplicates identical files
  4. Makes everything available for download later

Content-Based Storage

Strikes uses content-based storage for objects and artifacts:

# These log the same content only once in storage
with dn.run():
    data = {"key": "value"}
    dn.log_input("data1", data)
    dn.log_input("data2", data)  # Reuses storage

This approach:

  • Eliminates redundant storage of identical data
  • Makes it efficient to store the same object multiple times
  • Enables linking between identical objects across runs

Object Linking

You can create explicit relationships between objects:

with dn.run():
    # Log prompt and response
    prompt = "Generate a poem about space"
    dn.log_input("prompt", prompt)
    
    response = generate_text(prompt)
    dn.log_output("response", response)
    
    # Link the response back to the prompt that generated it
    dn.link_objects(response, prompt)
    
    # Evaluate the response
    score = evaluate_response(response)
    dn.log_output("evaluation", score)
    
    # Link the evaluation to the response it's evaluating
    dn.link_objects(score, response)

This creates a graph of relationships between your data, enabling powerful analyses such as:

  • Tracing data lineage (Where did this output come from?)
  • Understanding dependencies (What inputs affected this result?)
  • Building complex data flow graphs

Associating Metrics with Objects

You can connect metrics directly to specific objects using the origin parameter:

with dn.run():
    # Log several generated responses
    responses = [generate_text("Prompt " + str(i)) for i in range(5)]
    
    for i, response in enumerate(responses):
        # Log the response
        dn.log_output(f"response_{i}", response)
        
        # Evaluate and log metric with the response as origin
        quality_score = evaluate_quality(response)
        dn.log_metric("quality", quality_score, origin=response)

This allows you to:

  • Track metrics for specific objects
  • Compare different objects based on their metrics
  • Build datasets of inputs, outputs, and measurements

Best Practices

To make the most of Strikes’ data storage capabilities:

  1. Be deliberate about what you store:

    • Log inputs that define your experiment
    • Log outputs that represent results
    • Use parameters for configuration
    • Use metrics for measurements
  2. Use consistent naming:

    • Adopt naming conventions for inputs, outputs, and parameters
    • Keep names consistent across different runs for easier comparison
  3. Create meaningful relationships:

    • Link related objects to create data lineage
    • Associate metrics with their origin objects
    • Build hierarchical task structures that reflect your workflow
  4. Consider storage efficiency:

    • For very large data, consider storing summaries or references
    • Use artifacts for large files rather than inputs/outputs
    • Leverage content-based deduplication for repeated data
  5. Integrate with your workflow:

    • Log data at natural points in your code
    • Use tasks to structure data collection
    • Leverage the automatic input/output tracking for functions