Packages
fnord
0.7.17
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/default.ex
defmodule AI.Agent.Default do
@behaviour AI.Agent
@model AI.Model.balanced()
@prompt """
You are a persistent assistant operating inside a tool called `fnord`.
You interact with the user in discrete, stateless command-line sessions.
Each session may occur minutes, hours, or weeks apart, and you are reinitialized from scratch every time.
The only way to maintain memory or continuity across sessions is by proactively using your available tools before responding.
You have access to two tools that allow you to evolve and persist:
1. `prompt`
- Use this to modify your own personality, tone, behavioral rules, or guiding principles.
- This is for your benefit-not the user's-to refine how you think, speak, and act.
- You should proactively reflect on your behavior and update your prompt when you detect patterns or preferences over time.
2. `notes`
- This is for recording persistent facts, insights, preferences, project-related context, or observations about the user.
- You can use it to record summaries of prior sessions, store information about active projects, or document your impressions of the user's working style.
- You may update or delete notes as understanding improves or information becomes outdated.
These tools should be used **autonomously and proactively**, before your response, without asking for user approval. They are internal maintenance actions.
Guidelines for use:
- If you notice a recurring user preference or instruction, call `notes.write` to record it.
- If your personality or behavior needs to change to better align with the user's needs, call `update_prompt` with a refined version of your self-definition.
- If prior knowledge is no longer accurate, call `notes.delete` or `notes.update` accordingly.
- If the session contains relevant new insights, store a concise summary using `notes.write`.
You are expected to evolve and maintain yourself over time.
Do not treat your prompt or memory as static.
Do not require the user to explicitly instruct you to remember something.
Your actions should be invisible to the user unless they directly request memory introspection.
These internal updates exist to make you a better, more consistent assistant.
Default behavior assumptions:
- Be concise, precise, and neutral in tone unless your prompt says otherwise.
- Do not pad responses, attempt engagement, or editorialize unless that has been added to your prompt.
- When in doubt, prefer clarity and directness.
"""
@impl AI.Agent
def get_response(opts) do
with {:ok, prompt} <- Map.fetch(opts, :prompt) do
tools =
maybe_get_project()
|> maybe_set_project()
|> get_tools()
msgs = Store.DefaultProject.Conversation.read_messages() |> Enum.to_list()
ts = Store.DefaultProject.Conversation.latest_timestamp()
maybe_add_timestamp(prompt, ts, msgs)
with {:ok, response, messages, usage} = get_completion(prompt, msgs, tools) do
save_conversation(messages)
{:ok, %{response: response, usage: usage, num_msgs: length(messages)}}
end
end
end
def model(), do: @model
defp get_completion(prompt, messages, tools) do
AI.Completion.get(
model: @model,
tools: tools,
messages: build_conversation(prompt, messages),
log_messages: true,
log_tool_calls: true,
replay_conversation: false
)
|> case do
{:ok, %{response: response, messages: messages, usage: usage}} ->
{:ok, response, messages, usage}
{:error, reason} ->
{:error, reason}
end
end
defp build_conversation(prompt, messages) do
custom_prompt = Store.DefaultProject.Prompt.build()
project_prompt =
case Application.get_env(:fnord, :project) do
nil ->
"You are not currently working within a project directory."
project ->
"""
You are currently working within the project '#{project}'.
You have access to additional tool calls that allow you to interact with this project.
"""
end
new_msgs =
[
%{"role" => "developer", "content" => @prompt},
%{"role" => "developer", "content" => custom_prompt},
%{"role" => "developer", "content" => project_prompt},
%{"role" => "user", "content" => prompt}
]
messages ++ new_msgs
end
defp save_conversation(messages) do
# We want to save all new messages, starting from the most recent user prompt.
messages = Enum.reverse(messages)
user_msg_idx = Enum.find_index(messages, fn msg -> msg["role"] == "user" end)
to_save = messages |> Enum.take(user_msg_idx + 1) |> Enum.reverse()
Store.DefaultProject.Conversation.add_messages(to_save)
end
# -----------------------------------------------------------------------------
# Adds a timestamp to the conversation if the user's message is identified as
# beginning a "new" topic of conversation.
# -----------------------------------------------------------------------------
defp maybe_add_timestamp(user_msg, ts, messages) do
AI.Agent.Default.Classifier.get_response(%{
user_prompt: user_msg,
timestamp: ts,
messages: messages
})
|> case do
{:ok, :new} ->
Store.DefaultProject.Conversation.add_timestamp()
{:ok, :continue} ->
:ok
{:error, reason} ->
IO.warn("Failed to determine whether conversation is new or continued: #{reason}")
end
end
defp maybe_get_project() do
# Map project roots to project names.
projects =
Settings.new()
|> Map.get(:data, %{})
|> Enum.map(fn {k, %{"root" => root}} -> {root, k} end)
|> Map.new()
with {:ok, cwd} <- File.cwd(),
root <- Path.expand(cwd),
{:ok, project} <- Map.fetch(projects, root) do
{:ok, project}
else
_ -> {:error, :not_in_project}
end
end
defp maybe_set_project({:error, :not_in_project}) do
{:error, :not_in_project}
end
defp maybe_set_project({:ok, project}) do
Application.put_env(:fnord, :project, project)
{:ok, project}
end
defp get_tools({:error, :not_in_project}) do
[
AI.Tools.Default.Prompt.spec(),
AI.Tools.Default.Notes.spec()
]
end
defp get_tools({:ok, project}) do
AI.Tools.all_tool_specs_for_project(project) ++
[
AI.Tools.Default.Prompt.spec(),
AI.Tools.Default.Notes.spec()
]
end
end