# Namma Agent — Cloud-Only configuration # The brain is an API call: native Anthropic/Google/OpenAI, or any # OpenAI-compatible endpoint (opencode / LM Studio / Ollama / custom). # ── Server (Phase 6 — self-hosting) ──────────────────────────────── # Where the web UI/API listens. The default binds LOOPBACK ONLY — the app is # never silently public. To expose it (a VPS, a home server): # 1. set host: 0.0.0.0 (or a specific interface) # 2. SET AN AUTH TOKEN — with a token set, every /api/* request and the # websocket require it (constant-time compare); the web UI shows a small # unlock screen and remembers it. NAMMA_AUTH_TOKEN in .env works too and # wins over this file. Generate one: python -c "import secrets;print(secrets.token_urlsafe(32))" # 3. still put TLS/a firewall in front for the open internet (docs/DEPLOY.md). # The Telegram-only path needs NONE of this — the gateway dials out. # /api/health stays token-free (Docker/systemd healthchecks); platform webhooks # under /webhooks/* have their own verification. # NAMMA_DATA_DIR relocates all runtime state (db/uploads/media/stores) for # volume mounts; PORT still overrides the port. # server: # host: 127.0.0.1 # port: 8000 # auth_token: "" # ── Assistant identity ───────────────────────────────────────────── # THE one place to rename the assistant. Set `name` to anything you like # (e.g. "Jarvis", "Aria", "Max") and it changes everywhere — the system # prompt, the web UI, the voice, and the messaging bridges. You can also # override it without editing this file via the ASSISTANT_NAME env var. assistant: name: Namma Agent # ── Provider (the brain) ─────────────────────────────────────────── provider: type: opencode # anthropic | openai | google | openai_compat | opencode | lmstudio | ollama model: big-pickle api_key_env: OPENAI_API_KEY # secrets live in .env, never here max_tokens: 8192 # per-RESPONSE output cap (not the context window). # 4096 truncated long answers mid-sentence and could # cut a turn before its closing tool call (e.g. a quiz). temperature: 0.3 timeout_s: 60 base_url: https://opencode.ai/zen/v1 # Ordered fallbacks tried when the primary is unavailable/errors. fallback: - type: opencode model: deepseek-v4-flash-free api_key_env: OPENAI_API_KEY # - type: ollama # local server via OpenAI-compat (no key) # model: llama3.1 # - type: openai_compat # any custom endpoint # model: your-model base_url: https://opencode.ai/zen/v1 # api_key_env: NAMMA_API_KEY - type: opencode model: nemotron-3-ultra-free api_key_env: OPENAI_API_KEY # - type: ollama # local server via OpenAI-compat (no key) # model: llama3.1 # - type: openai_compat # any custom endpoint # model: your-model base_url: https://opencode.ai/zen/v1 # api_key_env: NAMMA_API_KEY # ── Providers + Models (switchable brains) ───────────────────────── # Configure each PROVIDER once — with its OWN API key variable — then list the # MODELS you want to switch between (each names a provider + a model id). Manage # both from the UI: Settings → Providers, then Settings → Models (writes # config.local.yaml, applies live). Models show in the picker at the top of every # chat; switching mid-conversation starts a new session in the same chat. # providers: # - id: opencode # label: Opencode # type: opencode # base_url: https://opencode.ai/zen/v1 # api_key_env: OPENCODE_API_KEY # each provider keeps a DISTINCT key var # - id: groq # label: Groq # type: openai_compat # base_url: https://api.groq.com/openai/v1 # api_key_env: GROQ_API_KEY # models: # - label: Claude Opus # provider: opencode # → providers[].id # model: claude-opus-4-8 # - label: Llama 3.3 (Groq) # provider: groq # model: llama-3.3-70b # # # A LOCAL model (LM Studio / Ollama) with a small context window (~20K tokens) # # needs a lighter turn than a cloud brain — otherwise the fixed prompt (tool # # schemas + persona + history) plus one big tool result overflows the window, # # the endpoint silently truncates, and the model stops calling tools mid-task. # # Each knob below is optional and overrides the global setting FOR THIS MODEL # # ONLY (0 / unset = inherit), so cloud profiles stay full-fat: # - label: Qwen3 14B (LM Studio) # type: lmstudio # inline connection; base_url defaults to # model: qwen3-14b # http://localhost:1234/v1 # max_tokens: 2048 # output cap — reserved INSIDE the context window # timeout_s: 180 # local prefill is slow; don't kill the turn early # max_history_turns: 4 # fewer past turns in each prompt # tool_result_max_chars: 8000 # cap each tool result fed back to the model # tools_allow: [file_ops, web, system, memory, update_todos] # # expose only these toolsets/tools (names from # # Settings → Toolsets) — fewer schemas = a far # # smaller prompt + sharper tool selection # ── Tools exposed to the model ───────────────────────────────────── # By default the model sees EVERY enabled tool (~60-90 schemas ≈ 6-8K prompt # tokens per request). `allow` scopes that to a subset — entries may be tool # names or whole toolset (category) names from Settings → Toolsets. Essential # for small-context local models; prefer the per-model `tools_allow` override # in `models:` above so only local brains are scoped. (`disabled` is written by # the Toolsets tab into config.local.yaml — don't hand-edit it here.) # tools: # allow: [file_ops, web, system, memory, update_todos] # ── Persona ──────────────────────────────────────────────────────── persona: core # loaded from personas/.yaml # ── Skills (procedural memory / learning loop, ported from hermes) ── skills: user_dir: ~/.namma_agent/skills # where create_skill writes learned skills allow_inline_shell: false # expand !`cmd` snippets inside SKILL.md (off by default) # ── Browser (controlled, visible browser for real video playback + control) ── browser: engine: playwright # 'playwright' (controllable, visible) or 'webbrowser' (default browser, no control) headless: false # false = visible window you can watch (recommended) preferred: auto # which browser to drive: auto (detect OS default) | chrome | chromium | brave | edge | vivaldi | opera | firefox fullscreen: true # open videos in fullscreen # Profile = where logins are cached so your accounts stay signed in. With # use_system_profile true, Namma Agent copies your real profile's logins (cookies + # Default profile, minus caches) into a dedicated dir it can drive safely. use_system_profile: true # reuse your REAL browser logins (copied once into profile_dir-) profile_dir: ~/.namma_agent/browser-profile # base dir for the driveable profile copy # ── Background services (opt-in: "everything visible" by default) ─── scheduler: run_in_background: false # auto-fire due reminders from a polling thread # ── Proactive routines (the agent reaches out FIRST) ──────────────── # Standing scheduled tasks ("every morning at 08:00: brief me on my day"): each # run is a full agent turn whose result is delivered over your messaging # channels (or a desktop notification). Ask the assistant to "create a routine" # — creating one is the opt-in; the poll thread only runs while at least one # enabled routine exists. Destructive tools are always declined in routine runs. routines: enabled: true # false hides the tools and never polls # poll_seconds: 30 # how often due routines are checked # store_path: data/routines.json # ── Event watchers (Phase 2 — "tell me WHEN…") ────────────────────── # A watcher = trigger + condition + action. Triggers are cheap polls (file glob, # new Gmail matches, web-page text hash, calendar events within N minutes); when # one fires, a single cheap model pass — the "only if it matters" gate — decides # notify / act / ignore against the watcher's stated intent, so noisy pages # don't become notification spam. Actions run as routine-style agent turns # (destructive tools always declined) and deliver over your messaging channels. # Ask the assistant to "watch …" — creating a watcher is the opt-in; the poll # thread only runs while at least one enabled watcher exists. Web/email content # is injection-screened (Phase 1b) before the gate or you ever see it. watchers: enabled: true # false hides the tools and never polls # poll_seconds: 60 # how often due watchers are checked # store_path: data/watchers.json # ── Weekly self-review (Phase 3 — measured self-improvement) ──────── # Mines the week's sessions (tool failures, corrections you made, repeated # workflows, threads left hanging), measures memory recall offline (mock eval, # no API cost), snapshots the metrics to data/self_review/ so the Learning tab # shows trends, and drafts skill/routine/watcher proposals you accept or reject # — never auto-applied. The WEEKLY run is off by default; the "Run review now" # button in Settings → System → Learning always works. self_review: enabled: false # true: run weekly + deliver the report over comms # weekday: 6 # Sunday (Mon=0) # at: "18:00" # dir: data/self_review # ── Learning Room ─── learning: notify_progress: true # Telegram/Discord ping when a module completes (needs comms) nudge_after_days: 3 # "it's been a while" nudge for idle topics (0 disables; # the nudge thread rides scheduler.run_in_background) comms: inbound_enabled: true # reply to Telegram messages (needs NAMMA_TELEGRAM_TOKEN + CHAT_ID; polling thread) # Per-channel trust levels (Settings → Messaging): owner | trusted | untrusted. # An UNTRUSTED sender's turn runs with destructive tools stripped + declined, # the message wrapped in a data-not-instructions guard in the prompt, and its # memory writes quarantined for review instead of stored. Defaults: pinned-id # channels (telegram/signal) are owner; open webhooks (slack/whatsapp) are # untrusted; unknown channels are always untrusted. # trust: # telegram: owner # signal: owner # discord: trusted # slack: untrusted # whatsapp: untrusted # Transcribe Telegram voice messages (optional — local STT was removed; the web # UI uses the browser, but Telegram needs a transcription endpoint). Put the key # in .env under api_key_env. Works with OpenAI or any OpenAI-compatible STT # endpoint (set base_url, e.g. Groq). Leave the key unset to disable voice. stt: api_key_env: OPENAI_API_KEY # base_url: # e.g. https://api.groq.com/openai/v1 model: whisper-1 # ── Conversation / narration ─────────────────────────────────────── conversation: # Spoken progress is narrated by the model; these are the delays (seconds) # after which a context-aware "still working" line is spoken on long tasks. progress_delays_s: [4.0, 12.0, 25.0] max_history_turns: 12 tool_loop_limit: 0 # 0 = UNLIMITED tool steps (use the Stop button to halt); >0 = hard cap # Cap on EACH tool result fed back into the model's context, in characters # (0 = unlimited). Protects small-context models from one big document read # overflowing the whole window; the model is told the result was truncated so # it can re-query narrower. Prefer the per-model override in `models:` so only # local brains are capped (~8000 chars ≈ 2K tokens is a good local value). tool_result_max_chars: 0 memory_nudge_every: 6 # remind the model to curate memory every N exchanges (0=off) # Rolling context compaction: when a chat outgrows max_history_turns, a running # summary of the evicted part is maintained (background, provider chain) and # injected into every prompt — the model never silently loses the middle of a # long conversation. false disables it. compact_history: true # Self-verification: if a turn wrote/edited files and the model tries to answer # without any check after the write, nudge it ONCE to verify (re-read the file, # run a quick check) before finalizing. false disables the nudge. verify_after_writes: true default_mode: agent # 'agent' (all tools/skills) or 'chat' (pure conversation) auto_approve: false # Auto mode: run destructive tools (shell etc.) without asking permission # ── Voice ────────────────────────────────────────────────────────── # Voice is 100% browser-native (Web Speech API): the UI reads answers aloud and # the mic dictates input. The backend produces no audio and has no STT — there is # nothing to configure or install here. # ── Database ─────────────────────────────────────────────────────── database: path: data/namma_agent.db # ── Security tools (active scanning — OFF by default) ────────────── # Enable only on networks you are authorized to test. Targets must fall # inside authorized_scopes (loopback is always allowed). All scans are # approval-gated through the tool registry. security: lab_mode: false # set true to expose port_scan/ping_sweep/dir_enum/dns_enum authorized_scopes: [] # e.g. ["192.168.1.0/24", "10.0.0.0/8", "lab.example.com"] # ── Filesystem access policy ── # Namma Agent can READ files anywhere on the machine (your docs live all over the # disk). WRITES / deletes / renames are blocked inside the OS + installed- # software trees (C:\Windows, Program Files, ProgramData on Windows; /usr, # /etc, /bin, /lib, /opt, /System… on Linux/macOS) so it can read those files # but can't corrupt the OS or your programs. Secret files (ssh/gpg/aws keys, # /etc/shadow) stay blocked for reads too. Tune it here: filesystem: # extra_protected_write_paths: ["D:\\Games", "/mnt/backups"] # ALSO read-only # protected_write_paths: [...] # REPLACE the OS/software read-only roots # writable_anywhere: false # true = allow writes everywhere except secrets # ── Shell sandbox (Phase 1c) ── # Every run_shell child runs inside OS-level resource caps: a Windows Job # Object (kill-on-close — a timeout kill also takes down every descendant — # memory cap, fork-bomb guard, no breakaway) or POSIX rlimits (address space / # CPU / file size, inherited by descendants). If the OS refuses (rare), Namma # warns once in the log and runs as before. sandbox: enabled: true memory_mb: 4096 # per-process memory cap (0 = off) cpu_seconds: 0 # cumulative CPU-time cap (0 = off; the per- # command timeout is the first line of defense) fsize_mb: 0 # POSIX: largest file a child may create (0 = off) max_processes: 128 # Windows: active processes in the job (0 = off) # ── Confined shell root (optional, off by default) ── # When set, run_shell commands that reference absolute paths outside this # folder (or run while cd'd outside it) require your explicit in-chat # approval first. A tripwire against the agent wandering, not a jail. # shell: # confine_to: "D:\\AgentWork" # nmap_binary: nmap # gobuster_binary: gobuster # dig_binary: dig # wordlist: /usr/share/wordlists/dirb/common.txt # default_timeout_sec: 120 # dig_timeout_sec: 30 # ── Smart home (Home Assistant — optional) ───────────────────────── # Off until url + token are set. The token belongs in .env, named by token_env. # smart_home: # url: http://homeassistant.local:8123 # token_env: HASS_TOKEN # aliases: # bedroom lights: light.bedroom_main # ac: climate.living_room_ac # ── Scheduler / tasks / goals (persisted stores) ─────────────────── # Channel tokens for comms (Telegram/Discord) live in .env, not here. # scheduler: # store_path: data/reminders.json # tasks: # store_path: data/tasks.json # goals: # store_path: data/goals.json # scheduler: # poll_seconds: 30 # how often the reminder runner checks for due items # ── MCP servers (optional — external Model Context Protocol tools) ── # Each server's tools appear as mcp__ in the registry. # mcp: # servers: # - name: filesystem # command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/home/me"] # - name: git # command: ["uvx", "mcp-server-git"] # enabled: true # ── Memory (Engram — THE memory; see docs/MEMORY_SYSTEM_DESIGN.md) ── # Native in-process memory: bounded core memory in every prompt, bi-temporal facts # + entity graph in SQLite, fused millisecond recall, always-on salience-gated # learning, and a persistent host model (OS/drives/folders/installed tools). # All memory model calls use the model YOU picked in Settings (the provider # chain) — never a hardcoded one. Manage everything from Settings → Memory # (writes config.local.yaml, applies live); values below are optional tuning. # An external memory MCP server (e.g. Cognee) can still be attached as a plain # plugin under mcp.servers — it is additive, never load-bearing. # memory: # write: # salience_min_chars: 24 # user messages shorter than this are never extracted # budget_per_hour: 60 # cap on memory-pipeline model calls per hour # embeddings: # OPTIONAL vector recall channel (catches paraphrases # base_url: http://localhost:1234/v1 # any OpenAI-compatible /embeddings — # model: text-embedding-nomic-embed-text-v1.5 # OpenAI, Groq, LM Studio, Ollama # api_key_env: OPENAI_API_KEY # not needed for localhost endpoints # # unset = recall stays BM25-only (works fine offline) # recall: # prefetch: true # auto-inject relevant memory into each agent turn # k: 5 # max prefetched memories per turn # consolidate: # sleep-time self-improvement (design §8) — summarize, # background: true # promote, merge, decay, reflect, compact, re-probe host. # idle_minutes: 20 # run after this much inactivity (0 = never on idle) # daily_at: "03:30" # and once daily at this local time ("" = never) # half_life_days: 30 # decay: how fast unreinforced facts fade # archive_floor: 0.05 # decay score below which a fact is archived # event_horizon_days: 90 # 'event' facts older than this expire on their own # ── Logging ──────────────────────────────────────────────────────── logging: level: info # debug | info | warning | error (env NAMMA_LOG_LEVEL overrides) to_file: true # also write logs/namma_agent.log (rotating) file: logs/namma_agent.log