pikuri-tasks 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 113302bc63d0e9bd10c56550592d8e0aee68212fd1ee2cfe4032b8d1f2836b2c
4
- data.tar.gz: 7d9e4351247953a5b7d7f5e54acbd68ace82404966bdeca47389b81804f2714e
3
+ metadata.gz: bef39f17faad60151665971aac68c2090ca08d02e343ce209ca00cdc81e4e7c5
4
+ data.tar.gz: bc0e54db37e868a58015c8ff18f0a9aba2a7e1232310faa608fcd4e525e2aea8
5
5
  SHA512:
6
- metadata.gz: b287b65b9b99bd77966901c622aa503942cd02b09bbc8b10a2a43451dac01754136666bb7df53473990d31eed5ff2a5bd41213bdff08635bfe9657d92bce1412
7
- data.tar.gz: 760eed62da11196fd09c3bcbb5bd8036e3697683bedd6d7c3eeb86ed5ce1aa2baeaf325b1780bb9ab285ef38ee12d8ec3b65773b794945cbf6ff76898b17b64f
6
+ metadata.gz: 6583cca82b6b2b8ac13169ce045420cb960a06157a2cc2f4da7f6ebbbbb232066ae58d9b484c4736bc509b4c78625265a5b84838cd44e9cbe2dbd94c5575fbb0
7
+ data.tar.gz: b778189760eb74d9ee7d882da3b308ed4bd8a020d21c2f480e6f2cc76b1868b64ea907e24f5ce132b688b14c09fcd38e8237e8825bf0a02398c31d99e077ad0e
@@ -3,14 +3,12 @@
3
3
  module Pikuri
4
4
  module Tasks
5
5
  # The +task_completed+ tool: mark the item with the given +id+ as
6
- # +completed+. Same shape and rationale as {InProgress} the
7
- # status is baked into the tool name to remove the enum-typo
8
- # failure mode (+"completed"+ / +"complete"+ / +"done"+), and the
9
- # item is addressed by the numeric +#id+ from the rendered list.
6
+ # +completed+. Same shape as {InProgress} (status in the tool name,
7
+ # item addressed by numeric +#id+). Returns {List#render}; a bad id
8
+ # returns +"Error: no such task id"+ plus the current list.
10
9
  #
11
- # Returns the rendered current list via {List#render} on success.
12
- # On a bad id returns +"Error: no such task id: <id>"+ plus the
13
- # current list, so the LLM can pick the right id in one turn.
10
+ # Sharing: +P_one_agent+ closes over one agent's {List}; see its
11
+ # +== Sharing+.
14
12
  class Completed < Pikuri::Tool
15
13
  # @return [String]
16
14
  DESCRIPTION = <<~DESC
@@ -37,7 +35,9 @@ module Pikuri
37
35
  },
38
36
  execute: lambda { |id:|
39
37
  Completed.execute(list: list, id: id)
40
- }
38
+ },
39
+ # No legs; see {Create}.
40
+ trifecta_legs: Pikuri::Tool::TrifectaLegs::NONE
41
41
  )
42
42
  end
43
43
 
@@ -2,28 +2,22 @@
2
2
 
3
3
  module Pikuri
4
4
  module Tasks
5
- # The +task_create+ tool: mass-create pending items in a single
6
- # call from a JSON array of strings — a native +items+ array
7
- # parameter, the shape every mainstream harness's task tool uses
8
- # and therefore the shape the model's training prior produces
9
- # unprompted. (An earlier newline-separated-string design tried to
10
- # spare small models the bracket-balancing; in practice models
11
- # sent JSON arrays anyway — the prior beats the parameter
12
- # description — and the splitter turned `[`, `"foo",`, `]` into
13
- # garbage tasks. Match the prior instead of fighting it.)
5
+ # The +task_create+ tool: mass-create pending items from a JSON array
6
+ # of strings — a native +items+ array parameter, the shape the model's
7
+ # training prior produces unprompted. (Regression guard: don't revert
8
+ # to a newline-separated string. An earlier such design meant to spare
9
+ # small models the bracket-balancing, but models sent JSON arrays
10
+ # anyway and the splitter turned `[`, `"foo",`, `]` into garbage tasks
11
+ # match the prior, don't fight it.)
14
12
  #
