Skip to content

Neofox/neoharness

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

178 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Neoharness

A small, opinionated agent runtime on Elixir/OTP.

Drives any OpenAI-compatible chat-completions endpoint — OpenAI, Kimi / Moonshot, DeepSeek, Mistral La Plateforme, vLLM, Ollama in OpenAI-compat mode, or anything else that speaks the same wire format. Subagents are isolated OTP processes, scheduling is persistent (Oban), and reliability comes from supervision trees rather than hand-rolled error handling.

What you get

  • Agent loop — one GenServer per conversation, bounded tool-calling loop, durable in Postgres. Crash one and the others keep running.
  • Personas — multiple agents side-by-side, each with its own SOUL, user model, memories, skills, schedules, connectors. No cross-contamination.
  • Tools — ~30 built-ins covering memory, web fetch / search, shell, sandboxed Elixir eval, image generation, browser automation, file I/O, persona-to-persona messaging, subagent spawn, and more. Plus MCP servers via Hermes.
  • Memory — Postgres-backed long-term memory with tsvector full-text search; optional pgvector + OpenAI embeddings for hybrid semantic search. Append-only stream + an upsert-in-place user model.
  • Skills — agentskills.io-format SKILL.md files, hot-reloaded from disk. Names and descriptions inject into the system prompt; bodies load on demand.
  • Schedules — Oban-backed heartbeats and cron prompts per persona. Agents can also manage their own schedules via the cron tool.
  • Connectors — Telegram (ExGram) and Discord (Nostrum), routed per-persona by chat/channel id.
  • Web UI — Phoenix LiveView at http://localhost:4000: conversations list, chat page with streaming + tool calls, Oban Web, LiveDashboard.
  • Observability:telemetry.span/3 wrappers on LLM and tool calls with OpenInference-shaped attributes; ship to any OTLP collector.

Running

Prereqs: Elixir 1.19 / OTP 27+ and a running Postgres (with the vector extension if you want semantic search — brew install pgvector on macOS).

cp .env.example .env
# fill in at minimum LLM_API_KEY, LLM_API_BASE, LLM_MODEL

mix deps.get
mix ecto.setup
iex -S mix

config/runtime.exs auto-loads .env from the project root — no source .env step. Already-exported shell vars take precedence.

Two example env blocks:

# OpenAI
export LLM_API_KEY=sk-...
export LLM_API_BASE=https://api.openai.com/v1
export LLM_MODEL=gpt-4o-mini
export LLM_THINKING_STYLE=openai      # or "none" if not on a reasoning model
export LLM_PROVIDER_LABEL=openai

# DeepSeek
export LLM_API_KEY=...
export LLM_API_BASE=https://api.deepseek.com/v1
export LLM_MODEL=deepseek-chat
export LLM_THINKING_STYLE=anthropic
export LLM_PROVIDER_LABEL=deepseek

Once iex -S mix is up, browse http://localhost:4000:

  • / — Conversations list. Create / open, with an optional title.
  • /c/:id — Chat page. Streaming messages, collapsible tool calls, reasoning blocks.
  • /dashboard — Phoenix LiveDashboard.
  • /oban — Oban Web (queues, retries, inspect).

From iex:

Neoharness.say("remember that my favorite editor is nvim")
Neoharness.say("trader:main", "what's BTC doing today?")  # persona-prefixed

Connectors with no token silently skip boot — Telegram if TELEGRAM_BOT_TOKEN (or TELEGRAM_BOT) is unset, Discord if DISCORD_BOT_TOKEN is unset.

Personas

Each persona lives in its own directory under ~/.neoharness/agents/:

~/.neoharness/
├── agents/
│   └── main/                   # auto-seeded on first boot
│       ├── SOUL.md             # voice, principles, style
│       ├── schedules.jsonc     # heartbeats + cron prompts
│       ├── maintenance.jsonc   # heartbeat focus modules
│       ├── connectors.jsonc    # which chats / channels this persona owns
│       ├── tools.jsonc         # tool deny-list + thinking knob
│       └── skills/             # persona-scoped skills (optional)
├── skills/                     # shared across every persona (optional)
└── mcp.json                    # shared MCP servers (optional)

Add more personas by creating sibling directories under agents/. Each gets its own user model, memory namespace, schedules, and connector routing. See docs/personas.md.

MCP servers

Drop a ~/.neoharness/mcp.json (same schema Claude uses):

{
    "mcpServers": {
        "filesystem": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
        }
    }
}

Restart the app. Tools from each server appear as <server>__<tool> in the agent's tool list. See docs/mcp.md.

Schedules

Drop a file at ~/.neoharness/agents/<persona>/schedules.jsonc:

[
    { "name": "morning_check", "type": "heartbeat", "cron": "0 9 * * *" },
    {
        "name": "weekly_review",
        "type": "cron",
        "cron": "0 18 * * FRI",
        "prompt": "Summarize what we worked on this week and post to Telegram.",
    },
]

Restart the app — Oban Cron picks them up. Or let the agent manage its own schedules via the cron tool. See docs/schedules.md.

OpenTelemetry

Point a collector at the app to stream LLM and tool spans:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
iex -S mix

Without the env var, traces stay in-memory — no network calls.

Layout

lib/neoharness/
├── application.ex
├── repo.ex
├── telemetry.ex       # :telemetry → OpenTelemetry bridge
├── llm/               # Chat client, message + tool schema types
├── agent/             # GenServer + pure loop + context + summarizer
├── tools/             # registry, runner, builtin/, behaviour
├── memory/            # Ecto schemas + store + summary + user model
├── personas/          # registry + on-disk loader
├── subagent/          # supervisor + server + spawn tool
├── skills/            # loader + registry (file-watched)
├── mcp/               # pool + client_def + config + tool_adapter
├── scheduler/         # heartbeat + cron + maintenance modules
└── connectors/        # telegram, discord, router
lib/neoharness_web/    # Phoenix endpoint, router, LiveView, telemetry

Tests

mix test

The suite is kept fast (under 10s) and runs against a real Postgres. Real LLM calls are mocked at the Chat boundary so tests stay deterministic.

Docs

  • Setup — install, env vars, first boot.
  • Chatting — web UI, iex, Telegram, Discord.
  • Personas — SOUL, user model, schedules, skills.
  • Tools — every built-in tool the agent can call.
  • Skills — authoring reusable playbooks.
  • Memory — structured memory + semantic search.
  • MCP — external tool providers.
  • Schedules — heartbeats and cron.
  • Architecture — supervision tree, where each concern lives.

License

MIT.

About

My own take on openclaw

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages