New
Apps 1,288Makers 1,286This week 1,288
Promote your app−30%
Persei screenshot 1
0views in
the last 30 days

Persei

Autonomous coding agent built on LangGraph + deepagents. It natively plans, builds, verifies...

Built with Claude/Gemini/Claude Code

by djg3dk · added Oct 29, 2026
no ratings0 comments

An autonomous coding agent built on LangGraph and deepagents. It plans, executes, and verifies real changes against a real project checkout, with human approval required for anything risky. It also runs Planning Chat, a separate conversational assistant for research and design discussion that remembers what it learns across sessions and can hand a finished plan straight to the build pipeline.

It learns from its own work. After a failed attempt it reflects (what failed, why, what to do differently) before trying again; every finished task leaves an episode; once a day those episodes are consolidated into each project's memory; and months of past tasks stay searchable, so "have we hit this before?" has an answer. See Memory and reflection.

The backend is a FastAPI app (agent/server.py); the frontend is React/Vite. Together they cover submitting and watching build tasks, chatting with Planning Chat, and administering models, users, and repos.

The agent targets a fixed set of local projects (PROJECTS in agent/config.py / projects.json). Each task gets its own workspace — a git worktree of the live repo at /home/agent-workspaces/.tasks/<project>/<task-id>, on its own branch agent/<task-id>, filled from the project's workspace (/home/agent-workspaces/<project>) with dependencies hardlinked and build output copied (agent/workspaces.py). So tasks on the same project can run side by side: Settings → Tasks at once per project sets how many (default 10, at most 16; each task has its own 2 GB sandbox, so size it to the machine). They code in parallel and take turns only for checks, review and merge; a task that merges second is rebased onto the first and reviewed again. The limit is a set of Postgres advisory locks (agent/graph.py, project_slot) rather than anything in the process, because it is a property of the project: a second worker or an overlapping restart sees the same slots, and Postgres drops a claim when its connection closes.

Community

Questions, help with an install, or an idea for what it should do next: join the Telegram group. Bugs are best as a GitHub issue, and security problems go through a private advisory, never in public.

Screenshots

A task running live — tool calls, output, the agent's summary and the review gate's verdict Analytics — spend, outcomes, benchmarks, review cost and runs
Watch a task work. Every tool call and its output as it happens, which model made it, the agent's own summary, and the review gate's READY or NEEDS_FIXES at the end. You can nudge it mid-task from the box at the bottom. What it cost and whether it's getting better. Spend against your balance, outcomes, fix cycles, a benchmarks row comparing the last two weeks with the two before, and the review gate's own bill, from this box's router ledger. No tracing service.
GitHub inbox — security alerts and Dependabot pull requests Benchmarks — SWE-bench Verified runs
GitHub alerts become tasks. Security alerts, code scanning and Dependabot pull requests in one inbox. Approve one and it's a task; alerts on the same package arrive as one item, and one the fix didn't close comes back. Benchmarks you can rerun. The golden suite and SWE-bench Verified run from the dashboard. Each run keeps its score, cost, models, notes and what the host was doing.
Model configuration — build pipeline roles Settings — auto mode
Every role is a named alias you can repin. Planner, coder, frontend coder, investigator and test-writer each say what the role needs, with live pricing and an optional pinned provider. A swap is one dropdown. Hands-off, with lines it won't cross. Auto mode lets a task finish without you, per project. It still asks before a delete that would lose work, and when it has a question for you.
More: model probes, support roles, tool reliability, planning A role card listing the models verified to work and to fail

Models are probed, not assumed. A constrained role says which models passed its probes and which failed, and when they were last probed.

Classifier and summarizer roles

Cheap jobs get cheap models. The classifier needs structured output and nothing else; the summarizer is a plain completion, so the cheapest tier is fine.

Model usage by role and tool-call reliability

Where the tokens went, and which tools fail. Per-role calls, tokens, latency, cost and cache rate, then tool-call errors by tool and by day.

A benchmark run's host metrics and per-task results

What the machine was doing. Memory, model calls in flight, latency and containers across a benchmark run, then every task's result.

Plan a project — repo and model route pickers

Plan first. A planning session starts from a repo and a model route. It remembers what it learns about the project between sessions, and Build Now hands the finished plan to a build task.

Contents

Operating it, rather than reading about it: docs/architecture.md (the four processes, where each secret lives, the three checkouts, the two-node graph), docs/runbooks/ (symptom → check → action), and docs/backup.md (backup, and proving a restore works). Agreed-but-unbuilt work is in docs/todo.md.

How a build task runs

agent/outer_graph.py wires a small, two-node graph around a deepagents agent (agent/deep_agent.py):

