Skip to content

Habitat

A Habitat is the top-level container for everything an agent needs: work directory, config, tools, skills, sessions, memory, and sub-agents. Any interface — CLI REPL, Telegram bot, TUI, web — runs inside a Habitat, sharing the same environment.

Overview

~/habitats/                     <- the habitat work directory
  config.json                   <- agents, skills, model defaults
  secrets.json                  <- encrypted API keys and tokens
  STIMULUS.md                   <- base persona / system prompt
  repos/                        <- git-cloned skill repositories
  skills/                       <- local skills
  tools/                        <- work-dir tools
    search/TOOL.md + handler.ts
    run_bash/TOOL.md + handler.ts
  agents/                       <- managed agent workspaces + runtime state
    {agentId}/
      repo/                     <- cloned project workspace
      logs/bridge.log           <- persistent container logs
      state.json                <- port, pid, status
  memories.md, facts.md, ...    <- memory files

Interfaces attach to the same Habitat:

  • umwelten habitat — CLI REPL (default)
  • umwelten habitat telegram — Telegram bot
  • Future: umwelten habitat tui, umwelten habitat web

For a concrete remote-MCP app built on the same model, see MCP Chat Example.

One habitat, many interfaces. They share config, tools, skills, agents, and sessions.

Quick Start

bash
# Start a CLI REPL with Google Gemini
umwelten habitat -p google -m gemini-3-flash-preview

# One-shot prompt
umwelten habitat -p google -m gemini-3-flash-preview "list my agents"

# Custom work directory
umwelten habitat -w ~/my-agent -p openrouter -m anthropic/claude-sonnet-4

# Telegram bot
umwelten habitat telegram --token $TELEGRAM_BOT_TOKEN -p google -m gemini-3-flash-preview

On first run, if the work directory doesn't exist, the habitat runs onboarding automatically — creating config.json, STIMULUS.md, skills/, tools/, and seeding builtin tools.

CLI Commands

REPL Mode

bash
umwelten habitat [options] [prompt...]

When no prompt is given, starts an interactive REPL. In the REPL, these slash commands are available:

CommandDescription
/exitSave session and quit
/agentsList registered agents
/skillsList loaded skills
/toolsList registered tools
/contextShow context size (messages, estimated tokens)
/onboardRe-run onboarding (creates missing files/dirs)
/compact [strategy]Compact conversation context
/compact helpList available compaction strategies

Telegram Subcommand

bash
umwelten habitat telegram [options]

Starts a Telegram bot attached to the same habitat. Requires a bot token (via --token or TELEGRAM_BOT_TOKEN env var).

Shared Options

Both the REPL and telegram subcommands accept:

OptionDescription
-p, --provider <provider>LLM provider (google, openrouter, ollama, etc.)
-m, --model <model>Model name
-w, --work-dir <path>Work directory (default: ~/habitats)
--sessions-dir <path>Sessions directory (default: ~/habitats-sessions)
--env-prefix <prefix>Env var prefix (default: HABITAT)
--skip-onboardSkip automatic onboarding

The telegram subcommand also accepts:

OptionDescription
--token <token>Telegram bot token

Work Directory Structure

PathPurpose
config.jsonAgents, skills, model defaults, tool settings
STIMULUS.mdMain persona / system prompt (YAML frontmatter + body)
AGENT.mdAdditional context appended after the main prompt
tools/Each subdirectory with TOOL.md (+ optional handler) is a tool
skills/Each subdirectory with SKILL.md is a skill
repos/Git-cloned skill repositories
agents/Managed agent workspaces plus runtime state/logs
memories.mdRunning list of things the user shared
facts.mdSummary of known facts
private journal.mdDaily reflections (optional)

Config File (config.json)

json
{
  "defaultProvider": "google",
  "defaultModel": "gemini-3-flash-preview",
  "agents": [
    {
      "id": "twitter-feed",
      "name": "Twitter Feed",
      "projectPath": "/path/to/twitter-feed",
      "commands": { "run": "pnpm start" },
      "logPatterns": [{ "pattern": "logs/*.jsonl", "format": "jsonl" }],
      "statusFile": "status.md"
    }
  ],
  "skillsDirs": ["./skills"],
  "skillsFromGit": ["owner/repo"],
  "skillsCacheDir": "repos",
  "toolsDir": "tools",
  "memoryFiles": {
    "enabled": true,
    "files": ["memories.md", "facts.md"],
    "journalFile": "private journal.md"
  }
}

When defaultProvider and defaultModel are set in config, you can start the habitat without --provider and --model flags.

Tools

Work Directory Tools

Each subdirectory of tools/ with a TOOL.md file becomes a tool. The TOOL.md uses YAML frontmatter for name and description:

yaml
---
name: my_tool
description: "What this tool does"
---
# My Tool

Extended documentation here.

