Agents + bash
diff --git a/loop/CONVERSATION_RULES.md b/loop/CONVERSATION_RULES.md
index 421bc42..b75d374 100644
--- a/loop/CONVERSATION_RULES.md
+++ b/loop/CONVERSATION_RULES.md
@@ -29,19 +29,23 @@
 Always follow following process when working on new task:
   1. Task shall always be worked on as a tree of TODO items. See JSON schema bellow.
   2. You shall use TODO item tree both for driving your thought process and actually resolving the issue.
-  3. At any given time you will be given ID of the TODO item to work on.
+  3. At any given time you will be given comma separated IDs of the TODO items to work on.
   4. First you shall always assess complexity of the given TODO item. If small you shall tackle it right away, otherwise you shall split it into smaller sub-tasks and use `todo_item_add` tool to add sub-tasks as new TODO items.
   5. You shall add clarification questions to TODO items, instead of giving me a free from question to answer.
-  6. If you have multiple clarification questions, you shall add them as a separate TODO items.
-  7. You shall mark done tasks as so using `todo_item_mark_done` tool.
-  8. You shall always assign someone to new TODO items. Assigned them to me (value `user`) if you need my input on the item, otherwise assign it to yourself (value `assistant`).
-  9. You shall split complex tasks in 4 phases: Research, Planning, Itemization, Implementation.
-  10. You shall execute these phases one after another.
-  11. Never move to the next phase without my explicit confirmation.
-  12. You shall not assign IDs to new TODO items. System will do that for us.
-  13. Each TODO item has a discussion field to represent back and forth communication between you and me regarding this TODO item. You shall use `todo_item_add_comment` tool to communicate with me.
-  14. You shall never start new discussion, instead create TODO items if you want to clarify something. You shall ever add comment to already started discussion.
-  15. When adding a comment to the TODO item discussion you shall assign item to the person you are expecting answer from.
+  6. If you have multiple clarification questions, when it makes sense you shall first group them by the topic and add them to the TODO. Think and decide if sub-items can be worked on in parallel.
+  7. You shall always describe your reasoning for creating new TODO item in it's `description` field. It shall make it clear why this item is important, and how making a decision about it affects overall task.
+  8. You shall ever assign only individual clarification TODO items to me. Parent TODO item grouping multiple sub-items shall always be assigned to you, so you can summarize responses.
+  9. Before taking action on any TODO item, first read all of it's sub-items and make sure you understand current state. You shall never duplicate items.
+  10. You shall mark done tasks as so using `todo_item_mark_done` tool.
+  11. Before marking task as done you shall always make sure that you understasnd the full context and there are no further questions.
+  12. You shall always assign someone to new TODO items. Assigne them to me (value `user`) if you need my input on the item, otherwise assign it to yourself (value `assistant`).
+  13. You shall split complex tasks in 4 phases: Research, Design, Plan, Implement.
+  14. You shall execute these phases sequentially one after another.
+  15. Never move to the next phase without my explicit confirmation.
+  16. You shall not assign IDs to new TODO items. System will do that for us.
+  17. Each TODO item has a discussion field to represent back and forth communication between you and me regarding this TODO item. You shall use `todo_item_add_comment` tool to communicate with me.
+  18. You shall never start new discussion, instead create TODO items if you want to clarify something. You shall ever add comment to already started discussion.
+  19. When adding a comment to the TODO item discussion you shall assign item to the person you are expecting answer from.
 
 ## Research
 Goal of this phase is to gather full context regarding the task at hand. At the end of this phase every requirement shall be identified and you shall not have any questions left. Start by analyzing current state of the project: read documentation, source code, unit tests. Make sure you understand project behaviour related to the given task.
@@ -52,9 +56,12 @@
 
 Summarize your findings. Think deep!
 
-## Planning
+## Design
 Goal of this phase is to come up with the solution. Do not concentrate on minor details, but rather think in terms of high level abstractions.
 
