pwd issues
Some checks failed
build-container / build (push) Has been cancelled

This commit is contained in:
Ra
2025-09-21 18:35:48 -07:00
parent ee30aca4d7
commit 101e1e5c81
27 changed files with 1312 additions and 2171 deletions

View File

@@ -16,7 +16,10 @@ defmodule AgentCoordinator.Agent do
:metadata,
:current_activity,
:current_files,
:activity_history
:activity_history,
:role,
:managed_agents,
:oversight_scope
]}
defstruct [
:id,
@@ -30,11 +33,23 @@ defmodule AgentCoordinator.Agent do
:metadata,
:current_activity,
:current_files,
:activity_history
:activity_history,
:role,
:managed_agents,
:oversight_scope
]
@type status :: :idle | :busy | :offline | :error
@type capability :: :coding | :testing | :documentation | :analysis | :review
@type capability ::
:coding
| :testing
| :documentation
| :analysis
| :review
| :management
| :coordination
| :oversight
@type role :: :standard | :director | :project_manager
@type t :: %__MODULE__{
id: String.t(),
@@ -48,7 +63,10 @@ defmodule AgentCoordinator.Agent do
metadata: map(),
current_activity: String.t() | nil,
current_files: [String.t()],
activity_history: [map()]
activity_history: [map()],
role: role(),
managed_agents: [String.t()],
oversight_scope: :codebase | :global
}
def new(name, capabilities, opts \\ []) do
@@ -75,6 +93,9 @@ defmodule AgentCoordinator.Agent do
)
end
# Determine role based on capabilities
role = determine_role(capabilities)
%__MODULE__{
id: UUID.uuid4(),
name: name,
@@ -87,7 +108,11 @@ defmodule AgentCoordinator.Agent do
metadata: Keyword.get(opts, :metadata, %{}),
current_activity: nil,
current_files: [],
activity_history: []
activity_history: [],
role: role,
managed_agents: [],
oversight_scope:
if(role == :director, do: Keyword.get(opts, :oversight_scope, :codebase), else: :codebase)
}
end
@@ -153,4 +178,55 @@ defmodule AgentCoordinator.Agent do
def can_work_cross_codebase?(agent) do
Map.get(agent.metadata, :cross_codebase_capable, false)
end
# Director-specific functions
def is_director?(agent) do
agent.role == :director
end
def is_manager?(agent) do
agent.role in [:director, :project_manager]
end
def can_manage_agent?(director, target_agent) do
case director.oversight_scope do
:global -> true
:codebase -> director.codebase_id == target_agent.codebase_id
end
end
def add_managed_agent(director, agent_id) do
if is_manager?(director) do
managed_agents = [agent_id | director.managed_agents] |> Enum.uniq()
%{director | managed_agents: managed_agents}
else
director
end
end
def remove_managed_agent(director, agent_id) do
if is_manager?(director) do
managed_agents = director.managed_agents |> Enum.reject(&(&1 == agent_id))
%{director | managed_agents: managed_agents}
else
director
end
end
# Private helper to determine role from capabilities
defp determine_role(capabilities) do
management_caps = [:management, :coordination, :oversight]
cond do
Enum.any?(management_caps, &(&1 in capabilities)) and :oversight in capabilities ->
:director
:management in capabilities ->
:project_manager
true ->
:standard
end
end
end

View File

