Agents propose. Nothing happens until you approve.
Current is change control for AI agents. The agent hands over a complete proposal. Current validates it, waits for your decision, applies it, and records every step in a hash-chained log.
The agent is reading the project…
Proposal
waiting_for_approvalsha256:884ec559ec2e
First proposal for this workflow.
- edit_file app/api/login/route.ts
- edit_file app/api/login/route.ts
- edit_file tests/rate-limit.test.ts
- edit_file tests/rate-limit.test.ts
- report Summary for the reviewer
Your project
Untouched. The agent can only read what it asks for.
- app/api/login/route.tsunchanged
- tests/rate-limit.test.tsunchanged
- lib/rate-limit.tsunchanged
- lib/csrf.tsunchanged
- lib/logger.tsunchanged
View proposed changes 4 edits2 filesno commands
Why, according to the agent: Apply the existing login-specific fixed-window limiter to CSRF-valid login requests before parsing credentials or performing authentication, and verify that the endpoint rejects requests beyond the configured per-IP limit.
Each edit_file replaces one exact piece of text, which must match the file exactly once. Shown here as a diff.
-
edit_file app/api/login/route.ts +4 −0
import { isCsrfRequestValid } from "../../../../lib/csrf";+ import {+ getClientIp,+ loginRateLimiter+ } from "../../../../lib/rate-limit"; import { logFailedLogin } from "../../../../lib/logger";
-
edit_file app/api/login/route.ts +14 −0
if (!isCsrfRequestValid(request)) { return Response.json( { error: "Invalid CSRF origin" }, { status: 403 } ); } + const rateLimit = loginRateLimiter.consume(getClientIp(request));+ + if (!rateLimit.allowed) {+ return Response.json(+ { error: "Too many login attempts" },+ {+ status: 429,+ headers: {+ "Retry-After": String(rateLimit.retryAfterSeconds)+ }+ }+ );+ }+ const { username, password } = await request.json();
-
edit_file tests/rate-limit.test.ts +6 −1
- import { RateLimiter, getClientIp } from "../lib/rate-limit";+ import {+ LOGIN_RATE_LIMIT,+ LOGIN_RATE_LIMIT_WINDOW_MS,+ RateLimiter,+ getClientIp+ } from "../lib/rate-limit";
-
edit_file tests/rate-limit.test.ts +5 −0
describe("RateLimiter", () => {+ it("configures login attempts for a ten-attempt, fifteen-minute window", () => {+ expect(LOGIN_RATE_LIMIT).toBe(10);+ expect(LOGIN_RATE_LIMIT_WINDOW_MS).toBe(15 * 60 * 1000);+ });+ it("allows requests up to the limit and blocks the next one", () => {
-
run_command npm test
Runs in the project with the runtime's privileges. Allowed only because
npmis on the command allowlist. -
claim The test suite passes
Checked by Current when applied:
npm testmust exit with code 0, or the workflow fails. -
report No effect
“The login route will consume one rate-limit token per CSRF-valid request using the best-effort client IP. Requests after 10 attempts in 15 minutes will receive HTTP 429 with a Retry-After header before request-body parsing or authentication. Existing limiter tests will also pin the login-specific policy.”
Commit log
Real SHA-256 hashes, computed in your browser for this demo.Current's own log hashes each commit's canonical encoding on disk. These blocks show the same chaining, not Current's actual bytes.
Why not just use Claude Code or Codex?
You can. The difference is where the action happens. In a typical agent, a tool call is the action: the file is written the moment the model decides to write it. In Current, the agent can't act at all.
A typical coding agent
model decides, file is written
model decides, command runs
you approve step by step, as it goes
Review happens around actions that are already underway.
Current
model hands over typed intents
runtime decodes and validates
you approve, send back or reject
runtime applies, verifies, records
A small trusted interpreter is the only thing that touches your project, and only after an approval is on the record.
A proposal, not a tool call.
The agent sees only the files it asks for that the read policy allows, and can only propose from nine intent types. Approving and applying are separate steps. Only allowlisted commands run.
Check the work. Not the word.
Review the exact edits and commands, or pass the JSON review to another agent. Claims are checked against what actually ran, and a false claim fails the workflow.
Every change has a history.
When someone asks why a line changed and who approved it, the answer comes from a commit log, not from asking the model to explain itself.
Between the proposal and your project
A proposal does nothing on its own. Five steps stand between it and your files, and each is recorded as an event.
Decode
The model's reply is untrusted bytes. It becomes typed intents from a closed set, or it is rejected. Nothing is interpreted.
Validate
Paths stay inside the project, files exist, every edit matches exactly, every claim cites an earlier intent.
Decide
A person or another agent approves, rejects, or sends it back with feedback. Each revision is a new proposal with its own ID. Ten revisions by default.
Apply
A separate command. The interpreter re-checks each path against the filesystem, following symlinks, just before touching it.
Check
Claims are evaluated against recorded results. The first failed effect or false claim stops the workflow. Effects are not rolled back.
Nine intents. Anything else is rejected.
The agent speaks a closed vocabulary. Before proposing, it can look around with the three read-only intents: up to 20 per reply and 8 rounds, filtered by the read policy and recorded.
| Intent | What it does when applied | Usable before proposing |
|---|---|---|
| Look | ||
read_file | Reads a file and stores its contents | Yes |
glob | Lists files matching a relative pattern | Yes |
grep | Finds lines containing a literal string in listed files | Yes |
| Change | ||
write_file | Creates a new file. Fails if it already exists | No |
edit_file | Replaces exact text, byte for byte, and stores the file before and after | No |
delete_file | Deletes a file, storing its contents first | No |
run_command | Runs an allowlisted executable in the project, storing stdout, stderr and exit code | No |
| Assert | ||
claim | Checks a statement against an earlier intent's result, such as an exit code or match count | No |
report | A summary for the reader. No effect | No |
One runtime, three ways in
The CLI, the HTTP API and the MCP tools all go through the same runtime and the same rules. Review as a person, from a script, or with another agent in the loop. Pick an OpenAI or Anthropic model per task.
Every change traces back to a request
The runtime is the only thing that acts, and it records each step before taking the next. Read the chain without the model, and check that nobody quietly edited it.
TaskCreated prompt, model, project └─ WorkflowCreated └─ ProposalProduced intents, reads, raw reply └─ ProposalValidated ├─ ProposalRevisionRequested │ └─ … a new proposal, revision 1 └─ ProposalApproved who, when, message └─ EffectApplicationStarted ├─ EffectApplied file before and after ├─ EffectApplied exit code, stdout ├─ EffectApplied claim verified └─ WorkflowCompleted
- Who asked for this, and what exactly did they ask?
- The task: prompt, model and project directory.
- What did the model look at before deciding?
- Each read, its reason, and the exact bytes it was shown.
- What did the model actually say?
- Its raw reply, stored byte for byte.
- Who authorized it, and what did reviewers ask to change?
- The approval and any revision requests, with their messages.
- Did it do what the agent claimed?
- Each claim's verification and the result it was checked against.
- What didn't happen, and why?
- Rejections, validation failures and failed effects, each with a reason.
A history that checks its own links
The head hash fingerprints everything
Change any byte of any past commit and every link after it breaks. File contents, command output and model replies are covered too, by their SHA-256 ids.
Verified on every start
A gap, a broken link or corrupt bytes stop the runtime, naming the file. Commits are fsynced before anyone hears of them.
Rules enforced at commit time
One pure reducer decides every transition. No apply without approval, no decision on a stale proposal, and of two racing approvals only one is accepted.
Replay anywhere
The log and the artifact store are the whole state. Copy the data directory to another machine and the same history rebuilds.
Control isn't a sandbox.
Current controls when an agent's proposal takes effect and records everything about it. It does not limit what an approved proposal can do. It is early software: single node, local projects.
Read this before you approve a command
- Approved commands keep your privileges. Commands are denied unless allowlisted. Once allowed, they inherit the runtime's environment, files and network, and their arguments are unrestricted. Approving
npm testapproves running arbitrary code. - Local first, not authenticated. The HTTP API and MCP endpoint have no authentication. Anyone who can reach them can approve and apply, so keep them on loopback or behind a gateway.
- Tamper-evident, not signed. Someone who can write the data directory can rewrite history and recompute every hash. Signed or externally published heads are planned.
- The read policy filters by name. Dependencies, build output,
.gitand.env*are never shown to the model. A key in another file can be.
Works today
- Request, review, revise, approve, reject and apply from the CLI, HTTP or MCP
- Claims verified against results
- Hash-chained, segmented log, verified on every start
- Content-addressed artifacts for every input and output
- OpenAI and Anthropic models, chosen per task
- Default-deny command allowlist
Planned
current exportandcurrent replay: re-run elsewhere and diffcurrent verify: check the chain and every artifact- Signed or externally anchored log heads
- Approval policies and authenticated approvers
- Command timeout, output cap and environment filtering
- Atomic apply, and sandboxed commands
Make your first proposal
You need Elixir 1.19 on Erlang/OTP, git, make and bash, and an OpenAI or Anthropic API key. The full setup guide has the details.
Build Current
git clone https://github.com/santosh79/current.git
cd current
mix deps.get
mix compile
make client
Start the runtime
# in the repository root
export ANTHROPIC_API_KEY="your-key"
# commands are denied unless allowlisted
export CURRENT_ALLOWED_COMMANDS=npm
bin/current-server
Ask for a proposal
# another terminal, repository root
export PATH="$PWD:$PATH"
current models
# use an ID from the list above
MODEL="anthropic:model-id-from-the-list"
current run "add CSRF protection to this app" \
--dir examples/nextjs-auth --model "$MODEL"
Review, decide, apply
current review <proposal-id>
current revise <proposal-id> -m "add edge cases"
current approve <proposal-id> -m "reviewed"
current apply <proposal-id>
current events <workflow-id>
Want a proposal that can never be applied? Use current plan instead of current run.