Skip to content

feat: add pinnedFiles config for workspace files injected outside compaction DAG#214

Closed
liu51115 wants to merge 2 commits intoMartian-Engineering:mainfrom
liu51115:feat/pinned-files
Closed

feat: add pinnedFiles config for workspace files injected outside compaction DAG#214
liu51115 wants to merge 2 commits intoMartian-Engineering:mainfrom
liu51115:feat/pinned-files

Conversation

@liu51115
Copy link
Copy Markdown
Contributor

Summary

Adds a pinnedFiles config option that injects workspace-relative files into every context assembly outside the compaction DAG. These files are always current (read from disk each turn), never summarized, and never evicted.

Motivation

Long-running sessions lose procedural context as LCM compacts earlier turns into summaries. Critical files like fleet-wide rules or safety boundaries get compressed into vague references, causing behavioral drift. Agents need a way to keep certain workspace files permanently in context.

How it works

  1. Config: "pinnedFiles": ["FLEET.md", "BOUNDARIES.md"] in openclaw.plugin.json
  2. Engine: resolvePinnedFiles() reads files from the agent's workspace directory on each assemble() call
  3. Assembler: Files are prepended as synthetic user messages wrapped in <pinned_file path="..."> tags, with token budget deducted before eviction runs
  4. Workspace resolution: Reads agents.list from OpenClaw config to find the correct workspace path per agent, with ~/.openclaw/workspace-{agentId} fallback

Security

  • Path traversal guard: resolve() + startsWith() ensures paths stay within workspace
  • XML attribute escaping: &, ", <, > escaped in path attributes
  • Tilde expansion: Only leading ~/ or bare ~ (no mid-string replacement)
  • Size cap: stat() check before readFile(), 100KB per-file limit, oversized files silently skipped
  • Missing files: Silently skipped (no errors for absent or unreadable files)

Changes

  • src/db/config.ts: pinnedFiles: string[] field + toStrArray parsing
  • src/types.ts: resolveWorkspaceDir() on LcmDependencies interface
  • src/assembler.ts: PinnedFileEntry type, Step 0 prepend + budget deduction
  • src/engine.ts: resolvePinnedFiles() with disk I/O, path validation, size guard
  • src/plugin/index.ts: resolveWorkspaceDir() implementation reading OpenClaw agent config
  • openclaw.plugin.json: Schema + UI hints for pinnedFiles
  • test/pinned-files.test.ts: 7 new tests (injection, eviction immunity, budget, config parsing)

All 403 tests passing.

@liu51115 liu51115 force-pushed the feat/pinned-files branch 3 times, most recently from e399998 to a840f28 Compare March 31, 2026 03:12
@jalehman jalehman self-assigned this Mar 31, 2026
@liu51115 liu51115 force-pushed the feat/pinned-files branch 3 times, most recently from 3476c94 to d8e0bb4 Compare April 2, 2026 03:57
liu51115 and others added 2 commits April 4, 2026 07:58
Extends pinnedFiles with a Record<string, string[]> keyed by agent ID.
Per-agent paths merge with global pinnedFiles (deduped) at resolution time.
Unknown agent IDs get global-only paths. Backward compatible.
@liu51115
Copy link
Copy Markdown
Contributor Author

Closing this — we found a cleaner approach on the OpenClaw side. Custom workspace bootstrap files can be injected via the OC bootstrap loader, which avoids the complexity of maintaining pinnedFiles across LCM rebases. Thanks for the review feedback.

@liu51115 liu51115 closed this Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants