JuliusBrussee/caveman · error · Error

caveman agent: memory ttl must use positive m, h, or d durat

Error message

caveman agent: memory ttl must use positive m, h, or d duration

What it means

The memory() builder wraps any cave_memory_ttl_invalid failure from memoryTTLMilliseconds() in this clearer message: the ttl option must be a positive integer duration in m, h, or d units. It fires for both failure modes — wrong format (regex miss) and overflow past safe-integer milliseconds — because the builder intentionally re-raises a user-facing message instead of leaking the lower-level code.

Source

Thrown at packages/agent/src/primitives.ts:277

}

export function memory(options: {
  namespace: string;
  provenance?: MemoryDefinition["provenance"];
  ttl: string;
  recallBudget: number;
  consent?: MemoryDefinition["consent"];
}): MemoryDefinition {
  if (!/^[a-z0-9][a-z0-9_-]{0,95}$/.test(options.namespace)) {
    throw new Error(`caveman agent: invalid memory namespace ${JSON.stringify(options.namespace)}`);
  }
  if (!Number.isSafeInteger(options.recallBudget) || options.recallBudget < 0) {
    throw new Error("caveman agent: memory recallBudget must be a non-negative integer");
  }
  try {
    memoryTTLMilliseconds(options.ttl);
  } catch {
    throw new Error("caveman agent: memory ttl must use positive m, h, or d duration");
  }
  // Fail closed at CONSTRUCTION, not at tool-call time: the durable
  // store implements only local, single-tenant memory. A `project`/`external`
  // provenance or a `project_shared` consent is a shared-backend contract this
  // package does not provide, so it is refused here rather than burning a model
  // turn to discover a config the framework already knew was unsupported.
  const provenance = options.provenance ?? "local";
  if (provenance !== "local") {
    throw new Error("cave_memory_provenance_unsupported: only \"local\" memory is supported");
  }
  const consent = options.consent ?? "local_only";
  if (consent !== "local_only") {
    throw new Error("cave_memory_consent_unsupported: only \"local_only\" consent is supported");
  }
  return Object.freeze({
    kind: "memory",
    namespace: options.namespace,
    provenance,

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Express the TTL as '<positive-int>m|h|d', e.g. '30m', '12h', '7d'
  2. Convert other units before calling memory(): seconds -> Math.ceil(s/60) + 'm', days -> d + 'd'
  3. Lint/validate ttl in your config loader against ^([1-9][0-9]*)(m|h|d)$ so mistakes fail at config-parse time

Example fix

// before
memory({ namespace: 'notes', ttl: 'P1D', recallBudget: 10 });

// after
memory({ namespace: 'notes', ttl: '1d', recallBudget: 10 });
Defensive patterns

Strategy: validation

Validate before calling

function toCaveTtl(ms: number): string {
  if (!Number.isFinite(ms) || ms <= 0) throw new Error('ttl must be positive milliseconds');
  const units: [number, string][] = [[86_400_000, 'd'], [3_600_000, 'h'], [60_000, 'm']];
  for (const [scale, suffix] of units) { if (ms % scale === 0) return `${Math.min(ms / scale, 100_000)}${suffix}`; }
  return `${Math.ceil(ms / 60_000)}m`;
}

Type guard

function isCaveTtl(value: unknown): value is string { return typeof value === 'string' && /^([1-9][0-9]*)(m|h|d)$/.test(value); }

Try / catch

try { memory(opts); } catch (e) { if (e instanceof Error && e.message.includes('memory ttl must use positive m, h, or d')) throw new ConfigError(`ttl '${opts.ttl}' invalid; expected e.g. '30m'|'12h'|'7d'`, { cause: e }); throw e; }

Prevention

When it happens

Trigger: Calling memory({ ttl }) with '90s', 'P1D', '1w', '0h', '1.5d', '', '3600', or an overflowing value like '999999999999999d'.

Common situations: TTLs authored in ISO-8601 or human form in config ('1 day', 'P1D'); seconds-based durations carried over from cache libraries ('300s'); 'never expires' sentinel overflow values.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/2e65cff0512f807f. Report an issue: GitHub.