| name | growing |
| description | Golden path /grow — confirm proposed matches, classify evidence, update confidence, run decay, surface growth changes. Full L4 backpropagation orchestrator. |
| user-invocable | true |
| allowed-tools | Read, Write, Glob, Grep, Edit, Bash, Skill |
/grow — Growth Loop Orchestrator (L4 Backpropagation)
Operator-confirmed response attribution loop. Reviews proposed matches from /listen, classifies signal quality, updates hypothesis confidence, runs decay, and surfaces growth changes.
All matches require operator confirmation. Deterministic matches (token/thread) are pre-selected for batch confirm but the operator must explicitly approve. No --auto flag.
Arguments
/grow # Full growth loop for all users
/grow --user <user> # Single user only
Chain
- Preflight:
gp_check_growth_dir
- Per-user loop (sequential, RLM-isolated):
- Step 1: Review proposed matches → operator batch-confirm
- Step 2: Classify signal quality → apply to canonical state
- Step 3: Hypothesis confidence update
- Step 4: Decay engine
- Step 5: Per-user health report
- Cross-user: Step 6 — surface shared patterns
RLM Isolation
/grow processes users one at a time with a fresh model context per user. Classification prompts (Step 2) MUST only include:
- That user's growth state
- That signal's provenance record
Cross-user context is NEVER included in classification prompts. Reset context between users.
Execution
source scripts/observer/golden-path-lib.sh
source scripts/observer/growth-state.sh
gp_status_header "grow"
find grimoires/keeper/growth/ -name "*.tmp" -mmin +5 -delete 2>/dev/null
if ! gp_check_growth_dir; then
gp_status_fail "growth" "no growth files — run /listen first"
gp_status_blocked "run /listen to create growth state from follow-up responses"
gp_status_footer
return
fi
cycle_started_at = now_iso8601()
total_confirmed = 0
total_rejected = 0
total_decayed = 0
total_confidence_updated = 0
IF --user flag provided:
users = [specified_user]
ELSE:
users = list_growth_users()
FOR each user in users (sequential, fresh context per user):
growth_path = "grimoires/keeper/growth/${user}.yaml"
proposed_path = "grimoires/keeper/growth/${user}.proposed_matches.yaml"
lock_path = "grimoires/keeper/growth/${user}.yaml.lock"
IF NOT exists(growth_path): CONTINUE
has_pending = false
IF exists(proposed_path):
WITH flock(lock_path):
proposed_content = read_yaml(proposed_path)
pending_matches = [m for m in proposed_content.matches if m.status == "pending"]
IF len(pending_matches) > 0:
has_pending = true
IF NOT has_pending:
GOTO STEP_4
FOR each match in pending_matches:
Display:
- Match type: {match.match_type} ({match.match_confidence} confidence)
- Signal snippet: {match.evidence_snippet}
- Follow-up ID: {match.follow_up_id}
- Follow-up hypothesis: (look up from growth state)
- Token: {match.follow_up_token}
- Pre-selected: YES if match_type in [token, thread], NO otherwise
confirmed_matches = [operator-confirmed matches]
rejected_matches = [operator-rejected matches]
WITH flock(lock_path):
growth_content = read_yaml(growth_path)
proposed_content = read_yaml(proposed_path)
evidence_list = []
FOR each match in confirmed_matches:
follow_up = find_follow_up_by_id(growth_content, match.follow_up_id)
IF follow_up is null:
Log: "Follow-up {match.follow_up_id} not found, skipping"
CONTINUE
IF follow_up.outcome == "responded" AND follow_up.match_evidence is not null:
IF follow_up.match_evidence.signal_id == match.signal_id:
Log: "Already applied signal {match.signal_id} to {match.follow_up_id}, skipping"
CONTINUE
signal_record = Run: scripts/provenance/query.sh --hash {match.signal_id}
IF signal_record is null:
Log: "Provenance record not found for {match.signal_id}, skipping"
CONTINUE
evidence = {
signal_id: match.signal_id,
hypothesis_id: follow_up.hypothesis_ids[0] if follow_up.hypothesis_ids else null,
direction: <agent_classified>,
strength: <agent_classified>,
quote_span: match.evidence_snippet,
quote_hash: sha256(NFC_normalize(match.evidence_snippet)),
rationale: <agent_classified>,
classified_by: "agent",
classified_at: now_iso8601()
}
IF follow_up.outcome in ["pending", "unknown"]:
follow_up.outcome = "responded"
follow_up.response_date = match.signal_timestamp
follow_up.signal_quality = evidence.strength
follow_up.match_evidence = evidence
follow_up.outcome_at = now_iso8601()
pattern = follow_up.question_pattern
IF pattern in growth_content.question_patterns:
growth_content.question_patterns[pattern].responded_count += 1
quality_numeric = signal_quality_to_numeric(evidence.strength)
IF quality_numeric is not null:
growth_content.question_patterns[pattern].signal_quality_sum += quality_numeric
growth_content.question_patterns[pattern].signal_quality_count += 1
_recompute_pattern_metrics(growth_content, pattern)
evidence_list.append(evidence)
total_confirmed += 1
FOR m in confirmed_matches: m.status = "confirmed"; m.confirmed_at = now_iso8601()
FOR m in rejected_matches: m.status = "rejected"
total_rejected += len(rejected_matches)
FOR each evidence in evidence_list:
IF evidence.hypothesis_id is not null:
growth_content = echo "$growth_content" | growth_update_confidence \
evidence.hypothesis_id evidence.direction evidence.strength cycle_started_at
total_confidence_updated += 1
WITH flock(lock_path):
growth_content.last_updated = now_iso8601()
write_yaml(growth_path + ".tmp", growth_content)
mv(growth_path + ".tmp", growth_path)
write_yaml(proposed_path + ".tmp", proposed_content)
mv(proposed_path + ".tmp", proposed_path)
STEP_4:
cycles_to_decaying = config.observer.growth.decay.cycles_to_decaying
cycles_to_stale = config.observer.growth.decay.cycles_to_stale
WITH flock(lock_path):
content = read_yaml(growth_path)
modified = growth_run_decay(user, cycles_to_decaying, cycles_to_stale, now_iso8601())
FOR each hypothesis:
IF old_state != new_state:
total_decayed += 1
write_yaml(growth_path + ".tmp", modified)
mv(growth_path + ".tmp", growth_path)
Display for user:
- Proposed matches: {confirmed} confirmed, {rejected} rejected, {still_pending} still pending
- Hypotheses: {active} active, {decaying} decaying, {stale} stale
- Effective patterns: list patterns with effectiveness_score >= 60
- Ineffective patterns: list patterns with effectiveness_score < 20 and times_used > 0
- Pending follow-ups: {count} still awaiting response
- Score deltas: latest if behavioral_signal is true
IF len(users) >= 3:
Display any cross-user patterns found
gp_status_ok "matches" "${total_confirmed} confirmed, ${total_rejected} rejected"
gp_status_ok "decay" "${total_decayed} hypotheses transitioned"
gp_status_ok "confidence" "${total_confidence_updated} hypotheses evaluated"
gp_progression_summary
gp_suggest_next "grow"
gp_status_footer
Evidence Schema
Evidence is stored at follow_ups[batch].message_ids[msg].match_evidence in canonical growth state:
match_evidence:
signal_id: "abc123..."
hypothesis_id: "H4"
direction: supports
strength: high
quote_span: "first 200 chars"
quote_hash: "sha256..."
rationale: "User confirms..."
classified_by: agent
classified_at: "2026-02-14T..."
Signal Quality Mapping
| strength | quality_numeric | Description |
|---|
| high | 1.0 | Strong, clear signal |
| medium | 0.5 | Moderate signal |
| low | 0.0 | Weak signal |
Truenames Chained
| Truename | Purpose |
|---|
growth-state.sh | Growth state CRUD + growth_update_confidence + growth_run_decay |
growth-match.sh | Response attribution (called by /listen, results consumed here) |
scripts/provenance/query.sh | Retrieve provenance records for signal verification |