AgentsMD
Overview
agentsmd is an Eino ADK middleware that automatically injects the content of Agents.md into the model input messages on every model call. The injection is ephemeral: it is added dynamically for each model call and is not persisted into the session state, so it won’t be processed by summarization/compression middlewares.
Core value: define system-level behavior instructions and context for an agent via an Agents.md file (similar to Claude Code’s CLAUDE.md), without manually composing system prompts.
Package: github.com/cloudwego/eino/adk/middlewares/agentsmd
Quick Start
Minimal Example
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/middlewares/agentsmd"
)
func main() {
ctx := context.Background()
// 1. Prepare Backend (file reading backend)
backend := NewLocalFileBackend("/path/to/project")
// 2. Create agentsmd middleware
mw, err := agentsmd.New(ctx, &agentsmd.Config{
Backend: backend,
AgentsMDFiles: []string{"/home/user/project/agents.md"},
})
if err != nil {
panic(err)
}
// 3. Attach the middleware to the agent
// agent := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
// Middlewares: []adk.ChatModelAgentMiddleware{mw},
// })
_ = mw
fmt.Println("agentsmd middleware created successfully")
}
Configuration
Config
type Config struct {
// Backend provides file access to load Agents.md files.
// It can be a local filesystem, remote storage, or any other backend.
// Required.
Backend Backend
// AgentsMDFiles is an ordered list of Agents.md file paths to load.
// Files are loaded and injected in the given order.
// Files support recursive @import (max depth 5).
AgentsMDFiles []string
// AllAgentsMDMaxBytes limits the total bytes of all loaded Agents.md content.
// Files are loaded in order; once the cumulative size exceeds this limit,
// the remaining files will be skipped.
// Each individual file is always loaded in full.
// 0 means unlimited.
AllAgentsMDMaxBytes int
// OnLoadWarning is an optional callback invoked on non-fatal errors during loading
// (e.g. file not found, cyclic @import, depth limit exceeded).
// If nil, warnings are printed via log.Printf.
//
// Note: Backend.Read errors other than os.ErrNotExist (e.g. permission denied, I/O errors)
// are not treated as warnings and will abort the loading process.
OnLoadWarning func(filePath string, err error)
}
Parameters
| Parameter | Type | Required | Default | Description |
Backend | Backend | Yes | - | File reading backend that performs the actual I/O |
AgentsMDFiles | []string | Yes | - | List of Agents.md file paths to load (at least one) |
AllAgentsMDMaxBytes | int | No | 0(unlimited) | Total byte limit for all files |
OnLoadWarning | func(string, error) | No | log.Printf | Callback for non-fatal errors |
Backend Interface
Definition
type Backend interface {
// Read reads file content.
// If the file does not exist, implementations should return an error that wraps os.ErrNotExist
// (so errors.Is(err, os.ErrNotExist) returns true).
// This lets the loader skip missing files silently and notify via OnLoadWarning.
// Other errors (permission denied, I/O errors) abort the loading process.
Read(ctx context.Context, req *ReadRequest) (*FileContent, error)
}
Types
// ReadRequest defines request parameters for reading a file
type ReadRequest struct {
FilePath string // file path
Offset int // starting line number (1-based)
}
// FileContent defines the return structure of file content
type FileContent struct {
Content string // file text content
}
@import Syntax
Agents.md supports @import to recursively include other files.
Syntax
In Agents.md, use @path/to/file to reference another file:
# Project instructions
You are a coding assistant.
Please follow these rules:
@rules/code-style.md
@rules/api-conventions.md
Rules
- Path resolution: relative paths are resolved from the current file’s directory; absolute paths are used as-is
- Max recursion depth: 5 (beyond that the import is skipped and
OnLoadWarningis triggered) - Cycle detection: cyclic imports are detected and skipped (
OnLoadWarningis triggered) - Global de-duplication: the same file is not loaded twice
- Supported extensions (when the path contains no
/):.md,.txt,.mdx,.yaml,.yml,.json,.toml - False-positive filtering:
@refwithout/whose extension is not allowed will be ignored (to avoid treating@someoneor@example.comas an import)
Example Directory Layout
project/
├── Agents.md # entry file
├── rules/
│ ├── code-style.md # code style rules
│ ├── api-conventions.md # API conventions
│ └── testing.md # testing rules
└── context/
└── architecture.md # architecture notes
How It Works
Injection Flow
User message + history
│
▼
┌─────────────────────┐
│ agentsmd middleware │
│ (WrapModel) │
│ │
│ 1. Load Agents.md │
│ 2. Cache in RunLocal│
│ 3. Build injected msg│
└─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Injected message sequence │
│ │
│ [System] system prompt │
│ [User] ← Agents.md injection │ ← inserted before the first User message
│ [User] previous user message 1 │
│ [Assistant] assistant reply 1 │
│ [User] current user message │
└─────────────────────────────────────┘
│
▼
Model call (Generate / Stream)
Key Mechanics
- Ephemeral injection: Agents.md content is inserted only for model calls and not written into
ChatModelAgentState, so it won’t be summarized/compressed - Run-level caching: within a single agent
Run(), the loaded Agents.md content is cached inRunLocalValue; subsequent model calls reuse it to avoid repeated reads - Insertion position: injected as a
Userrole message before the first user message; if there is no user message, it is appended to the end - I18n: formatted output adapts to Chinese/English automatically (based on the system language environment)
Notes
Middleware Ordering
It is recommended to place the agentsmd middleware after summarization/compression middlewares. This ensures Agents.md content:
- won’t be compressed away by summarization
- is fully available on every model call
Middlewares: []adk.ChatModelAgentMiddleware{
summarizationMiddleware, // summarize first
agentsMDMiddleware, // then inject Agents.md
}
Error Handling
| Scenario | Behavior |
File not found (os.ErrNotExist) | Skip the file and triggerOnLoadWarning |
Cyclic@import | Skip the cyclic file and triggerOnLoadWarning |
@importdepth > 5 | Skip and triggerOnLoadWarning |
Total size exceedsAllAgentsMDMaxBytes | Skip remaining files and triggerOnLoadWarning(the first file is always loaded fully) |
| Permission denied / I/O error | Abort loading and return error |
| All file contents empty | Do not inject; pass through original messages |
Backend Requirements
- When a file does not exist, implementations must return an error that wraps
os.ErrNotExist(e.g.fmt.Errorf(\"... : %w\", os.ErrNotExist)), otherwise the loader cannot distinguish “missing file” vs “real I/O error” Readshould be concurrency-safe
Performance Considerations
- Set
AllAgentsMDMaxBytesreasonably to avoid injecting too much content and consuming the model context window - Agents.md is loaded once per
Run()(run-level caching), but every newRun()reloads it, so file edits take effect on the next run - Avoid importing too many files; the recursion depth limit is 5
Writing Agents.md
- Keep it concise and include only instructions that truly affect model behavior
- Use
@importto split concerns (code style, API conventions, architecture notes, etc.) - Avoid large code examples or datasets in Agents.md to prevent wasting context window
- The content is wrapped in
<system-reminder>tags when passed to the model, so the model treats it as system-level instructions
FAQ
Q: Will Agents.md content be saved into the conversation history?
A: No. The content is injected dynamically during model calls and is not written into ChatModelAgentState, so it won’t appear in history.
Q: What happens if an Agents.md file does not exist?
A: The file is skipped and OnLoadWarning is triggered (defaults to log.Printf). It does not fail the whole load.
Q: What is the base directory for @import paths?
A: The directory of the current file. For example, @rules/style.md in /project/Agents.md resolves to /project/rules/style.md.
Q: If multiple files import the same file, will it be loaded multiple times? A: No. The loader maintains a global de-duplication map; the same file path is read and injected only once.