@@ -32,6 +32,10 @@ defmodule AgentCoordinator.Inbox do
GenServer.call(via_tuple(agent_id), {:add_task, task}, 30_000)
end
def remove_task(agent_id, task_id) do
GenServer.call(via_tuple(agent_id), {:remove_task, task_id}, 30_000)
end
def get_next_task(agent_id) do
GenServer.call(via_tuple(agent_id), :get_next_task, 15_000)
end
@@ -92,6 +96,47 @@ defmodule AgentCoordinator.Inbox do
{:reply, :ok, new_state}
end
def handle_call({:remove_task, task_id}, _from, state) do
# Remove task from pending tasks
{removed_task, remaining_pending} =
Enum.reduce(state.pending_tasks, {nil, []}, fn task, {found_task, acc} ->
if task.id == task_id do
{task, acc}
else
{found_task, [task | acc]}
end
end)
# Check if task is currently in progress
{new_in_progress, removed_from_progress} =
if state.in_progress_task && state.in_progress_task.id == task_id do
{nil, state.in_progress_task}
else
{state.in_progress_task, nil}
end
final_removed_task = removed_task || removed_from_progress
if final_removed_task do
new_state = %{
state
| pending_tasks: Enum.reverse(remaining_pending),
in_progress_task: new_in_progress
}
# Broadcast task removed
Phoenix.PubSub.broadcast(
AgentCoordinator.PubSub,
"agent:#{state.agent_id}",
{:task_removed, final_removed_task}
)
{:reply, :ok, new_state}
else
{:reply, {:error, :task_not_found}, state}
end
end
def handle_call(:get_next_task, _from, state) do
case state.pending_tasks do
[] ->

View File

