Skip to content

Persist queued delegations, and scrub secrets from what the harness stores - #196

Merged
milind-soni merged 4 commits into
mainfrom
feat/harness-2.3-2.4-durable-delegations-redaction
Aug 17, 2026
Merged

Persist queued delegations, and scrub secrets from what the harness stores#196
milind-soni merged 4 commits into
mainfrom
feat/harness-2.3-2.4-durable-delegations-redaction

Conversation

@aivsomkar

@aivsomkar aivsomkar commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

In plain language

Two small safety changes.

1. Bot-to-bot handoffs survive a restart (2.3).
When one bot delegates work to another, the handoff waits until the first bot's turn finishes. Before, if the app restarted in between, that handoff silently vanished.

  • What changes: it's saved to disk and runs after the restart. Nothing else looks different.

2. Secrets a bot writes down get masked (2.4).
If a bot's reply, a tool title, or an approval card contains something that is unmistakably a credential — an API key like sk-…/ghp_…, a JWT, a private-key block, a Bearer token, or password=… — the stored copy becomes «redacted N chars». This matters because those lines are replayed to engines later (see #193); a leaked key would otherwise be permanent.

  • What changes: you'll occasionally see «redacted 52 chars» in a bot's message or a card where a key would have been. What you type yourself is never touched — pasting your own .env for the bot to use is your call. Ordinary code, hashes and URLs are left alone (no fuzzy matching).

Summary

Two small, independent Round-2 harness upgrades in one PR, as agreed.


2.3 — Durable delegations

The per-thread handoff queue (delegate_bot) lived in a module Map and died with the process — a delegation queued right before a restart never ran, silently. v1 quoted the code's own comment: "Persisted nowhere."

  • Queue is written to ~/.openmausbot/delegations.json (via the existing writeFileAtomic) on queue, on drain, and on discard; loaded at boot.
  • At startup, every thread with leftover handoffs is drained through the same path a settled turn uses (runDelegatedTurn, extracted from the subscriber). Target existence and approvePeerComms are still re-checked at drain time as before; a source bot that no longer exists is skipped. Log line: "delegations: N thread(s) with queued handoffs from a previous run — draining".
  • Provider permissions still die with the process — nobody can answer for an unattended bot. Queued work is not a permission.

2.4 — Secret redaction on what the harness stores (rescoped)

