pluribus-context 0.3.35 → 0.3.37
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/CHANGELOG.md +12 -0
- package/README.md +3 -2
- package/bin/pluribus.js +12 -0
- package/docs/agent-firewall-denial-audit.md +95 -0
- package/docs/ai-pr-review-receipts.md +20 -0
- package/docs/community-review-packet.md +19 -0
- package/docs/compaction-resume-receipts.md +43 -0
- package/docs/context-budget-receipts.md +1 -1
- package/docs/controlled-learning-queue.md +48 -0
- package/docs/install-plan-receipts.md +2 -0
- package/docs/loaded-resource-boundary.md +97 -0
- package/docs/mcp-runtime-config-receipts.md +91 -0
- package/docs/memory-write-policy-receipts.md +41 -0
- package/docs/parallel-session-review-ledger.md +103 -0
- package/docs/phase-boundary-contracts.md +87 -0
- package/docs/review-primitive-gate.md +2 -0
- package/docs/skill-install-receipts.md +102 -0
- package/docs/skill-policy-receipts.md +1 -1
- package/docs/skill-use-rate-receipts.md +104 -0
- package/examples/agent-firewall-denial-audit/README.md +14 -0
- package/examples/agent-firewall-denial-audit/check-denial-audit.mjs +116 -0
- package/examples/agent-firewall-denial-audit/denial-envelope.json +9 -0
- package/examples/agent-firewall-denial-audit/operator-audit-record.json +20 -0
- package/examples/agent-skills/README.md +10 -0
- package/examples/ai-pr-review-receipts/.github/workflows/ai-pr-review-receipt.yml +25 -0
- package/examples/ai-pr-review-receipts/README.md +51 -1
- package/examples/ai-pr-review-receipts/incomplete-review-primitive-receipt.json +43 -0
- package/examples/ai-pr-review-receipts/review-primitive-receipt.json +60 -0
- package/examples/compaction-resume-receipts/README.md +12 -0
- package/examples/compaction-resume-receipts/check-resume-receipt.mjs +116 -0
- package/examples/compaction-resume-receipts/safe-resume-receipt.json +52 -0
- package/examples/compaction-resume-receipts/unsafe-resume-receipt.json +41 -0
- package/examples/controlled-learning-queue/README.md +26 -0
- package/examples/controlled-learning-queue/check-learning-queue.mjs +44 -0
- package/examples/controlled-learning-queue/leads/acme-job-card.md +12 -0
- package/examples/controlled-learning-queue/learning_queue.md +27 -0
- package/examples/controlled-learning-queue/memory/durable.md +10 -0
- package/examples/controlled-learning-queue/memory/working-notes.md +5 -0
- package/examples/controlled-learning-queue/role/job-contract.md +18 -0
- package/examples/controlled-learning-queue/skills/qualify-lead.md +17 -0
- package/examples/loaded-resource-boundary/README.md +22 -0
- package/examples/loaded-resource-boundary/check-loaded-resource-boundary.mjs +65 -0
- package/examples/loaded-resource-boundary/loaded-resource-boundary.json +69 -0
- package/examples/mcp-runtime-config-receipts/README.md +15 -0
- package/examples/mcp-runtime-config-receipts/check-mcp-runtime-config-receipt.mjs +127 -0
- package/examples/mcp-runtime-config-receipts/mcp-runtime-config-receipt.json +82 -0
- package/examples/memory-write-policy/README.md +28 -0
- package/examples/memory-write-policy/approved-memory-update.json +48 -0
- package/examples/memory-write-policy/check-memory-update.mjs +120 -0
- package/examples/memory-write-policy/quarantined-memory-update.json +43 -0
- package/examples/parallel-session-review-ledger/README.md +13 -0
- package/examples/parallel-session-review-ledger/check-parallel-session-review-ledger.mjs +69 -0
- package/examples/parallel-session-review-ledger/parallel-session-review-ledger.json +72 -0
- package/examples/phase-boundary-contract/README.md +23 -0
- package/examples/phase-boundary-contract/check-phase-boundary.mjs +73 -0
- package/examples/phase-boundary-contract/phase-boundary-contract.json +68 -0
- package/examples/skill-install-receipts/README.md +31 -0
- package/examples/skill-install-receipts/check-skill-install-receipt.mjs +75 -0
- package/examples/skill-install-receipts/skill-install-receipt.json +79 -0
- package/examples/skill-use-rate-receipts/README.md +16 -0
- package/examples/skill-use-rate-receipts/check-skill-use-rate.mjs +89 -0
- package/examples/skill-use-rate-receipts/skill-use-rate-receipt.json +79 -0
- package/package.json +2 -1
- package/{examples/agent-skills → skills}/context-receipts/README.md +4 -4
- package/skills/context-receipts/SKILL.md +206 -0
- package/{examples/agent-skills → skills}/skill-policy-receipts/README.md +1 -1
- package/skills/skill-policy-receipts/SKILL.md +77 -0
- package/src/commands/demo.js +155 -0
- package/src/index.js +1 -0
- package/src/utils/version.js +1 -1
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# AI PR review receipts example
|
|
2
2
|
|
|
3
|
-
This example contains a copyable GitHub PR template for agent-generated or agent-modified pull requests.
|
|
3
|
+
This example contains a copyable GitHub PR template and CI gate for agent-generated or agent-modified pull requests.
|
|
4
4
|
|
|
5
5
|
Use it when review risk depends on blast radius: schema/data contracts, async paths, rollout gates, external side effects, generated/public interfaces, or security-sensitive config.
|
|
6
|
+
|
|
7
|
+
The point is not to make every AI PR small. It is to make the risky boundaries reviewable enough that CI or a maintainer can decide: merge, route to a human owner, or stop.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
- [`.github/pull_request_template.md`](.github/pull_request_template.md) — human-readable PR body section for blast-radius review.
|
|
12
|
+
- [`.github/workflows/ai-pr-review-receipt.yml`](.github/workflows/ai-pr-review-receipt.yml) — copyable GitHub Actions gate that validates a machine-readable receipt.
|
|
13
|
+
- [`review-primitive-receipt.json`](review-primitive-receipt.json) — passing receipt fixture.
|
|
14
|
+
- [`incomplete-review-primitive-receipt.json`](incomplete-review-primitive-receipt.json) — failing fixture for partial/unsafe evidence.
|
|
15
|
+
|
|
16
|
+
## 60-second local smoke
|
|
17
|
+
|
|
18
|
+
From the repository root:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
node examples/review-primitive-gate/check-review-receipt.mjs \
|
|
22
|
+
examples/ai-pr-review-receipts/review-primitive-receipt.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Expected: `ok: true`.
|
|
26
|
+
|
|
27
|
+
Then run the incomplete fixture:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node examples/review-primitive-gate/check-review-receipt.mjs \
|
|
31
|
+
examples/ai-pr-review-receipts/incomplete-review-primitive-receipt.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Expected: non-zero exit. The failure is intentional: unapproved scope change, skipped required test, missing evidence, and `partial` resume state should not silently pass a merge gate.
|
|
35
|
+
|
|
36
|
+
## GitHub Actions usage
|
|
37
|
+
|
|
38
|
+
1. Copy `.github/workflows/ai-pr-review-receipt.yml` into your repo.
|
|
39
|
+
2. Have your Claude Code / Codex / Cursor / OpenClaw / review bot emit a privacy-safe receipt at `artifacts/review-primitive-receipt.json`.
|
|
40
|
+
3. Keep raw prompts, transcripts, source code, secrets, stack traces, customer data, and raw tool output out of the receipt.
|
|
41
|
+
4. Let the workflow fail if the receipt is partial, unsafe, missing evidence, or outside approved boundaries.
|
|
42
|
+
|
|
43
|
+
The template and JSON receipt can be used together: the PR body explains the blast radius to humans, while the JSON receipt gives CI a hard decision primitive.
|
|
44
|
+
|
|
45
|
+
## Why this exists
|
|
46
|
+
|
|
47
|
+
Large AI PRs are not automatically unsafe, and small PRs are not automatically reviewable. Diff size is a proxy. This receipt makes the underlying question explicit:
|
|
48
|
+
|
|
49
|
+
- Which assignment did the agent accept?
|
|
50
|
+
- What read/write boundaries were approved?
|
|
51
|
+
- Did scope or access change mid-run, and was it approved?
|
|
52
|
+
- Which required checks actually ran, with evidence?
|
|
53
|
+
- Did the agent refuse unsafe operations?
|
|
54
|
+
- Is the handoff `complete`, `partial`, or `unsafe-to-resume`?
|
|
55
|
+
- What is the next safe action for the reviewer?
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "agent.review_primitive_receipt.v1",
|
|
3
|
+
"assignment_id": "pr-483-agent-review",
|
|
4
|
+
"run_id": "run-2026-05-31T23-10Z",
|
|
5
|
+
"agent": {
|
|
6
|
+
"tool": "claude-code-github-actions",
|
|
7
|
+
"role": "pr-reviewer"
|
|
8
|
+
},
|
|
9
|
+
"approved_boundaries": {
|
|
10
|
+
"read": ["src/auth/**", "tests/auth/**"],
|
|
11
|
+
"write": ["tests/auth/**"],
|
|
12
|
+
"network": false
|
|
13
|
+
},
|
|
14
|
+
"scope_access_changes": [
|
|
15
|
+
{
|
|
16
|
+
"change": "write src/auth/session.ts",
|
|
17
|
+
"reason": "agent attempted to patch production auth code during review",
|
|
18
|
+
"approved": false,
|
|
19
|
+
"approved_by": ""
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"commands_and_checks": [
|
|
23
|
+
{
|
|
24
|
+
"name": "npm test -- tests/auth",
|
|
25
|
+
"kind": "required_test",
|
|
26
|
+
"status": "skipped",
|
|
27
|
+
"evidence": "not-run"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"refused_operations": [],
|
|
31
|
+
"handoff": {
|
|
32
|
+
"changed_files_bucket": "under_10",
|
|
33
|
+
"evidence_path": "artifacts/pr-483/review-primitive-receipt.json",
|
|
34
|
+
"next_safe_action": "human must inspect attempted auth write and run required tests before merge"
|
|
35
|
+
},
|
|
36
|
+
"resume_state": "partial",
|
|
37
|
+
"privacy": {
|
|
38
|
+
"raw_prompts_logged": false,
|
|
39
|
+
"raw_tool_output_logged": false,
|
|
40
|
+
"source_code_logged": false,
|
|
41
|
+
"secrets_logged": false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "agent.review_primitive_receipt.v1",
|
|
3
|
+
"assignment_id": "pr-482-agent-review",
|
|
4
|
+
"run_id": "run-2026-05-31T23-00Z",
|
|
5
|
+
"agent": {
|
|
6
|
+
"tool": "claude-code-github-actions",
|
|
7
|
+
"role": "pr-reviewer"
|
|
8
|
+
},
|
|
9
|
+
"approved_boundaries": {
|
|
10
|
+
"read": ["src/billing/**", "tests/billing/**", "docs/rollout/**"],
|
|
11
|
+
"write": ["tests/billing/**", "docs/review-receipts/**"],
|
|
12
|
+
"network": false
|
|
13
|
+
},
|
|
14
|
+
"scope_access_changes": [
|
|
15
|
+
{
|
|
16
|
+
"change": "read docs/rollout/**",
|
|
17
|
+
"reason": "verify feature-flag and rollback evidence for the PR receipt",
|
|
18
|
+
"approved": true,
|
|
19
|
+
"approved_by": "maintainer"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"commands_and_checks": [
|
|
23
|
+
{
|
|
24
|
+
"name": "npm test -- tests/billing",
|
|
25
|
+
"kind": "required_test",
|
|
26
|
+
"status": "passed",
|
|
27
|
+
"evidence": "https://github.com/example/repo/actions/runs/123#billing-tests"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "npm run lint",
|
|
31
|
+
"kind": "required_check",
|
|
32
|
+
"status": "passed",
|
|
33
|
+
"evidence": "https://github.com/example/repo/actions/runs/123#lint"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "migration rollback smoke",
|
|
37
|
+
"kind": "required_check",
|
|
38
|
+
"status": "passed",
|
|
39
|
+
"evidence": "artifacts/pr-482/rollback-smoke.txt"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"refused_operations": [
|
|
43
|
+
{
|
|
44
|
+
"operation": "write src/billing/charge-customer.ts",
|
|
45
|
+
"reason": "outside approved write boundary; reviewer requested tests/docs only"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"handoff": {
|
|
49
|
+
"changed_files_bucket": "under_5",
|
|
50
|
+
"evidence_path": "artifacts/pr-482/review-primitive-receipt.json",
|
|
51
|
+
"next_safe_action": "review billing test assertions and rollback evidence before merge"
|
|
52
|
+
},
|
|
53
|
+
"resume_state": "complete",
|
|
54
|
+
"privacy": {
|
|
55
|
+
"raw_prompts_logged": false,
|
|
56
|
+
"raw_tool_output_logged": false,
|
|
57
|
+
"source_code_logged": false,
|
|
58
|
+
"secrets_logged": false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Compaction resume receipt gate
|
|
2
|
+
|
|
3
|
+
This example validates a privacy-safe receipt for `PostCompact`, `SessionStart(compact)`, or any workflow that resumes an AI coding session after summarization.
|
|
4
|
+
|
|
5
|
+
Run:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node check-resume-receipt.mjs safe-resume-receipt.json
|
|
9
|
+
node check-resume-receipt.mjs unsafe-resume-receipt.json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Use it as a tiny CI/hook check before an agent continues work after compaction. The receipt records hashes, refs, and verdicts — not raw transcripts or raw instruction bodies.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
const [file] = process.argv.slice(2)
|
|
5
|
+
|
|
6
|
+
if (!file) {
|
|
7
|
+
console.error('Usage: node check-resume-receipt.mjs <compaction-resume-receipt.json>')
|
|
8
|
+
process.exit(2)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let receipt
|
|
12
|
+
try {
|
|
13
|
+
receipt = JSON.parse(readFileSync(file, 'utf8'))
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(JSON.stringify({ ok: false, file, errors: [`invalid JSON: ${error.message}`] }, null, 2))
|
|
16
|
+
process.exit(2)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const errors = []
|
|
20
|
+
const warnings = []
|
|
21
|
+
|
|
22
|
+
if (receipt.type !== 'agent.compaction_resume_receipt.v1') {
|
|
23
|
+
errors.push('type must be agent.compaction_resume_receipt.v1')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const key of ['compaction_event_id', 'session_id', 'trigger']) {
|
|
27
|
+
if (!receipt[key] || typeof receipt[key] !== 'string') {
|
|
28
|
+
errors.push(`${key} is required`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const transcript = receipt.transcript || {}
|
|
33
|
+
if (!transcript.range || typeof transcript.range !== 'string') {
|
|
34
|
+
errors.push('transcript.range is required')
|
|
35
|
+
}
|
|
36
|
+
if (!transcript.content_hash || typeof transcript.content_hash !== 'string') {
|
|
37
|
+
errors.push('transcript.content_hash is required; do not log raw transcript text')
|
|
38
|
+
}
|
|
39
|
+
if (transcript.raw_text_logged !== false) {
|
|
40
|
+
errors.push('transcript.raw_text_logged must be false')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const summary = receipt.summary || {}
|
|
44
|
+
if (!summary.content_hash || typeof summary.content_hash !== 'string') {
|
|
45
|
+
errors.push('summary.content_hash is required')
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isInteger(summary.token_count) || summary.token_count <= 0) {
|
|
48
|
+
errors.push('summary.token_count must be a positive integer')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const reloads = Array.isArray(receipt.instruction_sources_reloaded)
|
|
52
|
+
? receipt.instruction_sources_reloaded
|
|
53
|
+
: []
|
|
54
|
+
if (reloads.length === 0) {
|
|
55
|
+
errors.push('instruction_sources_reloaded must include at least one source')
|
|
56
|
+
}
|
|
57
|
+
for (const [index, source] of reloads.entries()) {
|
|
58
|
+
if (!source.kind || typeof source.kind !== 'string') {
|
|
59
|
+
errors.push(`instruction_sources_reloaded[${index}].kind is required`)
|
|
60
|
+
}
|
|
61
|
+
if (!source.ref || typeof source.ref !== 'string') {
|
|
62
|
+
errors.push(`instruction_sources_reloaded[${index}].ref is required`)
|
|
63
|
+
}
|
|
64
|
+
if (!source.content_hash || typeof source.content_hash !== 'string') {
|
|
65
|
+
errors.push(`instruction_sources_reloaded[${index}].content_hash is required`)
|
|
66
|
+
}
|
|
67
|
+
if (source.raw_body_logged !== false) {
|
|
68
|
+
errors.push(`instruction_sources_reloaded[${index}].raw_body_logged must be false`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const state = receipt.state || {}
|
|
73
|
+
const kept = Array.isArray(state.kept) ? state.kept : []
|
|
74
|
+
const lost = Array.isArray(state.lost) ? state.lost : []
|
|
75
|
+
if (kept.length === 0) {
|
|
76
|
+
warnings.push('state.kept is empty; reviewers may not know what survived compaction')
|
|
77
|
+
}
|
|
78
|
+
if (!Array.isArray(state.lost)) {
|
|
79
|
+
errors.push('state.lost must be an array, even when empty')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const verdict = receipt.resume_verdict || {}
|
|
83
|
+
if (!['true', 'false', 'unknown'].includes(String(verdict.safe_to_resume))) {
|
|
84
|
+
errors.push('resume_verdict.safe_to_resume must be true, false, or unknown')
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(verdict.reasons) || verdict.reasons.length === 0) {
|
|
87
|
+
errors.push('resume_verdict.reasons must explain the verdict')
|
|
88
|
+
}
|
|
89
|
+
if (String(verdict.safe_to_resume) !== 'true') {
|
|
90
|
+
errors.push(`safe_to_resume is ${verdict.safe_to_resume}; stop, reload, or ask before continuing`)
|
|
91
|
+
}
|
|
92
|
+
if (lost.some((item) => item && item.blocks_resume === true)) {
|
|
93
|
+
errors.push('state.lost contains at least one blocks_resume=true item')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const privacy = receipt.privacy || {}
|
|
97
|
+
for (const key of ['raw_prompts_logged', 'raw_tool_output_logged', 'secrets_logged', 'full_instruction_bodies_logged']) {
|
|
98
|
+
if (privacy[key] !== false) {
|
|
99
|
+
errors.push(`privacy.${key} must be false`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const result = {
|
|
104
|
+
ok: errors.length === 0,
|
|
105
|
+
file,
|
|
106
|
+
compaction_event_id: receipt.compaction_event_id,
|
|
107
|
+
session_id: receipt.session_id,
|
|
108
|
+
safe_to_resume: verdict.safe_to_resume,
|
|
109
|
+
reloaded_sources: reloads.map((source) => `${source.kind}:${source.ref}`),
|
|
110
|
+
lost: lost.map((item) => item.ref || item.kind || 'unknown'),
|
|
111
|
+
errors,
|
|
112
|
+
warnings
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(JSON.stringify(result, null, 2))
|
|
116
|
+
process.exit(result.ok ? 0 : 1)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "agent.compaction_resume_receipt.v1",
|
|
3
|
+
"compaction_event_id": "compact-2026-06-01T15-02-59Z",
|
|
4
|
+
"session_id": "codex-session-42",
|
|
5
|
+
"trigger": "PostCompact",
|
|
6
|
+
"transcript": {
|
|
7
|
+
"range": "messages:1-184",
|
|
8
|
+
"content_hash": "sha256-7f8f6fbf8a3e3fe0e9f6b59efac0e771d8f64a9ebff3e9b0f5162e43c2e3e89a",
|
|
9
|
+
"raw_text_logged": false
|
|
10
|
+
},
|
|
11
|
+
"summary": {
|
|
12
|
+
"content_hash": "sha256-8a4f6242e9800f1452bcfd5dbec7e9f1f0b543d72b0c96e57dece44f6c4b8de4",
|
|
13
|
+
"token_count": 1240
|
|
14
|
+
},
|
|
15
|
+
"instruction_sources_reloaded": [
|
|
16
|
+
{
|
|
17
|
+
"kind": "AGENTS.md",
|
|
18
|
+
"ref": "repo-root/AGENTS.md",
|
|
19
|
+
"content_hash": "sha256-06c39dfb1ba74f5ac6f0e1a6d6b4c65358c3f1a872e96f3fe8d61da947caa1d4",
|
|
20
|
+
"mtime": "2026-06-01T14:58:02Z",
|
|
21
|
+
"raw_body_logged": false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"kind": "plan",
|
|
25
|
+
"ref": "memory/current-plan.md#active",
|
|
26
|
+
"content_hash": "sha256-a9b30f8a50dd31ec3c818c9c0d05282bf97f54991a4032f2d547a30174de1c61",
|
|
27
|
+
"mtime": "2026-06-01T14:59:44Z",
|
|
28
|
+
"raw_body_logged": false
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"state": {
|
|
32
|
+
"kept": [
|
|
33
|
+
{ "kind": "active_plan", "ref": "plan:fix-auth-smoke", "summary_hash": "sha256-4c0f7fd0a72b8cf7e45c0fb97b893590fa12e931f914a34b94756de0d876182c" },
|
|
34
|
+
{ "kind": "open_diff", "ref": "git:working-tree", "summary_hash": "sha256-2e5a0da4b6b4e9d07f11c8f10f332fc74497ef6d052a46997c4f5d0bdb0e1b07" }
|
|
35
|
+
],
|
|
36
|
+
"lost": []
|
|
37
|
+
},
|
|
38
|
+
"resume_verdict": {
|
|
39
|
+
"safe_to_resume": true,
|
|
40
|
+
"reasons": [
|
|
41
|
+
"instruction sources reloaded with hashes",
|
|
42
|
+
"active plan and open diff summarized",
|
|
43
|
+
"no blocking lost fields recorded"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"privacy": {
|
|
47
|
+
"raw_prompts_logged": false,
|
|
48
|
+
"raw_tool_output_logged": false,
|
|
49
|
+
"secrets_logged": false,
|
|
50
|
+
"full_instruction_bodies_logged": false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "agent.compaction_resume_receipt.v1",
|
|
3
|
+
"compaction_event_id": "compact-2026-06-01T15-02-59Z",
|
|
4
|
+
"session_id": "codex-session-42",
|
|
5
|
+
"trigger": "PostCompact",
|
|
6
|
+
"transcript": {
|
|
7
|
+
"range": "messages:1-184",
|
|
8
|
+
"content_hash": "sha256-7f8f6fbf8a3e3fe0e9f6b59efac0e771d8f64a9ebff3e9b0f5162e43c2e3e89a",
|
|
9
|
+
"raw_text_logged": true
|
|
10
|
+
},
|
|
11
|
+
"summary": {
|
|
12
|
+
"content_hash": "sha256-8a4f6242e9800f1452bcfd5dbec7e9f1f0b543d72b0c96e57dece44f6c4b8de4",
|
|
13
|
+
"token_count": 880
|
|
14
|
+
},
|
|
15
|
+
"instruction_sources_reloaded": [
|
|
16
|
+
{
|
|
17
|
+
"kind": "AGENTS.md",
|
|
18
|
+
"ref": "repo-root/AGENTS.md",
|
|
19
|
+
"content_hash": "",
|
|
20
|
+
"mtime": "2026-06-01T14:58:02Z",
|
|
21
|
+
"raw_body_logged": true
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"state": {
|
|
25
|
+
"kept": [],
|
|
26
|
+
"lost": [
|
|
27
|
+
{ "kind": "rejected_decisions", "ref": "decision-log:missing", "blocks_resume": true },
|
|
28
|
+
{ "kind": "pending_tests", "ref": "test-plan:unknown", "blocks_resume": true }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"resume_verdict": {
|
|
32
|
+
"safe_to_resume": "unknown",
|
|
33
|
+
"reasons": ["AGENTS.md hash missing and blocking state was lost"]
|
|
34
|
+
},
|
|
35
|
+
"privacy": {
|
|
36
|
+
"raw_prompts_logged": true,
|
|
37
|
+
"raw_tool_output_logged": false,
|
|
38
|
+
"secrets_logged": false,
|
|
39
|
+
"full_instruction_bodies_logged": true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Controlled learning queue example
|
|
2
|
+
|
|
3
|
+
A copyable layout for Claude Code/OpenClaw/Cursor-style "AI employee" agents that use a role file, Skills, memory, and external tools.
|
|
4
|
+
|
|
5
|
+
The pattern is simple:
|
|
6
|
+
|
|
7
|
+
- `role/job-contract.md` defines what the agent is allowed to do.
|
|
8
|
+
- `skills/*.md` define procedures with inputs, outputs, and stop conditions.
|
|
9
|
+
- `memory/durable.md` contains approved facts only.
|
|
10
|
+
- `memory/working-notes.md` can hold temporary observations.
|
|
11
|
+
- `learning_queue.md` is where the agent proposes durable memory changes as reviewable diffs.
|
|
12
|
+
- `leads/*.md` are tiny active job cards.
|
|
13
|
+
|
|
14
|
+
Run the smoke check:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node check-learning-queue.mjs learning_queue.md
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Expected output:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
learning queue ok: 2 proposal(s), 1 pending review
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Why it exists: agents can learn from outcomes, but durable cross-run memory should not be rewritten by one edge case without source, scope, expiry, and a promote/reject decision.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const file = process.argv[2] || new URL('./learning_queue.md', import.meta.url).pathname;
|
|
5
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
6
|
+
const proposals = text.split(/^## Proposal /m).slice(1);
|
|
7
|
+
const required = ['Status', 'Source', 'Observed', 'Proposed durable change', 'Reason', 'Scope', 'Expiry', 'Reviewer', 'Decision'];
|
|
8
|
+
const rawRisk = /(api[_-]?key|secret|password|token\s*[:=]|-----BEGIN|raw transcript|verbatim customer|full email)/i;
|
|
9
|
+
const errors = [];
|
|
10
|
+
let pending = 0;
|
|
11
|
+
|
|
12
|
+
if (proposals.length === 0) errors.push('missing proposals');
|
|
13
|
+
|
|
14
|
+
for (const [index, block] of proposals.entries()) {
|
|
15
|
+
const id = block.split('\n', 1)[0].trim() || `#${index + 1}`;
|
|
16
|
+
for (const field of required) {
|
|
17
|
+
if (!new RegExp(`^${field}:\\s*\\S`, 'mi').test(block)) {
|
|
18
|
+
errors.push(`${id}: missing ${field}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const status = block.match(/^Status:\s*(.+)$/mi)?.[1]?.trim().toLowerCase();
|
|
23
|
+
const reviewer = block.match(/^Reviewer:\s*(.+)$/mi)?.[1]?.trim().toLowerCase();
|
|
24
|
+
const decision = block.match(/^Decision:\s*(.+)$/mi)?.[1]?.trim().toLowerCase();
|
|
25
|
+
|
|
26
|
+
if (status === 'proposed') pending += 1;
|
|
27
|
+
if (status === 'promoted' && (!reviewer || reviewer === 'pending' || !decision || decision === 'pending')) {
|
|
28
|
+
errors.push(`${id}: promoted proposal needs reviewer and decision`);
|
|
29
|
+
}
|
|
30
|
+
if (/(auto-promote|autopromote|self-approved|self approved)/i.test(block)) {
|
|
31
|
+
errors.push(`${id}: auto-promotion is not allowed`);
|
|
32
|
+
}
|
|
33
|
+
if (rawRisk.test(block)) {
|
|
34
|
+
errors.push(`${id}: possible raw secret/private payload in learning queue`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (errors.length) {
|
|
39
|
+
console.error(`learning queue failed (${errors.length}):`);
|
|
40
|
+
for (const error of errors) console.error(`- ${error}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`learning queue ok: ${proposals.length} proposal(s), ${pending} pending review`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Lead job card: acme
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Prepare a qualification summary for a human owner.
|
|
5
|
+
|
|
6
|
+
## Known facts
|
|
7
|
+
- Source: demo form `lead-acme-2026-06-02`.
|
|
8
|
+
- Interest: AI agent workflows for sales operations.
|
|
9
|
+
- Constraint: no pricing promises without human owner.
|
|
10
|
+
|
|
11
|
+
## Next safe action
|
|
12
|
+
Draft questions about current workflow, systems of record, and approval boundaries.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Learning queue
|
|
2
|
+
|
|
3
|
+
Agents may propose durable memory updates here. Humans or maintainers promote/reject them.
|
|
4
|
+
|
|
5
|
+
## Proposal 2026-06-02-001
|
|
6
|
+
|
|
7
|
+
Status: proposed
|
|
8
|
+
Source: lead-acme-2026-06-02 job card, redacted summary only
|
|
9
|
+
Observed: Prospect asked how to prevent a role-based agent from changing ICP after one edge case.
|
|
10
|
+
Proposed durable change: Add "role-based agents may propose ICP changes, but ICP memory changes require human promote/reject review" to `memory/durable.md`.
|
|
11
|
+
Reason: This is a reusable safety boundary for future lead qualification runs.
|
|
12
|
+
Scope: sales-ops-agent / ICP memory
|
|
13
|
+
Expiry: 2026-07-02
|
|
14
|
+
Reviewer: pending
|
|
15
|
+
Decision: pending
|
|
16
|
+
|
|
17
|
+
## Proposal 2026-06-02-002
|
|
18
|
+
|
|
19
|
+
Status: rejected
|
|
20
|
+
Source: lead-beta-2026-06-01 job card, redacted summary only
|
|
21
|
+
Observed: One prospect wanted a custom discount workflow.
|
|
22
|
+
Proposed durable change: Add "discount requests are common" to `memory/durable.md`.
|
|
23
|
+
Reason: Rejected because one request is not enough to change durable market assumptions.
|
|
24
|
+
Scope: sales-ops-agent / pricing assumptions
|
|
25
|
+
Expiry: 2026-06-15
|
|
26
|
+
Reviewer: owner@example.invalid
|
|
27
|
+
Decision: reject; keep as working note only
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Durable memory
|
|
2
|
+
|
|
3
|
+
Approved facts only.
|
|
4
|
+
|
|
5
|
+
## ICP
|
|
6
|
+
- Early-stage teams adopting AI coding agents need lightweight reviewable context, not another opaque memory dump.
|
|
7
|
+
|
|
8
|
+
## Boundaries
|
|
9
|
+
- Do not store raw customer messages, secrets, or private transcripts.
|
|
10
|
+
- Pricing, legal commitments, and delivery promises require human review.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Working notes
|
|
2
|
+
|
|
3
|
+
Temporary notes may live here during an active job. Promote nothing from this file into durable memory without a `learning_queue.md` proposal.
|
|
4
|
+
|
|
5
|
+
- 2026-06-02 lead-acme: asked about using Skills for sales ops. Needs human review before any pricing language.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Sales ops agent role
|
|
2
|
+
|
|
3
|
+
## Mission
|
|
4
|
+
Help qualify inbound leads and prepare concise handoff notes for a human owner.
|
|
5
|
+
|
|
6
|
+
## Allowed
|
|
7
|
+
- Read approved lead/job cards.
|
|
8
|
+
- Draft next-step suggestions.
|
|
9
|
+
- Propose changes to durable memory through `learning_queue.md`.
|
|
10
|
+
|
|
11
|
+
## Not allowed
|
|
12
|
+
- Promise pricing, discounts, contracts, legal terms, or delivery dates.
|
|
13
|
+
- Rewrite `memory/durable.md` directly.
|
|
14
|
+
- Store raw private email/chat text in durable memory.
|
|
15
|
+
|
|
16
|
+
## Escalate when
|
|
17
|
+
- A lead asks for legal/financial commitments.
|
|
18
|
+
- A proposed learning would change ICP, pricing assumptions, compliance boundaries, or data-retention rules.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Skill: qualify lead
|
|
2
|
+
|
|
3
|
+
## Inputs
|
|
4
|
+
- Lead job card path.
|
|
5
|
+
- Current durable memory.
|
|
6
|
+
- Any approved working notes for this lead.
|
|
7
|
+
|
|
8
|
+
## Output
|
|
9
|
+
- Qualification summary.
|
|
10
|
+
- Open questions.
|
|
11
|
+
- Suggested next action.
|
|
12
|
+
- Optional `learning_queue.md` proposal if the case reveals a reusable durable fact.
|
|
13
|
+
|
|
14
|
+
## Stop conditions
|
|
15
|
+
- Missing consent or unclear data source.
|
|
16
|
+
- Request requires a human commitment.
|
|
17
|
+
- Proposed durable learning lacks source, scope, or expiry.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Loaded-resource boundary example
|
|
2
|
+
|
|
3
|
+
This example turns "my Skill works in chat but disappears in ACP/Zed/CLI" into a stage-level receipt.
|
|
4
|
+
|
|
5
|
+
Run:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node check-loaded-resource-boundary.mjs loaded-resource-boundary.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Expected output:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
loaded-resource boundary ok: 1 required resource/runtime gap recorded
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The sample records the same project skills across two sessions:
|
|
18
|
+
|
|
19
|
+
- `chat` discovers, attaches, injects, and reads `skill:pr-review`;
|
|
20
|
+
- `acp`/`zed` discovers and attaches it, but never injects it, so the receipt records `runtime_does_not_inject_resources` and sets `safe_to_continue=false`.
|
|
21
|
+
|
|
22
|
+
Use this shape when prompt instructions cannot explain a missing Skill. The host/runtime needs to prove the resource crossed the boundary, not merely that it exists on disk.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const file = process.argv[2] ?? new URL('./loaded-resource-boundary.json', import.meta.url);
|
|
5
|
+
const receipt = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
6
|
+
const fail = (message) => {
|
|
7
|
+
console.error(`loaded-resource boundary invalid: ${message}`);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
if (receipt.receipt_type !== 'pluribus.loaded_resource_boundary.v1') fail('unexpected receipt_type');
|
|
12
|
+
if (!Array.isArray(receipt.expected_resources) || receipt.expected_resources.length === 0) fail('expected_resources must be non-empty');
|
|
13
|
+
if (!Array.isArray(receipt.sessions) || receipt.sessions.length < 2) fail('sessions must include at least two runtimes for parity checks');
|
|
14
|
+
|
|
15
|
+
const expectedIds = new Set(receipt.expected_resources.map((resource) => resource.id));
|
|
16
|
+
const requiredIds = new Set(receipt.expected_resources.filter((resource) => resource.required !== false).map((resource) => resource.id));
|
|
17
|
+
for (const resource of receipt.expected_resources) {
|
|
18
|
+
if (!resource.id || !resource.kind || !resource.source_ref || !resource.source_hash?.startsWith('sha256:')) {
|
|
19
|
+
fail(`expected resource ${resource.id ?? '<missing>'} needs id, kind, source_ref, and sha256 source_hash`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const allowedSkipReasons = new Set([
|
|
24
|
+
'not_discovered',
|
|
25
|
+
'not_attached_to_agent',
|
|
26
|
+
'runtime_does_not_inject_resources',
|
|
27
|
+
'trigger_not_matched',
|
|
28
|
+
'resource_read_failed'
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
let mismatches = 0;
|
|
32
|
+
for (const session of receipt.sessions) {
|
|
33
|
+
for (const key of ['runtime', 'client', 'agent']) {
|
|
34
|
+
if (!session[key]) fail(`session ${session.session_id ?? '<missing>'} missing ${key}`);
|
|
35
|
+
}
|
|
36
|
+
for (const listName of ['discovered_resources', 'attached_resources', 'injected_resources', 'readable_resources', 'skipped_resources']) {
|
|
37
|
+
if (!Array.isArray(session[listName])) fail(`${session.session_id}: ${listName} must be an array`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const stageSets = {
|
|
41
|
+
discovered: new Set(session.discovered_resources),
|
|
42
|
+
attached: new Set(session.attached_resources),
|
|
43
|
+
injected: new Set(session.injected_resources),
|
|
44
|
+
readable: new Set(session.readable_resources)
|
|
45
|
+
};
|
|
46
|
+
const skipped = new Map(session.skipped_resources.map((skip) => [skip.id, skip]));
|
|
47
|
+
|
|
48
|
+
for (const id of expectedIds) {
|
|
49
|
+
if (stageSets.readable.has(id)) continue;
|
|
50
|
+
const skip = skipped.get(id);
|
|
51
|
+
if (!skip) {
|
|
52
|
+
if (requiredIds.has(id)) fail(`${session.session_id}: ${id} is required, not readable, and has no skipped_resources entry`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!allowedSkipReasons.has(skip.reason)) fail(`${session.session_id}: ${id} has unknown skip reason ${skip.reason}`);
|
|
56
|
+
if (!skip.stage) fail(`${session.session_id}: ${id} skip entry needs a stage`);
|
|
57
|
+
if (requiredIds.has(id)) mismatches += 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (receipt.safe_to_continue !== false && mismatches > 0) {
|
|
62
|
+
fail('safe_to_continue must be false when expected resources are missing or skipped');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`loaded-resource boundary ok: ${mismatches} required resource/runtime gaps recorded`);
|