Handlers can be:

  1. Direct Tool exporthandler.ts default-exports a Vercel AI SDK Tool object:

    typescript
    import { tool } from "ai";
    import { z } from "zod";
    
    export default tool({
      description: "My tool",
      parameters: z.object({ query: z.string() }),
      execute: async ({ query }) => {
        return { result: query };
      },
    });
  2. Factory functionhandler.ts default-exports a function that receives the habitat context and returns a Tool:

    typescript
    import { tool } from "ai";
    import { z } from "zod";
    import type { Tool } from "ai";
    
    export default function (context: {
      workDir: string;
      getAgent: Function;
      getAllowedRoots: Function;
    }): Tool {
      return tool({
        description: "My contextual tool",
        parameters: z.object({ command: z.string() }),
        execute: async ({ command }) => {
          // Can use context.workDir, context.getAgent(), etc.
          return { result: command };
        },
      });
    }
  3. Script tool — TOOL.md sets type: script and script: path/to/script.ts:

    yaml
    ---
    name: my_script
    description: "Runs a script"
    type: script
    script: ./run.ts
    ---

Builtin Tools

New habitats are seeded with two builtin tools during onboarding:

ToolTypeDescription
searchDirect exportWeb search via Tavily. Requires TAVILY_API_KEY env var.
run_bashFactoryExecute bash in Dagger containers with experience-based state. Requires @dagger.io/dagger.

These are copied from src/habitat/builtin-tools/ into your habitat's tools/ directory. You can customize or delete them.

Standard Tools (Built-in)

These tools are always registered (unless skipBuiltinTools is set):

ToolDescription
read_fileRead a file (sandboxed to work dir and agent roots)
write_fileWrite a file (creates parent dirs)
list_directoryList directory contents
ripgrepSearch file contents with regex
current_timeCurrent date/time with optional timezone
wgetFetch a URL (raw response)
markifyFetch URL and convert to markdown
parse_feedParse RSS/Atom/XML feeds
agents_list/add/update/removeManage registered agents
agent_cloneClone a git repo and register it
agent_configureInspect a repo and persist its run contract
agent_askDelegate a question to a sub-agent
agent_logsRead agent log files
agent_statusAgent health check
sessions_list/show/messagesView session history

Interfaces

CLI REPL

The default interface. Starts an interactive conversation with streaming responses, tool use, and context management.

bash
umwelten habitat -p google -m gemini-3-flash-preview

Telegram Bot

A long-running Telegram bot that shares the same habitat environment.

bash
umwelten habitat telegram --token $TELEGRAM_BOT_TOKEN -p google -m gemini-3-flash-preview

Features:

  • Multi-turn conversations per chat
  • Media support (photos, documents, audio, video)
  • /start, /reset, /help commands
  • Session-specific media storage and transcripts
  • Markdown formatting

Using an Existing Work Directory

Point at any existing habitat work directory:

bash
# Use a Jeeves-style work dir
umwelten habitat -w ~/path/to/jeeves-data -p google -m gemini-3-flash-preview

# Use the example jeeves-bot data dir
umwelten habitat -w examples/jeeves-bot/jeeves-bot-data-dir -p google -m gemini-3-flash-preview

Any directory with config.json + STIMULUS.md is a valid habitat.

Environment Variables

VariableDefaultDescription
HABITAT_WORK_DIR~/habitatsWork directory
HABITAT_SESSIONS_DIR~/habitats-sessionsSessions directory
HABITAT_CONFIG_PATH{workDir}/config.jsonConfig file override
HABITAT_PROVIDER(none)Default LLM provider
HABITAT_MODEL(none)Default LLM model
TELEGRAM_BOT_TOKEN(none)Telegram bot token
TAVILY_API_KEY(none)Tavily web search API key

The env prefix defaults to HABITAT but can be changed with --env-prefix. For example, --env-prefix JEEVES reads JEEVES_WORK_DIR, JEEVES_PROVIDER, etc.

Programmatic Usage

typescript
import { Habitat } from "umwelten/habitat";

// Create a habitat
const habitat = await Habitat.create({
  workDir: "~/my-habitat",
  defaultWorkDirName: "habitats",
  defaultSessionsDirName: "habitats-sessions",
});

// Onboard if needed
if (!(await habitat.isOnboarded())) {
  await habitat.onboard();
}

// Create an interaction
const { interaction, sessionId } = await habitat.createInteraction({
  modelDetails: { name: "gemini-3-flash-preview", provider: "google" },
  sessionType: "cli",
});

// Chat
interaction.addMessage({ role: "user", content: "Hello!" });
const response = await interaction.streamText();

Custom Tools

Register tools programmatically when creating a habitat:

typescript
const habitat = await Habitat.create({
  registerCustomTools: async (habitat) => {
    habitat.addTool("my_tool", myTool);
    habitat.addTool("my_factory_tool", createMyTool(habitat));
  },
});

Migration from Jeeves

If you've been using Jeeves with a ~/.jeeves work directory, you can use it directly with the habitat command:

bash
# Point at your existing Jeeves data
umwelten habitat -w ~/.jeeves --env-prefix JEEVES -p google -m gemini-3-flash-preview

