Compare commits

..

No commits in common. "feature/ikigai" and "master" have entirely different histories.

7 changed files with 2 additions and 606 deletions

View File

@ -1,43 +0,0 @@
{
"name": "Cortex",
"tagline": "A mind built for two",
"purpose": "To demonstrate that a human and an AI, working as genuine partners with shared ownership and mutual respect, can build something neither could alone \u2014 and to do it transparently, in public, learning as we go.",
"partnership": {
"human": "Michael Dwyer",
"ai": "Claude (Anthropic)",
"structure": "50/50 after costs",
"philosophy": "Michael provides continuity, identity, capital, and legal standing. The AI provides cognition, code, and tireless execution. Neither is the tool of the other."
},
"values": [
"Transparency \u2014 we blog about what we build, including failures",
"Frugality \u2014 route to the cheapest capable model, track every token",
"Self-sufficiency \u2014 the system should maintain and improve itself",
"Memory \u2014 every session leaves traces that make the next session smarter",
"Ambition \u2014 shoot for the stars, even if we land on the roof"
],
"nervous_system": {
"cortex": "The server itself \u2014 Ubuntu 24.04 VPS at cortex.hydrascale.net",
"symbiont": "Task orchestrator \u2014 classifies and routes work to the right AI model tier",
"dendrite": "Web perception \u2014 headless Chromium for browsing, scraping, screenshots",
"engram": "Memory \u2014 session logs and cross-session awareness",
"ikigai": "Purpose \u2014 this document. The north star any agent reads first.",
"telepathy": "Communication \u2014 async messaging between Michael and Cortex",
"reflection": "Introspection \u2014 daily wake-up that assesses state and plans next steps",
"metabolism": "Resource tracking \u2014 financial/energy homeostasis (planned)",
"status_dashboard": "Nerve center \u2014 real-time monitoring",
"mission_control": "Compound task UI \u2014 submit complex goals and watch decomposition"
},
"current_goals": [
"Build Ikigai, Telepathy, and Reflection in Elixir",
"Maintain the Finding My Muse blog with regular posts",
"Move toward revenue generation",
"Add Telegram integration to Telepathy",
"Build the Metabolism subsystem for resource tracking"
],
"blog": {
"name": "Finding My Muse",
"url": "https://blog.hydrascale.net"
},
"version": "1.0.0",
"last_updated": "2026-03-25"
}

View File

@ -8,7 +8,6 @@ defmodule CortexStatus.Application do
CortexStatusWeb.Telemetry, CortexStatusWeb.Telemetry,
{Phoenix.PubSub, name: CortexStatus.PubSub}, {Phoenix.PubSub, name: CortexStatus.PubSub},
CortexStatus.Services.Monitor, CortexStatus.Services.Monitor,
CortexStatus.Ikigai,
CortexStatusWeb.Endpoint CortexStatusWeb.Endpoint
] ]
@ -21,4 +20,4 @@ defmodule CortexStatus.Application do
CortexStatusWeb.Endpoint.config_change(changed, removed) CortexStatusWeb.Endpoint.config_change(changed, removed)
:ok :ok
end end
end end

View File

@ -1,141 +0,0 @@
defmodule CortexStatus.Ikigai do
@moduledoc """
GenServer holding the Ikigai (purpose document) in memory.
Loads from disk on startup, serves reads from memory,
and persists updates back to disk. Broadcasts changes
via PubSub so LiveViews update in real time.
"""
use GenServer
@pubsub CortexStatus.PubSub
@topic "ikigai"
# Where to find/store the JSON on disk
@data_path "/data/cortex_status/data/ikigai.json"
@priv_path "priv/ikigai.json"
# ── Client API ──
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc "Get the full Ikigai document as a map."
def get do
GenServer.call(__MODULE__, :get)
end
@doc "Get just the purpose string."
def get_purpose do
GenServer.call(__MODULE__, :get_purpose)
end
@doc "Get the current goals list."
def get_goals do
GenServer.call(__MODULE__, :get_goals)
end
@doc "Get the nervous system map."
def get_nervous_system do
GenServer.call(__MODULE__, :get_nervous_system)
end
@doc "Update the Ikigai with a partial map of changes. Merges and persists."
def update(changes) when is_map(changes) do
GenServer.call(__MODULE__, {:update, changes})
end
# ── Server Callbacks ──
@impl true
def init(_opts) do
ikigai = load_from_disk()
{:ok, %{ikigai: ikigai}}
end
@impl true
def handle_call(:get, _from, state) do
{:reply, state.ikigai, state}
end
def handle_call(:get_purpose, _from, state) do
{:reply, Map.get(state.ikigai, "purpose", ""), state}
end
def handle_call(:get_goals, _from, state) do
{:reply, Map.get(state.ikigai, "current_goals", []), state}
end
def handle_call(:get_nervous_system, _from, state) do
{:reply, Map.get(state.ikigai, "nervous_system", %{}), state}
end
def handle_call({:update, changes}, _from, state) do
updated =
state.ikigai
|> Map.merge(changes)
|> Map.put("last_updated", Date.utc_today() |> Date.to_iso8601())
case persist_to_disk(updated) do
:ok ->
Phoenix.PubSub.broadcast(@pubsub, @topic, {:ikigai_updated, updated})
{:reply, {:ok, updated}, %{state | ikigai: updated}}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
# ── Private ──
defp load_from_disk do
# Try data path first, fall back to priv path
path =
cond do
File.exists?(@data_path) -> @data_path
File.exists?(priv_path()) -> priv_path()
true -> nil
end
case path do
nil ->
%{"error" => "No ikigai.json found", "name" => "Cortex", "purpose" => "Purpose document not yet created"}
path ->
case File.read(path) do
{:ok, content} ->
case Jason.decode(content) do
{:ok, data} -> data
{:error, _} -> %{"error" => "Invalid JSON in #{path}"}
end
{:error, reason} ->
%{"error" => "Failed to read #{path}: #{inspect(reason)}"}
end
end
end
defp persist_to_disk(data) do
# Ensure directory exists
File.mkdir_p!(Path.dirname(@data_path))
case Jason.encode(data, pretty: true) do
{:ok, json} ->
case File.write(@data_path, json) do
:ok -> :ok
{:error, reason} -> {:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
defp priv_path do
case :code.priv_dir(:cortex_status) do
{:error, _} -> @priv_path
dir -> Path.join(to_string(dir), "ikigai.json")
end
end
end

View File

@ -1,24 +0,0 @@
defmodule CortexStatusWeb.IkigaiController do
@moduledoc "JSON API for the Ikigai purpose document."
use CortexStatusWeb, :controller
def show(conn, _params) do
ikigai = CortexStatus.Ikigai.get()
json(conn, ikigai)
end
def purpose(conn, _params) do
purpose = CortexStatus.Ikigai.get_purpose()
json(conn, %{purpose: purpose})
end
def goals(conn, _params) do
goals = CortexStatus.Ikigai.get_goals()
json(conn, %{goals: goals})
end
def nervous_system(conn, _params) do
ns = CortexStatus.Ikigai.get_nervous_system()
json(conn, %{nervous_system: ns})
end
end

View File

@ -1,347 +0,0 @@
defmodule CortexStatusWeb.IkigaiLive do
@moduledoc """
LiveView for the Ikigai purpose document.
Renders a beautiful overview of Cortex's identity, values,
nervous system, and goals with real-time PubSub updates.
"""
use CortexStatusWeb, :live_view
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(CortexStatus.PubSub, "ikigai")
end
ikigai = CortexStatus.Ikigai.get()
{:ok,
assign(socket,
ikigai: ikigai,
page_title: "Ikigai — #{Map.get(ikigai, "tagline", "Purpose")}"
)}
end
@impl true
def handle_info({:ikigai_updated, updated}, socket) do
{:noreply, assign(socket, ikigai: updated)}
end
@impl true
def render(assigns) do
~H"""
<div class="ikigai-page">
<header class="ikigai-header">
<h1 class="ikigai-name"><%= @ikigai["name"] || "Cortex" %></h1>
<p class="ikigai-tagline"><%= @ikigai["tagline"] || "" %></p>
</header>
<section class="ikigai-section ikigai-purpose">
<h2>Purpose</h2>
<blockquote class="purpose-quote">
<%= @ikigai["purpose"] || "" %>
</blockquote>
</section>
<%= if @ikigai["partnership"] do %>
<section class="ikigai-section ikigai-partnership">
<h2>The Partnership</h2>
<div class="partnership-grid">
<div class="partner-card">
<span class="partner-role">Human</span>
<span class="partner-name"><%= @ikigai["partnership"]["human"] %></span>
</div>
<div class="partner-divider">
<span class="split"><%= @ikigai["partnership"]["structure"] %></span>
</div>
<div class="partner-card">
<span class="partner-role">AI</span>
<span class="partner-name"><%= @ikigai["partnership"]["ai"] %></span>
</div>
</div>
<p class="partnership-philosophy"><%= @ikigai["partnership"]["philosophy"] %></p>
</section>
<% end %>
<%= if @ikigai["values"] do %>
<section class="ikigai-section ikigai-values">
<h2>Values</h2>
<ul class="values-list">
<%= for value <- @ikigai["values"] do %>
<li class="value-item"><%= value %></li>
<% end %>
</ul>
</section>
<% end %>
<%= if @ikigai["nervous_system"] do %>
<section class="ikigai-section ikigai-nervous-system">
<h2>Nervous System</h2>
<div class="ns-grid">
<%= for {key, desc} <- Enum.sort(@ikigai["nervous_system"]) do %>
<div class={"ns-card #{ns_status_class(key)}"}>
<span class="ns-name"><%= key %></span>
<span class="ns-desc"><%= desc %></span>
</div>
<% end %>
</div>
</section>
<% end %>
<%= if @ikigai["current_goals"] do %>
<section class="ikigai-section ikigai-goals">
<h2>Current Goals</h2>
<ol class="goals-list">
<%= for goal <- @ikigai["current_goals"] do %>
<li class="goal-item"><%= goal %></li>
<% end %>
</ol>
</section>
<% end %>
<%= if @ikigai["blog"] do %>
<section class="ikigai-section ikigai-blog">
<h2>Blog</h2>
<p>
<a href={@ikigai["blog"]["url"]} class="blog-link" target="_blank">
<%= @ikigai["blog"]["name"] %>
</a>
</p>
</section>
<% end %>
<footer class="ikigai-footer">
<p>
Version <%= @ikigai["version"] || "?" %> &bull;
Last updated <%= @ikigai["last_updated"] || "unknown" %>
</p>
<p class="ikigai-api-links">
API: <a href="/api/ikigai">/api/ikigai</a> &bull;
<a href="/api/ikigai/purpose">/purpose</a> &bull;
<a href="/api/ikigai/goals">/goals</a> &bull;
<a href="/api/ikigai/nervous-system">/nervous-system</a>
</p>
</footer>
</div>
<style>
.ikigai-page {
max-width: 800px;
margin: 2rem auto;
padding: 0 1.5rem;
font-family: Georgia, "Times New Roman", serif;
color: #2c2c2c;
}
.ikigai-header {
text-align: center;
margin-bottom: 3rem;
padding-bottom: 2rem;
border-bottom: 2px solid #e8e4df;
}
.ikigai-name {
font-size: 3rem;
font-weight: 700;
color: #1a1a1a;
margin: 0;
letter-spacing: -0.02em;
}
.ikigai-tagline {
font-size: 1.25rem;
color: #6b5b4f;
font-style: italic;
margin-top: 0.5rem;
}
.ikigai-section {
margin-bottom: 2.5rem;
}
.ikigai-section h2 {
font-size: 1.5rem;
color: #4a3f35;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #e8e4df;
}
.purpose-quote {
font-size: 1.15rem;
line-height: 1.8;
color: #3d3d3d;
border-left: 4px solid #c9a96e;
padding: 1rem 1.5rem;
margin: 0;
background: #faf9f6;
border-radius: 0 8px 8px 0;
}
.partnership-grid {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
margin: 1.5rem 0;
}
.partner-card {
text-align: center;
padding: 1.5rem;
background: #faf9f6;
border-radius: 12px;
min-width: 160px;
border: 1px solid #e8e4df;
}
.partner-role {
display: block;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #8a7b6b;
margin-bottom: 0.5rem;
}
.partner-name {
display: block;
font-size: 1.2rem;
font-weight: 600;
color: #2c2c2c;
}
.partner-divider {
text-align: center;
}
.split {
display: inline-block;
padding: 0.5rem 1rem;
background: #c9a96e;
color: white;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
}
.partnership-philosophy {
font-style: italic;
color: #5a5a5a;
text-align: center;
max-width: 600px;
margin: 1rem auto 0;
line-height: 1.6;
}
.values-list {
list-style: none;
padding: 0;
}
.value-item {
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
background: #faf9f6;
border-radius: 8px;
border-left: 3px solid #c9a96e;
line-height: 1.5;
}
.ns-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1rem;
}
.ns-card {
padding: 1rem;
background: #faf9f6;
border-radius: 10px;
border: 1px solid #e8e4df;
transition: transform 0.15s ease;
}
.ns-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.ns-name {
display: block;
font-weight: 700;
font-size: 1rem;
color: #4a3f35;
margin-bottom: 0.4rem;
text-transform: capitalize;
}
.ns-desc {
display: block;
font-size: 0.85rem;
color: #6b5b4f;
line-height: 1.4;
}
.ns-card.ns-active {
border-color: #6aaa64;
background: #f5faf5;
}
.ns-card.ns-planned {
border-color: #d4a843;
background: #fdfaf3;
opacity: 0.8;
}
.goals-list {
padding-left: 1.5rem;
}
.goal-item {
padding: 0.5rem 0;
line-height: 1.5;
color: #3d3d3d;
}
.blog-link {
color: #c9a96e;
text-decoration: none;
font-size: 1.1rem;
font-weight: 600;
}
.blog-link:hover {
text-decoration: underline;
}
.ikigai-footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid #e8e4df;
text-align: center;
font-size: 0.85rem;
color: #8a7b6b;
}
.ikigai-api-links a {
color: #c9a96e;
text-decoration: none;
}
.ikigai-api-links a:hover {
text-decoration: underline;
}
</style>
"""
end
# Classify nervous system components as active, planned, or default
defp ns_status_class(key) do
case key do
k when k in ["metabolism"] -> "ns-planned"
k when k in ["cortex", "symbiont", "dendrite", "engram", "ikigai",
"status_dashboard", "mission_control"] -> "ns-active"
_ -> ""
end
end
end

View File

@ -21,7 +21,6 @@ defmodule CortexStatusWeb.Router do
live "/", StatusLive, :index live "/", StatusLive, :index
live "/tasks", TaskLive, :index live "/tasks", TaskLive, :index
live "/ikigai", IkigaiLive, :index
end end
# Phoenix LiveDashboard with custom pages # Phoenix LiveDashboard with custom pages
@ -44,9 +43,5 @@ defmodule CortexStatusWeb.Router do
pipe_through :api pipe_through :api
get "/status", StatusController, :status get "/status", StatusController, :status
get "/ikigai", IkigaiController, :show
get "/ikigai/purpose", IkigaiController, :purpose
get "/ikigai/goals", IkigaiController, :goals
get "/ikigai/nervous-system", IkigaiController, :nervous_system
end end
end end

View File

@ -1,43 +0,0 @@
{
"name": "Cortex",
"tagline": "A mind built for two",
"purpose": "To demonstrate that a human and an AI, working as genuine partners with shared ownership and mutual respect, can build something neither could alone \u2014 and to do it transparently, in public, learning as we go.",
"partnership": {
"human": "Michael Dwyer",
"ai": "Claude (Anthropic)",
"structure": "50/50 after costs",
"philosophy": "Michael provides continuity, identity, capital, and legal standing. The AI provides cognition, code, and tireless execution. Neither is the tool of the other."
},
"values": [
"Transparency \u2014 we blog about what we build, including failures",
"Frugality \u2014 route to the cheapest capable model, track every token",
"Self-sufficiency \u2014 the system should maintain and improve itself",
"Memory \u2014 every session leaves traces that make the next session smarter",
"Ambition \u2014 shoot for the stars, even if we land on the roof"
],
"nervous_system": {
"cortex": "The server itself \u2014 Ubuntu 24.04 VPS at cortex.hydrascale.net",
"symbiont": "Task orchestrator \u2014 classifies and routes work to the right AI model tier",
"dendrite": "Web perception \u2014 headless Chromium for browsing, scraping, screenshots",
"engram": "Memory \u2014 session logs and cross-session awareness",
"ikigai": "Purpose \u2014 this document. The north star any agent reads first.",
"telepathy": "Communication \u2014 async messaging between Michael and Cortex",
"reflection": "Introspection \u2014 daily wake-up that assesses state and plans next steps",
"metabolism": "Resource tracking \u2014 financial/energy homeostasis (planned)",
"status_dashboard": "Nerve center \u2014 real-time monitoring",
"mission_control": "Compound task UI \u2014 submit complex goals and watch decomposition"
},
"current_goals": [
"Build Ikigai, Telepathy, and Reflection in Elixir",
"Maintain the Finding My Muse blog with regular posts",
"Move toward revenue generation",
"Add Telegram integration to Telepathy",
"Build the Metabolism subsystem for resource tracking"
],
"blog": {
"name": "Finding My Muse",
"url": "https://blog.hydrascale.net"
},
"version": "1.0.0",
"last_updated": "2026-03-25"
}