Choose files from a folder
Pick which files in a knowledge folder an agent reads, with include and exclude patterns.
A @Knowledge source with a folder path reads every supported file under it. The include and exclude lists narrow that set. They run before the format check, so a file they drop is never opened.
Before you start
- TnsAI 0.18.7 with
tnsai-ragon the classpath. - A role or agent with a
@Knowledgesource (Declarative knowledge). - PDF and Office files need the optional PDFBox or POI dependency (File formats).
How a pattern matches
Each pattern is matched against the file's path relative to path, with / between folders. For path = "notes", the file notes/2026/review.pdf is matched as 2026/review.pdf.
| Token | Matches |
|---|---|
* | Any characters inside one folder name or file name. It never crosses a /. |
? | Exactly one character, inside one name. |
** | Zero or more whole folders. It must be followed by / or end the pattern, so **.md is refused. Write it as a whole folder name, as in **/*.md or guides/**. |
{a,b} | Any one of the comma-separated alternatives, since 0.18.7. **/*.{md,pdf} selects either extension, and {guides,api}/** either folder. Each alternative may use *, ? and **. |
Any other character matches itself, and so does a comma outside braces. A pattern is anchored at both ends, so drafts/** means a drafts folder directly under path, not one deeper down.
The order is fixed:
- If
includeis empty, every file is a candidate. Otherwise a file must match at least one include pattern. - A file that matches any exclude pattern is dropped. Exclude always wins.
- The default skips and the ignore files act as extra exclude patterns (What is always skipped).
Letter case
Since 0.18.7, matching ignores letter case. **/*.pdf selects B.PDF and c.Pdf, and drafts/** also drops Drafts/x.md.
The pattern and the path are lower-cased one character at a time, the same way in every locale. So I, i and İ match one another, but the Turkish dotless ı matches only itself. The Greek final ς matches σ and Σ.
Ignore files are the exception: their lines are still case-sensitive (What is always skipped).
Patterns that are refused
A refused pattern fails at binding, and the error names the source and the pattern.
[,]and\are not part of the pattern syntax.- Brace groups cannot nest, as in
{a,{b,c}}. - A group cannot be empty (
{}) or hold an empty alternative ({md,}). - Every
{needs a matching}, and every}a matching{. - Inside a group, an alternative may end in
**only if the group ends the pattern or is followed by/. So{docs,guides/**}works, but{docs/**}.mdis refused.
Examples
Select by extension
@RoleSpec(knowledge = @Knowledge(
name = "notes",
path = "notes",
include = {"**/*.{md,pdf}"},
search = @Search(queryParam = "question")))Selects todo.md, 2026/review.pdf and README.MD, because case does not matter. Skips plan.txt.
Select whole folders
@RoleSpec(knowledge = @Knowledge(
name = "handbook",
path = "handbook",
include = {"guides/**", "api/**"},
search = @Search(queryParam = "question")))Selects everything under guides/ and api/, at any depth: guides/setup.md, guides/deploy/linux.md and api/endpoints.txt. Skips blog/launch.md and the top-level README.md. The single pattern {guides,api}/** selects the same files.
Leave folders out
@RoleSpec(knowledge = @Knowledge(
name = "wiki",
path = "wiki",
exclude = {"archive/**", "drafts/**"},
search = @Search(queryParam = "question")))Reads every file except those under the top-level archive/ and drafts/ folders. team/drafts/notes.md is still read, because drafts/** is anchored at path. Use **/drafts/** to drop a drafts folder at any depth.
Include, then exclude
@RoleSpec(knowledge = @Knowledge(
name = "library",
path = "library",
include = {"guides/**/*.md", "reports/**/*.pdf"},
exclude = {"reports/old/**"},
search = @Search(queryParam = "question")))Selects Markdown under guides/, including guides/start.md directly in it, because **/ also matches zero folders. Selects PDFs under reports/, except those in reports/old/. So it reads guides/start.md, guides/admin/users.md and reports/q3.pdf. It skips guides/cheatsheet.pdf, reports/summary.md and reports/old/q1.pdf.
Several sources on one agent
@AgentSpec(
description = "Support agent that reads guides and tickets",
knowledge = {
@Knowledge(
name = "guides",
path = "docs/guides",
include = {"**/*.md"}),
@Knowledge(
name = "tickets",
path = "support/tickets",
include = {"**/*.txt"},
exclude = {"private/**"})
})Each source has its own path and its own patterns, and a pattern is relative to its own source. guides reads install.md but not images/logo.txt. tickets reads 1001.txt. It skips private/1002.txt and 1003.md.
What is always skipped
Some folders and files are skipped before your patterns run. An include pattern cannot bring them back.
- Folders, at any depth, whatever their letter case:
.aws,.git,.gnupg,.gradle,.hg,.idea,.kube,.next,.ssh,.svn,.vscode,__pycache__,build,coverage,dist,node_modules,out,target,vendor. - Files, at any depth, with the exact name:
.env,.env.*,.git-credentials,.netrc,.npmrc,.pypirc,id_dsa,id_ecdsa,id_ed25519,id_rsa. A file named.ENVis not on this list and is read. - Ignore files: every
.gitignoreand.tnsignorein the folder adds its rules as exclude patterns. The ignore files themselves are never read. Their lines use only*,**and?, so a brace there is refused. They also stay case-sensitive:secret.mdin.tnsignoredoes not dropSECRET.md. Ignore files and default skips has the pattern rules and the size caps.
Warning: a real content folder named
build,out,vendorordistis skipped without an error. That includes the source folder itself.path = "docs/build"loads nothing, andpath = "site"skipssite/build/andsite/guides/out/. Rename such a folder, or pointpathat a folder with another name.
Check that it works
To see what a source selects, ingest it once with new FileIngestionService().ingest(KnowledgeSourceConfig.from(knowledge)). Each returned chunk names its file in document().origin().
Every example on this page is checked that way against real folders, PDFs included. The test is FileSelectionContractTest in the Docs repository's tests/java-contracts. Run it from that folder:
mvn -q test -Dtest=FileSelectionContractTestSee also
- File formats: which files a format accepts once your patterns pass them.
- Server RAG pipeline: ignore-file rules, size caps and walk limits.
- Declarative knowledge: declaring sources on roles and agents.