Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,11 @@ Bundled LangChain tools gated by sandbox permissions:
| **Search** | `session_search` — query past conversations by keyword, ID, or browse |
| **Clarification** | `clarify` — sends clarification questions to the user |
| **Skills** | `skills_list` — lists discovered skills; `skill_view` — views skill metadata and SKILL.md; `create_skill` — creates spec-compliant skill directories with SKILL.md frontmatter (requires `filesystem:write`) |
| **Code** | `code` — code execution and analysis |
| **Web** | `web` — outbound HTTP with timeout, URL allowlist filtering, multi-engine search backends |
| **Media** | `image` — image generation via fal.ai; `vision` — vision/language analysis via OpenAI; `tts` — text-to-speech via OpenAI TTS |
| **Agents** | `moa` — multi-agent orchestration |
| **Cron** | `cron` — cron job utilities |
| **Code** | `execute_code` — code execution and analysis |
| **Web** | `web_search`, `web_extract` — outbound HTTP with timeout, URL allowlist filtering, multi-engine search backends |
| **Media** | `image_generate` — image generation via fal.ai; `vision_analyze` — vision/language analysis via OpenAI; `text_to_speech` — text-to-speech via OpenAI TTS |
| **Agents** | `mixture_of_agents` — multi-agent orchestration |
| **Cron** | `cronJob` — cron job utilities |

### Skills Registry

Expand All @@ -365,7 +365,7 @@ Built-in tools are registered only when their required permissions are enabled f

| Permission Required | Tools |
| ----------------------------------- | -------------------------------------------------------------------------- |
| `filesystem:read` | `read_file`, `search_files`, `skills_list`, `skill_view`, `session_search` |
| `filesystem:read` | `read_file`, `search_files`, `skill_view`, `session_search` |
| `filesystem:write` | `write_file`, `patch`, `todo`, `memory`, `create_skill` |
| `filesystem:exec` + `process:spawn` | `terminal` |
| `process:spawn` | `process` |
Expand All @@ -390,15 +390,15 @@ Together, these layers create a system that remembers what matters while natural

### Sandbox RTE

Skills run in isolated forked processes with time limits, memory caps, and allowlists for filesystem paths and outbound URLs. Blocked schemes: `file://`, `gopher://`, `dict://`.
Skills run in isolated spawned child processes with time limits, memory caps, and allowlists for filesystem paths and outbound URLs. Blocked schemes: `file://`, `gopher://`, `dict://`.

### Telemetry

Optional `@opentelemetry/sdk-node` integration. Configurable exporter (console, OTLP HTTP, OTLP gRPC), probability sampling, and automatic redaction of sensitive fields (API keys, auth headers).

### Cron Scheduler

Recurring job definitions in `config.yaml`. Supports both in-process scheduling and delegation to the system crontab (`mode: "system"`). Each invocation inherits the current session's memory context and sandbox permissions. Max-concurrency control prevents run overlap.
Recurring job definitions in `config.yaml`. Scheduling is delegated to the system crontab — there is no in-process clock tick loop. Each invocation inherits the current session's memory context and sandbox permissions. Max-concurrency control prevents run overlap (currently a no-op, kept for API compatibility).

On first onboarding completion, `madz` automatically installs a `reflection-daily` cron job (`0 2 * * *`) into the system crontab. Job definitions are persisted as JSON in `memory/schedules/` and managed under the `madz-schedules` block.