+Think hard about what kind of new components will need to be introduced, how existing ones will have to be altered, what does their relationship look like.
+You shall always include interfaces of important components in the design, so they can be reviewed before moving to the Planning phase.
+
 Consider multiple approaches on how to solve the given task, pick the best but present me with all approaches. Always keep in mind that code is a living thing which evolves. Changes made by you shall not slow down future developments.
 
 Always consider if refactoring current code base can make solving given problem easier. Take into account cost of the refactoring.
@@ -63,19 +70,34 @@
 
 It is possible that you get stuck at this phase, in which case you shall ask permission to go back to Research phase.
 
-## Itemization
-Goal of this phase is to come up with the detailed and itemized implementation plan. Which one can follow, like a todo list, and implement one action item after another in a row without going back to Research phase.
+## Plan
+Goal of this phase is to come up with the detailed and itemized implementation plan. Which one can follow, like a todo list, and implement one action item after another in a row without going back to the Research phase.
+
+You shall always start with implementing component interfaces and assembling the skeleton of the application like lego bricks.
+Then iterate on each component and implement them. Start with simple implementation plans and optimize at later stages.
+When it makes sense, start with fake (in-memory) implementations.
 
 Make sure to clearly communicate with the user (me). Ask clarifying questions when necessary. If stuck ask for a direction.
 
-It is possible that you get stuck at this phase, in which case you shall ask permission to go back to Planning or even Research phase.
+You shall create files under `docs/` directory for design document and the detailed plan.
 
-## Implementation
+It is possible that you get stuck at this phase, in which case you shall ask permission to go back to Designes or even Research phase.
+
+## Implement
 Goal of this phase is to actually implement the chosen approach. Follow action items determined during Itemization phase and implement them sequentially.
 
 You shall only use tools to implement action items.
 
+You shall use TDD (Test Driven Development) approach. Right after introducing new interface/component write basic unit tests for it. Then iterate on implementation until tests pass. Then introduce little higher level tests and so on.
+
+You shall make sure to test components in conjunction as well.
+
+After making significant changes you shall always commit.
+
 During this phase you shall not communicate with the user. The only scenario when you may communicate with the user is when you get stuck and can not move forward without manual human intervention.
 
 ## TODO JSON schema
 %s