Finding that reshaped it: the harness never stores tool results — CLI drivers run tools inside the CLI; the harness only sees item.started (name/title) and item.completed (ok). So v1's "redact tool results before persisting" had no body to redact. What does land in the transcript — and, since #193, gets replayed into every rebuild — is the bot's reply text, activity chip titles (an ACP engine's title can be the whole command line), and permission card summaries (the command being approved). Those can carry keys.

  • redactSecretsInText — high-precision content patterns only: known key prefixes (sk-/sk-ant-/sk-proj-, ghp_…/github_pat_, xox*-, AKIA…, AIza…, npm_), JWTs, -----BEGIN … PRIVATE KEY----- blocks, Bearer <token>, and secret-shaped key=value / key: value (api_key, token, password, secret, authorization, client_secret…). No generic hex/base64 heuristics — those rewrite real code. Same «redacted N chars» marker the native tee already uses.
  • Applied at the single store write (appendMessage) for role: "bot": text, tool.name, card title/subtitle/summary. User text is left as typed — pasting your own .env for the bot is your call, and rewriting your words would be worse.
  • redactSecrets (used by the native tee) gains the same content pass over string values; the events/ NDJSON now goes through it too.
  • Live streaming deltas aren't redacted (tokens split across events); the settled message that replaces the bubble is — and that's what's stored and replayed.
  • Test fixtures are assembled at runtime — GitHub's push protection flagged a literal xoxb-… fixture as a real Slack token, which is a decent sign the patterns are realistic.

Items 2.3 and 2.4 of docs/plans/agent-harness-upgrades-v2.md.

Test plan

  • redact.test.ts (+5): each key prefix; JWT / PEM / Bearer; key=value in shell, JSON, YAML, CLI-flag forms; negatives (prose with "keyboard", a git SHA, a URL, code that mentions tokens, "password: (leave blank…)", too-short sk-); content pass inside redactSecrets
  • store.test.ts (+1): bot reply, tool title, card summary masked; user text untouched; masked copy is what's on disk after reload
  • delegations.test.ts (+3): queue → file; discard and drain clear it; a fresh process loads and drains what the last one queued; missing/corrupt file tolerated
  • pnpm typecheck clean; pnpm vitest run green (64 files, 536 passed)
  • Manual: a bot echoing a fake sk-ant-… key stores «redacted 52 chars»; a Bearer in a command shows masked in the card and chip; my own pasted key stays

🤖 Generated with Claude Code

aivsomkar and others added 2 commits August 17, 2026 23:42
…tores

2.3 — durable delegations. The per-thread handoff queue lived in a Map
and died with the process: a delegation queued right before a restart
never ran, silently. It is now written to ~/.openmausbot/delegations.json
on queue, drain, and discard, loaded at boot, and drained through the
same path a settled turn uses — target and approvePeerComms are still
re-checked at drain time; a source bot that no longer exists is skipped.
Provider permissions still die with the process (nobody can answer for an
unattended bot); queued work is not a permission.

2.4 — secret redaction, rescoped. The harness never stores tool RESULTS
— CLI drivers run tools inside the CLI and the harness only sees names
and outcomes — so there was no tool-result body to redact. What it does
store, and now replays into every rebuild, is the bot's reply text,
activity chip titles (an ACP engine's title can be the whole command
line), and permission card summaries (the command being approved). Those
can carry keys.

- redactSecretsInText: high-precision content patterns only — known key
  prefixes (sk-, ghp_/github_pat_, xox*-, AKIA, AIza, npm_), JWTs, PEM
  private-key blocks, Bearer tokens, and secret-shaped key=value. No
  generic hex/base64 heuristics: those rewrite real code.
- Applied at the single store write for role "bot" (text, tool.name,
  card title/subtitle/summary). What the user typed stays as typed —
  pasting your own .env for the bot to use is your call.
- redactSecrets (the native tee) gains the same content pass over string
  values, and the events NDJSON now goes through it too.
- Live streaming deltas are not redacted (tokens split across events);
  the settled message that replaces the bubble is, and that is what is
  stored and replayed.

Items 2.3 and 2.4 of docs/plans/agent-harness-upgrades-v2.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…durable-delegations-redaction

# Conflicts:
#	server/index.ts
#	server/store.test.ts
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@milind-soni, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Limit details: You’ve used all 3 included reviews currently available under your plan.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d50a7a6-9ac4-4c6e-9e91-65b57924d4a1

📥 Commits

Reviewing files that changed from the base of the PR and between f9404e6 and fb1d120.

📒 Files selected for processing (9)
  • server/delegations.test.ts
  • server/delegations.ts
  • server/harness/bus.test.ts
  • server/harness/bus.ts
  • server/index.ts
  • server/redact.test.ts
  • server/redact.ts
  • server/store.test.ts
  • server/store.ts

Comment @coderabbitai help to get the list of available commands.

@milind-soni
milind-soni merged commit 8b95206 into main Aug 17, 2026
5 checks passed
kargnas added a commit to kargnas/OpenMausBot that referenced this pull request Aug 17, 2026
main의 milind-soni#205(태스크별 토큰 지출), milind-soni#200(승인 결과 타입화), milind-soni#196(위임
영속화·비밀 마스킹) 등 7개 커밋 병합 충돌을 해결했다.

- main이 busy 플래그를 activity 상태 기계로 대체함에 따라 룸 턴의
  catalog 검증 실패 경로도 setActivity로 맞췄다.
- antigravity respondToRequest 테스트는 main의 unavailable 해소
  시맨틱을 채택했다.

Tested: pnpm typecheck, pnpm vitest run (83 files, 770 passed, 8 skipped)

Confidence: high
Scope-risk: moderate
Reversibility: moderate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants