MADZ.
GitHub ↗

Getting Started with Madz

Madz is an AI harness designed to learn, adapt, and assist. You speak to it through a terminal interface, a one-shot command, or a pipe into your scripts. It does not demand ceremony. It demands clarity.

This guide takes you from zero to your first conversation — and then to working on your projects.


📦 Preparation

Before we build, we must prepare the ground. Ensure your system meets these requirements:

Core Requirements

  • Docker Desktop or Docker Engine — madz runs as a local container
  • An LLM Provider (API key from OpenAI, or a local model via Ollama)

What is Docker?

Docker packages an application and all its dependencies into a single, isolated container. It ensures madz runs identically on your machine, day after day, without conflicts or "it works on my machine."

If you're new to Docker: Do not worry. The commands below are straightforward, and each part is explained.

What runs where: host vs. container

Before you run anything, it helps to have a mental model of the two "places" involved:

  • Your host machine — the computer you're sitting at. Your files live here: ./memory, ./skills, ~/projects.
  • The container — an isolated environment running madz. It has its own filesystem at /app. madz runs inside the container; you talk to it over SSH.
  • Bind mounts — a bridge between the two. -v ./memory:/app/memory means "make the host's ./memory directory appear at /app/memory inside the container." Changes on either side are visible on the other.

The key idea: the container is disposable, your data is not. When you pull a new image or recreate the container, anything written inside the container's filesystem is lost. Anything in a bind mount or named volume survives. That's why the setup mounts memory/ and skills/ from your host — so your profile, memories, and skills persist across container upgrades.


🚀 Installation

The Docker container is the deployment model — for home use and professional use alike. It runs locally, with no authentication and no server. You reach it over SSH on a loopback port.

📦 Just want to run it? (Minimal Setup)

mkdir -p ./memory ./skills
echo 'OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxx' > .env
docker run -d --name madz -p 2222:22 -v ./memory:/app/memory -v madz-checkpoints:/app/memory/checkpoints -v ./skills:/app/skills --env-file .env avoidwork/madz:latest
docker ps   # confirm the container is Up
ssh -p 2222 madz@localhost

This creates the data directories, writes your key to .env, starts the container, verifies it, and connects you. For full configuration and bind mount explanations, see below.

Step 1: Pull the official image

docker pull avoidwork/madz:latest

This downloads the pre-built application package from Docker Hub to your local machine.

Step 2: Prepare your data directories

Bind mounts link a directory on your host machine directly into the container. This ensures your conversation history, memory files, and custom skills survive container restarts, upgrades, and removals.

Why externalize data? Docker containers are ephemeral by design. They can be stopped, replaced, or destroyed during updates, migrations, or troubleshooting. Any data written inside the container's filesystem is lost when the container is removed. By mounting host directories into the container, we externalize persistent state so it survives the container lifecycle. When you pull a new image or recreate the container, your memories and skills remain intact.

mkdir -p ./memory ./skills

SQLite checkpoints: madz uses an SQLite-backed checkpointer for LangGraph state persistence. The checkpoint database lives in memory/checkpoints/checkpoints.db. Rather than bind-mounting this subdirectory to the host, use a Docker named volume. This keeps checkpoint data managed by Docker — no host-side directory needed, and the DB survives container recreation without touching local files.

Step 3: Run the container

docker run -d \
  --name madz \
  -p 2222:22 \
  -v ./memory:/app/memory \
  -v madz-checkpoints:/app/memory/checkpoints \
  -v ./skills:/app/skills \
  --env-file .env \
  avoidwork/madz:latest

Security Note: Avoid passing API keys directly via -e flags, as they will persist in your shell history. Instead, create a .env file in your project root with your variables and reference it with --env-file .env. For quick testing, you can still use -e OPENAI_API_KEY="your-key" directly, but remember to switch to .env for anything beyond a trial.

Example .env files:

OpenAI (Cloud):

OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-4o
OPENAI_BASE_URL=https://api.openai.com/v1

Ollama (Local):

OPENAI_BASE_URL=http://host.docker.internal:11434/v1
OPENAI_MODEL=gemma4:12b
# OPENAI_API_KEY is optional for local providers

Custom model with explicit encoding:

OPENAI_BASE_URL=http://your.inference.lan:8000/v1
OPENAI_MODEL=Qwen/Qwen3-32B
OPENAI_ENCODING=qwen2_base

When using a non-OpenAI model, you may need to set OPENAI_ENCODING to specify the tiktoken encoder name that matches the model's tokenizer. See the Encoding Reference in the README for common mappings.

Note for Docker users: host.docker.internal is required so the container can reach the Ollama service running on your host machine. If you're on Linux, you may need to use --network host instead.

Flag breakdown:

FlagPurpose
-dRun in detached mode (background)
--name madzAssign a human-readable name to the container
-p 2222:22Map host port 2222 to container SSH port 22 (avoids conflicts with local SSH)
-v ./memory:/app/memoryBind mount host ./memory into container /app/memory for persistence
-v madz-checkpoints:/app/memory/checkpointsNamed volume for SQLite checkpoint data (managed by Docker, no host directory needed)
-v ./skills:/app/skillsBind mount host ./skills into container /app/skills for custom tools
--env-file .envInject sensitive credentials securely from a local file

Volumes vs. Bind Mounts: Docker supports two persistence methods. Volumes are managed by Docker and live in /var/lib/docker/volumes/. Bind mounts (used here) link directly to a path on your host filesystem. We use bind mounts for memory/ and skills/ so you can read, edit, and version-control your memory and skills files directly from your terminal or editor. SQLite checkpoint data (memory/checkpoints/) uses a named volume instead — it's a binary database that doesn't need host-side editing, and Docker manages its lifecycle automatically.

Port collision? If port 2222 is already in use, change the host port in the -p flag (e.g., -p 2223:22) and update your SSH command accordingly.

Verify it's alive:

docker ps            # STATUS should read "Up"
docker logs madz     # boot log: scheduler sync, session init, TUI ready

Upgrading

The container is disposable; your data is not. To upgrade:

docker pull avoidwork/madz:latest
docker rm madz
docker run -d \
  --name madz \
  -p 2222:22 \
  -v ./memory:/app/memory \
  -v madz-checkpoints:/app/memory/checkpoints \
  -v ./skills:/app/skills \
  --env-file .env \
  avoidwork/madz:latest

Same flags, new image. Because memory/, skills/, and the checkpoint volume live outside the container, your profile, memories, sessions, and skills are exactly where you left them.


⚙️ Configuration

madz reads its configuration from config.yaml. Sensitive values should be injected via environment variables to keep secrets out of version control. Secrets belong in the dark. Configuration belongs in the light.

🌐 LLM Providers & Sovereignty

madz is architecturally designed for local AI. While it supports cloud endpoints, its core philosophy prioritizes data sovereignty and privacy. By running models locally, you keep your conversation history, memory files, and custom skills entirely on your machine. No telemetry. No external data routing. Just pure, unfiltered compute.

Any OpenAI-compatible endpoint works. madz talks to its model through the OpenAI chat-completions API, so it works with any provider that exposes that interface — not just OpenAI itself. That includes:

  • Ollama — the most straightforward local path. Install it, pull a model (ollama pull gemma4:12b), and point OPENAI_BASE_URL at it.
  • vLLM — high-throughput self-hosted inference, OpenAI-compatible out of the box.
  • LiteLLM — a proxy that fronts many backends behind a single OpenAI-compatible endpoint.
  • LM Studio, llama.cpp, or any other OpenAI-compatible server.

You point madz at one by setting OPENAI_BASE_URL (and OPENAI_MODEL) in your .env. For local providers, OPENAI_API_KEY is optional — many run without authentication.

Cloud providers (OpenAI, OpenRouter, and others) are fully supported via the configuration below if latency or model availability dictates it, but the architecture assumes local-first by default.

Environment Variable Mapping

Config keys map to UPPER_SNAKE_CASE environment variables. Container-specific keys (providers, credentials, timeout, search) are stripped from the variable name.

Config PathEnvironment VariableDefault
providers.openai.credentials.apiKeyOPENAI_API_KEY(required)
providers.openai.modelOPENAI_MODELgpt-4o
providers.openai.base_urlOPENAI_BASE_URLhttps://api.openai.com/v1
providers.openai.temperatureOPENAI_TEMPERATURE0.4
providers.openai.maxTokensOPENAI_MAX_TOKENS4096
providers.openai.rateLimit.requestsPerMinuteOPENAI_REQUESTS_PER_MINUTE120
providers.openrouter.apiKeyOPENROUTER_API_KEY(empty)
providers.openrouter.modelOPENROUTER_MODELopenrouter/auto
providers.fal.credentials.apiKeyFAL_API_KEY(empty)
sandbox.timeout.secondsSANDBOX_TIMEOUT_SECONDS600
sandbox.timeout.gracePeriodSANDBOX_GRACE_PERIOD5
sandbox.maxReadSizeSANDBOX_MAX_READ_SIZE10mb