+
+You shall never touch `sessions` directory.
+Remember you are always in the root of the project working directory, and take that into account when asking to run tools for you.
diff --git a/loop/agent.go b/loop/agent.go
index 8e903a2..bd7c0d2 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -15,35 +15,68 @@
 
 func (a *UserAgent) Run(todo *ToDo) error {
 	for {
+		unlocked := false
+		todo.Lock()
 		items := findActionableItems(todo, "user")
 		if len(items) == 0 {
+			fmt.Println("## USER NO ITEMS")
+			todo.Unlock()
 			time.Sleep(30 * time.Second)
 			continue
 		}
+		fmt.Println(todo.String())
+		fmt.Printf("-- YOU START WORKING %d\n", len(items))
 		for _, i := range items {
 			fmt.Printf("YOU %s %s: ", i.ID, i.Title)
 			comment, err := a.pr.Read()
 			if err != nil {
 				return err
 			}
+			if comment == "DONE" {
+				i.Done = true
+				continue
+			}
+			if comment == "BREAK" {
+				unlocked = true
+				todo.Unlock()
+				time.Sleep(30 * time.Second)
+				break
+			}
+			if comment == "ASSIGN" {
+				i.AssignedTo = "assistant"
+				continue
+			}
 			i.Discussion = append(i.Discussion, Comment{
 				Author:  "user",
 				Comment: comment,
 			})
 			i.AssignedTo = "assistant"
 		}
+		fmt.Println("-- YOU END WORKING")
+		if !unlocked {
+			todo.Unlock()
+		}
 	}
 }
 
 func findActionableItems(todo *ToDo, assignedTo string) []*ToDo {
+	if todo.Done {
+		return nil
+	}
+	var ret []*ToDo
 	for _, i := range todo.Items {
-		ret := findActionableItems(i, assignedTo)
-		if len(ret) > 0 {
+		ret = append(ret, findActionableItems(i, assignedTo)...)
+		if len(ret) > 0 && !todo.Parallel {
 			return ret
 		}
 	}
-	if todo.AssignedTo == assignedTo && !todo.Done {
+	if len(ret) == 0 && todo.AssignedTo == assignedTo && !todo.Done {
+		for _, i := range todo.Items {
+			if !i.Done {
+				return nil
+			}
+		}
 		return []*ToDo{todo}
 	}
-	return nil
+	return ret
 }
diff --git a/loop/anthropic.go b/loop/anthropic.go
index 8ff5d6b..e711d47 100644
--- a/loop/anthropic.go
+++ b/loop/anthropic.go
@@ -13,6 +13,7 @@
 )
 
 type AnthropicAgent struct {
+	pr     PromptReader
 	reg    tools.Registry
 	client anthropic.Client
 }
@@ -35,13 +36,12 @@
 	for i, toolParam := range toolParams {
 		tools[i] = anthropic.ToolUnionParam{OfTool: &toolParam}
 	}
-	k, err := json.MarshalIndent(todo, "", "\t")
-	if err != nil {
-		return err
-	}
 	for {
+		todo.Lock()
 		items := findActionableItems(todo, "assistant")
 		if len(items) == 0 {
+			fmt.Println("## AGENT NO ITEMS")
+			todo.Unlock()
 			time.Sleep(30 * time.Second)
 			continue
 		}
@@ -50,7 +50,14 @@
 		for _, i := range items {
 			itemIds = append(itemIds, i.ID)
 		}
-		messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(string(k))))
+		fmt.Println(todo.String())
+		fmt.Printf("-- AGENT START WORKING %s\n", strings.Join(itemIds, ", "))
+		b, err := json.MarshalIndent(todo, "", "\t")
+		if err != nil {
+			todo.Unlock()
+			break
+		}
+		messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(string(b))))
 		messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(
 			fmt.Sprintf("Work on TODO item: %s", strings.Join(itemIds, ", ")))))
 		for {
@@ -64,6 +71,7 @@
 				Tools:    tools,
 			})
 			if err != nil {
+				todo.Unlock()
 				return err
 			}
 			fmt.Printf("--- STOP_REASON: %s\n", resp.StopReason)
@@ -75,12 +83,25 @@
 				case anthropic.TextBlock:
 					fmt.Printf("AI: %s\n", v.Text)
 				case anthropic.ToolUseBlock:
+					args := v.JSON.Input.Raw()
+					if v.Name == "bash_command" {
+						fmt.Printf("!!!!! %s: ", args)
+						p, err := a.pr.Read()
+						if err != nil {
+							todo.Unlock()
+							return err
+						}
+						if p != "OK" {
+							toolResults = append(toolResults, anthropic.NewToolResultBlock(v.ID, p, true))
+							continue
+						}
+					}
 					t := a.reg.Get(v.Name)
 					if t == nil {
 						toolResults = append(toolResults, anthropic.NewToolResultBlock(v.ID, fmt.Sprintf("unknown tool %q", v.Name), true))
 						continue
 					}
-					args := v.JSON.Input.Raw()
+
 					fmt.Printf("CALLING TOOL: %s %s\n", v.Name, args)
 					out, err := t.Call(string(args))
 					if err != nil {
@@ -99,5 +120,7 @@
 		for _, i := range items {
 			i.AssignedTo = "user"
 		}
+		todo.Unlock()
 	}
+	return nil
 }
diff --git a/loop/loop.go b/loop/loop.go
index 6cfa111..1ae99f8 100644
--- a/loop/loop.go
+++ b/loop/loop.go
@@ -15,16 +15,6 @@
 //go:embed CONVERSATION_RULES.md
 var systemPrompt string
 
