Open source · self-hosted · v0.5.3
A self-hosted agent harness that logs what it did.
A self-hosted AI agent harness for work outside the editor: run agents from CI, chat commands, schedules or webhooks, on Copilot, Anthropic, OpenAI or a local Ollama model. With database storage, every model call and tool call goes to an audit log you can read back. Claude Code connects to its memory over MCP.
Penstock runs your agents on your data, with receipts. A self-hosted agent harness for the work that happens where no editor is open (CI hooks, chat commands, scheduled triage, webhooks) against your own internal systems, on models you choose: Copilot, Claude, OpenAI, or a local one. It runs on your own hardware; the audit log and credentials are stored in your deployment, and prompts go to the model you choose. Through the pod tool it reads a person's data in a Cistern pod under rules that person wrote, receipted on their side. With database storage, every model call and every tool invocation is written to an audit log the owner reads back, in the app as well as on disk. Since v0.4.0 it also remembers: with memory on, it recalls at the start of each turn your own notes from a memory you keep as plain files, and the log shows what it recalled.
start.sh, attached to the release, sets up Penstock's memory in Claude Code in one command: it checks the jar against the release digest, creates or extends a memory root, and registers the MCP server. A draft written from Claude Code or Claude Desktop now waits in drafts/, and recall does not read it until you move it into episodes/. A recalled fact brings the episode it was learned from. scripts/journal.sh prints what was built, decided and believed in a window, and the loops still open; with database storage, GET /api/audit/recall counts how often each pattern, fact and episode was loaded. No dependency changes. v0.5.2 asked the model to cite the card behind each decision.
Penstock and Cistern
The pod tool asks a Cistern pod for a file over plain HTTPS as its own principal. It is refused outside its grant, and what it read is receipted on the owner's side.
Every model call and every tool invocation, attributed to the authenticated user. It needs database storage: SQLite, as the shipped Compose file sets, or Postgres.
What it was allowed to read, under which rule, recorded by Cistern rather than by the agent. Cistern writes it, so it does not depend on the agent's own log.
On your own machine, for now
The release carries a jar, a bill of materials and a multi-architecture image. It has not cleared the stranger test yet (two documentation fixes are under way), so treat it as something to run locally rather than expose.
# the jar and its bill of materials are release assetsjava -jar penstock-0.5.3.jar# or as the agent inside an editor that speaks ACP, over stdiojava -jar penstock-0.5.3.jar --acp# or as a memory server for Claude Code or Claude Desktop, over stdiojava -jar penstock-0.5.3.jar --mcp# or the multi-architecture image, bound to loopbackdocker run --rm -p 127.0.0.1:8080:8080 \ghcr.io/enrichmeai/penstock:0.5.3
Since v0.2.0 the published image requires a login by default: set AGENT_AUTH_PASSWORD, read the generated one from the container logs, or opt out deliberately with AGENT_AUTH_ENABLED=false. Exact coordinates, configuration and the model options are in the release notes and the shipped docs: v0.5.3 →.
It remembers, in files you own
With memory on (AGENT_MEMORY_ENABLED=true), Penstock recalls at the start of each turn. It loads matching facts, the latest episodes of the project you are in, and matching patterns into the system prompt. Every card it loads is logged. With database storage it also goes to the audit log, and the web UI's Audit view lists the cards ahead of the model call they went into.
The memory is plain files in a folder or a private git repository you own: estate/ for what holds everywhere, and one projects/<name>/ per project. Every card carries a format version and a visibility, the JSON Schemas ship in the repository, and nothing in the format depends on one model.
Who sees which card
Since v0.5.0, your root's memory.yaml can declare your projects and the audience that sees each one's output. A card loads into a project only if everyone who sees that project's output may see the card. Every turn logs each section as loaded or refused, with the reason, and with database storage the Audit view shows both.
| Into a project that is | public | private | private, personal: none |
|---|---|---|---|
| Its own project's cards | Loaded | Loaded | Loaded |
A public project it uses | Loaded | Loaded | Loaded |
A private project it uses | Refused | Loaded | Loaded |
| Your personal cards marked shareable or public | Loaded | Loaded | Not loaded |
| Your private personal cards | Not loaded, by default | Loaded | Not loaded |
projects:oss-lib: { audience: public }platform: { audience: private, uses: [oss-lib] }standalone: { audience: private, personal: none, uses: [oss-lib] }
An explicit personal: all loads every personal card, so leave it off a public project. A root without a projects: block behaves as it did in v0.4.0. The rules are in the shipped docs.
Adding to the memory
You draft episodes and choose which facts to keep. Nothing is added on its own.
write-episode.sh --root drafts a dated record of the work as a file. From Claude Code, the draft_episode tool does the same and never overwrites; since v0.5.3 the draft waits in drafts/, and recall does not read it until you move it into episodes/.
consolidate.sh --root --write turns what an episode learned into proposed facts in the same project's facts/.
Both are files for you to read and commit. A line that looks like a credential stops the run before anything is written: a pattern check, not a guarantee.
In Claude Code and Claude Desktop
--mcp runs Penstock as a local MCP server over stdio. The client starts it on your machine, so there is no network listener and no credential. recall returns the memory a Penstock turn would give its model for the question, built by the same code under the same project rules, and read-only. A start-with-memory prompt asks the client to call it before it plans. Every call is logged: to stderr with the default in-memory storage, to the audit log with database storage. The claude mcp add command is in the README.
--mcp, not the Docker image: the image's entrypoint writes to stdout, which belongs to MCP messages. With MCP the client decides when to call recall; inside Penstock, with memory on, recall happens at the start of each turn. Hosted sessions and Claude Cowork cannot read the memory yet, and there is no memory browser or review inbox in the web UI: review happens in the files.
Surfaces, and what each one logs
| Surface | What it is for | Written to the audit log (with database storage) |
|---|---|---|
| Web | A person working with the agent directly | Every model call and tool invocation |
| REST | CI hooks and scheduled triage | Every model call and tool invocation |
| Streaming | Chat commands and long-running work | Every model call and tool invocation |
Editor (--acp) | The agent inside an editor that speaks ACP | Nothing: --acp runs with in-memory storage only, so card loads go to the application log |
Memory server (--mcp) | Your memory in Claude Code or Claude Desktop | Every recall and draft, with the sections and cards a recall returned (to stderr with the default in-memory storage) |
| Models | Pluggable, including a local one | The call, whichever model served it |
Where it stands
What has shipped, what is planned, and what is in progress.
- Today
- Open source: five surfaces (web, REST, streaming, the editor over ACP and the memory server over MCP), pluggable models including a local one, and, with database storage, an audit log of every model and tool call, attributed to the authenticated user and readable from the app itself. live
- Release
- v0.5.3: a jar, a bill of materials and a multi-architecture image. The memory block now asks the model to cite the cards it uses and to propose episodes rather than write facts. It builds on v0.5.1's security fixes and v0.5.0's project audiences and MCP mode. live
- Governed access
- The pod tool has shipped. Penstock asks a Cistern pod for a file over plain HTTPS as its own principal, is refused outside its grant, and what it read is receipted on the owner's side. That is application-level consent: the receipt names the application, not the person who asked it. Per-user credentials are available by configuration for deployments that want attribution per person. live
- In your IDE
- Penstock speaks the Agent Client Protocol. Launched with
--acpit runs as a selectable agent inside an editor that speaks ACP, over stdio. The read-only tools and pod reads run unprompted;write_file,edit_file,shell,gitand pod writes each go through the editor's own permission prompt first. Registration steps for both editors are in the shipped docs. v0.3.0 - Memory
- Recall at the start of each turn from a memory root you own, with each loaded card logged, and on the audit log with database storage (v0.4.0). Project audiences decide which cards each project may load, and
--mcpserves the same memory to Claude Code and Claude Desktop (v0.5.0). The block asks the model to cite each card it relies on, or to mark an assumption, and to propose an episode rather than write a fact (v0.5.2). v0.5.0 - Planned
- Not in a release, and named here so the gap is visible. Hosted sessions and Claude Cowork reading the memory from a pod (#89). A memory browser and a review inbox in the web UI (#107, #108). Falling back to a local model when no key is set (#106). A Gemini provider and a Perplexity search tool (#114, #115). The move to Spring Boot 4, for the three Spring criticals the scan still lists (ADR 0004, #128). planned
- Later
- The stranger test: deploy and use it from the published artifacts alone, following only the shipped docs. Cistern is held to it. Penstock v0.5.3 went through it on 17 October 2026 from the published jar and docs: nothing blocked, two documentation faults are being fixed (penstock#171), and the image was not tested. Until those are done this page says to run it locally. in progress
- Licence
- Apache 2.0, free for any use including commercial.
Security scans
Each release's image is scanned for high and critical findings, and the report is attached to the release. From v0.1.0 to v0.5.3 the report total went from 138 to 17. A report counts a finding once for each copy of the library it finds in the image, so its totals run higher than the number of distinct vulnerabilities.
v0.5.3 changes no dependency. Its report no longer lists the one libssl3 high (CVE-2026-84782, in the Ubuntu base), so it lists 17 findings, one fewer than v0.5.2. v0.5.1 moved Tomcat, Netty, Jackson and the Postgres driver in the application to the fixed versions the v0.5.0 report listed, and moved the image's Gradle to 8.14.6. Three distinct critical vulnerabilities remain, which the report totals as 8. All three are in Spring Framework 6.2, and the only fix listed for each is Spring Framework 7, which needs Spring Boot 4. One, in XsltView, is not reachable because Penstock renders no XSLT view, and a test fails the build if one is ever added (ADR 0004). The other two were not in the v0.5.0 report, and whether either is reachable is still being checked (#128). The nine highs all have listed fixes that are not applied yet. All nine are build-time copies of Jackson and httpcore5 in the image's seeded Gradle home, not in the application, and they will be handled separately. Earlier versions of this page said every finding with a fix had been fixed. The v0.3.0 report already showed that was no longer true.
Every scan report is attached to its release.
High and critical findings, as each report totals them, counting every copy.
All in Spring Framework 6.2, with a fix listed only in Spring Framework 7. One is not reachable and guarded by a test; two are being checked.
Can I put this on a network?
Not yet. v0.5.3 passed a stranger test with nothing blocking, but it has not cleared it the way Cistern has: two documentation fixes are under way (penstock#171) and the image was not tested. Run it on your own machine for now.
Can I run it inside my editor?
Yes, since v0.3.0: --acp speaks the Agent Client Protocol over stdio to any editor that supports it. The read-only tools and pod reads run unprompted; writes, shell and git go through the editor's permission prompt first.
Can Claude Code use my memory?
Yes, since v0.5.0: register java -jar penstock-0.5.3.jar --mcp as a local MCP server, and recall answers from your memory root under the same project rules. The client decides when to call it; inside Penstock, with memory on, recall runs at the start of each turn.
Do I have to send anything to a model provider?
Models are pluggable and one of them is local. Whichever serves the call, with database storage the call is written to the audit log.
Do I need Cistern to use it?
No. Penstock runs on its own. With the pod tool and a Cistern pod, its reads of your data are also receipted on your side. Run together, the two are Headworks. See Cistern →
Can my open-source work see my private notes?
Not if you declare it public. A public project never loads a private project's cards, and by default gets only the personal cards you marked shareable or public. Each refusal is recorded with its reason.
Why publish open critical findings?
The reports are attached to each release, and this page repeats them. Three criticals are open in v0.5.3; the scan record above says which, and why none of them has a fix that applies to Spring Boot 3.5.
Is any of it held back for a paid tier?
No. Apache 2.0, free for commercial use. Support, integration and hosting are how the company earns. COMMERCIAL.md →