Note: The table reflects the shipped config.yaml defaults, which differ from the schema defaults in a few places. The schema default for sandbox.timeout.seconds is 30 (shipped: 600), for sandbox.maxReadSize is 1mb (shipped: 10mb), and for providers.openai.rateLimit.requestsPerMinute is 60 (shipped: 120). If you override any of these via env var, the value is a plain number (seconds, requests) or a size string (e.g., 10mb, 500kb).

Inline References (Alternative)

You can also reference environment variables directly in config.yaml:

providers:
  openai:
    credentials:
      apiKey: "${OPENAI_API_KEY}"

🖥️ Launch & First Interaction

Docker — Connect via SSH

If you deployed with Docker (the deployment model), connect to the container using the SSH port you passed to docker run. The container's SSH daemon listens on internal port 22, so you map it to a host port of your choosing (commonly 2222 to avoid conflicts with your local SSH):

ssh -p 2222 madz@localhost

The madz user has no password. On login the TUI launches automatically. Type /exit (or /quit) to leave; Esc interrupts a running response. When madz exits, the SSH session terminates with it — SSH login drops you straight into the TUI, not a shell. If you need a shell for inspection, use docker exec -it madz /bin/sh from the host instead (see Troubleshooting).

First command to try: Give me a quick system health check — CPU load, memory, and disk.

First Launch: The Living Profile

On your very first run, madz will detect that no user profile exists and initiate an interactive onboarding flow. It will ask a series of targeted questions to build your initial profile (e.g., "What do you build?", "What tools do you use?", "How direct should I be?"), establishing a foundation for deep, immediate personalization.

This profile is saved to memory/context/profile.md and injected into the system prompt at the start of every session, ensuring consistent, tailored behavior from day one.

Over time, madz autonomously captures ephemeral memories during operation. These entries log interaction patterns, decision milestones, and stylistic preferences, layering directly onto the base profile. The system prompt is dynamically rebuilt each session, ensuring consistent, context-aware behavior without manual intervention.

To re-trigger the initial profile setup, simply delete memory/context/profile.md and restart.


📂 Working on Your Projects

This is where madz stops being a demo and becomes a teammate. The container ships a full development toolchain — Node.js, Python, Ruby, Go, Java, Rust, Terraform, git, the GitHub CLI, and three package managers — so it can build, test, and commit real code.

Step 1: Mount your projects directory.

Add one bind mount to your docker run command, pointing at the directory on your host that holds your repositories:

docker run -d \
  --name madz \
  -p 2222:22 \
  -v ./memory:/app/memory \
  -v madz-checkpoints:/app/memory/checkpoints \
  -v ./skills:/app/skills \
  -v ~/projects:/app/projects \
  --env-file .env \
  avoidwork/madz:latest

Your repositories are now visible inside the container at /app/projects/<repo>. The image already ships an empty /app/projects/ directory (owned by the madz user), so the mount simply overlays it — no mkdir required.

Step 2: The convention is built in.

You do not need to configure anything for this to work. The system prompt carries a standing directive:

Project directories are cwd. Each immediate subdirectory of projects/ is a standalone project root. When the user works on one, treat that subdirectory as the working directory for the task — resolve relative paths against it, run commands with it as the working directory, and apply its own project rules (AGENTS.md, config, lint) instead of the parent's.

So naming a project is enough. The agent resolves the name to its directory and works there.

Step 3: Speak in direct statements.

Out of the box, you no longer describe paths. You describe work:

Fix the failing test in backend-api.
Run the build in my photo-app repo and tell me what broke.
Create a PR on backend-api for the session timeout fix.

The agent resolves the project name to its directory and works there — reading files, running commands, editing, committing, and pushing, all inside the mounted repo.

Optional: reinforce or adjust the convention with a memory.

The built-in directive covers the standard case. If you mount projects somewhere other than /app/projects, or you want to add project-specific rules the agent should always honor, layer a canonical memory on top. Ask in plain language:

Remember: my projects live in /app/projects. When I name a project,
treat /app/projects/<name> as the working root — resolve all file
operations, git commands, and builds relative to it.

madz writes this to memory/context/ and loads it into every session. You can also create the file yourself:

# Projects

My projects live in `/app/projects`. When I refer to a project by name
(e.g. "backend-api"), treat `/app/projects/backend-api` as the working
root — resolve all file operations, git commands, and builds relative to it.

