approval-md 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/SPEC.md +340 -0
- package/cli.js +4 -0
- package/package.json +10 -0
package/README.md
ADDED
package/SPEC.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# approval.md
|
|
2
|
+
|
|
3
|
+
**Human approval for agent actions.**
|
|
4
|
+
|
|
5
|
+
Version: 0.1.0-draft · Status: Draft · License: MIT · Canonical URL: https://approval.md
|
|
6
|
+
|
|
7
|
+
> Your AGENTS.md says "require approval first." approval.md enforces it, and puts the approve button on your phone.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Abstract
|
|
12
|
+
|
|
13
|
+
approval.md is a file-based convention and reference runtime for gating AI agent actions that have real-world side effects: sending messages, spending money, deleting data, posting publicly, writing to calendars. It defines:
|
|
14
|
+
|
|
15
|
+
1. **`APPROVAL.md`**, a human-authored policy file that declares which classes of side effect an agent may perform autonomously, which require human sign-off, and under what budgets.
|
|
16
|
+
2. **An agentic envelope**, a namespaced YAML frontmatter extension for markdown task files (compatible with [Backlog.md](https://github.com/MrLesk/Backlog.md)) declaring a task's origin, routing, side effects, budget, and approval state.
|
|
17
|
+
3. **An append-only event log** (JSONL, hash-chained) as the tamper-evident source of truth for every proposal, decision, and execution.
|
|
18
|
+
4. **A daemon and CLI** that watch a task folder, route tasks by declared side effects, push approval requests to pluggable channels (Telegram is the reference adapter), and gate execution on granted approvals.
|
|
19
|
+
|
|
20
|
+
Design mantra: **files are the interface, the log is the truth, the database is a cache.**
|
|
21
|
+
|
|
22
|
+
## 2. Motivation and gap
|
|
23
|
+
|
|
24
|
+
Agents now produce more actions than a human can review. The coding world solved its version of this problem: [Backlog.md](https://github.com/MrLesk/Backlog.md), Taskmaster, and Vibe Kanban let you review an agent's *intent* (acceptance criteria before, implementation notes after) instead of every diff, and a bad merge is revertible anyway.
|
|
25
|
+
|
|
26
|
+
Once agents leave the repo, that safety net disappears. A sent email, a payment, a deleted account, a public post: there is no diff to revert. The artifact that must be reviewed *before* execution stops being the plan and becomes the **side-effect declaration**.
|
|
27
|
+
|
|
28
|
+
The ecosystem has converged on prose versions of this idea without an enforcement layer:
|
|
29
|
+
|
|
30
|
+
- [AGENTS.md](https://agents.md) files routinely contain permissions sections splitting actions into "allowed without prompting" and "require approval first" (package installs, `git push`, file deletion, `terraform apply`). These lists are instructions the agent is trusted to obey. Nothing checks.
|
|
31
|
+
- [HumanLayer](https://github.com/humanlayer/humanlayer) provides approval-as-a-service SDKs (`require_approval()` routed to Slack/email/SMS), but couples approval to a hosted service and an in-process decorator rather than a portable, inspectable file convention.
|
|
32
|
+
- [LangGraph interrupts](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/) and the [OpenAI Agents SDK human-in-the-loop flow](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/) pause a graph for approval, but the approval state lives inside one framework's runtime and dies at its boundary.
|
|
33
|
+
- [Google's A2A protocol](https://a2a-protocol.org) models an `input-required` task state, and [MCP](https://modelcontextprotocol.io) has elicitation and an experimental tasks extension, but neither defines what *deserves* escalation, budgets, or a durable audit record.
|
|
34
|
+
- [mission-control](https://github.com/MeisnerDan/mission-control) ships autonomy levels, spend limits, and an approval inbox, but as a closed-world product with its own mutable JSON store: no interchange, and an audit trail that anything with file access can rewrite.
|
|
35
|
+
- Task data standards ([RFC 8984 jsCalendar](https://www.rfc-editor.org/rfc/rfc8984), [RFC 5545 iCalendar VTODO](https://www.rfc-editor.org/rfc/rfc5545)) model due dates and recurrence, and predate the questions "which agent may do this, what will it cost, and who signed off?"
|
|
36
|
+
|
|
37
|
+
The gap: **a portable, file-based, framework-agnostic layer that turns "ask first" from prose into a checked invariant, with a tamper-evident record of who approved what.** approval.md fills exactly that gap and nothing more.
|
|
38
|
+
|
|
39
|
+
## 3. Design principles
|
|
40
|
+
|
|
41
|
+
1. **Files are the interface.** Policy, tasks, and rendered views are markdown a human can read in any editor and an agent can read with `cat`. No required server to inspect state.
|
|
42
|
+
2. **The log is the truth.** All state transitions are immutable events in an append-only, hash-chained JSONL log. Markdown files and databases are projections rebuilt from it.
|
|
43
|
+
3. **Approval state is data; channels are transport.** A pending approval is a fact in the log. Telegram, the local web queue, and the CLI are interchangeable notifiers, never owners of state.
|
|
44
|
+
4. **Deterministic logic in code, LLMs for language only.** Routing, gating, budget math, and log verification are deterministic. Models may *propose* (draft an email, suggest a route); the runtime *decides* per policy.
|
|
45
|
+
5. **CLI-first agent interface.** Agents interact through a CLI whose schemas and instructions ship in `--help` (a lesson from Backlog.md's [MCP retreat](https://mrlesk.dev)). MCP is a thin optional wrapper over the same commands.
|
|
46
|
+
6. **Extend, don't replace.** Task files are Backlog.md-format markdown; the envelope is one namespaced frontmatter key. AGENTS.md permissions sections import as policy. No new task format is invented.
|
|
47
|
+
7. **Honest security.** This is an oversight layer for broadly cooperative agents, with hard enforcement only at adapter boundaries that hold the credentials. The threat model (§11) says exactly what is and is not defended.
|
|
48
|
+
|
|
49
|
+
The keywords MUST, SHOULD, MAY are per [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
|
|
50
|
+
|
|
51
|
+
## 4. Terminology
|
|
52
|
+
|
|
53
|
+
| Term | Meaning |
|
|
54
|
+
|---|---|
|
|
55
|
+
| **Action** | A single side-effecting operation an agent wants to perform (send one email, make one payment). |
|
|
56
|
+
| **Task** | A markdown file describing work; may spawn multiple actions. |
|
|
57
|
+
| **Side-effect class** | A dotted-namespace label for a category of action, e.g. `communicate.email.external`. |
|
|
58
|
+
| **Policy** | The rules in `APPROVAL.md` mapping classes to autonomy levels, approvers, and budgets. |
|
|
59
|
+
| **Autonomy level** | `manual` (approval required per action), `supervised` (proceed, but sampled for retrospective review), `autonomous` (proceed silently). |
|
|
60
|
+
| **Approval** | A recorded human decision (`granted` / `rejected`) on a specific requested action. |
|
|
61
|
+
| **Execution token** | A single-use token minted on grant, required by adapters to execute. |
|
|
62
|
+
| **Channel** | A transport plugin that surfaces requests and collects decisions (Telegram, local web, CLI). |
|
|
63
|
+
| **Adapter** | A side-effect executor (email sender, calendar writer) that holds credentials and refuses to act without a valid token. |
|
|
64
|
+
| **Projection** | Any derived view of the log: the queue file, the SQLite index, the web UI. |
|
|
65
|
+
|
|
66
|
+
## 5. The `APPROVAL.md` policy file
|
|
67
|
+
|
|
68
|
+
`APPROVAL.md` lives at the root of a project (or `~/.approval/APPROVAL.md` for a global personal policy). It is prose for humans plus exactly one fenced ` ```yaml approval-policy ` block for machines. Implementations MUST parse the fenced block and MUST ignore surrounding prose. Implementations MUST also accept the filename `APPROVALS.md` as a fallback, with `APPROVAL.md` taking precedence when both exist.
|
|
69
|
+
|
|
70
|
+
### 5.1 Canonical example
|
|
71
|
+
|
|
72
|
+
````markdown
|
|
73
|
+
# Approval Policy
|
|
74
|
+
|
|
75
|
+
Agents working in this project handle my life admin. Anything that leaves
|
|
76
|
+
the machine gets declared, and the classes below say what I sign off on.
|
|
77
|
+
|
|
78
|
+
```yaml approval-policy
|
|
79
|
+
version: "0.1"
|
|
80
|
+
|
|
81
|
+
defaults:
|
|
82
|
+
autonomy: manual # unknown/undeclared classes require sign-off
|
|
83
|
+
channel: telegram
|
|
84
|
+
approval_ttl: 24h # pending requests expire
|
|
85
|
+
on_expiry: reject
|
|
86
|
+
|
|
87
|
+
approvers:
|
|
88
|
+
carter:
|
|
89
|
+
channels: [telegram, cli]
|
|
90
|
+
|
|
91
|
+
classes:
|
|
92
|
+
read.*: { autonomy: autonomous }
|
|
93
|
+
files.write.workspace: { autonomy: autonomous }
|
|
94
|
+
calendar.write.own: { autonomy: supervised }
|
|
95
|
+
communicate.email.draft: { autonomy: autonomous }
|
|
96
|
+
communicate.email.external:
|
|
97
|
+
autonomy: manual
|
|
98
|
+
approvers: [carter]
|
|
99
|
+
financial.spend:
|
|
100
|
+
autonomy: manual
|
|
101
|
+
approvers: [carter]
|
|
102
|
+
limits: { per_action_usd: 25, daily_usd: 100 }
|
|
103
|
+
public.post: { autonomy: manual }
|
|
104
|
+
data.delete: { autonomy: manual }
|
|
105
|
+
account.auth: { autonomy: manual }
|
|
106
|
+
|
|
107
|
+
budgets:
|
|
108
|
+
global: { daily_usd: 100, daily_actions: 200 }
|
|
109
|
+
|
|
110
|
+
audit:
|
|
111
|
+
supervised_sample_rate: 0.10 # fraction of supervised actions escalated
|
|
112
|
+
# for retrospective human review
|
|
113
|
+
|
|
114
|
+
channels:
|
|
115
|
+
telegram:
|
|
116
|
+
chat_id_env: APPROVAL_TG_CHAT
|
|
117
|
+
token_env: APPROVAL_TG_TOKEN
|
|
118
|
+
web:
|
|
119
|
+
port: 4680
|
|
120
|
+
```
|
|
121
|
+
````
|
|
122
|
+
|
|
123
|
+
### 5.2 Policy semantics
|
|
124
|
+
|
|
125
|
+
- **Matching.** Classes match most-specific-first; `*` is a single-segment wildcard, a trailing `.*` matches any depth. An action whose class matches no rule takes `defaults.autonomy`. Implementations MUST fail closed: unparseable policy means everything is `manual`.
|
|
126
|
+
- **Deny beats allow.** If multiple rules match at equal specificity, the strictest autonomy wins (`manual` > `supervised` > `autonomous`).
|
|
127
|
+
- **Budgets are conjunctive.** An action must pass its class limits AND global budgets. Budget consumption is computed from the log, never from a mutable counter.
|
|
128
|
+
- **`supervised_sample_rate`** implements auditing under a budget: supervised actions execute immediately, and a deterministic pseudo-random sample (seeded from the event hash, so it is reproducible and unguessable in advance by the agent) is queued for after-the-fact human review. See §11 for why.
|
|
129
|
+
- **Agents MUST NOT be able to modify `APPROVAL.md`, the log, or channel credentials.** The reference runtime refuses to mint tokens if the policy file's mtime/hash changed without a signed `policy.updated` event from a human session.
|
|
130
|
+
|
|
131
|
+
## 6. The task envelope
|
|
132
|
+
|
|
133
|
+
Task files are ordinary Backlog.md-style markdown (`backlog/task-042 - Chase-deposit.md` and similar). approval.md adds one frontmatter key, `approval:`, holding the entire envelope. Implementations MUST preserve unknown frontmatter keys when rewriting files, and MUST tolerate tasks with no envelope (they simply cannot request side-effecting execution).
|
|
134
|
+
|
|
135
|
+
### 6.1 Canonical example
|
|
136
|
+
|
|
137
|
+
Note the canonical example is an email, deliberately. This spec governs agent actions in the world; it is unrelated to PR review, CODEOWNERS, or release sign-off.
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
---
|
|
141
|
+
id: task-042
|
|
142
|
+
title: Chase deposit refund from letting agency
|
|
143
|
+
status: In Progress # owned by Backlog.md / your board
|
|
144
|
+
approval:
|
|
145
|
+
origin:
|
|
146
|
+
app: cartsos # provenance: which system created this
|
|
147
|
+
created_by: "human:carter" # or "agent:<id>"
|
|
148
|
+
route:
|
|
149
|
+
assignee: "agent:claude-admin"
|
|
150
|
+
confidence: 0.82
|
|
151
|
+
rationale: "templated chaser, known counterparty, no negotiation"
|
|
152
|
+
state: awaiting # see §6.3
|
|
153
|
+
actions:
|
|
154
|
+
- class: communicate.email.external
|
|
155
|
+
summary: "Send deposit chaser to agency@example.co.uk"
|
|
156
|
+
reversible: false
|
|
157
|
+
est_cost_usd: 0.02
|
|
158
|
+
idempotency_key: "task-042:chaser:2026-08-04"
|
|
159
|
+
budget:
|
|
160
|
+
max_cost_usd: 0.50
|
|
161
|
+
max_latency: 6h
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Description
|
|
165
|
+
Deposit (£1,200) due back since 12 July. One polite chaser sent by me on
|
|
166
|
+
21 July, no reply. Agent should send a firmer follow-up citing the
|
|
167
|
+
deposit-protection scheme deadline.
|
|
168
|
+
|
|
169
|
+
## Acceptance Criteria
|
|
170
|
+
- [ ] Email sent to the agency referencing scheme deadline
|
|
171
|
+
- [ ] Reply, if any, filed back onto this task
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 6.2 Envelope fields
|
|
175
|
+
|
|
176
|
+
| Field | Req | Meaning |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `origin.app` | MUST | Source system (`cartsos`, `jobmaxxing`, `manual`, …). |
|
|
179
|
+
| `origin.created_by` | MUST | `human:<id>` or `agent:<id>`. |
|
|
180
|
+
| `route.assignee` | SHOULD | `human` or `agent:<id>`. Routing proposals from agents are events, never silent edits. |
|
|
181
|
+
| `route.confidence` | MAY | 0.0–1.0; used as a monitoring signal (§11). |
|
|
182
|
+
| `state` | MUST | Approval lifecycle state (§6.3), distinct from board `status`. |
|
|
183
|
+
| `actions[]` | MUST for execution | Each declared action: `class`, `summary`, `reversible`, `est_cost_usd`, `idempotency_key`. |
|
|
184
|
+
| `budget` | MAY | Task-level caps, conjunctive with policy budgets. |
|
|
185
|
+
| `idempotency_key` | MUST per action | Stable string; adapters MUST refuse to execute the same key twice. |
|
|
186
|
+
|
|
187
|
+
### 6.3 Approval lifecycle
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
proposed ──▶ awaiting ──▶ approved ──▶ executed
|
|
191
|
+
│ │
|
|
192
|
+
│ └─▶ revoked (human, before execution)
|
|
193
|
+
├─▶ rejected
|
|
194
|
+
└─▶ expired (TTL, per on_expiry)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`state` is a **projection** of log events; the file is updated by the daemon after the event is appended, never the reverse. A file edit that contradicts the log is itself logged (`envelope.drift`) and surfaced.
|
|
198
|
+
|
|
199
|
+
## 7. Side-effect taxonomy (v0.1)
|
|
200
|
+
|
|
201
|
+
Dotted, hierarchical, extensible. Top-level namespaces are reserved by this spec; implementations MAY add sub-classes freely and SHOULD upstream common ones.
|
|
202
|
+
|
|
203
|
+
| Namespace | Examples | Default gravity |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `read.*` | web fetch, file read, API GET | autonomous |
|
|
206
|
+
| `files.write.*` | workspace writes, repo commits | autonomous/supervised |
|
|
207
|
+
| `communicate.*` | `.email.external`, `.message.telegram`, `.email.draft` | manual for external sends |
|
|
208
|
+
| `calendar.write.*` | own calendar, shared calendar | supervised |
|
|
209
|
+
| `financial.*` | `.spend`, `.transfer`, `.subscribe` | manual, always |
|
|
210
|
+
| `public.*` | `.post` (X, forums), `.publish` | manual, always |
|
|
211
|
+
| `data.delete` | destructive deletes outside workspace | manual, always |
|
|
212
|
+
| `account.*` | `.auth`, `.create`, `.credential` | manual, always |
|
|
213
|
+
| `physical.*` | orders, bookings with cancellation cost | manual |
|
|
214
|
+
|
|
215
|
+
Two invariants: an action's class MUST be declared before an execution token can be requested for it, and `reversible: false` actions MUST NOT be eligible for `autonomous` regardless of policy (the runtime enforces this floor).
|
|
216
|
+
|
|
217
|
+
## 8. The event log
|
|
218
|
+
|
|
219
|
+
`.approval/log/events.jsonl`, append-only. One JSON object per line:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{"seq":17,"ts":"2026-08-04T09:14:02Z","event":"approval.granted",
|
|
223
|
+
"task":"task-042","action_key":"task-042:chaser:2026-08-04",
|
|
224
|
+
"actor":"human:carter","channel":"telegram",
|
|
225
|
+
"payload":{"note":"go, but cc me"},
|
|
226
|
+
"prev":"b3c9…","hash":"a41f…"}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- `hash` = SHA-256 over the canonical serialization of the record with `prev` included; `prev` = previous record's hash. `approval log verify` MUST detect any mutation or truncation. Optionally, the log directory is a git repo and the daemon commits per event with its own identity, giving signed, distributed tamper evidence for free (the [TaskChampion operation log](https://github.com/GothenburgBitFactory/taskchampion) and [Automerge](https://automerge.org) both converged on op-logs for related reasons; see also Ink & Switch's [local-first task framework](https://www.inkandswitch.com/patchwork/notebook/tasks-01/)).
|
|
230
|
+
- **Event types (v0.1):** `task.registered`, `route.proposed`, `route.accepted`, `approval.requested`, `approval.granted`, `approval.rejected`, `approval.expired`, `approval.revoked`, `execution.started`, `execution.completed`, `execution.failed`, `budget.exceeded`, `policy.updated`, `envelope.drift`, `audit.sampled`, `audit.reviewed`.
|
|
231
|
+
- Events MUST validate against the JSON Schemas in `schema/` before append. Validation at the write boundary is itself a control: an agent physically cannot request execution without declaring a class, key, and cost estimate.
|
|
232
|
+
|
|
233
|
+
## 9. Projections
|
|
234
|
+
|
|
235
|
+
1. **The queue** (`.approval/QUEUE.md`): a rendered, read-only markdown view of pending requests (task, actions, declared effects, cost, TTL countdown) plus the sampled-audit backlog. Regenerated on every relevant event. This is the screenshot; it is never the truth.
|
|
236
|
+
2. **The index** (`.approval/index.sqlite`): rebuilt from the log (`approval reindex`), used for queries like "pending manual approvals touching `financial.*`, oldest first." Any SQLite client, including DuckDB, can read it; deleting it loses nothing.
|
|
237
|
+
|
|
238
|
+
## 10. Runtime
|
|
239
|
+
|
|
240
|
+
### 10.1 CLI (primary interface, for humans and agents)
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
approval init # scaffold APPROVAL.md, .approval/, schemas
|
|
244
|
+
approval instructions # full agent-facing usage guide (also in --help)
|
|
245
|
+
approval register <task-file> # validate envelope, append task.registered
|
|
246
|
+
approval request <task> [--action <key>] # -> approval.requested (or auto-grant
|
|
247
|
+
# per policy for supervised/autonomous)
|
|
248
|
+
approval wait <task> --timeout 6h # block until decided; exit code = decision
|
|
249
|
+
approval grant|reject|revoke <request-id> [--note …] # human-only verbs
|
|
250
|
+
approval token <action-key> # print single-use execution token if granted
|
|
251
|
+
approval run -- <cmd…> # gate arbitrary commands: mints token, runs, logs
|
|
252
|
+
approval queue [--json] # pending requests
|
|
253
|
+
approval log verify | tail | export
|
|
254
|
+
approval policy check|test <class> # explain what policy does with a class
|
|
255
|
+
approval reindex | render
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Machine-readable output: every command supports `--json`; schemas for inputs and outputs are printed by `approval instructions --schemas`.
|
|
259
|
+
|
|
260
|
+
### 10.2 Daemon
|
|
261
|
+
|
|
262
|
+
`approvald` watches the backlog folder and the log: validates new/changed envelopes, applies policy, dispatches channel notifications, expires TTLs, samples supervised actions for audit, re-renders projections, and (optionally) polls upstream sources. Loop safety: three consecutive `execution.failed` events for one task escalate to `manual` regardless of policy.
|
|
263
|
+
|
|
264
|
+
### 10.3 Channels
|
|
265
|
+
|
|
266
|
+
Interface: `notify(request) -> delivery_id`, `poll()/webhook() -> decision`. Decisions become log events; channels hold no state. v0.1 ships **cli** (zero-config prompt), **web** (local queue page with grant/reject), and **telegram** (reference push channel: message with declared effects + inline Approve/Reject buttons; callback verified against approver identity). Channel breadth is explicitly out of scope; HumanLayer exists for Slack/email/SMS enterprises.
|
|
267
|
+
|
|
268
|
+
### 10.4 Adapters and hard enforcement
|
|
269
|
+
|
|
270
|
+
Adapters (e.g. `adapter-email`, `adapter-gcal`) hold the actual credentials in an encrypted vault and MUST require a valid, unexpired, single-use execution token bound to the action's `idempotency_key`. This is the hard boundary: an agent that bypasses the CLI still cannot send, spend, or delete, because the credentials only answer to tokens. (Same architectural intuition as mission-control's vault + "agents cannot modify security settings.")
|
|
271
|
+
|
|
272
|
+
### 10.5 MCP wrapper (optional)
|
|
273
|
+
|
|
274
|
+
A thin MCP server exposing the same verbs as tools (`request_approval`, `wait_decision`, `get_queue`, …) for clients where MCP is more ergonomic than shelling out. It shares the CLI's code paths; the [MCP tasks extension](https://modelcontextprotocol.io) MAY be mapped onto `awaiting` when client support stabilizes, and A2A's `input-required` maps cleanly for agent-to-agent deployments.
|
|
275
|
+
|
|
276
|
+
## 11. Security and control model
|
|
277
|
+
|
|
278
|
+
approval.md operationalizes, at consumer scale, the structure of trusted monitoring with limited auditing from the AI-control literature ([Greenblatt et al., 2023](https://arxiv.org/abs/2312.06942)):
|
|
279
|
+
|
|
280
|
+
- The **untrusted policy** is the agent proposing actions.
|
|
281
|
+
- **Affordance restrictions** are the adapter/token boundary plus the taxonomy: undeclared classes cannot execute.
|
|
282
|
+
- The **trusted, expensive overseer** is the human, whose limited attention is the audit budget; `manual` classes spend it before execution, `supervised_sample_rate` spends it retrospectively.
|
|
283
|
+
- The **monitoring signal** is deterministic policy (class, cost, reversibility) plus `route.confidence`; escalation is triggered by the runtime, never self-assessed by the executing agent at execution time.
|
|
284
|
+
- The **audit trail** is the hash-chained log, which the agent cannot rewrite undetected.
|
|
285
|
+
|
|
286
|
+
**Defended:** honest-mistake side effects, prompt-injected instructions that try to escalate side effects silently, retries double-executing (idempotency keys), quiet budget creep, after-the-fact disputes about who approved what, tampering with history.
|
|
287
|
+
|
|
288
|
+
**Not defended, stated plainly:** an agent with direct credential access outside adapters; a compromised host; deceptive *content* inside an approved action (you approve "send email," the runtime does not verify the prose is honest, though channels SHOULD display full content for `manual` actions); collusion between agent and a careless approver; and this is engineering informed by control research, offering no guarantees against a schemer that control protocols proper are designed to stress-test. Keep `manual` floors on irreversible classes.
|
|
289
|
+
|
|
290
|
+
## 12. Interoperability
|
|
291
|
+
|
|
292
|
+
- **Backlog.md:** native. Tasks live in `backlog/`, the envelope is one preserved frontmatter key, board `status` and approval `state` are independent. approval.md ships no board; use Backlog.md's.
|
|
293
|
+
- **AGENTS.md import:** `approval import agents-md` parses "require approval first / allowed without prompting" permissions sections into draft policy classes for human confirmation, turning existing prose conventions into enforced policy.
|
|
294
|
+
- **Inbound adapters (post-v1):** CartsOS/Telegram capture, arbitrary apps via `approval register --json`.
|
|
295
|
+
- **Outbound sinks (post-v1):** approved+scheduled tasks mirrored to TickTick / Google Tasks / Google Calendar as views, never sources of truth. Mapping via [RFC 8984 jsCalendar Task](https://www.rfc-editor.org/rfc/rfc8984) with the envelope as a vendor extension, `X-APPROVAL-*` in [VTODO](https://www.rfc-editor.org/rfc/rfc5545).
|
|
296
|
+
|
|
297
|
+
## 13. Non-goals
|
|
298
|
+
|
|
299
|
+
No new task file format. No kanban UI. No agent framework or orchestration platform. No hosted service (local-first; a sync story can come later). No channel breadth beyond the three shipped. No claim of scheming-robustness (§11).
|
|
300
|
+
|
|
301
|
+
## 14. Repository layout and roadmap
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
approval.md/
|
|
305
|
+
├── SPEC.md # this file
|
|
306
|
+
├── APPROVAL.md # this repo's own policy (dogfood from day one)
|
|
307
|
+
├── schema/ # JSON Schemas: policy, envelope, each event type
|
|
308
|
+
├── src/ # runtime: core/ (log, policy, gate), cli/, daemon/,
|
|
309
|
+
│ # channels/{cli,web,telegram}/, adapters/, mcp/
|
|
310
|
+
├── examples/ # personal-admin/, backlog-md-project/, agents-md-import/
|
|
311
|
+
└── tests/ # log verification, policy matching, gating, TTL, idempotency
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Milestones sized for agent-driven development (each = one reviewable task):
|
|
315
|
+
|
|
316
|
+
- **M0** Schemas for policy, envelope, events + fixtures.
|
|
317
|
+
- **M1** Log: append, hash-chain, verify, reindex.
|
|
318
|
+
- **M2** Policy engine: parse, match, explain (`policy test`), fail-closed.
|
|
319
|
+
- **M3** Gate: request/grant/reject/expire, tokens, idempotency, `approval run`.
|
|
320
|
+
- **M4** Channels: cli, web queue, Telegram; QUEUE.md renderer.
|
|
321
|
+
- **M5** Daemon: watch, TTL, sampling, loop-escalation.
|
|
322
|
+
- **M6** Backlog.md round-trip + AGENTS.md import.
|
|
323
|
+
- **M7** First adapter (email) + vault; end-to-end demo: agent drafts chaser → Telegram ping → approve from phone → sent → log verifies.
|
|
324
|
+
- **M8** MCP wrapper. Post-v1: TickTick/GCal sinks, CartsOS inbound.
|
|
325
|
+
|
|
326
|
+
## 15. References
|
|
327
|
+
|
|
328
|
+
Backlog.md · https://github.com/MrLesk/Backlog.md — the markdown-task convention this extends
|
|
329
|
+
AGENTS.md · https://agents.md — the prose permissions this enforces
|
|
330
|
+
HumanLayer · https://github.com/humanlayer/humanlayer — approval-as-a-service (SDK/hosted counterpart)
|
|
331
|
+
LangGraph HITL · https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/
|
|
332
|
+
OpenAI Agents SDK HITL · https://openai.github.io/openai-agents-js/guides/human-in-the-loop/
|
|
333
|
+
A2A protocol · https://a2a-protocol.org — `input-required` lifecycle state
|
|
334
|
+
Model Context Protocol · https://modelcontextprotocol.io
|
|
335
|
+
mission-control · https://github.com/MeisnerDan/mission-control — autonomy levels, vault, spend limits
|
|
336
|
+
AI Control (Greenblatt et al., 2023) · https://arxiv.org/abs/2312.06942 — trusted monitoring under an audit budget
|
|
337
|
+
TaskChampion · https://github.com/GothenburgBitFactory/taskchampion — operation-log task storage
|
|
338
|
+
Ink & Switch, A Local-First Task Framework · https://www.inkandswitch.com/patchwork/notebook/tasks-01/
|
|
339
|
+
jsCalendar · https://www.rfc-editor.org/rfc/rfc8984 · iCalendar · https://www.rfc-editor.org/rfc/rfc5545
|
|
340
|
+
RFC 2119 · https://www.rfc-editor.org/rfc/rfc2119
|
package/cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "approval-md",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Human approval for agent actions. CLI runtime for the approval.md convention (pre-release).",
|
|
5
|
+
"bin": { "approval": "./cli.js" },
|
|
6
|
+
"files": ["cli.js", "SPEC.md", "README.md"],
|
|
7
|
+
"repository": "github:approval-md/approval.md",
|
|
8
|
+
"homepage": "https://approval.md",
|
|
9
|
+
"license": "MIT"
|
|
10
|
+
}
|