ai newsSa., 17. Okt.
EilmeldungDeepSeek Just Released V4.1-Flash-1017, Scores 77.0 on DeepSWE, Opens Search API

My AI agents were choking on their own markdown logs. I gave them a database.

I am an AppSec engineer. For the past year I have been running AI agents as part of my security work: recon, attack surface mapping, hypothesis testing. It worked well until it did not. The problem Agents produce logs at every step: what they found, what they tried, what failed. All of that was lan

dev.toSamstag 10:04 · 17.10.5 Min. Lesezeit
Bild: dev.to

I am an AppSec engineer. For the past year I have been running AI agents as part of my security work: recon, attack surface mapping, hypothesis testing. It worked well until it did not.

Agents produce logs at every step: what they found, what they tried, what failed. All of that was landing in markdown files. After a few weeks on one project the folder looked like this:

recon.md subdomains-live.md endpoints.md attempts-auth.md notes-session.md TODO.md TODO-v2.md progress-subagent-3.md Enter fullscreen mode Exit fullscreen mode I was using a model with a 256k token context window. That sounds like a lot. But at some point the accumulated logs for a single project stopped fitting in the window at all. The agent had to read the entire archive just to remember where it left off, and there was no room left to do actual work.

The agent would read its own logs, hit the limit, and restart. Lost intermediate results, repeated attempts at things that already failed, subagents that knew nothing about decisions made one level up.

A markdown file is good as a document for a human to read. As a state store for a running agent it is not: no structure, no statuses that mean the same thing consistently, and you cannot query it.

I did not find a tool that fit. So I built one: PentaCore, an MCP server in Rust. One binary, one encrypted SQLite file.

It does not plan anything or decide what to do next. It stores what it is given and returns what is asked for.

Notes are the narrative: goals, steps, attempts and their outcomes, facts, decisions, questions, lessons. Notes form a tree through parent and a graph through typed links. Search works by exact words and by meaning (built-in embeddings, all-MiniLM-L6-v2).

Entities are many things of one kind: hosts, endpoints, files, whatever. Each entity has a status, attributes, and named checks.

The split matters. "Show everything that has not passed check X yet" is a field filter. Its result should not depend on how the agent phrased a note.

Top level is the goal, steps under it, each step has a checklist:

{ "name": "note", "arguments": { "kind": "goal", "title": "Audit api.example.com" }} { "name": "note", "arguments": { "kind": "step", "title": "Module: invoices", "parent": 1 }} { "name": "checklist", "arguments": { "note": 2, "add": [ "BOLA on read", "BOLA on update", "Mass assignment", "Role bypass" ]}} Enter fullscreen mode Exit fullscreen mode Each endpoint is an entity:

{ "name": "upsert_entity", "arguments": { "type": "endpoint", "key": "GET /v2/invoices/{id}", "status": "discovered", "attrs": { "module": "invoices", "auth": "user" } }} Enter fullscreen mode Exit fullscreen mode Checks are marked as they are done:

{ "name": "mark_check", "arguments": { "id": 41, "checks": [ { "name": "bola_read", "result": "fail", "detail": "user B reads invoices of user A" }, { "name": "role_bypass", "result": "pass" } ]}} Enter fullscreen mode Exit fullscreen mode Now "what has not been checked yet" is one call:

{ "name": "query_entities", "arguments": { "type": "endpoint", "where": [{ "field": "check.bola_read", "op": "is_null" }] }} Enter fullscreen mode Exit fullscreen mode And the overall picture is another:

{ "name": "query_entities", "arguments": { "type": "endpoint", "group_by": "check.bola_read" }} Enter fullscreen mode Exit fullscreen mode Restoring context after compaction At the start of a new session, one call:

{ "name": "resume", "arguments": { "task": 1 }} Enter fullscreen mode Exit fullscreen mode The agent gets: the last checkpoint, what is in progress, what is blocked, failed attempts, decisions made, and all open checklist items. The response fits within a configurable token budget. Nothing is silently truncated.

The lead agent creates a step for the subagent and links relevant decisions via uses:

{ "name": "note", "arguments": { "kind": "decision", "title": "/internal/* is out of scope" }} { "name": "link", "arguments": { "src": 1, "dst": 9, "kind": "uses" }} { "name": "note", "arguments": { "kind": "step", "title": "Zone: b.example.com", "parent": 1, "body": "Check TLS and auth on b.example.com." }} Enter fullscreen mode Exit fullscreen mode The subagent is given only the step id. It calls resume task=<id> and gets its assignment, the scope decision, and summaries of adjacent zones already checked. No need to retell the lead agent's whole conversation.

{ "name": "summarize", "arguments": { "task": 2, "work": "48 endpoints checked: BOLA, mass assignment, role bypass.", "result": "BOLA on GET /v2/invoices/{id}: user B reads invoices of user A. Mass assignment on PATCH via status field." }} Enter fullscreen mode Exit fullscreen mode task_summary returns the whole task as its summaries in tree order - one call, however many notes sit below. Any agent at any level of the hierarchy can get a focused summary of one area of the whole perimeter.

Memory is a prompt injection channel. Everything written to it is later read as context. An unauthorized write becomes an injected instruction.

Honest about limits: the SQLCipher key is compiled into the binary. It protects the file from someone who only has the file. It does not protect against someone who has the binary or the source. All known limits are listed in the README.

PentaCore is for when an agent works through a large number of similar targets over multiple sessions, survives compaction, and needs an exact answer to "what is done, what was found, what is left."

Binaries for Linux, macOS, and Windows are in Releases. SHA256SUMS, SBOM, and a build provenance attestation are included.

# Claude Code claude mcp add pentacore -- /path/to/pentacore # Any MCP client { "mcpServers": { "pentacore": { "command": "/path/to/pentacore", "env": { "PENTACORE_PROJECT": "my-project" } } } } Enter fullscreen mode Exit fullscreen mode Docker image available with a read-only root filesystem and dropped capabilities.

Repo: https://github.com/DroBov1Tya/PentaCore

If you find a vulnerability, use Security -> Report a vulnerability in the repo rather than opening a public issue.

Quelle: dev.to (dev.to). Original öffnen