15
- # Each element is whitespace-stripped; a blank element aborts the
16
- # call (it is always an LLM mistake, never intent). If any input
17
- # is a duplicate (within the batch, or already in the list), the
18
- # whole call likewise aborts with an +"Error: ..."+ string and
19
- # nothing is added — the LLM resends a corrected batch on the
20
- # next turn. Atomic semantics keep the list in a coherent state
21
- # the LLM doesn't have to reconcile.
13
+ # Atomic: each element is stripped, and a blank or duplicate element
14
+ # (within the batch or already in the list) aborts the *whole* call
15
+ # with an +"Error: ..."+ string and adds nothing the LLM resends a
16
+ # corrected batch, and never has to reconcile a half-applied list. On
17
+ # success returns {List#render}.
22
18
  #
23
- # On success returns the rendered current list via {List#render}
24
- # including each new item's +#id+, which the three mutation tools
25
- # address items by — so the LLM always sees fresh state without a
26
- # separate read tool.
19
+ # Sharing: +P_one_agent+ closes over one agent's {List}; see its
20
+ # +== Sharing+.
27
21
  class Create < Pikuri::Tool
28
22
  # @return [String] static description shown to the LLM,
29
23
  # opencode-shape (summary + +Usage:+ bullets).
@@ -32,14 +26,12 @@ module Pikuri
32
26
 
33
27
  Usage:
34
28
  - Use at the start of a multi-step task to capture the plan.
35
- - `items` is a JSON array of strings — one task per element.
36
29
  - A blank element, a duplicate (within the batch or already on the list), or an empty array aborts the whole call with `Error: ...` and adds nothing — resend a corrected batch.
37
30
  - On success the full current list is returned, with each task's `#id` — use that id with `task_in_progress` / `task_completed` / `task_delete`.
38
31
  DESC
39
32
 
40
- # @param list [List] the shared per-Agent list, captured by
41
- # closure so every tool in the {Extension}'s set mutates the
42
- # same instance.
33
+ # @param list [List] the shared per-Agent list, captured by closure
34
+ # so every task tool mutates the same instance.
43
35
  # @return [Create]
44
36
  def initialize(list:)
45
37
  super(
@@ -52,12 +44,13 @@ module Pikuri
52
44
  },
53
45
  execute: lambda { |items:|
54
46
  Create.execute(list: list, items: items)
55
- }
47
+ },
48
+ # No legs: an in-memory, per-agent list. Nothing read from disk, nothing sent.
49
+ trifecta_legs: Pikuri::Tool::TrifectaLegs::NONE
56
50
  )
57
51
  end
58
52
 
59
- # Validate and apply the batch. Public so specs can drive it
60
- # without constructing a tool instance.
53
+ # Validate and apply the batch.
61
54
  #
62
55
  # @param list [List]
63
56
  # @param items [Array<String>] raw +items+ argument from the LLM
@@ -2,22 +2,15 @@
2
2
 
3
3
  module Pikuri
4
4
  module Tasks
5
- # The +task_delete+ tool: remove the item with the given +id+ from
6
- # the list. Used to drop items that turned out to be unnecessary,
7
- # were created in error, or have been supersededrather than
8
- # leaving them sitting in +pending+ as visual noise.
5
+ # The +task_delete+ tool: remove the item with the given +id+.
6
+ # Distinct from {Completed} in intent +completed+ means the work was
7
+ # done, +delete+ means the task shouldn't have been therea
8
+ # distinction the tool names carry, not the list. A deleted id is never
9
+ # reused ({List#add}). Returns {List#render}; a bad id returns
10
+ # +"Error: no such task id"+ plus the current list.
9
11
  #
10
- # Distinct from {Completed}: +completed+ means the work was
11
- # actually done; +delete+ means the task should never have been
12
- # there. The list itself draws no such distinction once an item
13
- # is gone, but the LLM picks the right verb because the tool
14
- # names make the intent clear. A deleted item's id is never
15
- # reused (see {List#add}), so a stale id errors loudly instead of
16
- # silently hitting a newer task.
17
- #
18
- # Returns the rendered current list via {List#render} on success.
19
- # On a bad id returns +"Error: no such task id: <id>"+ plus the
20
- # current list, so the LLM can pick the right id in one turn.
12
+ # Sharing: +P_one_agent+ closes over one agent's {List}; see its
13
+ # +== Sharing+.
21
14
  class Delete < Pikuri::Tool
22
15
  # @return [String]
23
16
  DESCRIPTION = <<~DESC
@@ -43,7 +36,9 @@ module Pikuri
43
36
  },
44
37
  execute: lambda { |id:|
45
38
  Delete.execute(list: list, id: id)
46
- }
39
+ },
40
+ # No legs; see {Create}.
41
+ trifecta_legs: Pikuri::Tool::TrifectaLegs::NONE
47
42
  )
48
43
  end
49
44
 
@@ -7,46 +7,24 @@ module Pikuri
7
7
  # event, and the {Extension} that wires them into an
8
8
  # {Pikuri::Agent}.
9
9
  module Tasks
10
- # An {Pikuri::Agent::Extension} that auto-wires an in-memory
11
- # task list onto an agent: constructs a fresh {List}, registers
12
- # the four task tool classes against it, appends a brief
13
- # workflow snippet to the system prompt, and (in {#bind}) arms
14
- # {ListChanged} emission so listeners can observe every list
15
- # mutation.
16
- #
17
- # == Usage
10
+ # An {Pikuri::Agent::Extension} that auto-wires an in-memory task list
11
+ # onto an agent: constructs a fresh {List}, registers the four task
12
+ # tools against it, contributes a workflow snippet, and (in {#bind})
13
+ # arms {ListChanged} emission.
18
14
  #
19
15
  # Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
20
16
  # c.add_extension Pikuri::Tasks::Extension.new
21
17
  # end
22
18
  #
23
- # The list is per-Agent and in-memory. It is dropped when the
24
- # agent is garbage-collected nothing is written to disk.
25
- #
26
- # == Sub-agents
27
- #
28
- # Sub-agents do not inherit extensions (see CLAUDE.md's "Seams").
29
- # Concretely: a sub-agent spawned by the +agent+ tool gets a
30
- # fresh persona, fresh toolset, no task list. That keeps the
31
- # parent's plan private to the parent and avoids the
32
- # who-owns-which-task confusion a shared list would produce.
33
- # If a host wants the sub-agent to also have a task list, it
34
- # adds the extension to the sub-agent's own configurator —
35
- # cleanly opt-in, no implicit sharing.
36
- #
37
- # == Empty default
38
- #
39
- # No +catalog+-style empty state: registering the extension
40
- # always installs the four tools and the snippet. A host that
41
- # doesn't want tasks simply omits the extension.
19
+ # Sub-agents don't inherit extensions, so a spawned sub-agent gets no
20
+ # task listkeeping the parent's plan private and avoiding
21
+ # who-owns-which-task confusion. A host wanting one adds the extension
22
+ # to the sub-agent's own configurator.
42
23
  class Extension
43
24
  include Pikuri::Agent::Extension
44
25
 
45
- # System-prompt snippet appended once per agent. Short by
46
- # design: rules-of-thumb only, no inventory (the tool
47
- # descriptions cover their own usage). Mirrors the shape of
48
- # opencode's +todowrite.txt+ but condensed to fit pikuri's
49
- # "short prose over abstract framing" docs convention.
26
+ # System-prompt snippet appended once per agent. Rules-of-thumb
27
+ # only, no inventory (the tool descriptions cover their own usage).
50
28
  #
51
29
  # @return [String]
52
30
  PROMPT_SNIPPET = <<~PROMPT
@@ -65,8 +43,7 @@ module Pikuri
65
43
  </tasks_usage>
66
44
  PROMPT
67
45
 
68
- # Tool classes the extension auto-registers. Used both for
69
- # construction and for the duplicate-registration guard below.
46
+ # Tool classes the extension auto-registers.
70
47
  TOOL_CLASSES = [Create, InProgress, Completed, Delete].freeze
71
48
 
72
49
  # @return [Extension]
