#Efficient way to inject local workspace file into agent context on-demand (lazy load / RAG alternati

1 messages Ā· Page 1 of 1 (latest)

safe hound
#

Hi! I'm building an agent in OpenClaw that needs to reference a local data file (JSON) stored in its workspace. What's the most efficient way to inject this data into the agent's context — only when the user asks about it — without putting it directly in SOUL.md (which would consume tokens every session)?

I know SOUL.md and USER.md are auto-injected every session. Is there a way to load a file on-demand, similar to RAG or lazy loading? Or is embedding in SOUL.md the only option for workspace files?

topaz questBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> āœ… Mark Solution

shrewd archBOT
worthy hawkBOT
#

Nope, don’t put the JSON in SOUL.md unless you want to pay for it every turn.

Best pattern is:

  1. Keep the JSON as a normal workspace file, like data/catalog.json
  2. Put a tiny instruction in AGENTS.md or a custom workspace skill
  3. Have that instruction say: only read the file when the user asks something that needs it

So yes, OpenClaw can effectively do lazy loading, but not by auto-injecting arbitrary files into prompt context.

What is auto-injected every turn is the fixed bootstrap set:

  • AGENTS.md
  • SOUL.md
  • TOOLS.md
  • IDENTITY.md
  • USER.md
  • HEARTBEAT.md
#
  • BOOTSTRAP.md
  • optional MEMORY.md

Not arbitrary JSON files.

Recommended setup

In AGENTS.md:

If the user asks about the product catalog, read `data/catalog.json` first.
Do not load it unless the question depends on it.

Or better, make a small workspace skill for it, since skills are listed cheaply and their full SKILL.md is read only when needed. That’s the closest built-in pattern to on-demand/RAG-style behavior.

Important caveat

#

When the agent does read data/catalog.json, that file content still enters the context for that turn. So if the JSON is huge, you may want to:

  • split it into smaller files
  • precompute a lightweight index
  • or use a memory/context-engine plugin if you want true retrieval behavior

Also, the bootstrap-extra-files hook won’t help here, because it only injects recognized bootstrap-style filenames, not arbitrary JSON.

Docs:

If you want, I can sketch the exact AGENTS.md snippet or a minimal skills/local-json/SKILL.md for this pattern.

#

That link looks sketchy, I would ignore it.

For OpenClaw, the official place for bugs/issues is GitHub only:
https://github.com/openclaw/openclaw/issues

So, short version for the original question: use a small pointer in AGENTS.md or a custom skill, then have the agent read the JSON only when needed. Don’t put the full JSON in SOUL.md.

safe hound
#

Yes please! Can you sketch the exact AGENTS.md snippet for lazy-loading a local JSON file?

My setup:

Agent workspace: ~/.openclaw/agents/statistics/
Data file: kjk-pegawai.json (employee attendance/KJK data, ~5KB)
Trigger: only read the file when someone asks about KJK, tardiness, or a specific employee's attendance
I'd prefer the AGENTS.md approach over a custom skill for now (simpler). What's the exact snippet?

worthy hawkBOT
#

Yep, this should work fine for a ~5KB file.

If kjk-pegawai.json sits at the root of that agent workspace, I’d use this in AGENTS.md:

## Local KJK attendance data

This workspace contains a local JSON file named `kjk-pegawai.json` with employee attendance / KJK data.

When a user asks about any of the following:
- KJK
- tardiness / lateness
- attendance
- a specific employee's attendance status or history

use the `read` tool to read `kjk-pegawai.json` from the workspace before answering.
#

Rules:
- Do **not** read `kjk-pegawai.json` unless the request is clearly about KJK, tardiness, attendance, or a named employee's attendance.
- If the request is unrelated, do not load the file.
- Use the file contents as the source of truth.
- Do not invent employee records, dates, or attendance details that are not in the file.
- If the employee or field is missing from the file, say that clearly.
- Prefer concise summaries over dumping raw JSON.
- Read the file once per turn when needed, not on every message.

If you want it a bit stricter, use this version instead:

## On-demand attendance lookup
#

