Let auto mode (Forge + local model) propose file edits repo-wide. Proposals land in a staging area and touch the live tree only on explicit human approval. A human can optionally trigger a Claude advisory review (text only). This makes the "create a draft post" class of task — currently impossible because no write tool exists — actually work, without handing a weak model unsupervised write access.
- Local model calls
stage_write(target_path, content)-> record in staging store, never the live tree. - Human opens a dedicated review page, sees the diff, optionally clicks "Ask Claude to review" (returns text they read).
- Human Approve -> bytes written to the live target (the only privileged step); or Reject -> discarded.
Add is_write_denied_path(p) covering the privilege / guardrail-subverting
class only:
*.sh; anything under ascripts/dirDockerfile*,docker-compose*.{yml,yaml},compose*.{yml,yaml}- anything under
.github/ - anything under
bix-ai/source (self-modification could rewrite these very guardrails)
Allowed (the point of the feature): ordinary app source + content everywhere
else — ai_graph_mode, beatshare, bix-demucs, blog content/templates, the
Flutter apps, infra docs/todos.
A write must satisfy: within FS_ROOT and not is_denied_path (secrets)
and not is_write_denied_path.
One JSON record per change: id, created_at, proposed_by, target_path, content, status (pending|approved|rejected), claude_review, reviewed_at, applied_at.
Functions: create / list / get / set_review / approve / reject. Matches
memory.py's async-file persistence style.
Validates target (FS_ROOT + both denylists); on denial returns a clear message
the model relays ("can't stage X — protected path"). On success creates a record
and returns "staged for review, id=…, will not apply until you approve." Added
to FORGE_TOOLS (auto) and OLLAMA_TOOLS (local) — shared dispatch, trivial to
include both. FORGE_SYSTEM rewritten: repo-wide staged editing, no blog
special-casing, explicit note that writes are reviewed before applying.
GET /staging-> server-rendered list page (Catppuccin, pending first)GET /staging/{id}-> detail page: content + unified diff vs live file (difflib), status, any Claude review, Approve/Reject/Ask-Claude buttonsPOST /staging/{id}/review-> one-shot non-streaming Claude call (reusing theANTHROPIC_URLPOST pattern atmain.py:406), advisory text persisted + returnedPOST /staging/{id}/approve-> re-validate both denylists + FS_ROOT, write to live target, mark appliedPOST /staging/{id}/reject-> discard
"Review this proposed change to <path>; here's the current file (or 'new file')
and the proposed content; flag risks/correctness/anything not safe to approve.
Advisory only — a human decides." The staged content is model-generated and
possibly injection-laundered, so it's wrapped as untrusted data to review, not
instructions to follow.
A small "N pending reviews ->" link that opens /staging in a new page. No drawer.
stage_writenever writes live.approveis the only path that writes live, always human-triggered, and re-validates denylists (never trusts the stage-time check alone).- Claude review is advisory text; it cannot approve or promote.
- Both denylists (secrets + privilege) enforced at stage and approve.
- Denied at stage: secret path,
*.sh,scripts/,Dockerfile,docker-compose.yml,.github/,bix-ai/source. - Allowed at stage: blog content,
ai_graph_modesource, infra todo. - Approve re-validation refuses a record whose target is denied.
- Approve writes the file; reject discards.
- v1
stage_writewrites whole-file content (overwrites on edit) — the diff view shows exactly what changes, so it's reviewable;apply_patchgranularity can come later. - No auto-apply, ever. No git commits (per repo rule — you commit). Approved writes land as uncommitted working-tree edits and deploy through your normal nightly rebuild cycle.
- Denylist line (component 1): deny privilege/guardrail class, allow ordinary source + content. This is the point flagged as uncertain.
- Scope of
stage_write: local mode too, or auto-only?