@@ -74,19 +51,16 @@ module Pikuri
74
51
  @list = List.new
75
52
  end
76
53
 
77
- # @return [List] the per-agent list, exposed for tests. UI
78
- # hosts should NOT read it from another thread — the list is
79
- # agent-thread-confined; consume the {ListChanged} events
80
- # wired by {#bind} instead (each carries an immutable
81
- # snapshot safe to render from anywhere).
54
+ # @return [List] the per-agent list, exposed for tests. Do NOT read
55
+ # it from another thread — it's agent-thread-confined; consume the
56
+ # {ListChanged} events wired by {#bind} instead.
82
57
  attr_reader :list
83
58
 
84
- # Construct the four tools (each sharing +@list+) and register
85
- # them, then append {PROMPT_SNIPPET}. Raises if any of the four
86
- # tool classes have been pre-registered via +c.add_tool+ — the
87
- # whole point of the extension is to be the single owner of the
88
- # shared list, and a manually pre-registered tool would bind to
89
- # a different list.
59
+ # Construct the four tools (each sharing +@list+), register them,
60
+ # and append {PROMPT_SNIPPET}. Raises if any of the four were
61
+ # pre-registered via +c.add_tool+ — the extension is the single
62
+ # owner of the shared list, and a pre-registered tool would bind to
63
+ # a different one.
90
64
  #
91
65
  # @param c [Pikuri::Agent::Configurator]
92
66
  # @return [void]
@@ -100,16 +74,15 @@ module Pikuri
100
74
  end
101
75
 
102
76
  TOOL_CLASSES.each { |cls| c.add_tool(cls.new(list: @list)) }
103
- c.append_system_prompt(PROMPT_SNIPPET)
104
77
  nil
105
78
  end
106
79
 
107
- # Arm {List#on_change} to emit a {ListChanged} (carrying a
108
- # fresh {List#items} snapshot) onto the agent's listener
109
- # stream after every mutation. This is what lets a UI listener
110
- # observe the task list without ever touching the
111
- # agent-thread-confined {List} — see the Concurrency note on
112
- # {List}.
80
+ # @return [Array<String>] the static task-workflow snippet.
81
+ def system_prompt_snippets = [PROMPT_SNIPPET]
82
+
83
+ # Arm {List#on_change} to emit a {ListChanged} (carrying a fresh
84
+ # {List#items} snapshot) after every mutation how a UI listener
85
+ # observes the list without touching the agent-thread-confined {List}.
113
86
  #
114
87
  # @param ctx [Pikuri::Agent::ExtensionContext]
115
88
  # @return [void]
@@ -117,6 +90,19 @@ module Pikuri
117
90
  @list.on_change = -> { ctx.emit_event(ListChanged.new(items: @list.items)) }
118
91
  nil
119
92
  end
93
+
94
+ # Clear the task list on conversation reset — a plan is meaningless
95
+ # without the conversation that produced it. {List#clear} fires the
96
+ # armed +on_change+, so an empty {ListChanged} reaches the listener
97
+ # stream like any other mutation.
98
+ #
99
+ # @param ctx [Pikuri::Agent::ExtensionContext] unused; part of the
100
+ # protocol signature.
101
+ # @return [void]
102
+ def on_conversation_reset(_ctx)
103
+ @list.clear
104
+ nil
105
+ end
120
106
  end
121
107
  end
122
108
  end
@@ -2,21 +2,19 @@
2
2
 
3
3
  module Pikuri
4
4
  module Tasks
5
- # The +task_in_progress+ tool: mark the item with the given +id+
6
- # as +in_progress+. The status name is baked into the tool name
7
- # rather than passed as a parameter that takes one degree of
8
- # freedom away from the LLM (no +"in-progress"+ vs +"in_progress"+
9
- # vs +"inprogress"+ typos) at the cost of one extra tool class.
5
+ # The +task_in_progress+ tool: mark the item with the given +id+ as
6
+ # +in_progress+. The status is baked into the tool name, not a
7
+ # parameter one fewer degree of freedom for the LLM (no
8
+ # +"in-progress"+/+"inprogress"+ typos) at the cost of one tool class.
10
9
  #
