Skip to content

Auto-approve from flux7-memory

Before submitting a human_approval call to the approval queue, flux7-mesh asks flux7-memory whether a human has already approved the same thing enough times. If so, the call passes at once, traced as supervisor:mem7.

What counts as "the same thing" was tightened on 2026-09-29, after the rule turned out to be far looser than it read:

  • only human approvals count, of exactly this tool and this agent;
  • one refusal, from anyone (a human or a supervisor), blocks;
  • only tools that read may be approved from precedents at all (auto_approve_writes restores the former behaviour).

How it works

Tool call → policy: human_approval
  │
  ├─ Level 0: Policy engine (static rules, instant)
  │
  ├─ Level 1: flux7-memory precedents (~10 ms)
  │   the tool reads (named read tool)?          no → skip
  │   human approvals of this tool + agent  ≥ min_approvals
  │   and no refusal of this tool + agent           → auto-approve (supervisor:mem7)
  │   else                                          → escalate
  │
  ├─ Level 1+: External supervisor (sup7: rules, then Jev or a local model)
  │   polls the approval queue, evaluates, resolves
  │
  └─ Level 2: Human (terminal, CLI or flux7-console)

Every decision is written to flux7-memory as a fact tagged with who settled it: by:human, by:supervisor (sup7), by:mem7 (an auto-approval) or by:system (an expiry). Precedents are read with memory_list on exact tags (decision, approved, by:human, <tool>, agent:<id>; then denied for refusals), never with a semantic search.

Why these limits

The flaws below were found in production on 2026-09-29, and each was confirmed on real data:

Flaw Consequence Now
A precedent was keyed on tool and agent, not on the arguments three approved filesystem.write_file in a project let a fourth write anywhere (~/.bashrc) through, around the supervisor precedents approve tools that read only
Auto-approvals were written back as approvals, and counted "3 prior approvals", then 4, 5, 6, without a human only by:human approvals count
sup7's approvals counted like a human's an automatic L1 decision became a way around L1 idem
The count read the first 10 results of a semantic search a refusal past the tenth result was never seen, and nothing checked the line was about this tool exact tags, every decision of the couple

Decisions written before the by: tag carry no resolver and no longer count: precedents start again from human decisions.

"Reads" is decided by tool classification: a named tool whose declared metadata says it reads. Writes, generic tools (shell, SQL, code) and tools of unknown access always go on to the supervisor or a human.

Configuration

memory:
  url: http://localhost:9070    # flux7-memory daemon URL
  token: ""                     # optional Bearer token

supervisor:
  auto_approve: true            # default true when memory.url is set
  min_approvals: 3              # human approvals needed (default 3)
  auto_approve_writes: false    # default: precedents approve reads only

These three settings, with the approval timeout and wait, can be changed at runtime from the control plane (PUT /approvals/settings) or from the flux7-console Approvals page: see Approval flow.

Seeing and forgetting precedents

# every tool + agent flux7-memory remembers, and what the next call would do
curl -s http://localhost:9090/approvals/precedents | python3 -m json.tool
{
  "enabled": true,
  "min_approvals": 3,
  "precedents": [
    {
      "tool": "filesystem.write_file", "agent": "claude",
      "human_approved": 0, "other_approved": 1, "refused": 0, "untagged": 5,
      "auto_approvable": false, "would_auto_approve": false
    }
  ]
}

other_approved are approvals by sup7 or by auto-approval (never counted), untagged are decisions from before the by: tag. To start a couple over:

curl -s -X POST http://localhost:9090/approvals/precedents/forget \
  -H "Content-Type: application/json" -d '{"tool":"filesystem.write_file","agent":"claude"}'

Both are control-plane endpoints (loopback, or Authorization: Bearer <admin_token>). flux7-console shows the same table, with a Forget button, under the approval history.

Example: testing end-to-end

Prerequisites: flux7-mesh built after 2026-09-29, flux7-memory running on :9070, and a policy that sends a read tool to approval, for the test:

rules:
  - tools: ["filesystem.read_text_file"]
    action: human_approval

1. Seed 3 human approvals

for i in 1 2 3; do
  curl -s -X POST http://localhost:9070/rpc -H "Content-Type: application/json" -d "{
    \"jsonrpc\": \"2.0\", \"id\": $i, \"method\": \"tools/call\",
    \"params\": {\"name\": \"memory_store\", \"arguments\": {
      \"key\": \"decision.filesystem.read_text_file.test${i}\",
      \"value\": \"approved by user:alice — agent:claude tool:filesystem.read_text_file\",
      \"tags\": [\"decision\", \"approved\", \"filesystem.read_text_file\", \"by:human\", \"agent:claude\"],
      \"agent\": \"flux7-mesh\"}}}"
done

2. Call the tool

curl -s -X POST http://localhost:9090/tool/filesystem.read_text_file \
  -H "Authorization: Bearer agent:claude" -H "Content-Type: application/json" \
  -d '{"params":{"path":"/tmp/test.txt"}}' | python3 -m json.tool

Expected: no approval, "policy": "allow", and the trace carries "policy_rule": "supervisor:mem7".

3. Check what does not pass

  • the same seeds with filesystem.write_file: the call goes to the approval queue (a write is never approved from precedents);
  • seeds tagged by:supervisor instead of by:human: the call goes to the queue (a supervisor's approval is not a precedent);
  • one more fact tagged denied: the call goes to the queue.

Behavior summary

Scenario Action Traced as
Read tool, ≥ min_approvals human approvals, no refusal Auto-approve supervisor:mem7
Write, generic or unknown-access tool Skip check (unless auto_approve_writes) Normal approval flow
Any refusal of this tool + agent Escalate Normal approval flow
Not enough human approvals Escalate Normal approval flow
flux7-memory down or unreadable answer Escalate Normal approval flow
Params look like a prompt injection Skip check, escalate Normal approval flow
auto_approve: false Skip check Normal approval flow

Graceful degradation

  • flux7-memory unreachable → escalate (3 s timeout, never blocks)
  • flux7-memory returns an error or an unexpected answer → escalate
  • All failure modes fall back to the normal approval flow: auto-approval is additive, never subtractive.

Injection guard

Auto-approval replays a human's past decision on a routine pattern. A call whose parameters carry a prompt-injection marker is no longer routine, so the flux7-memory lookup is skipped entirely: the call is logged (injection risk detected, forcing human review) and goes to the normal approval queue.

The guard is a single function shared by every transport (REST, MCP stdio, MCP Streamable HTTP). It is a regex tripwire on known phrasings, not a classifier; see the known limits on the overview.

Metrics

Monitor the flux7-memory write path at GET /metrics (Prometheus text format). It is a control-plane endpoint: scrape it from loopback, or send Authorization: Bearer <admin_token> (see Control Plane Auth).

agent_mesh_mem7_writes_attempted_total
agent_mesh_mem7_writes_succeeded_total
agent_mesh_mem7_writes_failed_total

A growing failed count means flux7-memory is down: auto-approval escalates everything until it recovers.