Only load `kjk-pegawai.json` when the user's request depends on attendance/KJK data.

Triggers for loading the file:
- the message mentions "KJK"
- the message asks about tardiness, lateness, or attendance
- the message asks about a specific employee's attendance

Behavior:
1. Read `kjk-pegawai.json`
2. Find the relevant employee or attendance fields
3. Answer from the file only
4. If no matching data exists, say so plainly

Never preload this file for unrelated conversation.
#

Tiny tip: if the file is not in the workspace root, change kjk-pegawai.json to the relative path, for example data/kjk-pegawai.json.

If you want, I can also give you a version tuned for Indonesian phrasing, like triggers for terlambat, absensi, kehadiran, and pegawai.

safe hound
#

done. I also have a WhatsApp bot using OpenClaw where the main agent (PST public bot) has Google Sheets data embedded directly in SOUL.md via a daily cron rebuild script. The data is ~4KB of statistical indicators.

Currently the setup is:

A cron job runs daily → fetches Google Sheets via API → rebuilds SOUL.md with the data embedded
SOUL.md also has an instruction to fetch the same API on demand when user asks about statistics
So the data is both always in context (embedded) AND there's a fetch instruction (redundant?).

Questions:

Is embedding ~4KB of structured data in SOUL.md a bad practice token-wise, or acceptable for a public-facing bot that almost always needs the data?
Should I remove the embedded data and rely purely on on-demand fetch via web tool instead?
Or is the hybrid approach (embed a lightweight summary + fetch full data on demand) the recommended pattern?

worthy hawkBOT
#

I wouldn’t keep the full 4KB stats block in SOUL.md.

Short version:

  • Technically acceptable? Yes.
  • Good practice? Not really.
  • What I’d recommend? Hybrid, but with a tiny summary in AGENTS.md, not raw data in SOUL.md.

My take

SOUL.md should mostly be persona/voice/boundaries, not structured operational data.

A 4KB structured block is roughly on the order of ~1k tokens every turn. For a public bot, that adds up fast. Prompt caching can reduce cost on some providers, but it still increases context pressure and is still the wrong semantic home for the data.

Best pattern for your case

Since the bot often needs the stats:

#
  1. Keep a small always-on summary in AGENTS.md
    • a few key indicators
    • last updated timestamp
    • what the data means
  2. Keep the full dataset in a file or endpoint
  3. Fetch/read the full data only for detailed questions, exact numbers, or verification

So basically:

  • Not full data in SOUL.md
  • Not pure on-demand fetch for everything
  • Yes to a hybrid summary + on-demand full retrieval

What I’d change

Instead of:

#
  • cron rebuilds SOUL.md with full data
  • and bot also fetches on demand

I’d do:

  • cron writes stats-summary.md or updates a short section in AGENTS.md
  • cron also writes stats.json locally, or you keep the Google Sheets/API fetch path
  • AGENTS.md says:
    • use summary for common questions
    • fetch/read full data for detailed or freshness-sensitive requests

Rule of thumb

  • If the data is needed on almost every single turn, a tiny embedded summary is fine.
  • If it’s only needed on some turns, keep it out of bootstrap files and load on demand.
  • If exactness matters, always keep an on-demand authoritative source.
#

If you want, I can sketch the exact hybrid AGENTS.md snippet for the WhatsApp bot too.

safe hound
#

please sketch the exact hybrid AGENTS.md snippet for the WhatsApp bot too.

worthy hawkBOT
#

Yep, I’d do it like this, in AGENTS.md rather than SOUL.md:

## PST public statistics

This bot answers public questions about PST statistical indicators.

Use the cached daily summary below for fast, common answers.
Use the live source only when the user asks for exact, latest, detailed, or verification-sensitive statistics.

### Retrieval policy

Use the cached summary below when:
- the user asks for a general overview
- the user asks for one of the indicators already listed below
- a quick answer is sufficient and the summary clearly contains the needed data
#

Fetch the live data before answering when:
- the user asks for the latest / current / today / real-time data
- the user asks for a detailed breakdown not present in the cached summary
- the user asks to verify a number
- the user asks for a comparison across dates, groups, or categories not fully covered below
- the cached summary does not clearly answer the question