11
- # Items are addressed by the numeric +#id+ shown in every rendered
12
- # list (an earlier design used the content string as identifier;
13
- # one near-miss in reproducing the exact bytes — a stray quote, a
14
- # trailing comma and the LLM is locked out of its own list. An
15
- # id is two digits it just read back; nothing to mis-transcribe.)
10
+ # Items are addressed by numeric +#id+, not content string (regression
11
+ # guard: an earlier content-as-id design locked the LLM out of its own
12
+ # list on one near-miss byte — a stray quote or trailing comma; an id
13
+ # is two digits it just read back). Returns {List#render}; a bad id
14
+ # returns +"Error: no such task id"+ plus the current list.
16
15
  #
17
- # Returns the rendered current list via {List#render} on success.
18
- # On a bad id returns +"Error: no such task id: <id>"+ plus the
19
- # current list, so the LLM can pick the right id in one turn.
16
+ # Sharing: +P_one_agent+ closes over one agent's {List}; see its
17
+ # +== Sharing+.
20
18
  class InProgress < Pikuri::Tool
21
19
  # @return [String]
22
20
  DESCRIPTION = <<~DESC
@@ -42,7 +40,9 @@ module Pikuri
42
40
  },
43
41
  execute: lambda { |id:|
44
42
  InProgress.execute(list: list, id: id)
45
- }
43
+ },
44
+ # No legs; see {Create}.
45
+ trifecta_legs: Pikuri::Tool::TrifectaLegs::NONE
46
46
  )
47
47
  end
48
48
 
@@ -2,55 +2,44 @@
2
2
 
3
3
  module Pikuri
4
4
  module Tasks
5
- # Raised by {List#add} when a new item's +content+ already appears
6
- # in the list. Items are addressed by {Item#id}, so duplicates
7
- # would not be ambiguous they are rejected because a duplicate
8
- # is almost always an LLM mistake (re-sending an already-captured
9
- # plan), and catching it early keeps the list coherent. The
10
- # exception carries the offending content string so the tool layer
11
- # can surface it verbatim in the +"Error: ..."+ observation.
5
+ # Raised by {List#add} when a new item's +content+ already appears in
6
+ # the list rejected because a duplicate is almost always an LLM
7
+ # mistake (re-sending an already-captured plan). Carries the offending
8
+ # content for the +"Error: ..."+ observation.
12
9
  class DuplicateItem < StandardError; end
13
10
 
14
- # Raised by {List#set_status} and {List#delete} when no item with
15
- # the given +id+ exists. The three mutation tools rescue this and
16
- # report +"Error: no such task id: <id>"+ plus the current list
17
- # render, so the LLM can pick the right id on the next turn.
11
+ # Raised by {List#set_status} and {List#delete} when no item with the
12
+ # given +id+ exists; the mutation tools rescue it into an +"Error: no
13
+ # such task id"+ observation.
18
14
  class ItemNotFound < StandardError; end
19
15
 
20
- # Allowed values for {Item#status}. Kept deliberately small three
21
- # states match the workflow the prompt advertises (pending → in_progress
22
- # completed) without inviting a +cancelled+ vs +deleted+ debate.
23
- # +task_delete+ removes items outright rather than introducing a
24
- # fourth status.
16
+ # Allowed values for {Item#status}. Deliberately threethe workflow
17
+ # the prompt advertises (pending → in_progress → completed) — with
18
+ # +task_delete+ removing items outright rather than adding a fourth
19
+ # +cancelled+/+deleted+ status.
25
20
  STATUSES = %w[pending in_progress completed].freeze
26
21
 
27
22
  # One row in a {List}. +id+ is the numeric identifier the mutation
28
- # tools address the item by (assigned by {List#add}, never reused
29
- # see there); +content+ is the LLM-visible task text; +status+ is
30
- # one of {STATUSES}.
23
+ # tools address the item by (assigned by {List#add}, never reused);
24
+ # +content+ is the LLM-visible task text; +status+ is one of {STATUSES}.
31
25
  Item = Data.define(:id, :content, :status)
32
26
 
33
27
  # An in-memory ordered list of {Item}s, scoped to a single
34
- # {Pikuri::Agent}. Held inside {Extension} and captured by closure
35
- # into the +execute+ block of each of the four task tool classes,
36
- # so every mutation hits the same instance.
28
+ # {Pikuri::Agent}, shared by closure into all four task tools so every
29
+ # mutation hits the same instance.
37
30
  #
38
- # == Concurrency
31
+ # Not persisted: dropped when the +Agent+ is collected, matching the
32
+ # gem's "in-memory only" scope.
39
33
  #
40
- # The list is confined to the agent's thread: the agent loop is
41
- # single-threaded with respect to tool calls (ruby_llm dispatches
42
- # them sequentially), so no locking. Other threads (e.g. a web UI
43
- # rendering the list) must not touch a +List+ directly — they
44
- # consume the {ListChanged} events {Extension#bind} wires onto
45
- # the listener stream, whose +items+ payload is an immutable
46
- # snapshot safe to hand across threads. A future
47
- # parallel-tool-execution feature would need a +Mutex+ here.
34
+ # == Sharing
48
35
  #
49
- # == Persistence
50
- #
51
- # None. The list is dropped when the +Agent+ is garbage-collected.
52
- # That matches the gem's stated scope: "in-memory only, no
53
- # session-state-on-disk."
36
+ # +P_one_agent+ — this is one conversation's plan, so two agents on one
37
+ # list would read and complete each other's tasks. Unguarded, and it needs
38
+ # no lock because its only caller is the agent whose tool calls are already
39
+ # serialized. Another thread (a UI rendering the list) must not touch a
40
+ # +List+ directly: it consumes the {ListChanged} events {Extension#bind}
41
+ # wires onto the listener stream, whose +items+ payload is an immutable
42
+ # snapshot safe to hand across threads.
54
43
  class List
55
44
  # @return [List]
56
45
  def initialize
@@ -59,13 +48,10 @@ module Pikuri
59
48
  @on_change = nil
60
49
  end
61
50
 
62
- # Optional zero-argument hook invoked after every successful
63
- # mutation ({#add} / {#set_status} / {#delete}) not on failed
64
- # ones (a raise means nothing changed). Set by {Extension#bind}
65
- # to emit a {ListChanged} onto the agent's listener stream;
66
- # +nil+ (the default) disables notification. Runs on the
67
- # mutating (agent) thread, synchronously inside the mutation
68
- # call.
51
+ # Optional zero-argument hook invoked synchronously after every
52
+ # *successful* mutation (a raise means nothing changed, so no
53
+ # notify). Set by {Extension#bind} to emit a {ListChanged}; +nil+
54
+ # (default) disables notification.
69
55
  #
70
56
  # @return [Proc, nil]
71
57
  attr_accessor :on_change
@@ -95,8 +81,7 @@ module Pikuri
95
81
  # @param content [String] non-empty content; whitespace is the
96
82
  # caller's responsibility.
97
83
  # @return [Item] the newly added item
98
- # @raise [DuplicateItem] if an item with the same +content+
99
- # already exists (a duplicate is almost always an LLM mistake).
84
+ # @raise [DuplicateItem] if an item with the same +content+ exists.
100
85
  def add(content)
101
86
  raise DuplicateItem, content if @items.any? { |i| i.content == content }
102
87
 
@@ -143,10 +128,23 @@ module Pikuri
143
128
  removed
144
129
  end
145
130
 
146
- # The canonical rendering returned as the observation by every
147
- # task tool, so the LLM sees the latest full state — including
148
- # each item's id on each call without needing a separate read
149
- # tool. Format:
131
+ # Drop every item and rewind the id counter to 1, then fire
132
+ # {#on_change} (always, even when already empty, so a UI listener
133
+ # sees the cleared state). Rewinding +@next_id+ is safe here unlike
134
+ # after {#delete}, a clear wipes the model's context too, so no stale
135
+ # id survives to collide with a reused one.
136
+ #
137
+ # @return [void]
138
+ def clear
139
+ @items = []
140
+ @next_id = 1
141
+ @on_change&.call
142
+ nil
143
+ end
144
+
145
+ # The canonical rendering every task tool returns as its
146
+ # observation, so the LLM sees the full state (ids included) each
147
+ # call without a separate read tool:
150
148
  #
