Module: RubyPi::Context::Transform

Defined in:
lib/ruby_pi/context/transform.rb

Overview

Factory methods for building transform_context callables. Each method returns a Proc that accepts an Agent::State and mutates it. Use ‘compose` to chain multiple transforms.

Examples:

Composing transforms

transform = RubyPi::Context::Transform.compose(
  RubyPi::Context::Transform.inject_datetime,
  RubyPi::Context::Transform.inject_user_preferences { |state| state.user_data[:prefs] }
)
agent = RubyPi::Agent.new(transform_context: transform, ...)

Constant Summary collapse

DATETIME_START_MARKER =

Marker delimiters for idempotent injection. Each injection type has unique start/end markers so they can be independently stripped and re-added without affecting each other.

"<!-- RUBYPI_DATETIME_START -->"
DATETIME_END_MARKER =
"<!-- RUBYPI_DATETIME_END -->"
PREFS_START_MARKER =
"<!-- RUBYPI_PREFS_START -->"
PREFS_END_MARKER =
"<!-- RUBYPI_PREFS_END -->"
WORKSPACE_START_MARKER =
"<!-- RUBYPI_WORKSPACE_START -->"
WORKSPACE_END_MARKER =
"<!-- RUBYPI_WORKSPACE_END -->"

Class Method Summary collapse

Class Method Details

.compose(*transforms) ⇒ Proc

Chains multiple transform callables into a single callable that executes them in order. Each transform receives the same State object and can mutate it freely.

Examples:

combined = Transform.compose(transform_a, transform_b, transform_c)
combined.call(state) # runs a, then b, then c

Parameters:

  • transforms (Array<Proc>)

    transform callables to chain

Returns:

  • (Proc)

    a single callable that runs all transforms in sequence



54
55
56
57
58
# File 'lib/ruby_pi/context/transform.rb', line 54

def compose(*transforms)
  ->(state) do
    transforms.each { |t| t.call(state) }
  end
end

.inject_datetimeProc

Returns a transform that appends the current date and time to the system prompt. Useful for giving the LLM temporal awareness.

This injection is IDEMPOTENT: it strips any existing datetime injection (identified by markers) before re-adding, so calling it N times in a loop produces exactly one datetime block.

Examples:

transform = Transform.inject_datetime
# Appends: "\n\nCurrent date and time: 2025-01-15 14:30:00 UTC"

Returns:

  • (Proc)

    transform callable



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ruby_pi/context/transform.rb', line 72

def inject_datetime
  ->(state) do
    timestamp = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")

    # Strip any existing datetime injection before re-adding.
    # This makes the transform idempotent — calling it multiple times
    # across loop iterations does not accumulate duplicate timestamps.
    state.system_prompt = strip_between_markers(
      state.system_prompt,
      DATETIME_START_MARKER,
      DATETIME_END_MARKER
    )

    state.system_prompt += "\n\n#{DATETIME_START_MARKER}\nCurrent date and time: #{timestamp}\n#{DATETIME_END_MARKER}"
  end
end

.inject_user_preferences {|state| ... } ⇒ Proc

Returns a transform that appends user preferences to the system prompt. The block is called with the state and should return a string or hash of preferences. If nil is returned, nothing is appended.

This injection is IDEMPOTENT: existing preferences are stripped before re-adding.

Examples:

transform = Transform.inject_user_preferences { |s| s.user_data[:prefs] }

Yields:

  • (state)

    block that extracts preferences from the state

Yield Parameters:

Yield Returns:

  • (String, Hash, nil)

    preferences to inject

Returns:

  • (Proc)

    transform callable



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/ruby_pi/context/transform.rb', line 104

def inject_user_preferences(&block)
  ->(state) do
    preferences = block.call(state)
    # Always strip existing preferences injection first for idempotency
    state.system_prompt = strip_between_markers(
      state.system_prompt,
      PREFS_START_MARKER,
      PREFS_END_MARKER
    )
    return if preferences.nil?

    prefs_text = preferences.is_a?(Hash) ? format_hash(preferences) : preferences.to_s
    state.system_prompt += "\n\n#{PREFS_START_MARKER}\n[User Preferences]\n#{prefs_text}\n#{PREFS_END_MARKER}"
  end
end

.inject_workspace_context {|state| ... } ⇒ Proc

Returns a transform that appends workspace context to the system prompt. The block is called with the state and should return contextual information about the current workspace/project.

This injection is IDEMPOTENT: existing workspace context is stripped before re-adding.

Examples:

transform = Transform.inject_workspace_context { |s| s.user_data[:workspace] }

Yields:

  • (state)

    block that extracts workspace context from the state

Yield Parameters:

Yield Returns:

  • (String, Hash, nil)

    workspace context to inject

Returns:

  • (Proc)

    transform callable



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/ruby_pi/context/transform.rb', line 134

def inject_workspace_context(&block)
  ->(state) do
    context = block.call(state)
    # Always strip existing workspace injection first for idempotency
    state.system_prompt = strip_between_markers(
      state.system_prompt,
      WORKSPACE_START_MARKER,
      WORKSPACE_END_MARKER
    )
    return if context.nil?

    ctx_text = context.is_a?(Hash) ? format_hash(context) : context.to_s
    state.system_prompt += "\n\n#{WORKSPACE_START_MARKER}\n[Workspace Context]\n#{ctx_text}\n#{WORKSPACE_END_MARKER}"
  end
end