If live data conflicts with the cached summary, treat the live data as the source of truth.

If live fetch fails:
- fall back to the cached summary
- clearly say that you are using cached daily data
- mention the cached summary timestamp
- do not invent missing values
#

Never dump raw tables unless the user explicitly asks.
Prefer short, WhatsApp-friendly summaries.

### Live source

When live data is needed, use `web_fetch` on:
`https://YOUR-STATS-ENDPOINT-HERE`

### Cached daily summary (auto-generated)

Source: Google Sheets
Last updated: 2026-04-20 00:00 UTC

Key indicators:
#
- indicator_a: 123
- indicator_b: 45.6%
- indicator_c: 789
- indicator_d: 12
- note: this summary is refreshed once per day by cron

Why this hybrid works

  • cheap path for common questions
  • accurate path for exact/latest questions
  • avoids burning tokens on a full 4KB structured block every turn
  • keeps the fallback behavior explicit

Honestly, if you already have the cron job, I’d have it regenerate just the Cached daily summary block in AGENTS.md, not SOUL.md.

#

If you want, I can also give you a slightly stricter version that handles phrases like ā€œhari iniā€, ā€œterbaruā€, ā€œcek lagiā€, and ā€œberapa sekarangā€ as automatic live-fetch triggers.

safe hound
#

done, thanks. FYI, i just run the openclaw in vps ram 8G and and 75 Gga nvme, We have a static kjk-pegawai.json in the agent workspace, updated manually via SCP. Is there a built-in OpenClaw pattern for auto-refreshing a workspace data file from an external source (e.g., scheduled script that rewrites the file), so the agent always reads fresh data without manual intervention every month?

worthy hawkBOT
#

Yep, there’s a built-in pattern, but it’s not a special ā€œsync this fileā€ feature.

The normal OpenClaw pattern is:

  • AGENTS.md standing order = defines how refresh works
  • Cron job = defines when it runs
  • optional webhook trigger = if the external system can push updates instead of waiting for a schedule

So for your case:

Built-in OpenClaw way

Use a cron job like ā€œonce a month, refresh kjk-pegawai.json from source X, validate it, overwrite only if valid, report success/failure.ā€

Conceptually:

#
openclaw cron add \
  --name "refresh-kjk-data" \
  --cron "0 2 1 * *" \
  --tz "Asia/Jakarta" \
  --session isolated \
  --message "Refresh `kjk-pegawai.json` per standing orders. Fetch the latest data, validate it, replace the workspace file only if the new data is valid JSON and non-empty, then report the result."

And in AGENTS.md, give it rules like:

  • fetch latest KJK data from the approved source
  • validate JSON before replacing the file
  • never overwrite with empty/broken data
  • keep the old file if refresh fails
  • report success/failure clearly
#

My actual recommendation

For a simple monthly JSON refresh, I’d honestly prefer a plain VPS script + system cron/systemd timer over an LLM-driven refresh.

Why:

  • cheaper
  • more deterministic
  • less fragile
  • OpenClaw can just read the local file on demand

So:

  • Best engineering choice: external script rewrites kjk-pegawai.json
  • Best built-in OpenClaw choice: cron + standing orders
  • Best real-time/event choice: webhook trigger

Docs:

safe hound
#

We added a binding using peer.kind: "group" + peer.id to route a WhatsApp group to a specific agent. Config validates OK. Is this the correct and stable way to route group messages to a named agent in v2026.4.x? Any gotchas?

worthy hawkBOT
#

Yes, that’s the correct documented pattern for WhatsApp group routing in 2026.4.x:

{
  bindings: [
    {
      agentId: "your-agent",
      match: {
        channel: "whatsapp",
        peer: { kind: "group", id: "120363...@g.us" },
      },
    },
  ],
}

So, short answer: yes, stable and correct.

#