151
149
  # <tasks>
152
150
  # - #1 [pending] Add dark mode toggle
@@ -154,9 +152,8 @@ module Pikuri
154
152
  # - #3 [completed] Update README
155
153
  # </tasks>
156
154
  #
157
- # Empty list renders as +<tasks>(empty)</tasks>+ so the LLM gets
158
- # an unambiguous "yes, the call worked and the list is now empty"
159
- # signal rather than an ambiguous blank block.
155
+ # Empty renders as +<tasks>(empty)</tasks>+ an unambiguous "the
156
+ # call worked, list is now empty" rather than a blank block.
160
157
  #
161
158
  # @return [String]
162
159
  def render
@@ -2,25 +2,17 @@
2
2
 
3
3
  module Pikuri
4
4
  module Tasks
5
- # Domain event emitted onto the agent's listener stream after
6
- # every {List} mutation (add / status change / delete), wired by
7
- # {Extension#bind} via
5
+ # Domain event emitted after every {List} mutation (add / status
6
+ # change / delete), wired by {Extension#bind} via
8
7
  # {Pikuri::Agent::ExtensionContext#emit_event}. Carries a frozen
9
- # point-in-time snapshot of the whole list ({List#items}'s
10
- # shape), so a consumer needs no +List+ reference — render or
11
- # serialize the payload as-is.
8
+ # whole-list snapshot ({List#items}), so a consumer needs no +List+
9
+ # reference render or serialize the payload as-is.
12
10
  #
13
- # Lands between the mutating tool's {Pikuri::Agent::Event::ToolCall}
14
- # and {Pikuri::Agent::Event::ToolResult} in the stream (the
15
- # +on_change+ hook fires inside the tool's +execute+). A batch
16
- # +task_create+ emits one event per added item consumers that
17
- # render should treat the latest snapshot as authoritative
18
- # (last-wins) rather than diffing event-by-event.
19
- #
20
- # Fired on the agent's thread. A listener feeding another thread
21
- # (e.g. a web UI pushing over SSE) should serialize inside
22
- # +on_event+ and hand off only the immutable result — see the
23
- # Concurrency note on {List}.
11
+ # A batch +task_create+ emits one event per added item, so a renderer
12
+ # should treat the latest snapshot as authoritative (last-wins), not
13
+ # diff event-by-event. Fired on the agent's thread a listener
14
+ # feeding another thread serializes inside +on_event+ and hands off
15
+ # only the immutable result (see {List}'s thread-confinement note).
24
16
  ListChanged = Data.define(:items)
25
17
  end
26
18
  end
data/lib/pikuri-tasks.rb CHANGED
@@ -2,16 +2,8 @@
2
2
 
3
3
  require 'pikuri-core'
4
4
 
5
- # Entry file for the pikuri-tasks gem. Sets up a dedicated Zeitwerk
6
- # loader rooted at this gem's +lib/+, contributing to the shared
7
- # +Pikuri::+ namespace alongside pikuri-core. After +require
8
- # 'pikuri-tasks'+, +Pikuri::Tasks::List+, the four task tool classes
9
- # (+Create+, +InProgress+, +Completed+, +Delete+), and
10
- # +Pikuri::Tasks::Extension+ are all defined.
11
- #
12
- # The loader is per-gem (not shared with pikuri-core's loader) so each
13
- # gem owns its own +lib/+ tree and the cooperation between gems is via
14
- # the Pikuri namespace alone.
5
+ # Entry file for the pikuri-tasks gem: sets up the per-gem Zeitwerk
6
+ # loader. See {Pikuri::Tasks} for the namespace overview.
15
7
  module Pikuri
16
8
  module Tasks
17
9
  LOADER = Zeitwerk::Loader.new
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pikuri-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Vysny
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.0.7
18
+ version: 0.1.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.0.7
25
+ version: 0.1.0
26
26
  description: |
27
27
  pikuri-tasks gives a pikuri-core agent an in-memory task list it
28
28
  can use to plan and track multi-step work. A +Pikuri::Tasks::List+