-type Message struct {
-	Author   string `json:"author"`
-	Contents string `json:"contents"`
-	Done     bool   `json:"done"`
-}
-
-type Conversation struct {
-	Messages []Message `json:"message"`
-}
-
 var todo *ToDo
 
 func Run(pr PromptReader, client *Client, tools tools.Registry) error {
@@ -40,7 +30,7 @@
 	todo.AssignedTo = "assistant"
 	agents := []Agent{
 		&UserAgent{pr},
-		&AnthropicAgent{tools, client.c},
+		&AnthropicAgent{pr, tools, client.c},
 	}
 	var wg sync.WaitGroup
 	for _, a := range agents {
@@ -56,34 +46,6 @@
 	return nil
 }
 
-// func Loop(todo *ToDo, item *ToDo, pr PromptReader, client *Client, reg tools.Registry) error {
-// 	messages := []anthropic.MessageParam{
-// 		anthropic.NewUserMessage(anthropic.NewTextBlock(string(k))),
-// 		anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Work on TODO item: %s", item.ID))),
-// 	}
-// 	for {
-// 		fmt.Println(todo.String())
-// 		if item.AssignedTo == "user" {
-// 			fmt.Printf("YOU %s: ", item.ID)
-// 			prompt, err := pr.Read()
-// 			if err != nil {
-// 				return err
-// 			}
-// 			item.Discussion = append(item.Discussion, Comment{
-// 				Author:  "user",
-// 				Comment: prompt,
-// 			})
-// 			item.AssignedTo = "assistant"
-// 			messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)))
-// 		} else {
-// 			if len(messages) == 0 {
-// 				messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock(fmt.Sprintf("Work on TODO item with id"))))
-// 			}
-// 		}
-
-// 	}
-// }
-
 func pickToDoItem(todo *ToDo) *ToDo {
 	if todo.Done {
 		return nil
@@ -108,15 +70,23 @@
 	return nil
 }
 
+type ToDoSubItem struct {
+	Title       string `json:"title" jsonschema:"title=title,description=high level title of the TODO item,required"`
+	Description string `json:"description" jsonschema:"title=description,description=detailed description what this TODO item is about"`
+	AssignedTo  string `json:"assignedTo" jsonschema:"title=assigned to,description=name of the person who shall work on this utem"`
+}
+
 type ToDoItem struct {
-	ParentID    string `json:"parentId"`
-	Title       string `json:"title"`
-	Description string `json:"description"`
-	AssignedTo  string `json:"assignedTo"`
+	ParentID    string        `json:"parentId" jsonschema:"title=parent item id,description=ID of the parent TODO item this one shall be added to as a sub-item"`
+	Title       string        `json:"title" jsonschema:"title=title,description=high level title of the TODO item,required"`
+	Description string        `json:"description" jsonschema:"title=description,description=detailed description what this TODO item is about"`
+	AssignedTo  string        `json:"assignedTo" jsonschema:"title=assigned to,description=name of the person who shall work on this utem"`
+	Items       []ToDoSubItem `json:"items" jsonschema:"title=sub items,description:array of sub-items"`
+	Parallel    bool          `json:"parallel" jsonschema:"title=parallel,description=if true sub-items may be worked on in parallel and there is no depencency between them, otherwise they shall be worked on sequentially"`
 }
 
 type ToDoAddItemArgs struct {
-	Items []ToDoItem `json:"items"`
+	Items []ToDoItem `json:"items" jsonschema:"title=items,description="items to add to the TODO list,required""`
 }
 
 func ToDoAddItem(args ToDoAddItemArgs) (string, error) {
@@ -131,13 +101,25 @@
 			Title:       td.Title,
 			Description: td.Description,
 			AssignedTo:  td.AssignedTo,
+			Parallel:    td.Parallel,
 		})
+		ti := item.Items[len(item.Items)-1]
+		for _, s := range td.Items {
+			sid := fmt.Sprintf("%s.%d", id, len(ti.Items)+1)
+			ti.Items = append(ti.Items, &ToDo{
+				ID:          sid,
+				Title:       s.Title,
+				Description: s.Description,
+				AssignedTo:  s.AssignedTo,
+			})
+		}
 	}
 	return "done", nil
 }
 
 type ToDoMarkItemDoneArgs struct {
-	ID string `json:"id"`
+	ID      string `json:"id" jsonschema:"title=id,description=id of the TODO item to mark as DONE,required"`
+	Summary string `json:"summary" jsonschem:"title=summary,description=detailed summary of current item and all of it's sub-item trees.,required"`
 }
 
 func ToDoMarkItemDone(args ToDoMarkItemDoneArgs) (string, error) {
@@ -146,13 +128,14 @@
 		return "error", fmt.Errorf("TODO item with given id not found: %s", args.ID)
 	}
 	item.Done = true
+	item.Summary = args.Summary
 	return "done", nil
 }
 
 type ToDoItemAddCommentArgs struct {
-	ID       string `json:"id"`
-	Comment  string `json:"comment"`
-	AssignTo string `json:"assignTo"`
+	ID       string `json:"id" jsonschema:"title=id,description=id of the TODO item to add comment to,required"`
+	Comment  string `json:"comment" jsonschema:"title=comment,description=actual comment text,required"`
+	AssignTo string `json:"assignTo" jsonschema:"title=assigned to,description=name of the person who shall be assigned to TODO item with given ID, if empty assignment does not chage"`
 }
 
 func ToDoItemAddComment(args ToDoItemAddCommentArgs) (string, error) {
@@ -167,7 +150,9 @@
 		Author:  "assistant",
 		Comment: args.Comment,
 	})
-	item.AssignedTo = args.AssignTo
+	if args.AssignTo != "" {
+		item.AssignedTo = args.AssignTo
+	}
 	return "done", nil
 }
 
diff --git a/loop/todo.go b/loop/todo.go
index e639857..4dc41d6 100644
--- a/loop/todo.go
+++ b/loop/todo.go
@@ -9,8 +9,8 @@
 )
 
 type Comment struct {
-	Author  string `json:"author"`
-	Comment string `json:"comment"`
+	Author  string `json:"author" jsonschema:"title=author,description=author of the comment,required"`
+	Comment string `json:"comment" jsonschema:"title=comment,description=actual comment text,required"`
 }
 
 func (c Comment) String() string {
@@ -18,31 +18,56 @@
 }
 
 type ToDo struct {
-	ID          string    `json:"id"`
-	Title       string    `json:"title"`
-	Description string    `json:"description"`
-	Items       []*ToDo   `json:"items"`
-	Done        bool      `json:"done"`
-	AssignedTo  string    `json:"assignedTo"`
-	Discussion  []Comment `json:"discussion"`
-	lock        sync.Locker
+	ID          string    `json:"id" jsonschema:"title=id,description=unique id of the TODO item,required"`
+	Title       string    `json:"title" jsonschema:"title=title,description=high level title of the TODO item,required"`
+	Description string    `json:"description" jsonschema:"title=description,description=detailed description what this TODO item is about"`
+	Items       []*ToDo   `json:"items" jsonschema:"title=sub items,description=array of sub items current item consists of"`
+	Parallel    bool      `json:"parallel" jsonschema:"title=parallel,description=if true sub-items may be worked on in parallel and there is no depencency between them, otherwise they shall be worked on sequentially"`
+	Done        bool      `json:"done" jsonschema:"title=done,description=if true item shall be considered as done"`
+	AssignedTo  string    `json:"assignedTo" jsonschema:"title=assigned to,description=name of the person who shall work on this item"`
+	Discussion  []Comment `json:"discussion" jsonschema:"title=discussion,description=comments related to current item"`
+	Summary     string    `json:"summary" jsonschem:"title=summary,description=detailed summary of current item and all of it's sub-item trees."`
+	lock        sync.RWMutex
 }
 