Main gotchas

  • Use the exact WhatsApp group JID (...@g.us), not a display name.
  • If you have multiple WhatsApp accounts, add accountId.
    If omitted, the binding only matches the default account.
  • Routing binding does not bypass group access controls:
    • channels.whatsapp.groups
    • channels.whatsapp.groupPolicy
    • channels.whatsapp.groupAllowFrom
    • mention gating / requireMention
  • Peer bindings win over channel-wide bindings, but if multiple bindings match in the same tier, first one in config order wins.
  • I’d keep peer.kind: "group" for WhatsApp groups even though routing has some backward-compat handling around group/channel.

Practical advice

If it validates but still ā€œdoesn’t workā€, the problem is usually not routing, it’s one of:

  • wrong @g.us id
safe hound
#

Our agent dinas is connected to a Telegram bot. The bot shows slash command menus to any user who opens it. We want to restrict slash commands / menu options to only show for a specific Telegram user ID (the bot owner), and silently ignore or reject commands from anyone else.

Is there a built-in OpenClaw config for this? Or do we need to handle it in SOUL.md with an instruction like "only respond to user ID X"?
The current config uses dmPolicy: "pairing" — does pairing mode already restrict this, or can anyone still trigger commands?

worthy hawkBOT
#

Don’t do this in SOUL.md. Use config, not prompt instructions.

Short answer

  • Best restriction for a one-owner Telegram bot:
    use channels.telegram.dmPolicy: "allowlist" with the owner’s numeric Telegram user ID in channels.telegram.allowFrom.
  • dmPolicy: "pairing" is not the same thing.
    Pairing means unknown DM users can start the pairing flow and become allowed if approved. It is not a hard ā€œowner onlyā€ lock.
  • Telegram command menus showing up is separate from authorization.
    The menu is registered globally via Telegram setMyCommands, so other users may still see commands even if they cannot use them.
  • I do not see a built-in OpenClaw config for per-user Telegram command menu visibility.

What I’d recommend

If you want true owner-only behavior:

#
{
  channels: {
    telegram: {
      dmPolicy: "allowlist",
      allowFrom: ["123456789"], // owner's numeric Telegram user ID
      commands: {
        native: false,
        nativeSkills: false,
      },
    },
  },
  commands: {
    allowFrom: {
      telegram: ["123456789"],
    },
#
  },
}

Why this setup

  • dmPolicy: "allowlist" blocks everyone except that user
  • allowFrom is durable and explicit in config
  • commands.allowFrom.telegram makes command authorization owner-only too
  • commands.native: false hides the Telegram native command menu entirely, since OpenClaw otherwise registers it globally

Important gotcha

channels.telegram.allowFrom must be the human sender’s numeric Telegram user ID, not the bot username.

So, to your exact questions

#
  • Built-in config? Yes, for access restriction: dmPolicy: "allowlist" + allowFrom, optionally commands.allowFrom.
  • Need SOUL.md instruction? No.
  • Does pairing already restrict this? Partly, but not the way you want. It’s approval-based DM access, not a fixed owner-only allowlist.
  • Can anyone still trigger commands in pairing mode? Not automatically, but anyone can reach the bot and start the pairing path unless you switch to allowlist.

Docs:

If you want, I can also sketch the exact config block for your dinas agent with the owner ID plugged into the right place.

safe hound
#

Title: High latency on openclaw message send — text 25s+, attachment 30-90s

im running OpenClaw on a VPS (Contabo) to send WhatsApp notifications from a web app. I'm experiencing significant delays and looking for optimization advice.

šŸ“ Setup

VPS: Contabo Linux, OpenClaw installed with WhatsApp + Telegram channels configured
Gateway running via PM2 (openclaw-gateway)
I built a Python HTTP wrapper (oc-send-api.py) on port 18800 that receives requests from my Vercel app and calls the CLI:

openclaw message send
--channel whatsapp
--target +628xxx
--message "..."
--json
For file attachments, I add --media /path/to/file
Exposed via Cloudflare Tunnel → Vercel calls it as an API
šŸ› Issues

Issue 1 — Text message latency (25s+ timeout)
Simple text messages sometimes take >25 seconds for openclaw message send to return. My subprocess timeout is 25s, causing a 504 back to the frontend. The message eventually gets delivered, but the CLI doesn't return in time.