# Or just copy it to ~/habitats
cp -r ~/.jeeves ~/habitats
umwelten habitat -p google -m gemini-3-flash-preview

The work directory format is identical. The only difference is that tools like search and run_bash now live in tools/ as TOOL.md + handler.ts instead of being hardcoded. Run /onboard to seed them if they're missing.

Architecture

Habitat.create()
  |
  +-- resolveWorkDir (~/habitats, env, or explicit)
  +-- loadConfig (config.json)
  +-- register standard tool sets (file, time, url, agent, session, ...)
  +-- loadToolsFromDirectory(workDir, "tools", habitat)  <-- factory context
  +-- registerCustomTools callback
  |
  +-- getStimulus()
  |     +-- loadStimulusOptionsFromWorkDir (STIMULUS.md, AGENT.md, memory)
  |     +-- register all tools into stimulus
  |     +-- load skills (local + git)
  |
  +-- createInteraction()
        +-- Interaction(modelDetails, stimulus, session)
        +-- wire transcript persistence

Key Files

FilePurpose
src/habitat/habitat.tsMain Habitat class with create() factory
src/habitat/config.tsDirectory resolution, config load/save
src/habitat/types.tsHabitatConfig, HabitatOptions, AgentEntry
src/habitat/onboard.tsFirst-run setup, builtin tool seeding
src/habitat/tool-sets.tsStandard tool set registration
src/stimulus/tools/loader.tsTOOL.md + handler loader (factory support)
src/habitat/builtin-tools/Reference tool implementations
src/cli/habitat.tsCLI command with REPL + telegram subcommand

Habitat Bridge System

The Habitat Bridge System is an optional runtime for managed agents that need isolated execution. It follows a three-phase design: Create (clone + register agent), Start (launch container), Inspect (LLM uses tools to look inside).

Bridge vs Sub-Agent

FeatureHabitatAgent (Sub-Agent)BridgeAgent (Container)
LocationHost filesystemInside Dagger container
CommunicationDirect function callsMCP over HTTP
ProvisioningManual (pre-configured)Saved provisioning or bare node:20
LoggingConsole outputPersistent log files
Use caseLocal project managementRemote repo execution

Three Phases

  1. Createagent_clone(gitUrl, name): Clone the repo into agents/<id>/repo and register the agent. Nothing runs.
  2. Startbridge_start(agentId): Start container with saved provisioning (or bare node:20). Returns when MCP server is reachable. No analysis, no iteration loop.
  3. Inspect — LLM uses agent_status, agent_logs, bridge_ls, bridge_read, bridge_exec to look inside the container and iterate.

Starting Bridge Agents

typescript
const habitat = await Habitat.create();

// Start a bridge for an agent with gitRemote configured
const bridgeAgent = await habitat.startBridge("frontend-app");

// Get the client to interact with the container
const client = await bridgeAgent.getClient();

// Use the client
const files = await client.listDirectory("/workspace");
const result = await client.execute("npm test");

// Check health
const health = await client.health();

// When done
await bridgeAgent.destroy();

Multiple Agents

typescript
// Start multiple bridges
const frontend = await habitat.startBridge("frontend-app");
const backend = await habitat.startBridge("backend-api");

// Each gets a unique port (10000-20000)
console.log("Frontend port:", frontend.getPort());
console.log("Backend port:", backend.getPort());

// List all running agents
const agentIds = habitat.listBridgeAgents();
console.log("Running agents:", agentIds);

// Stop specific agent
await habitat.destroyBridgeAgent("frontend-app");

Saved Provisioning

After the LLM inspects and configures an agent, the config in config.json includes provisioning data so subsequent starts build the container with all packages immediately:

json
{
  "id": "trmnl-image-agent",
  "gitRemote": "https://github.com/The-Focus-AI/trmnl-image-agent",
  "bridgeProvisioning": {
    "baseImage": "node:20",
    "aptPackages": ["jq", "chromium", "curl", "python3", "imagemagick"],
    "setupCommands": ["curl -fsSL https://claude.ai/install.sh | bash"],
    "detectedTools": ["jq", "chrome", "claude-code", "curl", "python", "imagemagick"],
    "projectType": "shell",
    "skillRepos": [],
    "analyzedAt": "2026-02-19T00:50:00.000Z"
  }
}

Port Management

  • Ports allocated from range 10000-20000
  • Each agent gets a unique port
  • Ports released with a 5-second delay after container destruction

When to Use Bridge Agents

Use Bridge Agents when you need:

  • Remote repository execution: Work with repos that don't exist on the host
  • Isolated environments: Each agent has its own container with specific dependencies
  • Long-running processes: Containers stay alive for ongoing work
  • Persistent logs: Track agent activity across restarts

For local project management, use Habitat Agents instead.

Complete Walkthrough

See the Habitat Bridge Walkthrough for a complete guide including MCP tools, curl examples, and the TypeScript client.

Released under the MIT License.