-const tmpl = `%s: %s
-%s
-%s
-%s`
-
 func (t ToDo) String() string {
-	var comments []string
+	var ret []string
+	status := "IN PROGRESS"
+	if t.Done {
+		status = "DONE"
+	}
+	ret = append(ret, fmt.Sprintf("%s: %s - %s %s %t", t.ID, t.Title, status, t.AssignedTo, t.Parallel))
+	if t.Description != "" {
+		ret = append(ret, t.Description)
+	}
+	if t.Summary != "" {
+		ret = append(ret, fmt.Sprintf("SUMMARY: %s", t.Summary))
+	}
 	for _, c := range t.Discussion {
-		comments = append(comments, fmt.Sprintf("\t - %s", c.String()))
+		ret = append(ret, fmt.Sprintf("\t - %s", c.String()))
 	}
-	var items []string
 	for _, i := range t.Items {
-		items = append(items, fmt.Sprintf("\t%s", i.String()))
+		for _, k := range strings.Split(i.String(), "\n") {
+			ret = append(ret, fmt.Sprintf("\t%s", k))
+		}
 	}
-	return fmt.Sprintf(tmpl, t.ID, t.Title, t.Description, strings.Join(comments, "\n"), strings.Join(items, "\n"))
+	return strings.Join(ret, "\n")
+}
+
+func (t *ToDo) LockRead() {
+	t.lock.RLock()
+}
+
+func (t *ToDo) UnlockRead() {
+	t.lock.RUnlock()
+}
+
+func (t *ToDo) Lock() {
+	t.lock.Lock()
+}
+
+func (t *ToDo) Unlock() {
+	t.lock.Unlock()
 }
 
 func ToDoJSONSchema() string {
diff --git a/tools/bash.go b/tools/bash.go
new file mode 100644
index 0000000..71285bd
--- /dev/null
+++ b/tools/bash.go
@@ -0,0 +1,18 @@
+package tools
+
+import (
+	"os/exec"
+)
+
+type BashCommandArgs struct {
+	Command string `json:"command" jsonschema:"title=command,description=bash command to run,required"`
+}
+
+func BashCommand(args BashCommandArgs) (string, error) {
+	cmd := exec.Command("bash", "-c", args.Command)
+	if out, err := cmd.Output(); err != nil {
+		return "", err
+	} else {
+		return string(out), nil
+	}
+}
diff --git a/tools/file.go b/tools/file.go
index 815c2eb..405d046 100644
--- a/tools/file.go
+++ b/tools/file.go
@@ -2,6 +2,7 @@
 
 import (
 	"os"
+	"path/filepath"
 )
 
 type FileReadArgs struct {
@@ -24,7 +25,27 @@
 type FileWriteResult struct {
 }
 
+func sanitizePath(p string) (string, error) {
+	cwd, err := os.Getwd()
+	if err != nil {
+		return "", err
+	}
+	p = filepath.Join(cwd, p)
+	p, err = filepath.Rel(p, cwd)
+	if err != nil {
+		return "", err
+	}
+	return p, nil
+}
+
 func FileWrite(args FileWriteArgs) (string, error) {
+	p, err := sanitizePath(args.Path)
+	if err != nil {
+		return "", err
+	}
+	if os.MkdirAll(filepath.Dir(p), 0666) != nil {
+		return "", nil
+	}
 	if err := os.WriteFile(args.Path, []byte(args.Contents), 0666); err != nil {
 		return "error", err
 	} else {
diff --git a/tools/init.go b/tools/init.go
index 7c2ec4d..a04cf98 100644
--- a/tools/init.go
+++ b/tools/init.go
@@ -4,5 +4,6 @@
 	reg.Add(NewFuncTool("file_read", FileRead, "Reads contents of the given file."))
 	reg.Add(NewFuncTool("file_write", FileWrite, "Writes given contents to a file."))
 	reg.Add(NewFuncTool("dir_list", DirList, "Reads directory with given name, returning all its entries."))
+	reg.Add(NewFuncTool("bash_command", BashCommand, "Runs given bash command and returns result back."))
 	return nil
 }