summaryrefslogtreecommitdiff
path: root/internal/storage/db.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/db.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/db.go')
-rw-r--r--internal/storage/db.go32
1 files changed, 29 insertions, 3 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go
index a871c71..adb7c02 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -190,15 +190,41 @@ func (s *DB) CreateTask(t *task.Task) error {
return fmt.Errorf("marshaling depends_on: %w", err)
}
- _, err = s.db.Exec(`
+ tx, err := s.db.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback() //nolint:errcheck
+
+ if _, err = tx.Exec(`
INSERT INTO tasks (id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, story_id, acceptance_criteria, checker_for_task_id, checker_report)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ID, t.Name, t.Description, t.ElaborationInput, t.Project, t.RepositoryURL, string(configJSON), string(t.Priority),
t.Timeout.Duration.Nanoseconds(), string(retryJSON), string(tagsJSON), string(depsJSON),
t.ParentTaskID, string(t.State), t.CreatedAt.UTC(), t.UpdatedAt.UTC(), t.StoryID,
t.AcceptanceCriteria, t.CheckerForTaskID, t.CheckerReport,
- )
- return err
+ ); err != nil {
+ return err
+ }
+
+ // A subtask records a subtask_spawned event on its parent's stream so the
+ // parent's history shows the children it dispatched.
+ if t.ParentTaskID != "" {
+ payload, _ := json.Marshal(struct {
+ SubtaskID string `json:"subtask_id"`
+ Name string `json:"name"`
+ }{SubtaskID: t.ID, Name: t.Name})
+ if err = createEventTx(tx, &event.Event{
+ TaskID: t.ParentTaskID,
+ Kind: event.KindSubtaskSpawned,
+ Actor: event.ActorAgent,
+ Payload: payload,
+ }); err != nil {
+ return err
+ }
+ }
+
+ return tx.Commit()
}
// GetTask retrieves a task by ID.