Packages
fnord
0.9.36
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/agent/coordinator/tasks.ex
defmodule AI.Agent.Coordinator.Tasks do
@moduledoc """
Task-specific behaviors for AI.Agent.Coordinator, including generating
messages related to task management.
"""
@typep t :: AI.Agent.Coordinator.t()
@doc """
Return a list of task list IDs that have incomplete tasks. This function
filters the task lists associated with the conversation to identify those
that are still in progress and contain at least one task with an outcome of
`:todo`.
"""
@spec pending_lists(t) :: list
def pending_lists(state) do
Services.Task.list_ids()
|> Enum.filter(fn list_id ->
state.conversation_pid
|> Services.Conversation.get_task_list_meta(list_id)
|> case do
{:ok, %{} = m} -> Map.get(m, :status)
_ -> nil
end
|> case do
"in-progress" ->
list_id
|> Services.Task.get_list()
|> case do
{:error, _} -> false
tasks -> Enum.any?(tasks, fn t -> t.outcome == :todo end)
end
_ ->
false
end
end)
end
@doc """
Appends a message to the conversation, instructing the agent to use a task
list for managing research. It emphasizes creating tasks for new lines of
inquiry, resolving tasks with clear outcomes, and reviewing the task list
before proceeding to the next steps.
"""
@spec research_msg(t) :: t
def research_msg(%{conversation_pid: conversation_pid} = state) do
"""
Use your task list to manage all research:
- For every new line of inquiry, create a task
- When you conclude or drop a line, resolve it with a clear outcome
- Before moving to the next, call `tasks_show_list` to review and update open tasks
- Create new task lists for each discrete line of work or inquiry
- Tasks help prevent you from losing track or hallucinating about what you have done
"""
|> AI.Util.system_msg()
|> Services.Conversation.append_msg(conversation_pid)
state
end
@doc """
Appends a message to the conversation, reminding the agent to check their
task lists for any incomplete tasks. It provides guidelines on how to manage
tasks, including resolving completed tasks, addressing stale or irrelevant
tasks, and ensuring that all tasks are concrete and actionable. The message
also includes a list of task IDs that still have incomplete tasks.
"""
@spec penultimate_check_msg(t, list) :: t
def penultimate_check_msg(%{conversation_pid: conversation_pid} = state, list_ids) do
md_list =
list_ids
|> Enum.map(&" - ID: `#{&1}`")
|> Enum.join("\n")
"""
# Task lists check-in
Task lists are persisted with the conversation.
It is OK to leave tasks open across multiple sessions when they represent real follow-up work.
- Use `tasks_show_list` and read it carefully.
- If a task is done, resolve it.
- If a task should not persist (stale, superseded, or no longer relevant), resolve it with a short note explaining why.
- If a task is vague, rewrite it into a concrete follow-up (label + detailed description + rationale).
The following task lists still have incomplete tasks:
#{md_list}
"""
|> AI.Util.system_msg()
|> Services.Conversation.append_msg(conversation_pid)
state
end
@doc """
Appends a message to the conversation, providing an overview of the current
task lists. It lists the IDs of all task lists and instructs the agent to use
the `tasks_show_list` tool to view the tasks in more detail, including their
descriptions and statuses.
"""
@spec list_msg(t) :: t
def list_msg(%{conversation_pid: conversation_pid} = state) do
tasks =
Services.Task.list_ids()
|> Enum.map(fn list_id ->
tasks = Services.Task.as_string(list_id)
"""
## Task list ID: `#{list_id}`
Use this ID when invoking task management tools for this list.
#{tasks}
"""
end)
|> Enum.join("\n\n")
"""
# Tasks
The `tasks_show_list` tool displays these tasks in more detail, including descriptions and statuses.
#{tasks}
"""
|> AI.Util.system_msg()
|> Services.Conversation.append_msg(conversation_pid)
state
end
@spec log_summary(t) :: map
def log_summary(%{conversation_pid: convo} = state) do
convo
# task_summary produces markdown text. Allow an external FNORD_FORMATTER to
# transform the markdown into nicer terminal output if configured.
|> task_summary()
# Remove leading "# Tasks\n" header; that was originally intended for the
# LLM-facing message. Here, it's redundant, since we're using the "Tasks"
# label in our call to UI.debug.
|> String.replace_prefix("# Tasks\n", "")
# UI.format runs the external FNORD_FORMATTER (if set) and respects
# quiet mode and TTY detection.
|> UI.format()
# UI.debug accepts both chardata and iodata; pass formatted text directly.
|> then(&UI.debug("Task lists", &1))
state
end
@doc """
Return a formatted Coordinator-scoped task summary for the given conversation
PID.
"""
@spec task_summary(pid()) :: binary()
def task_summary(conversation_pid) when is_pid(conversation_pid) do
conversation_pid
|> Services.Conversation.get_task_lists()
|> Enum.map(&format_task_list(conversation_pid, &1))
|> Enum.join("\n\n")
|> then(fn body ->
"""
# Tasks
#{body}
"""
end)
end
@doc """
Return a formatted summary of a single task list, including its description,
status, and the status of each individual task. The summary derives the list
status from the current tasks when possible, reflecting the concrete state of
work.
"""
@spec format_task_list(pid, binary) :: binary
def format_task_list(conversation_pid, list_id) do
with tasks = Services.Conversation.get_task_list(conversation_pid, list_id),
{:ok, meta} <- Services.Conversation.get_task_list_meta(conversation_pid, list_id),
{:ok, desc} <- Map.fetch(meta, :description),
{:ok, status} <- Map.fetch(meta, :status) do
name = desc || list_id
label =
case status do
"done" -> "Complete"
"in-progress" -> "In Progress"
_ -> "Planning"
end
"## #{name} :: #{label}" <> format_tasks(status, tasks)
end
end
defp format_tasks(_, []), do: ""
defp format_tasks("done", _), do: ""
defp format_tasks(_, tasks) do
tasks
|> Enum.map(&format_task/1)
|> Enum.join("\n")
|> then(&("\n" <> &1))
end
defp format_task(%{id: id, outcome: outcome, result: result}) do
tty? = UI.colorize?()
result =
if result do
IO.ANSI.format([": ", :italic, :light_black, result, :reset], tty?)
else
""
end
format_task_for_ui(id, outcome, result, tty?)
end
defp format_task_for_ui(id, :done, result, false), do: "- [✓] #{id}#{result}"
defp format_task_for_ui(id, :done, result, true), do: done("- #{id}#{result}")
defp format_task_for_ui(id, :failed, result, false), do: "- [✗] #{id}#{result}"
defp format_task_for_ui(id, :failed, result, true), do: failed("- #{id}#{result}")
defp format_task_for_ui(id, :todo, _, false), do: "- [ ] #{id}"
defp format_task_for_ui(id, :todo, _, true), do: todo("- #{id}")
defp done(v), do: IO.ANSI.format([:green, v, :reset], true)
defp failed(v), do: IO.ANSI.format([:red, :crossed_out, v, :reset], true)
defp todo(v), do: IO.ANSI.format([:cyan, v, :reset], true)
end