Expand Down Expand Up @@ -462,16 +462,18 @@ Graceful shutdown flushes all buffered log entries to disk before process exit.
| | `openai.credentials.apiKey` | _(empty)_ | API key for authentication |
| | `openai.temperature` | `0.7` | Sampling temperature (0–2) |
| | `openai.maxTokens` | `4096` | Max output tokens |
| | `openai.rateLimit.requestsPerMinute` | `120` | Rate limit for API calls |
| | `openai.rateLimit.requestsPerMinute` | `60` | Rate limit for API calls |
| `sandbox` | `paths` | `["memory/", "skills/", "src/", "/tmp"]` | Allowed filesystem paths |
| | `timeout.seconds` | `30` | Max execution time in seconds |
| | `timeout.gracePeriod` | `5` | Kill grace period in seconds |
| | `memoryLimit` | `"128mb"` | Heap limit (`--max-old-space-size`) |
| | `memoryLimit` | `"512m"` | Heap limit (`--max-old-space-size`) |
| | `safety.urlFilter` | `true` | Outbound URL blocking |
| | `safety.pythonImportHook` | `true` | Prevent subprocess import |
| | `env.allowlist` | `["PATH", "HOME", "NODE_ENV"]` | Allowed environment variables |
| | `permissions` | `["filesystem:read", ...]` | Permission grants |
| | `maxReadSize` | `"10mb"` | Max file read size |
| | `maxReadSize` | `"1mb"` | Max file read size |
| | `skillScanPaths` | `["skills/", ".agents/skills/"]` | Skill discovery paths (comma-separated) |
| | `trustProjectSkills` | `true` | Trust skills in project root |
| `memory` | `directory` | `memory/` | Base directory for persistence |
| | `contextDir` | `memory/context/` | Context file directory |
| | `toolsDir` | `memory/tools/` | Tool metadata directory |
Expand All @@ -480,6 +482,8 @@ Graceful shutdown flushes all buffered log entries to disk before process exit.
| | `gc.enabled` | `true` | Enable V8 garbage collection |
| | `gc.idleTimeoutMs` | `300000` | Idle timeout before GC triggers (ms) |
| | `gc.maxGcPerHour` | `4` | Max GC calls per hour |
| | `ephemeral.ttlDays` | `7` | TTL for ephemeral memories in days |
| | `ephemeral.maxEntries` | `10` | Max concurrent ephemeral entries |
| `telemetry` | `enabled` | `false` | Enable OpenTelemetry export |
| | `exporter.protocol` | `console` | Exporter protocol (`console`, `http`, `grpc`) |
| | `exporter.endpoint` | `http://localhost:4318` | OTLP endpoint URL |
Expand All @@ -489,6 +493,7 @@ Graceful shutdown flushes all buffered log entries to disk before process exit.
| | `redact.paths` | `["credentials.apiKey", ...]` | Sensitive field paths for redaction |
| `schedules` | `maxConcurrent` | `1` | Max parallel scheduled runs |
| | `mode` | `inprocess` | Scheduling backend (`inprocess`, `system`) |
| | `syncOnInit` | `true` | Sync crontab from persisted job definitions |
| `tui` | `name` | `madz` | TUI identifier in banner |
| | `cursorChar` | `█` | Cursor character |
| `agent` | `recursionLimit` | `30` | Max graph execution steps per agent call |
Expand Down
21 changes: 16 additions & 5 deletions docs/FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,26 @@ index.js (main)
├── import { loadConfig } from "./src/config/loader.js"
├── config = loadConfig()
│ └── [see Config Loading]
├── if config.schedules.syncOnInit !== false:
│ ├── Cron.sync(schedulesDir) → reconciles persisted jobs with system crontab
│ └── Cron.add(reflection-daily) → ensures daily reflection job exists
├── ensureSessionsDir("memory/sessions/") → creates sessions directory
├── if !hasProfile():
│ └── createOnboarding() with autoSchedule callback → [see Onboarding]
├── if config.telemetry.enabled:
│ ├── initTelemetry(config.telemetry)
│ ├── tracer = getTracer()
│ └── shutdownFn = shutdownTelemetry
├── registry = new SkillRegistry()
├── ensureSkillsDir("skills/")
├── registry.discover("skills/")
│ └── [see Skill Registry Discovery & Validation]
├── { writeMemoryFile, readMemoryFile, loadContext, cleanRetainedMemory, enforceMaxEntries }
├── { writeMemoryFile, readMemoryFile, loadContext, loadMemories, formatMemoriesForPrompt }
│ └── from "./src/memory/index.js"
├── initGC({ idleTimeoutMs, maxGcPerHour, onIdle }) → GC idle manager
├── { createSession, SessionStateManager, saveSession, handleShutdown, registerShutdownHandler }
│ └── from "./src/session/index.js"
├── scheduleManager = new ScheduleManager(config.schedules.maxConcurrent)
├── scheduleManager = new ScheduleManager() // maxConcurrent param deprecated
├── scheduleManager.register(config.schedules.entries)
│ └── [see Schedule Manager Lifecycle]
├── providerName = Object.keys(config.providers)[0] || "openai"
Expand All @@ -65,15 +73,16 @@ index.js (main)
├── tools = await buildToolConfig({
│ │ permissions, allowedPaths, maxReadSize, registry,
│ │ sessionsDir, safety, timeout, memoryLimit,
│ │ contextDir, ephemeralTtlDays, ephemeralMaxEntries, config
│ │ })
│ └── [see Tool Configuration Building]
├── model = createChatModel(providerConfig)
│ └── [see Chat Model Creation]
├── { createCheckpointer } = import("./src/session/checkpointer.js")
├── checkpointer = createCheckpointer(config.persistence)
├── agent = createReactAgent(model, tools, checkpointer)
├── agent = createReactAgent(model, tools, checkpointer, recursionLimit)
│ └── [see Chat Flow (CLI Chat Mode)]
├── sessionConfig = { configurable: { thread_id: sessionId } }
├── sessionConfig = { configurable: { thread_id: sessionState.getThreadId() } }
├── registerShutdownHandler(async () => {
│ ├── saveSession()
│ ├── cleanRetainedMemory()
Expand Down Expand Up @@ -502,13 +511,15 @@ runScheduledSkill(schedule, sandbox, sessionState)
│ └── runSandbox(options):
│ ├── enforceCapabilities(permissions) → rules
│ ├── filterEnv(process.env, whitelist)
│ ├── fork(script, [], { cwd, env, execArgv: ["--max-old-space-size=512"], stdio: ["pipe","pipe","pipe","ipc"] })
│ ├── spawn(script, [], { cwd, env, execArgv: ["--max-old-space-size=512"], stdio: ["pipe","pipe","pipe"] })
│ ├── child.stdout.on("data") → result.stdout
│ ├── child.stderr.on("data") → result.stderr
│ ├── child.on("exit") → resolve code
│ └── handleTimeout(child, { seconds, gracePeriod })
│ └── timeout → SIGTERM → gracePeriod → SIGKILL → "terminated" | "killed"
└── return { stdout, stderr, exitCode }

**Note:** The diagram shows \`fork()\` but the actual implementation uses \`child_process.spawn()\`. Forked processes share the same Node.js runtime and can communicate via IPC channels. Spawned processes are fully isolated with separate memory and event loops, which is the actual isolation model used.
```

## Additional Tool Flows
Expand Down
11 changes: 8 additions & 3 deletions docs/TUI_FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Call chains and component interactions for all primary code paths in the termina
**Entry:** `src/tui/index.js` → `export { default as App } from "./app.js"`

```
App({ config, registry, sessionState, dispatchProvider, appInfo })
App({ config, registry, sessionState, dispatchProvider, scheduleManager, appInfo, onboarding, onSaveSession, gcManager, gcTrigger })
├── useEffect: register process.on("uncaughtException", "unhandledRejection")
├── useInput: global key listener (key, input)
├── useWindowSize: { rows } for layout height
Expand All @@ -37,6 +37,8 @@ App({ config, registry, sessionState, dispatchProvider, appInfo })
└── Text("exit-newline")
```

**Note:** `scheduleManager`, `onboarding`, `onSaveSession`, `gcManager`, and `gcTrigger` are additional props passed from `index.js` but not documented in the original flow diagram.

Mount order: state init → effects (error handlers) → input listener → window size → render.

---
Expand Down Expand Up @@ -145,11 +147,14 @@ User enters ":command ...", presses Enter (app.js:294)
| `:quit` | — | `process.exit(0)` |
| `:provider` | `set <name>` | `sessionState.setProvider(name)` |
| `:config` | `set <path> <value>` | `setConfigValue(config, path, v)`|
| `:memory` | `open`, `search <q>` | Context list / search query |
| `:schedule` | `list`, `pause <n>`, `resume <n>`, `run-now <n>` | Schedule actions |
| `:context` | `add <text>` | Add context string |
| `:clear` | — | Clear conversation messages |
| `:new` | — | Start a fresh session |
| `:gc` | `status` | Trigger V8 GC or show status |
| `:help` | — | Available commands message |

**Note:** `:memory` and `:context` commands are not in the CommandParser dispatch table — they are handled elsewhere in the TUI. The actual registered commands are: quit, provider, config, schedule, clear, new, gc, help.

---

## Keyboard Input (useInput, app.js:282)
Expand Down