Description of the Change
Problem
Five backend writers wrap text PentAGI does not control in a fixed three-backtick fence: renderCode in pkg/tools/code.go (stored code samples), the Sploitus "Source Preview" in pkg/tools/searchers/sploitus.go, and three Graphiti formatters in pkg/tools/graphiti_search.go. A line of backticks inside that text closes the block early, so everything after it is mis-structured wherever the text is rendered as Markdown: the Vector Store tab, the Knowledge page, the Searches tab and the flow's message log for graphiti_search results. The reproduction is in #471.
Solution
A new leaf package pkg/markdown with one function, CodeBlock(lang, content). It makes the fence one backtick longer than the longest backtick run in the content, three at least, and all five writers call it. Content without a run of three or more backticks renders exactly as before, so existing expectations stay unchanged.
This is the rule the frontend editor already applies in markdown-editor-extensions.ts, applied a little more conservatively: the editor counts only the lines that could close the fence, CodeBlock counts any run. Its fence can come out longer than the editor's, and it is always valid.
On placement: pkg/tools/searchers must not import pkg/tools, and no existing neutral package fits a Markdown helper, so it is a one-function leaf package with a row in the CLAUDE.md package table. If you would rather not add a package, an exported searchers.CodeBlock works too, since pkg/tools already imports searchers.
Closes #471
Type of Change
- 🐛 Bug fix (non-breaking change which fixes an issue)
- 🚀 New feature (non-breaking change which adds functionality)
- 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
- 📚 Documentation update
- 🔧 Configuration change
- 🧪 Test update
- 🛡️ Security update
Areas Affected
- Core Services (Frontend UI/Backend API)
- AI Agents (Researcher/Developer/Executor)
- Security Tools Integration
- Memory System (Vector Store/Knowledge Base)
- Monitoring Stack (Grafana/OpenTelemetry)
- Analytics Platform (Langfuse)
- External Integrations (LLM/Search APIs)
- Documentation
- Infrastructure/DevOps
Testing and Verification
Test Configuration
PentAGI Version: main @ cffed94
Docker Version: n/a (unit tests)
Host OS: Linux
LLM Provider: n/a
Enabled Features: n/a
Go: 1.26.5Test Steps
cd backend && go test ./pkg/markdown/ ./pkg/tools/ ./pkg/tools/searchers/ -race -count=1- golangci-lint v2.12.2 on the same packages
Test Results
All three packages pass with -race, and golangci-lint reports 0 issues. New or extended tests:
TestMarkdown_CodeBlock_SizesTheFenceToTheContent, 12 literal rows: no backticks, empty content, a language, runs of 1, 2, 3 and 5, a run at the very start, at the very end and inside a line, the longest of several runs, and a run of 3 with a language.TestCode_Handle_StoresTheWholeSampleAndEmbedsWhatFits: a new row throughHandle(store_code)for a sample whose code holds a fence line.TestGraphitiSearch_FormatGraphiti_RendersTheWholeListingTheAgentReads: three new rows, one per formatter.TestSploitus_Handle_PostsTheSearchAndFormatsTheExploits: its source now holds a fence line, and the expected preview uses a four-backtick fence.
With CodeBlock forced back to a fixed three-backtick fence, every writer's test fails, and so do the helper rows with a run of three or more:
--- FAIL: TestCode_Handle_StoresTheWholeSampleAndEmbedsWhatFits/a_sample_whose_code_holds_a_fence_line
--- FAIL: TestGraphitiSearch_FormatGraphiti_RendersTheWholeListingTheAgentReads/episode_context_results_with_a_fence_line_in_the_content
--- FAIL: TestGraphitiSearch_FormatGraphiti_RendersTheWholeListingTheAgentReads/successful_tools_results_with_a_fence_line_in_the_output
--- FAIL: TestGraphitiSearch_FormatGraphiti_RendersTheWholeListingTheAgentReads/temporal_window_results_with_a_fence_line_in_the_content
--- FAIL: TestSploitus_Handle_PostsTheSearchAndFormatsTheExploits
--- FAIL: TestMarkdown_CodeBlock_SizesTheFenceToTheContent/a_run_of_three (and 6 more rows)Security Considerations
None. The content is unchanged; only the length of the fence around it changes.
Performance Impact
One linear pass over the wrapped content to find the longest backtick run.
Documentation Updates
- README.md updates
- API documentation updates
- Configuration documentation updates
- GraphQL schema updates
- Other: a CLAUDE.md package-table row for
pkg/markdown
Deployment Notes
None.
Checklist
Code Quality
- My code follows the project's coding standards
- I have added/updated necessary documentation
- I have added tests to cover my changes
- All new and existing tests pass
- I have run
go fmtandgo vet(for Go code) - I have run
pnpm run lint(for TypeScript/JavaScript code)
Security
- I have considered security implications
- Changes maintain or improve the security model
- Sensitive information has been properly handled
Compatibility
- Changes are backward compatible
- Breaking changes are clearly marked and documented
- Dependencies are properly updated
Documentation
- Documentation is clear and complete
- Comments are added for non-obvious code
- API changes are documented
Additional Notes
pkg/tools/executor.go also writes a fixed ```json fence, around tool-call arguments. It is left as is because the arguments are a JSON document, and JSON cannot hold a bare line of backticks.