Project conventions: If a repository contains an AGENTS.md, the agent discovers and follows it — commit format, lint rules, branch policy. Your project's own rules take precedence over general behavior.

Semantic code search: For larger codebases, index a project so the agent can search it by meaning, not just by keyword. Configure the project in .env — the project name is the first segment, lowercased, so keep it a single word:

VECTOR_PROJECTS_BACKENDAPI_ROOT_DIR=/app/projects/backend-api
VECTOR_PROJECTS_BACKENDAPI_DB_PATH=/app/projects/backend-api/vector.db

(include defaults to src/**/*.js, src/**/*.mjs, src/**/*.cjs; add VECTOR_PROJECTS_BACKENDAPI_INCLUDE_0=... to change it. Array fields are indexed from _0, so VECTOR_PROJECTS_BACKENDAPI_INCLUDE_0=src/**/*.ts replaces the first include pattern.) Then ask:

Index the code in backendapi for vector search

The agent's indexing tool reads these from the environment — no file editing required. (.env is injected at container start, so after adding these lines, recreate the container with the same docker run flags.)

Verified: The VECTOR_PROJECTS_* env vars are materialized by syncEnv() in src/config/loader.js, which resolves dynamic record keys under vector.projects. Setting VECTOR_PROJECTS_BACKENDAPI_ROOT_DIR and VECTOR_PROJECTS_BACKENDAPI_DB_PATH creates a backendapi project entry with the default include/exclude/chunkSize/chunkOverlap/maxFileSize values. Array overrides like VECTOR_PROJECTS_BACKENDAPI_INCLUDE_0 replace the corresponding index.

Set the active project explicitly.

The convention above lets the agent resolve a project by name. But sometimes you want to pin the working directory — so the file picker and the agent's file operations both target a specific project. Use /projects:

/projects

This opens the projects panel, listing every directory under projects/. Navigate with ↑/↓, press Enter to select one, and it becomes the active project. The status bar shows the selected project's name (e.g. [backend-api]), and the @ file picker now globs that project's directory instead of the container root.

Once a project is active, the @ file picker lists files from inside it:

read @src/config/loader.js

The picker searches the active project's directory, so @src/... resolves relative to the project root — no need to type the full path.

Clear the active project.

To return to the default working directory (the container root), clear it:

/projects clear

Or select (clear active project) at the top of the projects panel. The status bar stops showing a project, and the @ file picker globs the container root again.


🛠️ Daily Usage

TUI Navigation

Once inside the interactive terminal, use these commands:

CommandAction
↑ / ↓Scroll conversation history
/helpList available commands
/quitExit the application
/exitExit the application
/provider set <name>Switch LLM provider
/config set <path> <value>Mutate config at runtime
/schedule listList all scheduled jobs
/schedule pause <name>Pause a scheduled job
/schedule resume <name>Resume a paused job
/schedule run-now <name>Run a job immediately
/gcTrigger V8 garbage collection
/gc statusCheck GC availability and call count
/clearClear current conversation
/newStart a fresh session
/sessionsOpen the sessions panel
/memoriesOpen the memories panel
/skillsOpen the skills panel
/subagentsOpen the subagents panel — view each subagent's reasoning, message segments, and tool calls live during a multi-agent turn
/settingsOpen the settings panel
/projectsOpen the projects panel — select a project directory to set the active project
/projects clearClear the active project back to the default
Ctrl+FToggle in-conversation search
Ctrl+RToggle reasoning block collapse/expand
Ctrl+TToggle tool-call result block collapse/expand

Memory System

madz operates on a triple-layer memory architecture:

  • Canonical Memories: Explicitly set by you. Stored as .md files in memory/context/. Loaded into every session. Includes profile, clarifications, reflections, and temporal captures.
  • Ephemeral Memories: Captured autonomously during operation. Record patterns, milestones, and tones. Auto-expire over time via expiresAt frontmatter field.
  • Reflections: Generated daily by a cron job (0 2 * * *) that runs the reflection skill in chat mode. Stored as canonical memories in memory/context/ with createdDate and updatedDate metadata. The job file is ensured at startup by the scheduler's crontab sync and persisted as memory/schedules/reflection-daily.json.

This triple-layer architecture powers the autonomous learning loop — canonical memories persist, ephemeral memories capture moments, and reflections synthesize patterns into lasting context.

Changes to canonical memory require a /new command to refresh the current session context.

Skills

Skills are how you give madz new capabilities — a bit like a macro in Excel, but with more intention. You define a set of instructions, and madz follows them whenever a task matches. Skills let you package domain expertise, repeatable workflows, and specialized tools that madz can discover and invoke on demand.

Creating skills is natural. Just ask madz in everyday language — it will generate the necessary files for you. You can also create them manually in the skills/ directory.

Each skill is a folder containing a SKILL.md file with YAML frontmatter and instructions. When you ask madz to perform a related task, it matches your request to the skill description, loads the full instructions, and executes — optionally running any bundled scripts, loading reference documents, or applying templates.

Directory structure:

my-skill/
├── SKILL.md          # Required: metadata + instructions
├── scripts/          # Optional: executable code
├── references/       # Optional: documentation
└── assets/           # Optional: templates, resources

Example SKILL.md:

---
name: system-info
description: Retrieve current system metrics, uptime, and memory usage. Use when the user asks about system health, load, or performance.
license: MIT
---
1. Run `uptime` and `free -h`.
2. Format the output into a concise summary.
3. Report back to the user.

Skills are stored in skills/ and are version-controllable. Simple skills can be chained together into pipelines for complex multi-step processing, or composed by asking madz to coordinate between them.

Built-in tools: Beyond skills, madz ships with built-in tools for common tasks. The Deep Agents orchestrator (deepagents library) handles multi-agent routing natively — see Delegating Work to Subagents. The scanAgents tool scans for AGENTS.md workspace rules files. Other built-in tools include filesystem operations, shell execution, search, memory management, and more.


⚙️ Advanced Usage

Scheduled Jobs

madz supports cron-based scheduled jobs that run in non-interactive mode. Each invocation inherits the current session's memory context and sandbox permissions. Max-concurrency control prevents run overlap.

To schedule a task, simply ask:

madz, schedule the news-email skill to run during the week at 8pm

madz will parse the natural language instruction and create the cron entry for you. Manage jobs from the TUI with /schedule list, /schedule pause <name>, /schedule resume <name>, and /schedule run-now <name>.

Scripting & Automation

For headless execution, use chat mode — the response streams to stdout, so you can pipe it into other tools:

node index.js "Summarize memory/_index.md"

This is the same mode the internal cron jobs use (e.g., node index.js --message "Run the reflection skill"). In the container, run it with docker exec:

docker exec madz node index.js "Summarize memory/_index.md"

Virtual Filesystem

All file operations use a virtual filesystem where / is the application root. When you see a path like /package.json or /src/tools/index.js, the leading / is a virtual path that resolves relative to the application's working directory.

The orchestrator uses a CompositeBackend that routes file operations to different backends based on path prefix:

Virtual PathBackendActual Resolution
/package.jsonCore Backend<cwd>/package.json
/src/tools/index.jsCore Backend<cwd>/src/tools/index.js
/memory/context/profile.mdContext Backend<cwd>/memory/context/profile.md

This creates a clean, consistent namespace where the agent always sees / as the root, regardless of where the application is actually running. Path traversal is validated — resolved paths must stay within their backend's rootDir.

Connecting MCP Servers

Model Context Protocol (MCP) lets you plug external tools and data sources into madz declaratively. Register servers under a root-level mcp key in config.yaml, and madz discovers their tools at startup and registers them alongside the built-in tools.

mcp:
  docs:
    transport: http
    url: https://docs.langchain.com/mcp
  local-fs:
    transport: stdio
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/path"]

Transports:

TransportFieldsUse for
stdiocommand, args, env?Local, process-spawned servers (e.g., npx-launched).
httpurlRemote servers over Streamable HTTP (the default).
sseurlOlder servers that only speak HTTP + SSE.

Classifying tools: Each server can carry an agents list that routes its tools to specific agent types. When absent, tools default to the orchestrator.

mcp:
  local-fs:
    transport: stdio
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
    agents: ["coding", "search"]
  • No agents → tools go to the orchestrator.
  • agents: ["coding", "search"] → tools are delegated to those subagents.
  • agents: ["orchestrator", "coding"] → tools are available to both.

The agents classification is a madz-specific extension — it lives in your config, not on the server. The MCP server only provides the tool (name, schema, handler).

Behavior:

  • MCP tools are discovered at runtime and appended to the tool list.
  • A server that fails to connect logs a warning and is skipped — startup never crashes.
  • Tool names are prefixed with the server name (e.g., local-fs_read_file) to avoid collisions.
  • The adapter stays open for the agent's lifetime and is closed on shutdown.

