Prehen is a local-first Agent Gateway built with Elixir and OTP.
Current direction:
- users select profiles, not raw runtimes
- each profile lives under
~/.prehen/profiles/<profile_id> - the profile directory is the fixed runtime workspace
- Prehen builds the system prompt and owns session routing
PiCodingAgentlaunches localpi --mode jsonprocesses per turn- higher-level Prehen capabilities are exposed through local HTTP MCP
Included today:
POST /sessionsPOST /sessions/:id/messagesGET /sessions/:idGET /agentsGET /inbox/inbox/sessionsJSON endpointssession:<gateway_session_id>Phoenix Channel streaming- profile-based prompt and workspace resolution
- session-scoped MCP auth
skills.searchandskills.loadfs.read,fs.write,fs.edit,fs.grep,fs.find, andfs.ls
Not included yet:
- persistent session recovery
- multi-node routing
- database-backed config
mix deps.getStart the local gateway:
mix prehen.serverThen open http://localhost:4000/inbox.
For a one-shot CLI run through the same gateway flow:
mix prehen.run --agent coder "list lib and inspect prehen.ex"CLI usage:
prehen run --agent NAME "<task>" [--session-id ID] [--timeout-ms N] [--trace-json]
--workspace is gone. Workspace is fixed by the selected profile.
User-facing config now lives in ~/.prehen/config.yaml.
Example:
runtimes:
pi:
command: pi
args:
- --mode
- json
profiles:
- id: coder
label: Coder
runtime: pi
default_llm:
provider: prehen
model: coder_default
enabled: true
providers:
github-copilot:
adapter: openai_compatible
base_url: https://api.githubcopilot.com
auth_ref: github-copilot
llm_routes:
coder_default:
primary:
provider: github-copilot
model: gpt-5.4-mini
channels: {}
mounts: {}The current runtime implementation is still pi, but users work with profiles. User-editable runtime launch settings now live in ~/.prehen/config.yaml, not in the repo's config/*.exs.
Provider credentials are stored locally under ~/.prehen/providers/<auth_ref>/credentials.json.
Current provider control-plane endpoints:
GET /providersPUT /providers/:provider/credentials
Current local LLM gateway endpoint:
POST /llm/openai/v1/chat/completions
Current /agents payload also exposes each profile's default provider and model.
When a profile uses a virtual route-backed default, that will be provider: "prehen" plus model: "<llm_route_key>".
Each profile has a fixed directory:
~/.prehen/profiles/<profile_id>/
AGENTS.md
SOUL.md
skills/
memory/
Bootstrap one profile:
mkdir -p ~/.prehen/profiles/coder/skills
mkdir -p ~/.prehen/profiles/coder/memory
printf "You are Coder.\n" > ~/.prehen/profiles/coder/SOUL.md
printf "Always be precise.\n" > ~/.prehen/profiles/coder/AGENTS.mdPrompt composition is fixed in code:
- global Prehen instructions
- profile
SOUL.md - profile
AGENTS.md - runtime context
Prehen treats the profile directory itself as the runtime workspace. Sessions do not accept ad hoc workspace overrides.
Skills are no longer injected wholesale into the system prompt.
Prehen exposes profile-scoped MCP tools over local HTTP:
skills.searchskills.loadfs.readfs.writefs.editfs.grepfs.findfs.ls
Filesystem visibility is controlled through the session VFS:
/workspaceis the selected profile directory/tmpis the session scratch directory/mounts/<id>are extra configured mounts
Filesystem calls are session-scoped the same way skills calls are:
- each gateway session gets its own bearer token
- requests are filtered by the session capability set
- mount access stays bound to that session's frozen mount table
- write attempts to read-only mounts are rejected
mcp.fs.toolaudit events are recorded into the gateway trace
Skill visibility rules:
- global skills come from
~/.prehen/skills/ - profile-private skills come from
~/.prehen/profiles/<profile_id>/skills/ - a session can see global skills plus only its selected profile's private skills
MCP access is session-scoped:
- tokens are bound to one gateway session and profile
- tokens are invalidated when the session stops
- requests must be local-only
PiCodingAgent now launches pi from the fixed profile workspace and appends the resolved system prompt to each turn launch.
Prehen also probes whether the installed pi exposes a recognized MCP ingestion contract. Deterministic wrapper tests use a fake pi fixture; a real pi contract smoke remains opt-in:
PREHEN_REAL_PI_MCP_CONTRACT=1 mix test --no-start test/prehen/integration/pi_mcp_contract_smoke_test.exs- Create
~/.prehen/config.yamland the profile directory shown above. - Configure provider credentials through Prehen, for example with
PUT /providers/:provider/credentials. - Start the gateway with
mix prehen.server. - Check
GET /agentsor load/inbox. - Create a
codersession and send a short prompt. - Confirm streamed output appears and the session remains readable after stop.
If /agents is empty, the configured profile failed wrapper support validation in the current environment.
Focused commands used for the current runtime environment work:
mix test --no-start test/prehen/mcp/session_auth_test.exs test/prehen/mcp/tools/skills_test.exs
mix test --no-start test/prehen/vfs/path_test.exs test/prehen/vfs/mount_table_test.exs test/prehen/vfs/adapters/local_test.exs test/prehen/mcp/tools/fs_test.exs
mix test --no-start test/prehen/agents/wrappers/pi_launch_contract_test.exs test/prehen/agents/wrappers/pi_coding_agent_test.exs
PORT=4044 mix test test/prehen/integration/mcp_fs_test.exs test/prehen/client/surface_test.exs test/prehen/gateway/session_worker_test.exs test/prehen/integration/platform_runtime_test.exs
PORT=4033 mix test test/prehen/integration/platform_runtime_test.exs test/prehen/integration/web_inbox_test.exs- current system snapshot:
docs/architecture/current-system.md - profile runtime environment design:
docs/superpowers/specs/2026-04-02-profile-runtime-environment-design.md