Files
ironclaw/.gitignore
Henry Park cbc3c4fac4 feat(turns): subagent activation provenance, activate() primitive, and derived autonomous-wake cap (slice 1) (#7752)
* docs(subagent): record background-enable recon, shape decision, and slice 1-2 plan

Recon of the landed PR1 subagent path, the shape decision to ship the design's
PR2-PR6 before clearing the production deny-filter, and the TDD plan for
slices 1-2 (activation provenance + background completion delivery).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(turns): add ActivationProvenance vocabulary for subagent activation tagging

Set once at run creation and immutable thereafter, so the derived activation
streak caps (design section 6 and 8.3) can read bounded windows of run history
instead of maintaining a stored counter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(turns): persist subagent activation provenance on the run record

Threads ActivationProvenance from a submission into durable agent-turn process
metadata and back out onto TurnRunRecord, so the derived streak caps can read
it. Additive and serde-defaulted: rows written before the field stay readable
as None, which is also the value every ordinary human-initiated submission
carries. A fresh child run is a spawn rather than a re-activation, so it
records no provenance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(turns): add provenance-tagged activate() re-activation primitive

activate() is the single re-activation primitive for an existing thread. It is
not a second admission path: it builds an ordinary SubmitTurnRequest, so
one-active-run exclusivity, idempotency replay, and busy rejection behave as
they do for any other submission, and the only thing it adds is the provenance
stamp the derived streak caps read.

The trait method carries a fail-closed default so the many test doubles of
TurnCoordinator need not each restate it, and so a coordinator that has not
opted into activation refuses rather than silently creating an untagged run
that the streak cap could not see.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(processes): add bounded newest-first agent-turn query for a thread scope

The derived activation-streak caps need a fixed recent window of a thread's own
runs. Nothing returned that today: children_of is parent-keyed and unbounded,
and process_snapshots enumerates a whole scope.

Reuses the existing process_scope_v3 (scope_key, created_at, process_id) index
and the already-implemented Descending sort, so this needs no new index and no
backfilling migration. Because that index is deliberately not keyed on
process_kind, and a thread's scope also holds capability-invocation processes,
a flat LIMIT could come back holding no runs at all -- so this walks the
descending keyset a page at a time and filters by kind, bounded by a page
budget. The enumeration lives in rows.rs and reaches storage through
query_ordered, which keeps it outside the storage-scan gate gate's reach.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(turns): bound autonomous System activations with a derived streak cap

Nothing else bounds the cumulative spawn -> settle -> wake -> spawn cycle: a
parent that spawns a fresh child on every background completion would loop
indefinitely under every existing cap, with no human in it. The cap is derived
from a bounded window of the thread's own run records rather than a stored
counter, so it adds no new component and no new persistence.

Refusing costs nothing durable -- a settled await-edge stays settled and drains
via the run-start sweep or the boot pass -- so this gates the reactive wake
only, never delivery. Human activations reset the streak and are never capped;
ParentAgent runs sit outside this window entirely so the two caps stay
independent.

recent_runs_for_thread moves onto the base AgentTurnRuntimePort with a
fail-closed default: an empty window reads as 'streak not established', so a
runtime that cannot answer must refuse rather than silently disable the cap.

Re-pins the host_api contracts size ceiling for ActivationProvenance, which is
turn vocabulary and has no lower crate that may own it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(subagent): mark slice 1 complete in the background-delivery plan

Records the five landed commits with their evidence, the gates run at slice
close, and the two gates this environment could not run (clippy component
absent; WebUI frontend build broken via corepack).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(turns): forward activate() through the production coordinator decorator and exclude ParentAgent from the streak fetch

Two defects found by code review of slice 1, each with a regression test that
fails before its fix.

1. CancelReconcilingTurnCoordinator is the one TurnCoordinator production
   composes, and its doc says every other method forwards -- but activate()
   did not, so it inherited the trait's fail-closed default and every
   production activation would have been refused. Slice 2's background
   delivery would have been dead on arrival, in the integration harness too,
   which mirrors this wiring deliberately.

2. The System-wake streak cap fetched exactly K records and only then filtered
   out ParentAgent, so interleaved ParentAgent runs shrank the window below K.
   A short window reads as 'streak not established' and admits, which disabled
   the cap entirely on exactly the human-free interleaved sequences it exists
   to bound -- while the code comment claimed the opposite. The design's
   section 8.3 requires ParentAgent be excluded from the fetch and names this
   interleaving case as a required test; both are now honored, with the
   over-fetch factor and its fail-open residual documented.

Also fixes a dead guard in the new bounded read: ResourceScope::system() mints
a fresh invocation_id per call, so comparing a scope against it by equality can
never match. The new code now uses is_system(). The pre-existing
process_snapshots guard one screen up has the same dead comparison and is
reported as a follow-up rather than changed here, since tightening it would
alter behavior for existing callers outside this slice.

Adds the multi-page keyset walk and system-scope coverage the review found
missing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(subagent): move the slice 1-2 plan under docs/internal

AGENTS.md requires every internal engineering doc to live under docs/internal/
and nowhere else under docs/; docs/.mintignore is frozen, so a plan left at
docs/superpowers/plans/ would have been published to the public docs site.
scripts/ci/docs_publication_boundary.py failed on it and now passes. Placed
beside the existing superpowers plans, matching that convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address PR review feedback (#7752)

- Preserve activation provenance across terminal metadata rewrites. loop_exit
  writes the agent-turn envelope via agent_turn_metadata_from_claimed on every
  terminal transition, and from_claimed restored subagent_depth and
  spawn_tree_descendant_cap but not provenance -- so every completed run read
  back untagged and the wake cap could never fire in production. Provenance now
  rides on ClaimedTurnRun and is restored alongside its sibling lineage fields.
  The original tests missed this because the helper drove runs terminal
  directly, bypassing the rewrite; the regression test drives the real function
  loop_exit calls.

- Fail closed when the wake window cannot be established. A full raw fetch that
  still cannot yield a cap-sized non-ParentAgent window means the streak is
  unknown, not absent; admitting there was the fail-open residual the
  over-fetch alone left behind. A genuinely short fetch is still a young thread
  and still admits.

- Keep activate()'s advertised submission idempotency true at the cap boundary
  by excluding the caller's own accepted message from the window, so a retry of
  an accepted activation reaches the journal's operation-id replay instead of
  being refused by the run it already created.

- Give the cap refusal its own identity: AdmissionRejectionReason::SystemWakeStreak
  rather than a third indistinguishable InvalidRequest. Both downstream match
  sites audited and classified as retryable capacity, not caller error.

- Assert the cap refusal creates no run, add backend parity legs (libSQL and
  PostgreSQL) for the descending keyset window walk, and correct the stale
  slice number and plan snippet in the design docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Henry Park <16583448+henrypark133@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 00:26:34 +00:00

133 lines
3.9 KiB
Plaintext

.env
.env.local
.env.*
!.env.example
# macOS metadata
.DS_Store
# Local scratch directories — contain operator-only env files with real
# production admin/API tokens. Never commit.
tests_all/
integrations/abound/tests/source_env_vars.sh
# Stale insta snapshot proposals (review via `cargo insta review`)
*.snap.new
# Added by review-implementation skill
.review/
# Claude Code worktrees and lock files
.claude/worktrees/
.cr-worktrees/
.claude/scheduled_tasks.lock
# Sidecar tool data
.sidecar/
.todos/
.tmp/
target/
# Python
__pycache__/
*.pyc
/tests/e2e/.venv/
# Benchmark results (local runs, not committed)
bench-results/
# Coverage reports (local runs, not committed)
/coverage/
# Canary / E2E run outputs (per-run logs, screenshots, trace artifacts —
# CI uploads these via actions/upload-artifact; never commit local copies)
artifacts/
# WASM build artifacts (loaded from disk, not bundled)
*.wasm
!crates/extensions/packages/*/wasm/*.wasm
# Traces
trace_*.json
# Local Claude Code settings (machine-specific, should not be committed)
.claude/settings.local.json
.worktrees/
.ironclaw/
# De-slop loop ledger + local loop scratch (.claude/commands/deslop-reborn.md)
.work/
.review/
# Local-dev workspace artifacts: the WebChat v2 attachment lander writes
# uploaded files under a project workspace; when `serve` runs from the repo
# root they land in ./attachments/. These are user uploads, never source.
/attachments/
# The per-caller agent workspace. Local-dev sets `workspace_root` to the server's
# cwd, so `serve` from the repo root gives every caller
# ./tenants/<tenant>/users/<user>/, and anything the agent writes there -- a script
# it authored, a file it was asked to produce -- lands in the repo. Runtime output,
# never source: without this, a `git add -A` after a demo session commits an
# agent-written script into the tree (caught in review on #7171).
/tenants/
# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd
engine_trace_*.json
tests/fixtures/llm_traces/live/github_dev_workflow_full_loop.json
tests/fixtures/llm_traces/live/github_dev_workflow_full_loop.log
# Per-test live-replay logs — generated when running `--ignored` live
# tests locally. Only the .json fixtures for each scenario are checked
# in; the .log files are local debugging artifacts.
tests/fixtures/llm_traces/live/*.log
# Local test artifacts
.anvil/
.codegraph/
# codebase-memory knowledge graph. Share only the compressed bootstrap snapshot;
# local metadata and runtime state remain per-environment.
# See CLAUDE.md -> "Code Discovery".
.codebase-memory/*
!.codebase-memory/.gitattributes
!.codebase-memory/graph.db.zst
# test-tools fixture-bundle build artifacts — regenerate with
# scripts/build-test-tools.sh; only sources/manifests/schemas/prompts are tracked.
test-tools/*.zip
test-tools/*/wasm-src/target/
# node_modules must never be committed — vendored JS deps belong to the package
# manager + lockfile, not git. A bare `node_modules/` (no leading slash) matches
# a directory of that name at ANY depth, so this catches every frontend
# (webui_v2, e2e, tooling, …) and stops the recurring accidental adds
# (#6298/#6305).
node_modules/
# Built frontend bundles. Scoped per-crate .gitignore files only cover the
# crate they live in, which let a sibling crate ship 3.5 MB of build output.
crates/*/frontend/dist/
# cargo-mutants writes its report tree (caught/missed/timeout lists, per-mutant
# logs, and a copy of each mutated diff) next to the manifest it is run from.
# It is a regenerable build artifact of a local audit, never source.
mutants.out/
mutants.out.old/
# Local TypeScript app directory — anchored to repo root so it does not
# silently exclude nested `app/` directories (e.g. crates/*/frontend/src/app/).
# Prefer .git/info/exclude for personal working directories.
/app/
# Skill bundles staged into a workspace at activation. Build output, never source.
.skills/
# Added by cleanup skill: campaign state, local-only.
.cleanup/