Class: Pikuri::Tasks::List

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/tasks/list.rb

Overview

An in-memory ordered list of Items, scoped to a single Agent. Held inside Extension and captured by closure into the execute block of each of the four task tool classes, so every mutation hits the same instance.

Concurrency

The agent loop is single-threaded with respect to tool calls (ruby_llm dispatches them sequentially), so no locking. A future parallel-tool-execution feature would need a Mutex here.

Persistence

None. The list is dropped when the Agent is garbage-collected. That matches the gem’s stated scope: “in-memory only, no session-state-on-disk.”

Instance Method Summary collapse

Constructor Details

#initializeList



49
50
51
# File 'lib/pikuri/tasks/list.rb', line 49

def initialize
  @items = []
end

Instance Method Details

#add(content) ⇒ Item

Append a new item with status pending. Content matching is exact (no case- or whitespace-folding) since the tools quote the content back to the LLM as the identifier.

Parameters:

  • content (String)

    non-empty content; whitespace is the caller’s responsibility.

Returns:

  • (Item)

    the newly added item

Raises:

  • (DuplicateItem)

    if an item with the same content already exists.



79
80
81
82
83
84
85
# File 'lib/pikuri/tasks/list.rb', line 79

def add(content)
  raise DuplicateItem, content if find(content)

  item = Item.new(content: content, status: 'pending')
  @items << item
  item
end

#delete(content) ⇒ Item

Remove the item whose content matches.

Parameters:

  • content (String)

Returns:

  • (Item)

    the removed item.

Raises:



112
113
114
115
116
117
# File 'lib/pikuri/tasks/list.rb', line 112

def delete(content)
  idx = @items.index { |i| i.content == content }
  raise ItemNotFound, content if idx.nil?

  @items.delete_at(idx)
end

#empty?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/pikuri/tasks/list.rb', line 66

def empty?
  @items.empty?
end

#itemsArray<Item>

Returns a frozen snapshot of the current items, in insertion order. Callers cannot mutate the internal storage through this accessor.

Returns:

  • (Array<Item>)

    a frozen snapshot of the current items, in insertion order. Callers cannot mutate the internal storage through this accessor.



56
57
58
# File 'lib/pikuri/tasks/list.rb', line 56

def items
  @items.dup.freeze
end

#renderString

The canonical rendering returned as the observation by every task tool, so the LLM sees the latest full state on each call without needing a separate read tool. Format:

<tasks>
- [pending] Add dark mode toggle
- [in_progress] Write unit tests
- [completed] Update README
</tasks>

Empty list renders as <tasks>(empty)</tasks> so the LLM gets an unambiguous “yes, the call worked and the list is now empty” signal rather than an ambiguous blank block.

Returns:

  • (String)


134
135
136
137
138
139
# File 'lib/pikuri/tasks/list.rb', line 134

def render
  return '<tasks>(empty)</tasks>' if @items.empty?

  lines = @items.map { |i| "- [#{i.status}] #{i.content}" }
  "<tasks>\n#{lines.join("\n")}\n</tasks>"
end

#set_status(content:, status:) ⇒ Item

Update the status of the item whose content matches.

Parameters:

  • content (String)
  • status (String)

    one of STATUSES.

Returns:

  • (Item)

    the updated item (a fresh frozen Data instance — the old one is replaced in place).

Raises:



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/pikuri/tasks/list.rb', line 95

def set_status(content:, status:)
  unless STATUSES.include?(status)
    raise ArgumentError, "invalid status: #{status.inspect} (allowed: #{STATUSES.join(', ')})"
  end

  idx = @items.index { |i| i.content == content }
  raise ItemNotFound, content if idx.nil?

  @items[idx] = Item.new(content: content, status: status)
  @items[idx]
end

#sizeInteger

Returns:

  • (Integer)


61
62
63
# File 'lib/pikuri/tasks/list.rb', line 61

def size
  @items.size
end