Implement ADR 0012 so Tool and method capabilities persist across sessions and evolve based on runtime fitness rather than prompt lineage.
This plan turns "Tool Builders create durable Tools that compound over time" into a concrete runtime mechanism.
In scope:
- Cross-session persistence for tool registry metadata.
- Cross-session persistence for method artifacts (
role + method_nameidentity). - Artifact selection, repair, and regeneration policy with intrinsic/extrinsic/adaptive failure classification.
- Recency-bounded known-tools prompt injection and pruning lifecycle.
- Observability and maintenance operations for persisted artifacts.
Out of scope:
- Lua runtime parity.
- Provider-specific optimization beyond existing schema contract.
- New external storage systems (SQLite/Postgres); initial implementation remains file-based.
- Stable artifact identity is
role + method_name. prompt_versionis metadata/staleness signal, not a lookup key.- Persisted artifacts do not receive elevated trust, privileges, or capabilities.
- Existing dependency/environment contracts from ADR 0010/0011 remain authoritative.
- Tolerant
Outcomesemantics remain the dynamic-call contract. - File persistence uses atomic write semantics to avoid partial artifacts under concurrent writers.
Deliver in six incremental phases. Each phase is independently shippable and testable.
Goals:
- Capture deterministic before-persistence traces for comparison.
- Preserve prompt/runtime behavior snapshots before artifact read path exists.
Implementation:
- Run baseline scenarios with current tip:
runtimes/ruby/examples/assistant.rb(Google News + Yahoo News flow)runtimes/ruby/examples/philosophy_debate.rb
- Extract correlated JSONL traces from
~/.local/state/recurgent/recurgent.jsonl. - Store fixtures under
docs/baselines/<YYYY-MM-DD>/:assistant-google-yahoo.jsonlphilosophy-debate.jsonlREADME.mdwith exact commands, model, and timestamp.
- Reference these fixtures in acceptance tests as before/after evidence.
Exit criteria:
- Baseline fixtures committed and indexed in docs.
- Re-run process documented so future prompt/runtime changes can refresh baselines.
Goals:
- Define on-disk schema contracts.
- Introduce prompt/runtime version constants.
- Keep the runtime surface minimal and always-on for persistence.
Implementation:
- Add runtime constants:
TOOLSTORE_SCHEMA_VERSIONPROMPT_VERSIONMAX_REPAIRS_BEFORE_REGENKNOWN_TOOLS_PROMPT_LIMIT
- Add runtime configuration:
toolstore_root(storage location override)
- Define JSON schema docs for:
tools/registry.jsontools/<role>/<method>.json
Exit criteria:
- Constants and runtime configuration wired and documented.
- Schema docs and validation stubs committed.
Goals:
- Persist delegated tool contracts to disk.
- Load registry on startup.
- Hydrate
<known_tools>from disk-backed registry.
Implementation:
- Add
ToolStoremodule:load_registrysave_registryupsert_tool_contractfind_tool_metadata
- Integrate
delegate(...)andtool(...):- write-through updates to registry file.
- Startup load path:
- initialize
@context[:tools]from registry.
- initialize
- Add corruption handling:
- invalid JSON -> quarantine file and continue with empty registry.
Suggested files:
runtimes/ruby/lib/recurgent/tool_store.rbruntimes/ruby/lib/recurgent/tool_store_paths.rbruntimes/ruby/lib/recurgent.rb(init/load + delegate hooks)runtimes/ruby/lib/recurgent/prompting.rb(known-tool rendering already present; ensure disk-backed source)
Exit criteria:
- New process startup sees previously delegated tools.
tool("name")works after restart without re-forging.
Goals:
- Persist successful generated programs as method artifacts.
- Capture execution metadata for fitness scoring.
Implementation:
- On successful execution, persist artifact:
- identity:
role + method_name - payload: code, dependencies, metadata.
- identity:
- Track metrics:
- success/failure counts
- intrinsic/extrinsic/adaptive counts
- last failure reason/class.
- Persist both generated and repaired artifact generations.
- Keep generation history:
- latest + previous 2 artifacts per method.
- Persist artifact updates with atomic write (temp + rename).
Suggested files:
runtimes/ruby/lib/recurgent/artifact_store.rbruntimes/ruby/lib/recurgent/artifact_metrics.rbruntimes/ruby/lib/recurgent/call_execution.rb(write-through hook)
Exit criteria:
- Method artifact files are created/updated after successful calls.
- Metadata fields present and valid.
Goals:
- Use persisted artifacts before calling the provider.
- Enforce selection policy using health/staleness signals.
Implementation:
- Read-path before generation:
- load artifact by
role + method_name.
- load artifact by
- Selection policy:
- missing/corrupt/runtime-incompatible -> regenerate.
- contract fingerprint mismatch -> stale (repair/regenerate path).
- healthy -> execute persisted.
- degraded -> repair first if enabled.
- Add compatibility checks:
- schema version
- runtime version
- code checksum integrity.
- Add
program_sourcetracking:persisted | generated.
Suggested files:
runtimes/ruby/lib/recurgent/artifact_selector.rbruntimes/ruby/lib/recurgent.rb(_generate_and_executeorchestration)runtimes/ruby/lib/recurgent/observability.rb(source fields)
Exit criteria:
- Warm method calls execute from persisted artifacts with no provider call.
- Incompatible artifacts bypass cleanly to generation path.
Goals:
- Repair failed persisted artifacts before full regeneration.
- Bound repair chains with budget.
- Separate intrinsic vs extrinsic vs adaptive failures for fitness decisions.
Implementation:
- Failure classifier:
- intrinsic examples: syntax error, parse error, logic mismatch, arity mismatch.
- extrinsic examples: timeout, DNS/network failure, rate limit, remote 5xx.
- adaptive examples: upstream schema/format drift, API response contract drift, parser assumptions invalidated by source changes.
- Repair pipeline:
- input includes failed code + error + contract + args signature.
- validate repaired code via existing syntax gate.
- promote on success.
- Repair budget:
- increment
repair_count_since_regen. - if budget exhausted, force full regeneration on next failure.
- increment
- Classification policy:
- intrinsic: counts toward regeneration thresholds.
- adaptive: route to repair first and count separately.
- extrinsic: log for observability but do not penalize artifact health.
- Add
program_source: repairedand repair observability.
Suggested files:
runtimes/ruby/lib/recurgent/artifact_repair.rbruntimes/ruby/lib/recurgent/failure_classifier.rbruntimes/ruby/lib/recurgent.rbretry/generation orchestration
Exit criteria:
- Persisted artifact failures trigger repair path deterministically.
- Budget exhaustion triggers full regeneration.
- Extrinsic failure spikes do not demote healthy artifacts.
Goals:
- Keep prompt injection bounded as tool catalog grows.
- Support lifecycle management of stale tools/artifacts.
Implementation:
- Ranking service for
<known_tools>:- default utility score:
success_rate * recency_decay. - bounded to
KNOWN_TOOLS_PROMPT_LIMIT.
- default utility score:
- Pruning/archival policy:
- soft de-prioritize first.
- optional hard prune after retention window.
- Add maintenance command:
- list stale tools
- prune/archive candidates
- dry-run mode.
Suggested files:
runtimes/ruby/lib/recurgent/known_tool_ranker.rbruntimes/ruby/lib/recurgent/tool_maintenance.rbbin/recurgent-tools(optional CLI helper)
Exit criteria:
- Prompt size remains bounded with large registry.
- Maintenance operations are auditable and reversible.
{
"schema_version": 1,
"tools": {
"web_fetcher": {
"role": "web_fetcher",
"purpose": "fetch and parse web content from URLs, including RSS feeds",
"deliverable": { "type": "object", "required": ["status", "content"] },
"acceptance": [{ "assert": "status indicates success or failure" }],
"failure_policy": { "on_error": "return_error" },
"created_at": "2026-02-15T00:00:00Z",
"last_used_at": "2026-02-15T00:00:00Z",
"usage_count": 12
}
}
}{
"schema_version": 1,
"role": "web_fetcher",
"method_name": "fetch",
"contract_fingerprint": "sha256:...",
"prompt_version": "2026-02-15.depth-aware.v3",
"runtime_version": "0.1.0",
"model": "claude-sonnet-4-5-20250929",
"code_checksum": "sha256:...",
"code": "result = ...",
"dependencies": [],
"success_count": 34,
"failure_count": 3,
"intrinsic_failure_count": 1,
"adaptive_failure_count": 0,
"extrinsic_failure_count": 2,
"recent_failure_rate": 0.08,
"last_failure_reason": "HTTP 503 upstream",
"last_failure_class": "extrinsic",
"repair_count_since_regen": 1,
"created_at": "2026-02-15T00:00:00Z",
"last_used_at": "2026-02-15T00:00:00Z",
"last_repaired_at": "2026-02-15T00:00:00Z",
"history": [
{ "id": "gen-3", "parent_id": "gen-2", "trigger": "repair:parse_error", "created_at": "..." },
{ "id": "gen-2", "parent_id": "gen-1", "trigger": "regenerate:budget_exhausted", "created_at": "..." },
{ "id": "gen-1", "parent_id": null, "trigger": "initial_forge", "created_at": "..." }
]
}For each dynamic call (role, method_name):
- Load candidate artifact by stable identity.
- Validate integrity and compatibility.
- Select path:
- healthy -> execute persisted.
- stale/degraded -> repair if enabled and budget available.
- else regenerate.
- Execute code in existing sandbox.
- Classify failures (intrinsic/extrinsic/adaptive).
- Update artifact metrics and lineage trigger:
- intrinsic affects health score/regeneration threshold.
- adaptive prioritizes repair flow.
- extrinsic excluded from health demotion.
- Persist updated artifact/registry state.
- Emit observability fields.
- ToolStore load/save and corruption quarantine.
- Artifact serialization/validation/integrity checks.
- Selector decision matrix (healthy/stale/degraded/corrupt).
- Failure classifier intrinsic vs extrinsic vs adaptive mapping.
- Repair budget enforcement.
- Ranker and pruning policy logic.
- Cross-session tool reuse:
- forge tool in session A, use in session B.
- Artifact warm path:
- first call generates, second call skips provider.
- Repair path:
- persisted artifact fails, repair succeeds, no full regen.
- Budget path:
- repeated failures exceed repair budget -> forced regeneration.
- Contract mismatch:
- same method with changed contract fingerprint triggers stale handling.
- Concurrent writers:
- two sessions writing same
role + method_nameuse atomic write semantics without partial/corrupt artifacts.
- two sessions writing same
- News workflow continuity across restarts.
- Prompt refactor does not invalidate proven artifact.
- Network outage does not permanently demote healthy fetcher.
- Known-tools prompt remains bounded with 100+ tools.
- Baseline-vs-post-persistence trace comparison demonstrates behavior preservation where expected.
Add to log entry schema:
program_sourceartifact_hitartifact_prompt_versionartifact_contract_fingerprintartifact_success_countartifact_failure_countartifact_intrinsic_failure_countartifact_adaptive_failure_countartifact_extrinsic_failure_countartifact_generation_triggerrepair_attemptedrepair_succeededfailure_class
- Start with artifact read disabled, write enabled.
- Enable read path in canary mode (single role allowlist).
- Enable repair path only after read-path stability.
- Keep immediate kill switches:
- disable artifact read
- disable repair
- force generation only.
Rollback plan:
- Flip read/repair flags off.
- Continue writing metrics for diagnostics.
- Optionally archive suspect artifacts.
- Diagnose:
- inspect recent
program_sourceand failure class trends.
- inspect recent
- Quarantine:
- disable specific role/method artifact.
- Recover:
- force regenerate and reset
repair_count_since_regen.
- force regenerate and reset
- Promote:
- manually bless a validated artifact and optionally reset health counters (
--reset-metrics).
- manually bless a validated artifact and optionally reset health counters (
- Maintain:
- periodic prune/archive based on recency.
Prerequisites:
- ADR 0012 accepted.
- Existing prompting depth/known-tools mechanisms in place.
- Existing tolerant
Outcomeand syntax validation path available.
Execution order:
- Phase 0 -> Phase 1 -> Phase 2 -> Phase 3 -> Phase 4 -> Phase 5
- No phase advances without prior exit criteria met.
- Tool contracts persist and reload across restarts.
- Method artifacts execute from disk with deterministic selection.
- Prompt changes do not force full artifact invalidation.
- Repair budget prevents infinite patch chains.
- Intrinsic/adaptive/extrinsic failure separation influences selection correctly.
- Known-tools prompt remains bounded while registry grows.
- Full plan covered by unit, integration, and acceptance tests.
Defaults (adopt in this implementation):
- Utility score starts simple:
utility = success_rate * recency_decay.- add exploration bonus only if telemetry shows new tools are starved.
- Write strategy defaults to immediate atomic writes:
- write temp file then rename for artifact/registry commits.
- Artifact history defaults to embedded entries in each method JSON:
- keep latest 3 generations with
id,parent_id,trigger, and timestamp.
- keep latest 3 generations with
Remaining open decisions:
- Maintenance command placement:
- extend
bin/recurgent-watchvs dedicatedbin/recurgent-toolsCLI.
- extend