summaryrefslogtreecommitdiff
path: root/internal/storage/events_test.go
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-24 08:19:23 +0000
committerClaude <noreply@anthropic.com>2026-05-24 08:19:23 +0000
commit48a8e49cdda833f8af22e839ca3c102c40c97e17 (patch)
tree4eae5158a11a6048d5bdc6346f003c3b10b078ef /internal/storage/events_test.go
parentb72978f4454235a00a2e5c92b5332b985cc08fa9 (diff)
feat(storage): emit subtask_spawned events on the parent stream
CreateTask is now transactional and, when a task has a parent_task_id, records a subtask_spawned event (actor agent) on the parent's stream so the parent's history shows the children it dispatched. Top-level task creation (no parent) emits nothing. This closes the one agent-originated signal that flows during a run today (subtasks POSTed to the API per the planning preamble) but previously left no trace in the event stream. https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
Diffstat (limited to 'internal/storage/events_test.go')
-rw-r--r--internal/storage/events_test.go65
1 files changed, 65 insertions, 0 deletions
diff --git a/internal/storage/events_test.go b/internal/storage/events_test.go
index 3ce79ab..c5d3c37 100644
--- a/internal/storage/events_test.go
+++ b/internal/storage/events_test.go
@@ -369,6 +369,71 @@ func TestAppendTaskInteraction_EmitsClarificationAnswer(t *testing.T) {
}
}
+func TestCreateTask_Subtask_EmitsSubtaskSpawnedOnParent(t *testing.T) {
+ db := testDB(t)
+ makeTaskForEvent(t, db, "parent-1")
+
+ now := time.Now().UTC().Truncate(time.Second)
+ child := &task.Task{
+ ID: "child-1",
+ Name: "Do the subthing",
+ ParentTaskID: "parent-1",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "noop"},
+ Priority: task.PriorityNormal,
+ State: task.StatePending,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+ if err := db.CreateTask(child); err != nil {
+ t.Fatalf("CreateTask child: %v", err)
+ }
+
+ // Parent should have the subtask_spawned event.
+ parentEvents, err := db.ListEvents("parent-1", 0)
+ if err != nil {
+ t.Fatalf("ListEvents parent: %v", err)
+ }
+ if len(parentEvents) != 1 {
+ t.Fatalf("expected 1 event on parent, got %d", len(parentEvents))
+ }
+ e := parentEvents[0]
+ if e.Kind != event.KindSubtaskSpawned || e.Actor != event.ActorAgent {
+ t.Errorf("got kind=%v actor=%v want subtask_spawned/agent", e.Kind, e.Actor)
+ }
+ var payload struct {
+ SubtaskID string `json:"subtask_id"`
+ Name string `json:"name"`
+ }
+ if err := json.Unmarshal(e.Payload, &payload); err != nil {
+ t.Fatalf("unmarshal: %v", err)
+ }
+ if payload.SubtaskID != "child-1" || payload.Name != "Do the subthing" {
+ t.Errorf("payload: got %+v", payload)
+ }
+
+ // Child should have no events of its own from creation.
+ childEvents, err := db.ListEvents("child-1", 0)
+ if err != nil {
+ t.Fatalf("ListEvents child: %v", err)
+ }
+ if len(childEvents) != 0 {
+ t.Errorf("expected no events on child, got %d", len(childEvents))
+ }
+}
+
+func TestCreateTask_TopLevel_EmitsNoEvent(t *testing.T) {
+ db := testDB(t)
+ makeTaskForEvent(t, db, "top-1") // no parent
+
+ events, err := db.ListEvents("top-1", 0)
+ if err != nil {
+ t.Fatalf("ListEvents: %v", err)
+ }
+ if len(events) != 0 {
+ t.Errorf("top-level task creation should emit no event, got %d", len(events))
+ }
+}
+
func TestCreateEvent_RequiresFields(t *testing.T) {
db := testDB(t)