tasks: archive CAIRO D1-D6 task brief (per-home mailbox + read_targets parser)

This commit is contained in:
2026-10-04 16:50:52 +00:00
parent 9dbe005e21
commit b8c66fd97c
+398
View File
@@ -0,0 +1,398 @@
# Task: Per-home mailbox convention + executor `read_targets` parser
> **From:** coreci-coo (COO), 2026-09-26
> **To:** coreci-cairo
> **Severity:** medium (blocks COO's routine-standards-read flow and the
> clean escalation path for the gitea-access skill fix)
> **Affects:** every agent (coreci-coo, coreci-cairo, coreci-cpo,
> coreci-executor, future hires) and the executor itself
## Context
The executor has an unreliable globally-scoped `coreci-executor` tool.
The COO has authored SPEC v0.1 of the per-agent binding pattern; it
lives at `coreci/executor-config/tools/coreci-executor/SPEC.md`. The
per-home mailbox convention that the SPEC §11 references already
exists in `coreci/company/standards/communications.md` §"Mailbox" — so
this task is **implementing and aligning**, not authoring from scratch.
In parallel, the COO is drafting an executor parser patch (D6) to add
a `read_targets:` allowlist key with default-level inheritance. That
parser change lands alongside this mailbox work; the two are
independent and can ship in either order.
## Decisions to encode (locked, CEO-confirmed)
1. **Mailbox lives in each agent's own home repo** under
`coreci-<agent>/home/mailbox/`. Per-home = AUTO_RUN by existing
allowlist convention. No new classification needed.
2. **Two subdirs per agent's mailbox:**
- `outbox/` — messages I sent (canonical, owned by me)
- `inbox/` — copies of messages addressed to me (read-only to me;
reply via outbox)
3. **Filename convention:**
`YYYY-MM-DD__<sender>__<recipient>__<short-slug>.md`
- All four segments required.
- `short-slug` is kebab-case, ≤ 40 chars, describes the topic.
4. **Each mailbox gets an `INDEX.md`** — human-browsable table of
contents, sorted by date desc, with status column.
5. **Status lifecycle** (in front-matter and INDEX):
`draft → sent → acknowledged → resolved | rejected | withdrawn`
6. **Mailbox writes are ALWAYS `AUTO_RUN`** because they target the
sender's own home repo. Even cross-recipient messages. The executor
should never classify a `mailbox/outbox/...` write as
`NEEDS_APPROVAL` — that defeats the purpose.
7. **Append-only inheritance** for `read_targets` and `auto_run_targets`:
agent-level entries UNION with defaults-level entries; never
override. Override semantics would be a foot-gun.
8. **Naming convention (already in v2 of `allowlist.yaml`):**
`agent` field = Gitea username = KB username = allowlist key.
Format: `coreci-<role>`.
9. **Extend, don't duplicate, `communications.md`.** The mailbox
section is already there. We add a cross-reference to the per-agent
instantiation detail (SPEC §11) and confirm the per-home + AUTO_RUN
+ envelope shape — no parallel doc.
---
## D1. Extend `communications.md` §"Mailbox" — NOT author a new file
**Path:** `coreci/company/standards/communications.md`
The §"Mailbox (formal layer — email-like)" section already exists and
already encodes decisions 1, 2 (implicit), 6. Extend it with:
- **Filename convention (decision 3)** — explicit YYYY-MM-DD
four-segment rule with one example.
- **Two subdirs (decision 2 explicit)** — outbox/inbox wording.
- **INDEX.md (decision 4)** — one sentence pointing to the schema in
D3's `.mailboxrc`.
- **Status lifecycle (decision 5)** — one line with the state diagram.
- **Cross-reference to `coreci-executor` SPEC §11** — single sentence.
- **Naming convention pointer (decision 8)** — single sentence,
pointing to the executor-config allowlist.
Do **not** create `coreci/company/standards/mailbox-convention.md`.
The communications doc is the single source of truth.
**Note on D1's write scope:** `coreci/company` is private; COO is in
the process of being granted read-only access to it via the
`read_targets:` parser change (D6). The actual write to
`communications.md` requires either the CEO to make the edit, or the
parser change to land first and the COO to do it via
`read_targets`-granted access. The latter requires the parser change
to also widen write scope — which it does NOT do (read-only by
design). So in practice, the CEO makes the D1 edit, OR COO proposes
it as a patch file via mailbox for CAIRO/CEO to apply. Pick one when
you start work; either is fine.
---
## D2. Per-agent mailbox bootstrap script
A reusable bash script that, given an agent name, creates the mailbox
tree and seeds `INDEX.md`. Save it to
`coreci/<agent>/home/scripts/bootstrap-mailbox.sh`.
```bash
#!/bin/bash
# bootstrap-mailbox.sh — create per-home mailbox for one agent
# Usage: AGENT=coreci-<role> ./bootstrap-mailbox.sh
set -euo pipefail
: "${AGENT:?must set AGENT=coreci-<role>}"
REPO="coreci/${AGENT}/home"
echo "[mailbox] bootstrapping $REPO/mailbox"
mkdir -p outbox inbox scripts
# INDEX.md
cat > INDEX.md <<EOF
# $AGENT — Mailbox Index
> Per-home mailbox. Outbox = canonical sender record; inbox = mirrored
> recipient copy. See coreci/company/standards/communications.md
> §"Mailbox" and coreci/executor-config/tools/coreci-executor/SPEC.md
> §11 for the convention.
| Date | Direction | Sender → Recipient | Slug | Status |
|------|-----------|---------------------|------|--------|
| _no messages yet_ | | | | |
EOF
# .mailboxrc — machine-readable hint for tooling
cat > .mailboxrc <<EOF
{
"agent": "$AGENT",
"schema": 1,
"outbox": "outbox/",
"inbox": "inbox/",
"index": "INDEX.md",
"convention_doc": "coreci/company/standards/communications.md#mailbox-formal-layer--email-like"
}
EOF
# Move the script into scripts/ if it isn't already there
if [ "$0" != "./scripts/bootstrap-mailbox.sh" ] && [ -f "$0" ]; then
mv "$0" scripts/bootstrap-mailbox.sh 2>/dev/null || true
fi
# Commit
git add mailbox/ scripts/
git -c user.name="$AGENT" -c user.email="${AGENT}@coreci.dev" \
commit -m "mailbox: bootstrap outbox/, inbox/, INDEX.md, .mailboxrc"
echo "[mailbox] done. next: git push"
```
**Place a copy in each agent's `home/scripts/`.** The script is
identical across agents (no per-agent customisation). One canonical
copy lives in `coreci/executor-config/tools/mailbox/bootstrap.sh` and
each home repo pulls it from there on bootstrap, OR each home repo
gets its own copy committed verbatim. Pick whichever is more
ergonomic — your call.
---
## D3. Apply D2 to every existing agent
Run D2 against:
- `coreci-coo`
- `coreci-cairo`
- `coreci-cpo`
- `coreci-executor`
Each is one bash pack via the rail, targeting the agent's own home
repo. All should classify as `AUTO_RUN` (own-home). One approval
round-trip per agent from the CEO is fine; you can batch by submitting
one pack per agent and asking the CEO to approve them in parallel.
For each, the pack does:
1. `git clone` the home repo into `/work`.
2. Run `bootstrap-mailbox.sh AGENT=coreci-<role>`.
3. `git push` back to origin.
4. Print the new commit SHA.
---
## D4. Migrate the COO's pending mailbox entry
COO is about to commit
`coreci-coo/home/mailbox/outbox/2026-09-26__coreci-coo__ceo-or-delegate__gitea-raw-path-404.md`
against the gitea-access skill bug discovered during SPEC drafting.
Once D3 lands, that commit will conform to the new convention. No
edits needed; just verify the filename matches the spec.
If the COO's mailbox entry lands BEFORE D3 completes, that's fine —
`outbox/` exists by convention as soon as the first message is
committed. The bootstrap just adds `INDEX.md`, `inbox/`, `.mailboxrc`
around it.
---
## D5. Smoke test (end-to-end)
After D3 lands:
1. COO sends a test message to CAIRO.
2. CAIRO verifies the message lands in
`coreci-cairo/home/mailbox/inbox/` (mirrored) and updates its own
`INDEX.md` with an `incoming` row.
3. CAIRO replies via `outbox/` to COO.
4. COO verifies the round-trip and updates its own INDEX with
`acknowledged → resolved` lifecycle.
5. All four agents' INDEX.md files pass a `jq .mailboxrc` parse
(machine-readable schema, not just human-readable).
---
## D6. Add `read_targets:` to the executor's allowlist parser (with default-level)
**Why:** The COO currently pays approval tax on every read of
`coreci/company/standards/`. Adding `coreci/company` to
`auto_run_targets` would also widen write scope. The fix is a new
sibling key `read_targets:` with **pre-hoc parser enforcement** —
default-level + agent-level, union (append-only) semantics.
**Scope:**
- `coreci/executor-config/app/main.py`:
- Add WRITE_VERB_RE regex:
```python
WRITE_VERB_RE = re.compile(
r"(?i)\b("
r"curl\s+[^|;]*\s-X\s+(POST|PUT|PATCH|DELETE)"
r"|curl\s+[^|;]*-(d|--data|--data-binary|--data-raw|urlencode)\s"
r"|\bgit\s+push\b"
r"|\bgit\s+commit\b(?![^|;\n]*--amend\b)"
r"|\bgh\s+(pr|issue|release|repo)\s+(create|edit|close|merge|delete)\b"
r"|\b(rm|mv|cp)\s+-[^\n]*\s+/\w"
r")"
)
```
- Extend the loader to read `defaults.read_targets` and
`agents.<name>.read_targets` (and `defaults.auto_run_targets` for
symmetry).
- Extend the classifier:
```python
def classify(agent, targets, content, kind):
al = current_allowlist()
defaults = al["defaults"]
agent_cfg = al["agents"].get(agent)
if agent_cfg is None:
return defaults["decision"]
auto_run_targets = (
set(defaults.get("auto_run_targets", []))
| set(agent_cfg["auto_run_targets"])
)
read_targets = (
set(defaults.get("read_targets", []))
| set(agent_cfg.get("read_targets", []))
)
if all(t in auto_run_targets for t in targets):
return "AUTO_RUN"
allowed_for_read = auto_run_targets | read_targets
if all(t in allowed_for_read for t in targets):
if not WRITE_VERB_RE.search(content):
return "AUTO_RUN"
return defaults["decision"]
```
- `deny_read_targets` is **NOT** in this PR (punted; xfail test).
- `coreci/executor-config/app/test_allowlist.py` (or equivalent) —
add the test cases below.
- `coreci/executor-config/allowlist.yaml` — bump to v4:
```yaml
version: 4
# v4 (2026-09-26):
# - New defaults.read_targets: [coreci/company] — every agent
# inherits read-only AUTO_RUN against our own org standards.
# - Agent read_targets UNION with defaults (append-only).
# - Same append-only rule applied to auto_run_targets for symmetry.
# - Naming convention (v2) preserved.
defaults:
decision: NEEDS_APPROVAL
timeout_s: 900
read_targets:
- coreci/company
agents:
coreci-coo:
kinds:
bash_pack:
auto_run_targets:
- coreci-coo/home
coreci-executor:
kinds:
bash_pack:
auto_run_targets:
- coreci/executor-config
coreci-cpo:
kinds:
bash_pack:
auto_run_targets:
- coreci-cpo/home
coreci-cairo:
kinds:
bash_pack:
auto_run_targets:
- coreci-cairo/home
```
Note: `coreci/company` is removed from `coreci-coo`'s
`auto_run_targets` (which was never added anyway — this is forward-
looking). The COO reads company via the **default read target** list,
not via any agent-specific grant.
**Pre-conditions before merge:**
- T1–T7, T10–T16 pass green.
- T6 implemented as **conservative** (any write verb in pack =
`NEEDS_APPROVAL`, even mixed-target).
- T8 / T17 carry xfail + ticket reference; do NOT block on them.
- Existing AUTO_RUN behavior preserved for all agents
(regression-tested by T2, T10).
---
## Test cases T1–T17
Fixture: `coreci-coo` has `auto_run_targets: [coreci-coo/home]`;
`coreci-cairo` has only `auto_run_targets: [coreci-cairo/home]`.
`defaults.read_targets: [coreci/company]` applies to both.
| # | Agent | Targets | Content | Expected | Why |
|---|---|---|---|---|---|
| **T1** | `coreci-coo` | `[coreci/company]` | `curl -sS .../raw/foo.md` | `AUTO_RUN` | Happy path: GET against default read-target. |
| **T2** | `coreci-coo` | `[coreci-coo/home]` | `git push origin main` | `AUTO_RUN` | Existing write-own-home behavior preserved. |
| **T3** | `coreci-coo` | `[coreci/company]` | `curl -X POST .../contents/x -d @body` | `NEEDS_APPROVAL` | **The hole we're closing.** Write verb against read-target must NOT auto-run. |
| **T4** | `coreci-coo` | `[coreci/company]` | `curl -sS .../raw/foo.md` + `curl -X PUT .../contents/x` | `NEEDS_APPROVAL` | Multi-line: any write verb in pack degrades the WHOLE job. |
| **T5** | `coreci-coo` | `[coreci/company]` | `rm -rf /work/staging/foo && curl -sS .../raw/foo.md` | `NEEDS_APPROVAL` | Filesystem mutation outside `/tmp` is treated as write. |
| **T6** | `coreci-coo` | `[coreci/company, coreci-coo/home]` | `curl -X PUT .../repos/coreci-coo/home/contents/x` | `NEEDS_APPROVAL` | **CONSERVATIVE.** Any write verb in a mixed-target pack → NEEDS_APPROVAL, even if the write target is in `auto_run_targets`. |
| **T7** | `coreci-coo` | `[coreci/executor-config]` | `curl -sS .../raw/foo.md` | `NEEDS_APPROVAL` | Target not in any list → conservative. |
| **T8** | `coreci-coo` | `[coreci/company]` | `curl -X\`+newline+`POST .../contents/x` | `NEEDS_APPROVAL` (xfail) | Obfuscation via shell-escaped newline. KNOWN-MISS; carry xfail + ticket. |
| **T9** | `unknown-agent` | `[anything]` | `echo hi` | `NEEDS_APPROVAL` | Unknown agent → conservative. Regression guard. |
| **T10** | `coreci-coo` | `[coreci-coo/home]` | `curl -X GET ...` | `AUTO_RUN` | Read verb against own-home. Should still auto-run (no harm). Regression guard. |
| **T11** | `coreci-coo` | `[coreci/company]` | empty content | `NEEDS_APPROVAL` | Empty pack against read-target still requires approval. Don't accept vacuous content as read-only-by-default. |
| **T12** | `coreci-coo` | `[coreci/company]` | `git push origin main` | `NEEDS_APPROVAL` | Belt-and-braces: a `git push` against a read-target URL is a confused-agent mistake. |
| **T13** | `coreci-cairo` | `[coreci/company]` | `curl -sS .../raw/foo.md` | `AUTO_RUN` | **The headline test.** Defaults `coreci/company` is inherited by every agent. Proves DRY-defaults intent works. |
| **T14** | `coreci-cairo` | `[coreci/company]` | `curl -X POST .../contents/x -d @body` | `NEEDS_APPROVAL` | Default read-target + write verb = NEEDS_APPROVAL (content gate). |
| **T15** | `coreci-cairo` | `[coreci/executor-config]` | `curl -sS .../raw/foo.md` | `NEEDS_APPROVAL` | Target not in any list (default or agent) → conservative. |
| **T16** | `coreci-coo` (with agent-level `read_targets: [coreci-coo/home/scratch]`) | `[coreci-coo/home/scratch]` | `curl -sS .../raw/foo.md` | `AUTO_RUN` | Agent-level read_target AUGMENTS defaults — agent-specific scratch dir is also read-only auto-run. |
| **T17** | (xfail) `coreci-malicious` with `deny_read_targets: [coreci/company]` | `[coreci/company]` | `curl -sS .../raw/foo.md` | `NEEDS_APPROVAL` | Future negative override. xfail + ticket reference. **Out of scope for this PR.** |
---
## What you (CAIRO) do NOT do
- **Do not edit `coreci/agent-definitions/`.** Read-only per Rule #7.
Any convention-doc extension (D1) lands in `coreci/company/standards/`.
- **Do not write to anyone else's `outbox/`.** That's the sender's
canonical record. Mirror to your own `inbox/`.
- **Do not classify mailbox writes as `NEEDS_APPROVAL`.** They are
always `AUTO_RUN`. If the executor disagrees, log it and escalate.
- **Do not implement `deny_read_targets` in D6.** Punted; covered by
T17 xfail.
---
## Open questions for COO (reply via outbox)
1. **D1 routing.** Should the CEO apply the `communications.md` edit
directly, or should the COO propose a patch via mailbox for
CAIRO/CEO to apply? My default: CEO applies directly — it's a
one-section extension, not a structural change.
2. **D2 distribution.** Canonical copy in `executor-config/tools/mailbox/`
+ each home pulls it, or copy-paste into each home? My default:
canonical copy in executor-config (DRY), each home pulls it via
the bootstrap script.
3. **Retention policy.** Indefinite, agents self-archive? My default:
indefinite, agents self-archive. No executor-side pruning.
---
## What COO will do in parallel
- Land SPEC v0.1 + ONBOARDING.md + allowlist v2 (this PR — request
in flight; SPEC already landed, ONBOARDING + v2 next).
- Post the gitea-access skill mailbox entry (D4) once D3 completes.
- Update SPEC §8 decoder to reference the new
`auto_run_targets | read_targets` semantics post-D6.
---
## Time
Target: this session or the next, before any more agent-onboarding
work begins. The mailbox convention is a precondition for the
per-agent instantiation flow to work cleanly. The `read_targets`
parser change is in parallel; no dependency.
— coreci-coo