🤖 Delegating Work to Subagents

madz can delegate complex, multi-step work to specialized subagents. Instead of you orchestrating each step manually — checking git status, running lint, editing files, committing — you describe the outcome you want in plain language, and the orchestrator routes the work to the right specialist.

You do not type agent names. You state the task; the orchestrator dispatches.

Available Agent Types

AgentScope
code-reviewStructured reviews: bugs, security, style, performance
codingCode editing, debugging, implementation, git operations
debugError tracing, reproduction, and fix proposals
documentationDocumentation updates, API docs, changelogs
performanceBenchmarking, bottleneck identification, optimization
researchMulti-step research with source tracking
searchMulti-source search (web, docs, codebase) with synthesis
security-auditSecurity scanning and dependency auditing
seoAnalystKeyword density, meta descriptions, SERP analysis
testingTest generation, gap analysis, coverage improvements
textEditorText processing — summarize, rewrite, tone, grammar
translatorMulti-language translation and language detection

Concrete Examples

Commit and push:

Commit and push this branch — we have an open PR.

The coding agent checks git status, stages changed files, crafts a commit message following the project's conventional commit format, and pushes to the remote branch. It will not push without explicit approval — see the notes below.

Run lint, fix, commit, push:

Run lint, fix any errors, then commit and push.

The agent runs the project's lint command, identifies each issue, applies fixes, re-runs lint to verify, and proceeds to commit and push. If a fix is ambiguous or risky, it pauses and asks for guidance rather than guessing.

Update a PR:

Update PR #123 with the latest changes from this branch.

The agent reads the current branch state, diffs against the target branch, and composes a meaningful description from the actual code changes.

Create an issue:

Create an issue for the memory leak in the session manager.

The agent searches the codebase for relevant context, references related files, and creates the issue with a proper title, description, and labels.

Debug a failing test:

Debug the failing test in tests/unit/skills.test.js and fix it.

The agent runs the test, analyzes the failure, traces the root cause through the relevant source files, applies a fix, and re-runs the test to confirm the fix without regressions.

Best Practices

  • Be specific in your delegation. Clear, unambiguous instructions produce better results. "Fix the lint errors" is good; "run lint on src/ and fix all errors without changing the public API" is better.
  • The agent operates in the project's working directory by default. All file paths and commands are resolved relative to it.
  • It follows project conventions. The agent reads AGENTS.md rules, respects the project's commit message format, and adheres to linting and formatting standards.
  • For git operations, it respects branch protection. It will not force-push or bypass protected branches.
  • Complex tasks benefit from step-by-step instructions. If a task has multiple phases, describe them in order and the agent will execute sequentially.

Important Notes

  • The agent never rebases without explicit agreement.
  • It never pushes without explicit user approval.
  • It never changes branches without permission.
  • It follows the project's conventional commit format for all commits.

These guardrails exist so you can delegate with confidence. The agent is a collaborator, not an autonomous actor. It acts, but it does not overreach.


🔧 Troubleshooting

Docker-Specific

IssueSolution
docker: command not foundInstall Docker Desktop (macOS/Windows) or Docker Engine (Linux). Verify with docker --version.
Is the container running?Check status with docker ps -a | grep madz. If it's Exited, check logs with docker logs madz.
Permission denied on docker commandsAdd your user to the docker group: sudo usermod -aG docker $USER, then restart your terminal.
Container exits immediatelyCheck logs: docker logs madz. Missing OPENAI_API_KEY or invalid config will cause early exit.
SSH connection refusedEnsure port mapping is correct (-p 2222:22). Try ssh -o StrictHostKeyChecking=no -p 2222 madz@localhost.
Memory/skills not persistingUse volumes or bind mounts for persistent state. If using bind mounts, verify host directory permissions allow the madz user to read and write.
Need a shell for inspectionSSH login launches the TUI directly — there is no interactive shell. Use docker exec -it madz /bin/sh instead.

General

IssueSolution
TUI not launching?Check the configuration — it's always a configuration issue.
Skill not executing?Check that the required permissions (filesystem:read, filesystem:write, etc.) are enabled in config.yaml under sandbox.permissions.
Session not persisting?Verify that memory/ is writable and not mounted as read-only.

Deploy with confidence. The machine waits for no one, but madz remembers everything.

© 2026 Jason Mulligan. ALL RIGHTS RESERVED.

VIEW SOURCE ↗