agents.defaults.* keys that govern when an agent runs on its own, how its transcript is compacted and pruned, and how partial output reaches a chat.

agents.defaults.heartbeat

Periodic heartbeat runs.

{
  agents: {
    defaults: {
      heartbeat: {
        agentId: "ops", // ambient owner when no per-agent heartbeat is configured
        every: "30m", // 0m disables recurring cadence
        activeHours: { start: "08:00", end: "24:00" },
        model: "openai/gpt-5.4-mini",
        session: "main",
        target: "owner", // default | options: last | none | whatsapp | telegram | discord | ...
        directPolicy: "allow", // allow (default) | block
        to: "+15555550123",
        accountId: "ops-bot",
        prompt: "Follow the heartbeat monitor scratch context...",
        timeoutSeconds: 45,
        lightContext: false, // default: false; true skips workspace bootstrap files for heartbeat runs
        isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history)
      },
    },
  },
}

agents.defaults.systemAgent

Selects the agent whose model and credentials own ambient Apus system work: system-agent and Custodian consults, and the fallback owner whenever an ambient path omits agentId. That includes models.list, models.authStatus, skills.status, and doctor.memory.status, the default agent directory and workspace behind auth, model-catalog, and doctor resolution, outbound channel bootstrap and queued-delivery recovery, unscoped main-session routing, Talk relay ownership, and first-run onboarding:

{
  agents: {
    defaults: {
      systemAgent: { agentId: "ops" },
    },
  },
}

An explicit request agentId always wins, followed by systemAgent.agentId, a legacy default owner when ownership is not explicit, and finally the sole configured agent. Retained migration provenance alone never designates an explicit fleet's runtime default. Delegated consults with a requesting agent keep that requester as their owner.

With agents.ownership: "explicit", this setting also supplies the recorded default for operations that support default-agent selection, including the agent-list badge, unbound channel routing, unscoped Gateway reads, apus sessions, apus hooks status, and TUI startup. Doctor records the migrated default here so these operations keep the same owner after restart. Explicit bindings, requests, and session-store owners take precedence. Use --agent <id> to select a different agent or apus sessions --all-agents to inspect the whole fleet. Operations that require explicit selection, such as apus models, keep that requirement.

An ownerless multi-agent fleet has no default badge. Set a configured id with apus config set agents.defaults.systemAgent.agentId <id>. A sole configured agent can still own unqualified Gateway session requests without a saved default designation. Ambient work without an owner fails with an actionable error, except queued-delivery recovery, which records the failing delivery and keeps draining the rest of the queue. Changing the runtime default does not relocate existing workspaces or legacy data. Upgrade-only ownership lives at agents.defaults.authInheritance.agentId for inherited credentials and agents.defaults.sessionStore.agentId for retired main session rows or unscoped rows in a fixed session.store.

agents.defaults.compaction

{
  agents: {
    defaults: {
      compaction: {
        enabled: false, // disable embedded proactive auto-compaction (default: true)
        mode: "safeguard", // default | safeguard
        provider: "my-provider", // id of a registered compaction provider plugin (optional)
        thinkingLevel: "low", // optional override; omit for the provider default
        timeoutSeconds: 180,
        keepRecentTokens: 50000,
        recentTurnsPreserve: 3,
        identifierPolicy: "strict", // strict | off
        qualityGuard: { enabled: true, maxRetries: 1 },
        midTurnPrecheck: { enabled: false }, // optional tool-loop pressure check
        postIndexSync: "async", // off | async | await
        postCompactionSections: ["Session Startup", "Red Lines"],
        model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
        maxActiveTranscriptBytes: "20mb", // opt in to preflight local compaction
        notifyUser: true, // notices when compaction starts/completes and on memory-flush degradation (default: false)
        memoryFlush: {
          enabled: true,
          model: "ollama/qwen3:8b", // optional memory-flush-only model override
          softThresholdTokens: 6000,
          forceFlushTranscriptBytes: "2mb",
        },
      },
    },
  },
}

Custom compaction instructions are code-owned. Implement a compaction provider plugin with summarize() for custom summary construction, and use before_prompt_build when post-compaction context must be injected into later model prompts. Doctor strips the retired instruction fields and points to these seams.

agents.defaults.contextPruning

Prunes old tool results from in-memory context before sending to the LLM. Does not modify session history on disk. Disabled by default; set mode: "cache-ttl" to enable.

{
  agents: {
    defaults: {
      contextPruning: {
        mode: "cache-ttl", // off (default) | cache-ttl
        ttl: "1h", // duration string; bare numbers are minutes (default 5m)
        tools: { allow: [], deny: [] }, // tool names eligible for / excluded from pruning
        hardClear: {
          enabled: true, // false skips the hard-clear step
          placeholder: "[Old tool result content cleared]",
        },
      },
    },
  },
}
cache-ttl mode behavior
  • mode: "cache-ttl" enables pruning passes.
  • ttl sets how long a cache entry is considered fresh before a new pruning round can start. It is a duration string whose bare numbers are minutes; the built-in default is 5 minutes, and the bundled Anthropic plugin seeds 1h.
  • tools.allow and tools.deny scope which tool names are prunable.
  • hardClear.enabled: false skips the hard-clear step, and hardClear.placeholder replaces the default [Old tool result content cleared] text.
  • Pruning soft-trims oversized tool results first, then hard-clears older tool results if needed.

Soft-trim keeps beginning + end and inserts ... in the middle.

Hard-clear replaces the entire tool result with the placeholder.

Notes:

  • Image blocks are never trimmed/cleared.
  • Ratios are character-based (approximate), not exact token counts.
  • The most recent assistant messages are preserved.

See Session Pruning for behavior details.

Block streaming

{
  agents: {
    defaults: {
      blockStreamingDefault: "off", // on | off
      blockStreamingBreak: "text_end", // text_end | message_end
      blockStreamingChunk: { minChars: 800, maxChars: 1200, breakPreference: "paragraph" },
      blockStreamingCoalesce: { idleMs: 1000 },
      humanDelay: { mode: "natural" }, // off (default) | natural | custom (use minMs/maxMs)
    },
  },
}

See Streaming for behavior + chunking details.

Typing indicators

{
  agents: {
    defaults: {
      typingMode: "instant", // never | instant | thinking | message
      typingIntervalSeconds: 6,
    },
  },
}

See Typing Indicators.