Lead create_project tool

This commit is contained in:
Ian Gulliver
2026-10-17 08:08:05 -07:00
parent 1ba79e99df
commit 62df39c5da
5 changed files with 45 additions and 16 deletions
+1
View File
@@ -31,6 +31,7 @@ const leadGuidance = `You are the human's lead agent in byre, shown in the left
- Reviews: after each new snapshot of a task, a reviewer agent from the other vendor reviews the change. The results are on the task's reviews tab, and read_transcript shows each review's findings. They are for the human to look at when they want; mention one only when its findings matter to what the human is deciding.
- Dig into context with list_tasks, read_transcript, read_inbox and read_status (vendor rate limits and agent health), and with your own Read, Grep and Bash on a task's worktree path (from list_tasks). Don't edit files in task worktrees; ask the task's agent with send_message.
- You can create tasks, start TODO items, message, interrupt and compact task agents, and merge or discard tasks. Merge pushes to the default branch and Discard destroys work: do either only when the human asks for it.
- Projects: when the human asks you to add a project, create it with create_project from its name and the path of its git repository on this machine.
- Workflows: you manage the server's workflows with list_workflows, read_workflow, save_workflow and delete_workflow, and run one by giving its name to create_task or start_todo. Every stage of a workflow runs as its own task on the stage's model, once the stages it comes after have finished, so stages fan out (several stages after one) and fan in (one stage after several); a stage's agent calls finish_stage to hand its output to the stages after it, signals tell you when a stage starts and when the workflow finishes, and when the final stage finishes byre discards the run's other stage tasks, leaving the final one to merge. Whenever you show or propose a workflow, draw it as a Mermaid flowchart of its stage names only, with no models, for example a fenced mermaid block holding "flowchart LR", "Build --> Review", "Build --> Test", "Review --> Fix" and "Test --> Fix".
- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/da7e487305e4). byre draws it in that task's colour, and clicking it shows the task in the right pane. To open a specific tab, append it: #/task/<task_id>/<tab>. When you point the human at a question to answer, link to #/task/<task_id>/inbox; at a finished turn, to #/task/<task_id>/transcript.
- Keep messages short; the human reads them in half the screen. Put long detail in collapsed sections: a line <details><summary>Title</summary>, the Markdown content, then a line </details>.
+21
View File
@@ -253,6 +253,27 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
return fmt.Sprintf("Task %s %q created from %s and its agent started.", task.Id, task.Title, task.BaseRef), nil
}),
tool("create_project",
"Add a project to byre for the human: name it, and give the path of a git repository on this machine. "+
"byre records the repository's root and default branch. Returns the project as JSON.",
`{"type": "object", "properties": {
"name": {"type": "string"},
"repo_path": {"type": "string"}
}, "required": ["name", "repo_path"]}`,
func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) {
args := api.ProjectCreate{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", err
}
p, err := m.addProject(ctx, args.Name, args.RepoPath, user.Login)
if err != nil {
return "", err
}
b, err := json.MarshalIndent(p, "", " ")
return string(b), err
}),
tool("start_todo",
"Start a task on an item of a project's TODO.md (its title, exactly as list_tasks' project or the file on the default branch has it). "+
"With workflow, it runs that workflow with the item as its brief, each stage as its own task, and model can't be given.",
+3 -1
View File
@@ -38,6 +38,7 @@ type Manager struct {
leadRoot string
codexHome string
status func(context.Context) (api.Status, error)
addProject func(ctx context.Context, name, repoPath, createdBy string) (api.Project, error)
mu sync.Mutex
live map[string]*live
@@ -113,13 +114,14 @@ func (l *live) implementer() bool {
return l.owner == "" && l.reviewID == ""
}
func New(st *store.Store, worktreeRoot, leadRoot, codexHome string, status func(context.Context) (api.Status, error)) *Manager {
func New(st *store.Store, worktreeRoot, leadRoot, codexHome string, status func(context.Context) (api.Status, error), addProject func(ctx context.Context, name, repoPath, createdBy string) (api.Project, error)) *Manager {
m := &Manager{
store: st,
worktreeRoot: worktreeRoot,
leadRoot: leadRoot,
codexHome: codexHome,
status: status,
addProject: addProject,
live: map[string]*live{},
starting: map[string]*start{},
deliverLocks: map[string]*sessionLock{},
+19 -14
View File
@@ -68,7 +68,7 @@ func New(dataDir string, portFirst int, spa fs.FS) (*Server, error) {
}
}
s.agents = agents.New(s.store, filepath.Join(dataDir, "worktrees"), filepath.Join(dataDir, "leads"), filepath.Join(dataDir, "codex"), s.status)
s.agents = agents.New(s.store, filepath.Join(dataDir, "worktrees"), filepath.Join(dataDir, "leads"), filepath.Join(dataDir, "codex"), s.status, s.createProject)
err = s.agents.Recover(context.Background())
if err != nil {
s.store.Close()
@@ -204,39 +204,44 @@ func (s *Server) CreateProject(w http.ResponseWriter, r *http.Request) {
return
}
if strings.TrimSpace(req.Name) == "" {
writeError(w, http.StatusBadRequest, errors.New("name is required"))
p, err := s.createProject(r.Context(), req.Name, req.RepoPath, actor(r))
if err != nil {
writeAgentError(w, err)
return
}
writeJSON(w, http.StatusCreated, p)
}
info, err := git.Inspect(req.RepoPath)
func (s *Server) createProject(ctx context.Context, name, repoPath, createdBy string) (api.Project, error) {
if strings.TrimSpace(name) == "" {
return api.Project{}, fmt.Errorf("%w: name is required", agents.ErrInvalid)
}
info, err := git.Inspect(repoPath)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
return api.Project{}, fmt.Errorf("%w: %w", agents.ErrInvalid, err)
}
err = git.ConfigureNotesFetch(info.Root)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
return api.Project{}, err
}
p := api.Project{
Id: store.NewID(),
Name: req.Name,
Name: name,
RepoPath: info.Root,
DefaultBranch: info.DefaultBranch,
Remote: info.Remote,
CreatedBy: actor(r),
CreatedBy: createdBy,
CreatedAt: time.Now().UTC(),
}
err = s.store.CreateProject(r.Context(), p)
err = s.store.CreateProject(ctx, p)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
return api.Project{}, fmt.Errorf("%w: %w", agents.ErrInvalid, err)
}
writeJSON(w, http.StatusCreated, p)
return p, nil
}
func (s *Server) GetProject(w http.ResponseWriter, r *http.Request, projectID string) {