Open source · self-hosted · v0.5.3
An agent that writes down what it did.
Runs your agents on your data, with receipts: headless, self-hosted, and the reference client for governed access. With database storage, every model call and tool invocation is written to an audit log the owner reads back.
Penstock runs your agents on your data, with receipts. A self-hosted agent runtime 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. Three things define it. Where it runs: headless, not at a developer's keyboard. Whose hardware: yours; the transcript, the audit log and the credentials never leave the deployment. Whose data, provably: through the pod tool it reads a person's own data in a Cistern pod under rules that person wrote, and every access is receipted on their side, not ours. And it keeps the record its work deserves: 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: before every model call it recalls 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.
The first thing to ask Cistern for something
It earns its place here by being the first thing to ask Cistern for something. An agent that wants a file has to present an identity of its own, receive only what was granted, be refused the rest, and leave a receipt behind. The shape of that exchange is the same whether the agent is writing code, reading a case file, or doing something nobody has built yet. Proving it on an agent we run ourselves is how the pattern gets settled before anyone else has to depend on it.
So there are two things in one repository, on purpose. An agent worth running, and the reference implementation of how a client should ask for someone else's data. Neither would prove much alone.
Every model call and every tool invocation, attributed to the authenticated user. What the agent did, in its own words, on the record. 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. The half an agent cannot write for itself, and the half that stays true if the agent is wrong about what happened.
Next to JetBrains Air
JetBrains announced Air on 22 September 2026: agents inside the IDE, governed for which agent and model a developer may spend against. Penstock answers a different question: whose data an agent may touch while it works, and proof afterwards that it stayed inside the rules. The two fit together rather than compete.
| Penstock | JetBrains Air | |
|---|---|---|
| Where it runs | Headless: CI, chat, cron, webhooks. No editor needed. | Inside JetBrains IDEs, the browser, or the Air desktop app (docs) |
| Whose hardware | Yours. Self-hosted, Apache 2.0; nothing leaves the deployment. | Cloud environments: "a Docker container that runs on a virtual machine in the cloud" (docs) |
| What is governed | Whose data an agent touches, under rules that person wrote, receipted on their side through the pod tool and a Cistern pod | Agent and model permissions, spend and auditability for the organisation (announcement, product page) |
| Together | Penstock speaks the Agent Client Protocol, so it can run as one of the agents Air or Zed offers. It has been in the releases since v0.3.0; see Status below. | |
On your own machine, for now
The release carries a jar, a bill of materials and a multi-architecture image. Until it clears the stranger test, 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 JetBrains Air or Zed, 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 before every model call. 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.
How it gets better
Memory grows from the work, and you decide what stays. Nothing is promoted 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, recall still happens before every model call. 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.
Every surface, one log
However the work arrives, it is written down the same way and attributed to the authenticated user.
| 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 JetBrains Air or Zed | 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
This project has a rule: nothing is announced before it has run on real infrastructure. So here is the honest state, not the roadmap in disguise.
- 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 JetBrains Air or Zed, 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 before every model call 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 has not cleared it yet, which is why this page says to run it locally. later
- Licence
- Apache 2.0, free for any use including commercial.
Yours to read, not ours to claim
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 is attached to its release, so the whole series is yours to read rather than ours to claim.
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. It has not cleared the stranger test that Cistern is held to, so it is a thing to run on your own machine for now.
How is this different from JetBrains Air?
Air puts agents inside the IDE and governs which agent and model a developer may spend against. Penstock runs headless, on your hardware, and governs whose data an agent may touch, with a receipt afterwards. They fit together: Penstock speaks the Agent Client Protocol, so it can be one of the agents Air runs.
Can I run it inside my editor?
Yes, since v0.3.0: --acp speaks the Agent Client Protocol over stdio to JetBrains Air or Zed. 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, which a Penstock turn does not leave to chance.
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. It is an agent in its own right. The pod tool is what makes what the agent read receipted on the owner's side, and it is what turns the audit log into two records that can be checked against each other. 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?
Because the reports are attached to the releases anyway, and a page that disagreed with them would be the thing not to trust. 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 →