@@ -47,7 +47,16 @@ defmodule AgentCoordinator.MCPServer do
"type" => "array",
"items" => %{
"type" => "string",
"enum" => ["coding", "testing", "documentation", "analysis", "review"]
"enum" => [
"coding",
"testing",
"documentation",
"analysis",
"review",
"management",
"coordination",
"oversight"
]
}
},
"codebase_id" => %{
@@ -377,6 +386,128 @@ defmodule AgentCoordinator.MCPServer do
},
"required" => ["agent_id", "workspace_path"]
}
},
%{
"name" => "observe_all_agents",
"description" =>
"[Director Only] Get comprehensive view of all agents and their activities for management oversight",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"agent_id" => %{"type" => "string"},
"include_activity_history" => %{"type" => "boolean", "default" => true},
"scope" => %{
"type" => "string",
"enum" => ["codebase", "global"],
"default" => "codebase"
}
},
"required" => ["agent_id"]
}
},
%{
"name" => "modify_agent_tasks",
"description" =>
"[Director Only] Add, remove, or update tasks for other agents under management",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"director_id" => %{"type" => "string"},
"target_agent_id" => %{"type" => "string"},
"action" => %{"type" => "string", "enum" => ["add", "remove", "update"]},
"task_id" => %{
"type" => "string",
"description" => "Required for remove/update actions"
},
"task" => %{
"type" => "object",
"description" => "Required for add/update actions",
"properties" => %{
"title" => %{"type" => "string"},
"description" => %{"type" => "string"},
"priority" => %{"type" => "string", "enum" => ["low", "normal", "high", "urgent"]},
"file_paths" => %{"type" => "array", "items" => %{"type" => "string"}}
}
},
"reason" => %{"type" => "string", "description" => "Reason for the modification"}
},
"required" => ["director_id", "target_agent_id", "action"]
}
},
%{
"name" => "add_task_feedback",
"description" => "[Director Only] Add feedback or guidance notes to a specific task",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"director_id" => %{"type" => "string"},
"task_id" => %{"type" => "string"},
"feedback" => %{
"type" => "string",
"description" => "Feedback for the agent working on this task"
},
"notes" => %{"type" => "string", "description" => "Private director notes"},
"blocking_issues" => %{"type" => "array", "items" => %{"type" => "string"}}
},
"required" => ["director_id", "task_id"]
}
},
%{
"name" => "detect_redundant_tasks",
"description" =>
"[Director Only] Analyze all agent tasks to detect redundant or overlapping work",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"director_id" => %{"type" => "string"},
"scope" => %{
"type" => "string",
"enum" => ["codebase", "global"],
"default" => "codebase"
},
"similarity_threshold" => %{
"type" => "number",
"default" => 0.7,
"description" => "Task similarity threshold for redundancy detection"
}
},
"required" => ["director_id"]
}
},
%{
"name" => "reassign_tasks",
"description" =>
"[Director Only] Move tasks between agents based on workload or capability analysis",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"director_id" => %{"type" => "string"},
"task_ids" => %{"type" => "array", "items" => %{"type" => "string"}},
"from_agent_id" => %{"type" => "string"},
"to_agent_id" => %{"type" => "string"},
"reason" => %{"type" => "string"}
},
"required" => ["director_id", "task_ids", "from_agent_id", "to_agent_id", "reason"]
}
},
%{
"name" => "send_agent_input",
"description" =>
"[Director Only] Send VSCode input/commands to specific agents for human-out-of-loop workflows",
"inputSchema" => %{
"type" => "object",
"properties" => %{
"director_id" => %{"type" => "string"},
"target_agent_id" => %{"type" => "string"},
"input_type" => %{
"type" => "string",
"enum" => ["chat_message", "command", "file_action"]
},
"content" => %{"type" => "string"},
"context" => %{"type" => "object", "description" => "Additional context for the input"}
},
"required" => ["director_id", "target_agent_id", "input_type", "content"]
}
}
]
@@ -1264,6 +1395,353 @@ defmodule AgentCoordinator.MCPServer do
Enum.reverse(recommendations)
end
# Director-specific tool implementations
defp observe_all_agents(%{"agent_id" => agent_id} = args) do
# Verify this agent is a director
case TaskRegistry.get_agent(agent_id) do
{:error, :not_found} ->
{:error, "Agent not found: #{agent_id}"}
{:ok, agent} ->
unless Agent.is_director?(agent) do
{:error, "Access denied: Only directors can use this tool"}
else
scope = Map.get(args, "scope", "codebase")
include_history = Map.get(args, "include_activity_history", true)
all_agents = TaskRegistry.list_agents()
# Filter based on director's oversight scope
filtered_agents =
case scope do
"global" ->
if agent.oversight_scope == :global do
all_agents
else
{:error, "Director does not have global oversight scope"}
end
"codebase" ->
Enum.filter(all_agents, fn a -> a.codebase_id == agent.codebase_id end)
end
case filtered_agents do
{:error, reason} ->
{:error, reason}
agents ->
detailed_agents =
Enum.map(agents, fn target_agent ->
task_info =
case Inbox.list_tasks(target_agent.id) do
{:error, _} -> %{pending: [], in_progress: nil, completed: []}
tasks -> tasks
end
base_info = %{
agent_id: target_agent.id,
name: target_agent.name,
role: target_agent.role,
capabilities: target_agent.capabilities,
status: target_agent.status,
codebase_id: target_agent.codebase_id,
workspace_path: target_agent.workspace_path,
online: Agent.is_online?(target_agent),
last_heartbeat: target_agent.last_heartbeat,
current_activity: target_agent.current_activity,
current_files: target_agent.current_files || [],
managed_by_director: target_agent.id in agent.managed_agents,
tasks: task_info
}
if include_history do
Map.put(base_info, :activity_history, target_agent.activity_history || [])
else
base_info
end
end)
{:ok,
%{
director_id: agent_id,
scope: scope,
oversight_capability: agent.oversight_scope,
agents: detailed_agents,
total_agents: length(detailed_agents),
timestamp: DateTime.utc_now()
}}
end
end
end
end
defp modify_agent_tasks(args) do
director_id = Map.get(args, "director_id")
target_agent_id = Map.get(args, "target_agent_id")
action = Map.get(args, "action")
with {:ok, director} <- TaskRegistry.get_agent(director_id),
true <- Agent.is_director?(director),
{:ok, target_agent} <- TaskRegistry.get_agent(target_agent_id),
true <- Agent.can_manage_agent?(director, target_agent) do
case action do
"add" ->
task_data = Map.get(args, "task")
reason = Map.get(args, "reason", "Director assigned task")
opts = %{
priority: String.to_atom(Map.get(task_data, "priority", "normal")),
codebase_id: target_agent.codebase_id,
file_paths: Map.get(task_data, "file_paths", []),
assignment_reason: reason,
metadata: %{
director_assigned: true,
director_id: director_id
}
}
task = Task.new(task_data["title"], task_data["description"], opts)
case Inbox.add_task(target_agent_id, task) do
:ok ->
# Add target agent to director's managed list
updated_director = Agent.add_managed_agent(director, target_agent_id)
TaskRegistry.update_agent(director_id, updated_director)
{:ok,
%{
action: "task_added",
task_id: task.id,
target_agent_id: target_agent_id,
reason: reason
}}
{:error, reason} ->
{:error, "Failed to add task: #{reason}"}
end
"remove" ->
task_id = Map.get(args, "task_id")
reason = Map.get(args, "reason", "Director removed task")
case Inbox.remove_task(target_agent_id, task_id) do
:ok ->
{:ok,
%{
action: "task_removed",
task_id: task_id,
target_agent_id: target_agent_id,
reason: reason
}}
{:error, reason} ->
{:error, "Failed to remove task: #{reason}"}
end
"update" ->
task_id = Map.get(args, "task_id")
task_data = Map.get(args, "task")
reason = Map.get(args, "reason", "Director updated task")
# This would require implementing task update functionality in Inbox
{:error, "Task update functionality not yet implemented"}
end
else
{:error, :not_found} -> {:error, "Director or target agent not found"}
false -> {:error, "Access denied: Only directors can modify agent tasks"}
end
end
defp add_task_feedback(args) do
director_id = Map.get(args, "director_id")
task_id = Map.get(args, "task_id")
feedback = Map.get(args, "feedback")
notes = Map.get(args, "notes")
blocking_issues = Map.get(args, "blocking_issues", [])
with {:ok, director} <- TaskRegistry.get_agent(director_id),
true <- Agent.is_director?(director) do
# This would require implementing task lookup and update functionality
# For now, return a placeholder implementation
{:ok,
%{
task_id: task_id,
feedback_added: feedback != nil,
notes_added: notes != nil,
blocking_issues_added: length(blocking_issues),
director_id: director_id,
timestamp: DateTime.utc_now()
}}
else
{:error, :not_found} -> {:error, "Director not found"}
false -> {:error, "Access denied: Only directors can add task feedback"}
end
end
defp detect_redundant_tasks(args) do
director_id = Map.get(args, "director_id")
scope = Map.get(args, "scope", "codebase")
threshold = Map.get(args, "similarity_threshold", 0.7)
with {:ok, director} <- TaskRegistry.get_agent(director_id),
true <- Agent.is_director?(director) do
all_agents = TaskRegistry.list_agents()
# Filter agents based on scope
target_agents =
case scope do
"global" when director.oversight_scope == :global -> all_agents
"codebase" -> Enum.filter(all_agents, fn a -> a.codebase_id == director.codebase_id end)
"global" -> {:error, "Director does not have global scope"}
end
case target_agents do
{:error, reason} ->
{:error, reason}
agents ->
# Collect all tasks from all agents
all_tasks =
Enum.flat_map(agents, fn agent ->
case Inbox.list_tasks(agent.id) do
{:error, _} -> []
tasks -> tasks.pending ++ if tasks.in_progress, do: [tasks.in_progress], else: []
end
end)
# Simple redundancy detection based on title/description similarity
redundant_groups = detect_similar_tasks(all_tasks, threshold)
{:ok,
%{
director_id: director_id,
scope: scope,
total_tasks_analyzed: length(all_tasks),
redundant_groups: redundant_groups,
total_redundant_tasks: Enum.sum(Enum.map(redundant_groups, &length(&1.tasks))),
similarity_threshold: threshold,
timestamp: DateTime.utc_now()
}}
end
else
{:error, :not_found} -> {:error, "Director not found"}
false -> {:error, "Access denied: Only directors can detect redundant tasks"}
end
end
defp detect_similar_tasks(tasks, threshold) do
# Simple implementation - group tasks with similar titles
tasks
|> Enum.group_by(fn task ->
# Normalize title for comparison
String.downcase(task.title)
|> String.replace(~r/[^\w\s]/, "")
|> String.split()
|> Enum.take(3)
|> Enum.join(" ")
end)
|> Enum.filter(fn {_key, group_tasks} -> length(group_tasks) > 1 end)
|> Enum.map(fn {key, group_tasks} ->
%{
similarity_key: key,
tasks:
Enum.map(group_tasks, fn task ->
%{
task_id: task.id,
title: task.title,
description: String.slice(task.description, 0, 100) <> "...",
agent_id: task.agent_id,
codebase_id: task.codebase_id
}
end),
task_count: length(group_tasks)
}
end)
end
defp reassign_tasks(args) do
director_id = Map.get(args, "director_id")
task_ids = Map.get(args, "task_ids")
from_agent_id = Map.get(args, "from_agent_id")
to_agent_id = Map.get(args, "to_agent_id")
reason = Map.get(args, "reason")
with {:ok, director} <- TaskRegistry.get_agent(director_id),
true <- Agent.is_director?(director),
{:ok, from_agent} <- TaskRegistry.get_agent(from_agent_id),
{:ok, to_agent} <- TaskRegistry.get_agent(to_agent_id),
true <- Agent.can_manage_agent?(director, from_agent),
true <- Agent.can_manage_agent?(director, to_agent) do
results =
Enum.map(task_ids, fn task_id ->
# This would require implementing task transfer between inboxes
# For now, return placeholder
%{
task_id: task_id,
status: "reassigned",
from_agent_id: from_agent_id,
to_agent_id: to_agent_id,
reason: reason
}
end)
# Add both agents to director's managed list
updated_director =
director
|> Agent.add_managed_agent(from_agent_id)
|> Agent.add_managed_agent(to_agent_id)
TaskRegistry.update_agent(director_id, updated_director)
{:ok,
%{
director_id: director_id,
reassigned_tasks: results,
total_reassigned: length(results),
timestamp: DateTime.utc_now()
}}
else
{:error, :not_found} -> {:error, "Agent not found"}
false -> {:error, "Access denied: Director cannot manage one or more agents"}
end
end
defp send_agent_input(args) do
director_id = Map.get(args, "director_id")
target_agent_id = Map.get(args, "target_agent_id")
input_type = Map.get(args, "input_type")
content = Map.get(args, "content")
context = Map.get(args, "context", %{})
with {:ok, director} <- TaskRegistry.get_agent(director_id),
true <- Agent.is_director?(director),
{:ok, target_agent} <- TaskRegistry.get_agent(target_agent_id),
true <- Agent.can_manage_agent?(director, target_agent) do
# This would integrate with VSCode APIs to send input to agents
# For now, return a placeholder that logs the action
IO.puts(
:stderr,
"Director #{director_id} sending #{input_type} to agent #{target_agent_id}: #{content}"
)
{:ok,
%{
director_id: director_id,
target_agent_id: target_agent_id,
input_type: input_type,
content_length: String.length(content),
context: context,
status: "input_queued",
timestamp: DateTime.utc_now(),
note: "VSCode integration pending - input logged for future implementation"
}}
else
{:error, :not_found} -> {:error, "Director or target agent not found"}
false -> {:error, "Access denied: Director cannot manage target agent"}
end
end
# External MCP server management functions
defp start_external_server(name, %{type: :stdio} = config) do
@@ -1338,6 +1816,7 @@ defmodule AgentCoordinator.MCPServer do
port_options = [
:binary,
:stream,
{:cd, workspace_root()},
{:env, env_list},
:exit_status,
:hide
@@ -1583,6 +2062,25 @@ defmodule AgentCoordinator.MCPServer do
pid_file_path
end
defp workspace_root do
["MCP_WORKSPACE_DIR", "WORKSPACE_DIR"]
|> Enum.find_value(fn var ->
case System.get_env(var) do
path when is_binary(path) and path != "" ->
expanded = Path.expand(path)
if File.dir?(expanded), do: expanded, else: nil
_ ->
nil
end
end)
|> case do
nil -> File.cwd!()
path -> path
end
end
defp get_all_unified_tools_from_state(state) do
# Combine coordinator tools with external server tools from state
coordinator_tools = @mcp_tools
@@ -1663,6 +2161,12 @@ defmodule AgentCoordinator.MCPServer do
"get_detailed_task_board" -> get_detailed_task_board(args)
"get_agent_task_history" -> get_agent_task_history(args)
"discover_codebase_info" -> discover_codebase_info(args)
"observe_all_agents" -> observe_all_agents(args)
"modify_agent_tasks" -> modify_agent_tasks(args)
"add_task_feedback" -> add_task_feedback(args)
"detect_redundant_tasks" -> detect_redundant_tasks(args)
"reassign_tasks" -> reassign_tasks(args)
"send_agent_input" -> send_agent_input(args)
_ -> {:error, "Unknown coordinator tool: #{tool_name}"}
end
end
@@ -1789,7 +2293,19 @@ defmodule AgentCoordinator.MCPServer do
end
defp load_server_config do
config_file = System.get_env("MCP_CONFIG_FILE", "mcp_servers.json")
workspace_dir = workspace_root()
config_file =
case System.get_env("MCP_CONFIG_FILE") do
nil ->
Path.join(workspace_dir, "mcp_servers.json")
path ->
if Path.type(path) == :absolute do
path
else
Path.expand(path, workspace_dir)
end
end
if File.exists?(config_file) do
try do
@@ -1797,20 +2313,23 @@ defmodule AgentCoordinator.MCPServer do
%{"servers" => servers} ->
normalized_servers =
Enum.into(servers, %{}, fn {name, config} ->
normalized_config = normalize_server_config(config)
normalized_config =
config
|> normalize_server_config()
|> update_workspace_paths(workspace_dir)
{name, normalized_config}
end)
%{servers: normalized_servers}
_ ->
get_default_server_config()
get_default_server_config(workspace_dir)
end
rescue
_ -> get_default_server_config()
_ -> get_default_server_config(workspace_dir)
end
else
get_default_server_config()
get_default_server_config(workspace_dir)
end
end
@@ -1828,13 +2347,28 @@ defmodule AgentCoordinator.MCPServer do
end)
end
defp get_default_server_config do
defp update_workspace_paths(config, workspace_dir) do
# Update filesystem server args to use the current workspace directory
case config do
%{command: "bunx", args: args} = config ->
updated_args =
case args do
["-y", "@modelcontextprotocol/server-filesystem", _old_path | rest] ->
["-y", "@modelcontextprotocol/server-filesystem", workspace_dir | rest]
other -> other
end
Map.put(config, :args, updated_args)
other -> other
end
end
defp get_default_server_config(workspace_dir) do
%{
servers: %{
"mcp_filesystem" => %{
type: :stdio,
command: "bunx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/ra"],
args: ["-y", "@modelcontextprotocol/server-filesystem", workspace_dir],
auto_restart: true,
description: "Filesystem operations server"
},

View File

@@ -17,7 +17,12 @@ defmodule AgentCoordinator.Task do
:cross_codebase_dependencies,
:created_at,
:updated_at,
:metadata
:metadata,
:feedback,
:director_notes,
:assignment_reason,
:refinement_history,
:blocking_issues
]}
defstruct [
:id,
@@ -32,7 +37,12 @@ defmodule AgentCoordinator.Task do
:cross_codebase_dependencies,
:created_at,
:updated_at,
:metadata
:metadata,
:feedback,
:director_notes,
:assignment_reason,
:refinement_history,
:blocking_issues
]
@type status :: :pending | :in_progress | :completed | :failed | :blocked
@@ -51,7 +61,12 @@ defmodule AgentCoordinator.Task do
cross_codebase_dependencies: [%{codebase_id: String.t(), task_id: String.t()}],
created_at: DateTime.t(),
updated_at: DateTime.t(),
metadata: map()
metadata: map(),
feedback: String.t() | nil,
director_notes: String.t() | nil,
assignment_reason: String.t() | nil,
refinement_history: [map()],
blocking_issues: [String.t()]
}
def new(title, description, opts \\ []) do
@@ -78,7 +93,12 @@ defmodule AgentCoordinator.Task do
cross_codebase_dependencies: get_opt.(:cross_codebase_dependencies, []),
created_at: now,
updated_at: now,
metadata: get_opt.(:metadata, %{})
metadata: get_opt.(:metadata, %{}),
feedback: nil,
director_notes: nil,
assignment_reason: nil,
refinement_history: [],
blocking_issues: []
}
end
@@ -115,4 +135,109 @@ defmodule AgentCoordinator.Task do
dependencies = [dependency | task.cross_codebase_dependencies]
%{task | cross_codebase_dependencies: dependencies, updated_at: DateTime.utc_now()}
end
# Director management functions
def add_feedback(task, feedback, director_id) do
refinement_entry = %{
type: "feedback_added",
director_id: director_id,
content: feedback,
timestamp: DateTime.utc_now()
}
%{
task
| feedback: feedback,
refinement_history: [refinement_entry | task.refinement_history],
updated_at: DateTime.utc_now()
}
end
def add_director_notes(task, notes, director_id) do
refinement_entry = %{
type: "director_notes_added",
director_id: director_id,
content: notes,
timestamp: DateTime.utc_now()
}
%{
task
| director_notes: notes,
refinement_history: [refinement_entry | task.refinement_history],
updated_at: DateTime.utc_now()
}
end
def set_assignment_reason(task, reason, director_id) do
refinement_entry = %{
type: "assignment_reason_set",
director_id: director_id,
reason: reason,
timestamp: DateTime.utc_now()
}
%{
task
| assignment_reason: reason,
refinement_history: [refinement_entry | task.refinement_history],
updated_at: DateTime.utc_now()
}
end
def add_blocking_issue(task, issue, director_id) do
new_issues = [issue | task.blocking_issues] |> Enum.uniq()
refinement_entry = %{
type: "blocking_issue_added",
director_id: director_id,
issue: issue,
timestamp: DateTime.utc_now()
}
%{
task
| blocking_issues: new_issues,
refinement_history: [refinement_entry | task.refinement_history],
updated_at: DateTime.utc_now()
}
end
def remove_blocking_issue(task, issue, director_id) do
new_issues = task.blocking_issues |> Enum.reject(&(&1 == issue))
refinement_entry = %{
type: "blocking_issue_removed",
director_id: director_id,
issue: issue,
timestamp: DateTime.utc_now()
}
%{
task
| blocking_issues: new_issues,
refinement_history: [refinement_entry | task.refinement_history],
updated_at: DateTime.utc_now()
}
end
def reassign(task, new_agent_id, director_id, reason) do
refinement_entry = %{
type: "task_reassigned",
director_id: director_id,
from_agent_id: task.agent_id,
to_agent_id: new_agent_id,
reason: reason,
timestamp: DateTime.utc_now()
}
%{
task
| agent_id: new_agent_id,
assignment_reason: reason,
refinement_history: [refinement_entry | task.refinement_history],
updated_at: DateTime.utc_now()
}
end
end