START → work → verify_and_ship ──(findings / unfinished plan)──→ work (loop, same inner thread)
                    │
                    ├──(escalated / awaiting approval / awaiting merge)──→ END (resting; resume continues)
                    └──(READY + merge allowed)──→ merge+deploy (inside this node) → END
  • work (agent/nodes/work.py) drives the deep agent's own tool-calling loop against a sandboxed checkout (docker/agent-sandbox/ — only the target repo is mounted, so a shell command can't reach other projects or host secrets). It plans via write_todos and can delegate to three subagents: investigator (read-only research — no write/edit/shell tools at all), test-writer (writes tests, required to run the checks itself before reporting done) and verifier (agent-verifier, its own seat and ledger line since 2026-09-25: after a bug fix it runs the reported case and its neighbours and reports what still fails; it edits nothing). The verifier and the test-writer are bounded — 30 and 60 tool calls — and counted down in their own results, so the last allowed call carries no tools and has to be the report (WrapUpMiddleware) rather than "Tool call limit reached" and nothing. A run_checks tool lets it run the project's real typecheck/lint/test suite itself mid-task — the same commands the gate will run — so it finds its own breakage rather than learning about it a full round-trip later. On a frontend-routed task the seats run under the Frontend profile overlay (see Frontend routing and Model profiles). If a model gets stuck repeating a call the loop guard has refused, the rest of that pass moves to agent-coder-fallback, a different model, on the same conversation. The task is handed back only if that model gets stuck too. An empty reply — reasoning that ran to the output cap and left nothing to say — is retried once on that same fallback seat at low reasoning effort before the conversation sees it (EmptyReplyRetryMiddleware); every seat's output is capped at 16k tokens. With a GitHub token it can also read a pull request host-side (github_pull_request) — the sandbox never sees the token.
  • verify_and_ship (agent/nodes/verify_and_ship.py) is the actual gate. It always re-runs the real typecheck/lint/test suite itself — the agent's own "done" status carries no authority here. A check that fails on the branch is re-run at the merge-base; one that fails there too is marked pre-existing, does not force NEEDS_FIXES, and is listed for the agent with an instruction not to chase it. A pass with a real diff produces one commit on the task's own branch agent/<task-id>, handed to an independent review service. The review unit is that branch plus its merge-base, fixed at the fork point — not "whatever the sandbox HEAD is now". The old model compared two HEADs and inferred the rest, which produced inverted diffs whenever live moved ahead: a branch's additions read as deletions of everything live had gained since, and that manufactured two blocking findings against a commit that had in fact added the settings it was accused of removing. Those checks are agent-authored code, so on a host install they run inside a container rather than on the machine — in the image their toolchain needs, with no network, and with a refusal rather than a fall-back if the sandbox is unavailable (SECURITY.md). From there: NEEDS_FIXES loops back to work with the findings; READY merges and deploys (after the operator's merge approval, unless that switch is off). Merge and deploy live inside this node, not as a third graph node — a deploy preflight can fail as its own stage (a live URL the build depends on is down) and escalate to a human rather than being handed to the agent as a compile error.
  • A review round is an argument, and both sides are heard. The agent's closing message of a round rides in the follow-up commit under "Response to review round N"; the reviewer reads every answer on the branch before repeating a finding, withdraws a blocking finding that a reported run has disproved unless the diff itself shows otherwise, and must name the evidence it disputes when it does repeat one. The reviewer's own breaker still ends the argument — three NEEDS_FIXES in a row, or the same file in its findings across three rounds — and the gate escalates the task to a person. On a benchmark project ("benchmark": true in projects.json) there is no person waiting and the prediction is the tree, so the fix ships as it stands, recorded as disputed. The same projects get three diff-pattern gates (agent/nodes/diff_patterns.py) that send a fix back once, at the decision point: an invented error message where the existing template was expected, a grammar loosened without a must-still-reject test, a fix applied to one of two same-named functions.
  • The plan has to be finished before anything is committed. If the agent's own write_todos list still has open items, the gate holds the commit and sends it back to finish, naming what's left. Committing mid-plan means the review service reviews a deliberately-incomplete change and correctly reports the not-yet-written pieces as defects — bouncing the task on findings that describe scheduled work. It's a nudge budget, not a hard gate: a model that stops maintaining its own list can't strand working code as an uncommittable diff forever.
  • Two limits the model can't override: a budget ceiling (BudgetGuardMiddleware, checked after every model call, on both the coordinator and every subagent) and an iteration/retry ceiling on the work/verify loop. The ceiling is enforced against what the router billed, not a token × rate-table estimate: each call is carried at its estimate only until the router's line for that call id lands (agent/tools/router_ledger.py), then the billed figure replaces it. A planning turn was once ended at "$8.09 spent" when OpenRouter had billed $1.72.
  • Loops end. A third identical tool call whose two predecessors returned the same result is answered from cache; the fourth and later are refused; after eight refusals in a row the pass escalates naming the looping tool (RepeatCallGuardMiddleware). A call whose result changes (a poll, a flaky test) is never blocked — results are compared with addresses, durations and temp paths normalised away — but eight identical calls in a row are refused whatever they returned. Malformed tool calls are stripped from every model request (SanitizeToolCallsMiddleware) so a truncated write_todos cannot poison every later turn of the thread.
  • ask_user lets the agent pause and ask the operator a clarifying question mid-task instead of guessing, using the same human-in-the-loop interrupt that gates approval-required actions.
  • A task is never a dead end. Escalated, stopped, budget-exhausted, or orphaned by a backend restart mid-run — each has a way back in. The full graph state lives in the checkpointer, so resuming adds budget and continues the same inner thread rather than starting over. The dashboard detects an orphaned task (the store says "running" but nothing is driving it) and offers a resume instead of leaving it stuck looking busy forever.

Planning Chat

A separate agent (agent/planning_chat.py) for research, design discussion, and scoping a project before anything gets built. No write/bash access to the real repo — just research tools (web search, a headless-browser browse_page with screenshots, describe_image, read-only access to your other projects, search_project / find_files against the real tree, optional GitHub PR and inbox tools) and save_brief / save_plan. "Build Now" hands a finished plan to the real build pipeline above, as if it had been typed in directly. The app lands here first: Planning Chat is the front door, not the raw task composer.

One seat, set by the session's model profile. agent-planning-chat answers every turn. The profile chosen when the session starts sets its model, and a frontend session's Frontend overlay goes ahead of that, so a frontend plan is written by the model that will build it (see Model profiles). Until 2026-10-28 a classifier picked an Easy or Hard seat on every turn and frontend sessions had a third seat; profiles replaced all three. Whatever the model, the tool list, memory and permissions are the same.

The brief comes first and stays pinned. save_brief is the only tool a new session can call until a brief exists (BriefFirstMiddleware); the brief rides in the system message on every call after that (PinnedBriefMiddleware), so compaction cannot touch it, and it persists with the session so follow-up turns do not re-force it. Saving the brief also matches the request against the project's skills and names the architecture skills to read before any file.

Search, then read the window a hit points to. search_project (ripgrep, capped per file and overall) and find_files (the .gitignore-aware file list) against the real repo. Loop-proofed: an identical search is answered from cache and refused on the third; zero hits come back with what was scanned and what to change; a per-turn search budget ends searching with "write the plan from what you have". Both are dials under Settings → Runtime limits.

The draft gate. After N repo reads without a saved plan (default 50) read_project_file closes with "save a draft now" and reopens once a plan is saved. A gate-forced save is a checkpoint, never the end of the turn. Paged reads return at least 500 lines whatever limit asks.

Deliberate constraints, each earned by a real incident:

  • No subagents. create_deep_agent auto-adds a general-purpose subagent (and its task tool) even when none are declared; planning hides it at both the schema and execution layer (agent/middleware/hidden_tools.py). A hidden delegation primitive once burned a 30-minute turn timeout on nested agent loops. Built-in glob/grep are hidden for the same reason — they search the agent's own memory/skills space, never the repo, and a model will loop on the misleading "No matches found" forever. The repo search is search_project / find_files.
  • Codebase-map first. The cartographer's per-project map (/skills/codebase-map/SKILL.md) is advertised in the prompt and read before any directory walking — one read replaces a dozen exploratory listings. The map refreshes once a day, scheduled by the agent itself at the first quiet moment (agent/jobs.py; hash-gated: an unchanged repo costs a tree walk, no model call), and immediately after every merge+deploy. A companion recent-changes skill lists the newest commits with their files.
  • Large files page. read_project_file supports offset/limit; a truncated read says outright that re-requesting returns identical text and names the exact next call to make.
  • A per-turn dollar ceiling (Settings → Runtime limits, planning_turn_budget_usd, default $15; also seedable from PLANNING_TURN_BUDGET_USD) on top of whatever the session already spent. Planning previously ran uncapped — the one agent with no budget was the one that once spent $7 on a single 157-call turn. On breach the draft plan and real cost are banked and the operator decides whether another turn's allowance is worth it.
  • A plan written as chat text is not lost. A turn that ends with a plan-shaped final message and no save_plan call has that text adopted as the draft (narrow heuristic; an explicit save always wins).

The agent can propose a project that does not exist yet. When a request turns out to be a new application rather than a change to the current project, the plannin

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#agents · #coding · #langgraph