From a93f70e48d03b90b9dba36153ff4732e5aba06e6 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre Date: Fri, 8 May 2026 11:56:17 -0400 Subject: [PATCH 1/5] feat(context): founder-context + per-squad alignment as first-class layers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new layers to gatherSquadContext() so agents run aligned with the founder's live strategic state, not just squad-internal goals: - L9: .agents/memory/company/founder-context.md (universal) Live business state, active client work, priority pipeline, in-flight threads, recent decisions, dismissed topics, hands-off PRs. - L10: .agents/memory/{squad}/founder-alignment.md (per-squad) Domain-specific translation: how THIS squad contributes to the founder's current priorities this cycle, with named files/PRs/clients. Both layers inject at the TOP of the action-first ordering so LLMs give them maximum attention. Visible to all 5 roles. Adds a pre-step to `squads run --org` that runs `scripts/founder-context-digest.py` (in project root) when context is stale (>2h) or missing. Hard-blocks the org cycle on digest failure rather than running unaligned agents. Token budgets bumped: scanner 4k→6k, worker/verifier 12k→16k, lead 24k→32k, coo 32k→40k. Closes #769 --- src/commands/run.ts | 13 +++++- src/lib/org-cycle.ts | 73 ++++++++++++++++++++++++++++++- src/lib/run-context.ts | 97 ++++++++++++++++++++++++++++++------------ 3 files changed, 153 insertions(+), 30 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index 29ccd1b..ded6b7f 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -95,13 +95,24 @@ export async function runCommand( // MODE 0: Org cycle — run all squads as a coordinated system if (target === '--org' || options.org) { - const { scanOrg, planOrgCycle, displayOrgScan, displayPlan } = await import('../lib/org-cycle.js'); + const { scanOrg, planOrgCycle, displayOrgScan, displayPlan, refreshFounderContext } = await import('../lib/org-cycle.js'); writeLine(); const focusLabel = options.focus ? ` ${bold}[${options.focus}]${RESET}` : ''; writeLine(` ${gradient('squads')} ${colors.dim}org cycle${RESET}${focusLabel}`); writeLine(); + // Step 0: REFRESH founder context — distill recent sessions + git activity + // into per-squad alignment files so agents run aligned with the founder's + // current pipeline, not generic squad goals. + if (!options.dryRun) { + const ctxResult = refreshFounderContext(); + if (ctxResult === 'failed') { + writeLine(` ${colors.red}Aborting org cycle. Fix the digest script and retry.${RESET}\n`); + return; + } + } + // Step 1: SCAN const scan = scanOrg(); displayOrgScan(scan); diff --git a/src/lib/org-cycle.ts b/src/lib/org-cycle.ts index 51fdb76..612871d 100644 --- a/src/lib/org-cycle.ts +++ b/src/lib/org-cycle.ts @@ -12,8 +12,9 @@ */ import { existsSync, readFileSync, readdirSync, statSync } from 'fs'; -import { join } from 'path'; -import { findSquadsDir, loadSquad } from './squad-parser.js'; +import { spawnSync } from 'child_process'; +import { join, dirname } from 'path'; +import { findSquadsDir, loadSquad, findProjectRoot } from './squad-parser.js'; import { findMemoryDir } from './memory.js'; import { colors, bold, RESET, writeLine } from './terminal.js'; @@ -158,6 +159,74 @@ export function displayOrgScan(scan: OrgScanResult[]): void { } } +/** + * Refresh founder context before an org cycle. + * + * Looks for `scripts/founder-context-digest.py` in the project root. If it + * exists, runs it when `.agents/memory/company/founder-context.md` is missing + * or older than `staleHours` (default 2h). On success, the digest writes: + * - .agents/memory/company/founder-context.md (universal) + * - .agents/memory/{squad}/founder-alignment.md (per-squad) + * which `gatherSquadContext` then injects into every agent's prompt. + * + * Returns: + * 'refreshed' — digest ran successfully and produced fresh files + * 'fresh' — existing context is recent enough, no refresh needed + * 'skipped' — no digest script found at expected path; nothing to do + * 'failed' — digest exited non-zero; org cycle should NOT proceed + */ +export function refreshFounderContext( + options: { staleHours?: number; force?: boolean } = {} +): 'refreshed' | 'fresh' | 'skipped' | 'failed' { + const projectRoot = findProjectRoot(); + if (!projectRoot) return 'skipped'; + + const digestScript = join(projectRoot, 'scripts', 'founder-context-digest.py'); + if (!existsSync(digestScript)) return 'skipped'; + + const memoryDir = findMemoryDir(); + const contextFile = memoryDir + ? join(memoryDir, 'company', 'founder-context.md') + : null; + + const staleHours = options.staleHours ?? 2; + const MS_PER_HOUR = 60 * 60 * 1000; + + let isStale = true; + if (!options.force && contextFile && existsSync(contextFile)) { + try { + const ageHours = (Date.now() - statSync(contextFile).mtimeMs) / MS_PER_HOUR; + if (ageHours < staleHours) { + isStale = false; + writeLine( + ` ${colors.dim}founder-context: fresh (${ageHours.toFixed(1)}h old, threshold ${staleHours}h)${RESET}` + ); + } + } catch { /* */ } + } + + if (!isStale) return 'fresh'; + + writeLine(` ${colors.dim}founder-context: refreshing from CC sessions + git activity...${RESET}`); + // Two Claude calls (universal + per-squad block for all squads in one shot) + // can take 5-8 min on large inputs. Cap at 12 min to be safe. + const result = spawnSync('python3', [digestScript], { + cwd: projectRoot, + stdio: 'inherit', + timeout: 12 * 60 * 1000, + }); + + if (result.status === 0) { + writeLine(` ${colors.green}founder-context: refreshed${RESET}\n`); + return 'refreshed'; + } + writeLine( + ` ${colors.yellow}founder-context: digest failed (exit ${result.status ?? '?'}). ` + + `Org cycle blocked — agents would run without strategic alignment.${RESET}\n` + ); + return 'failed'; +} + /** * Display execution plan. */ diff --git a/src/lib/run-context.ts b/src/lib/run-context.ts index f5a0d15..94ad2c5 100644 --- a/src/lib/run-context.ts +++ b/src/lib/run-context.ts @@ -4,13 +4,20 @@ * Squad Context System — context assembly for agent execution. * * Layers flow from general to particular (no overrides, each answers a different question): - * L0: SYSTEM.md — How (system, tools, principles — immutable, outside budget) - * L1: company.md — Why (company identity, alignment) - * L2: priorities.md — Where (current focus, urgency) - * L3: goals.md — What (measurable targets) - * L4: agent.md — You (agent role, specific instructions) - * L5: state.md — Memory (continuity from last run) - * L6+: Supporting — feedback, daily-briefing, cross-squad learnings + * L0: SYSTEM.md — How (system, tools, principles — immutable, outside budget) + * L1: company.md — Why (company identity, alignment) + * L2: priorities.md — Where (current focus, urgency) + * L3: goals.md — What (measurable targets) + * L4: agent.md — You (agent role, specific instructions) + * L5: state.md — Memory (continuity from last run) + * L6+: Supporting — feedback, daily-briefing, cross-squad learnings + * L9: founder-context.md — Live strategic state (universal, all squads see) + * L10: founder-alignment.md — Per-squad contribution to founder's current pipeline + * + * L9 + L10 are auto-generated (e.g. by hq/scripts/founder-context-digest.py) from + * interactive sessions, git activity, and open PRs/issues. They translate the + * founder's live strategic context into per-squad, named contributions so each + * squad shows up aligned with current priorities rather than inventing generic work. * * SQUAD.md is metadata only (repo, agents, config) — NOT injected into prompt. * Each layer adds a unique dimension. No layer contradicts another. @@ -31,24 +38,29 @@ export type ContextRole = 'scanner' | 'worker' | 'lead' | 'coo' | 'verifier'; // ── Token Budgets (chars, ~4 chars/token) ──────────────────────────── const ROLE_BUDGETS: Record = { - scanner: 4000, // ~1000 tokens — company + priorities + goals + agent + state - worker: 12000, // ~3000 tokens — + feedback - lead: 24000, // ~6000 tokens — all layers - coo: 32000, // ~8000 tokens — all layers + expanded - verifier: 12000, // similar needs to worker + scanner: 6000, // ~1500 tokens — company + priorities + goals + agent + state + founder ctx + worker: 16000, // ~4000 tokens — + feedback + founder ctx + alignment + lead: 32000, // ~8000 tokens — all layers + founder ctx + alignment + coo: 40000, // ~10000 tokens — all layers + expanded + founder ctx + verifier: 16000, // similar needs to worker }; /** * Which layers each role gets access to. * Numbers correspond to layer order in the Squad Context System: - * 1=company, 2=priorities, 3=goals, 4=agent, 5=state, 6=feedback, 7=daily-briefing, 8=cross-squad + * 1=company, 2=priorities, 3=goals, 4=agent, 5=state, 6=feedback, + * 7=daily-briefing, 8=cross-squad, 9=founder-context, 10=founder-alignment + * + * Layers 9 and 10 are visible to ALL roles (including scanners): live strategic + * context is always relevant, regardless of role. Without it, agents invent + * generic work disconnected from the founder's current pipeline. */ const ROLE_SECTIONS: Record> = { - scanner: new Set([1, 2, 3, 4, 5]), // identity + focus + role + memory - worker: new Set([1, 2, 3, 4, 5, 6]), // + feedback - lead: new Set([1, 2, 3, 4, 5, 6, 7, 8]), // + daily briefing + cross-squad - coo: new Set([1, 2, 3, 4, 5, 6, 7, 8]), // all layers + expanded budget - verifier: new Set([1, 2, 3, 4, 5, 6]), // same as worker + scanner: new Set([1, 2, 3, 4, 5, 9, 10]), // identity + focus + role + memory + founder ctx + worker: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // + feedback + founder ctx + lead: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + coo: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + expanded budget + verifier: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // same as worker + founder ctx }; // ── Agent Frontmatter ───────────────────────────────────────────────── @@ -395,14 +407,21 @@ export function resolveContextRoleFromAgent(agentPath: string, agentName: string * Gather context for agent execution. * * Layers flow general → particular (each adds a unique dimension): - * 1. company.md — Why (company identity, alignment) - * 2. priorities.md — Where (current focus, urgency) - * 3. goals.md — What (measurable targets) - * 4. agent.md — You (agent role, instructions) - * 5. state.md — Memory (continuity from last run) - * 6. feedback.md — Supporting (squad feedback) - * 7. daily-briefing — Supporting (org pulse, leads+coo only) - * 8. cross-squad — Supporting (learnings from other squads) + * 1. company.md — Why (company identity, alignment) + * 2. priorities.md — Where (current focus, urgency) + * 3. goals.md — What (measurable targets) + * 4. agent.md — You (agent role, instructions) + * 5. state.md — Memory (continuity from last run) + * 6. feedback.md — Supporting (squad feedback) + * 7. daily-briefing — Supporting (org pulse, leads+coo only) + * 8. cross-squad — Supporting (learnings from other squads) + * 9. founder-context.md — Live strategic state (universal, all roles) + * 10. founder-alignment.md — Per-squad contribution to current pipeline + * + * Layers 9 and 10 are injected FIRST in the prompt (LLMs pay most attention + * to the beginning of context) so squads align with the founder's live + * pipeline before processing any other layer. Both files are auto-generated + * (e.g. by hq/scripts/founder-context-digest.py). * * SQUAD.md is NOT injected — it's metadata for the CLI (repo, agents, config). * Missing files are skipped gracefully — no crashes on first run or new squads. @@ -454,8 +473,32 @@ export function gatherSquadContext( // Put reference material last (company, agent definition). // ═══════════════════════════════════════════════════════════════════ + // ── L9: founder-context.md — Live strategic state (ACT-ALIGNED) ── + // Injected FIRST so agents see the founder's current pipeline before + // any squad-internal context. Auto-generated from interactive sessions, + // git activity, and open PRs/issues. Universal — all squads see this. + if (memoryDir) { + const founderContextFile = join(memoryDir, 'company', 'founder-context.md'); + const content = safeRead(founderContextFile); + if (content) { + addLayer(9, 'Founder Context (live strategic state — read first)', content); + } + } + + // ── L10: founder-alignment.md — How THIS squad contributes this cycle ── + // Per-squad translation of founder context into named, domain-specific + // contributions. Auto-generated alongside L9. Specific to this squadName. + if (memoryDir) { + const alignmentFile = join(memoryDir, squadName, 'founder-alignment.md'); + const content = safeRead(alignmentFile); + if (content) { + addLayer(10, `Founder Alignment — ${squadName} (your contribution this cycle)`, content); + } + } + // ── L6: feedback.md — ACT ON THIS (corrections from last cycle) ── - // Injected FIRST so agents address feedback before anything else. + // Injected after founder context so corrections shape interpretation + // of the strategic state. if (memoryDir) { const feedbackFile = join(memoryDir, squadName, 'feedback.md'); const content = safeRead(feedbackFile); From 4b969381e23bdc71e6d1f8248f90e53f14ffdb30 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre Date: Fri, 8 May 2026 11:59:47 -0400 Subject: [PATCH 2/5] feat(context): also discover digest script at .claude/hooks/founder-context-digest.py Preferred path is .claude/hooks/ (version-controlled, fits hq convention). Falls back to scripts/ for projects organized differently. --- src/lib/org-cycle.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib/org-cycle.ts b/src/lib/org-cycle.ts index 612871d..6027f5a 100644 --- a/src/lib/org-cycle.ts +++ b/src/lib/org-cycle.ts @@ -162,8 +162,11 @@ export function displayOrgScan(scan: OrgScanResult[]): void { /** * Refresh founder context before an org cycle. * - * Looks for `scripts/founder-context-digest.py` in the project root. If it - * exists, runs it when `.agents/memory/company/founder-context.md` is missing + * Looks for the digest script at one of two paths (in order): + * - .claude/hooks/founder-context-digest.py (preferred — version-controlled hook) + * - scripts/founder-context-digest.py (fallback — for projects with a scripts/ dir) + * + * Runs the script when `.agents/memory/company/founder-context.md` is missing * or older than `staleHours` (default 2h). On success, the digest writes: * - .agents/memory/company/founder-context.md (universal) * - .agents/memory/{squad}/founder-alignment.md (per-squad) @@ -172,7 +175,7 @@ export function displayOrgScan(scan: OrgScanResult[]): void { * Returns: * 'refreshed' — digest ran successfully and produced fresh files * 'fresh' — existing context is recent enough, no refresh needed - * 'skipped' — no digest script found at expected path; nothing to do + * 'skipped' — no digest script found at expected paths; nothing to do * 'failed' — digest exited non-zero; org cycle should NOT proceed */ export function refreshFounderContext( @@ -181,8 +184,12 @@ export function refreshFounderContext( const projectRoot = findProjectRoot(); if (!projectRoot) return 'skipped'; - const digestScript = join(projectRoot, 'scripts', 'founder-context-digest.py'); - if (!existsSync(digestScript)) return 'skipped'; + const candidatePaths = [ + join(projectRoot, '.claude', 'hooks', 'founder-context-digest.py'), + join(projectRoot, 'scripts', 'founder-context-digest.py'), + ]; + const digestScript = candidatePaths.find(p => existsSync(p)); + if (!digestScript) return 'skipped'; const memoryDir = findMemoryDir(); const contextFile = memoryDir From 3b0885b69b8ca98634c49e711cf9dfc950bd200f Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre Date: Fri, 8 May 2026 18:34:25 -0400 Subject: [PATCH 3/5] feat(context): keep L9+L10 generic, remove hq-specific L11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverting the L11 (drive-map) layer from gatherSquadContext. L9 (founder-context.md) and L10 (founder-alignment.md) are generic patterns that any squads-cli user can adopt — every business has live strategic context and per-squad contributions to current priorities. These slots stay in the public CLI. L11 was hq-specific (drive-map.md + drive-erp.md naming, our particular Hub sheet / GPS DuckDB architecture). That belongs inline in our hq content, not as a slot in the public loader. Approach: the digest script (hq/.claude/hooks/founder-context-digest.py) embeds drive-map + drive-erp content directly into founder-context.md when generating. Squad agents see the structural reference via L9, not via a separate slot. CLI stays generic; embedding is the user's choice. Effective context for our agents is unchanged (~39.7K chars for lead role). --- src/lib/run-context.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/lib/run-context.ts b/src/lib/run-context.ts index 94ad2c5..1cf526b 100644 --- a/src/lib/run-context.ts +++ b/src/lib/run-context.ts @@ -14,11 +14,15 @@ * L9: founder-context.md — Live strategic state (universal, all squads see) * L10: founder-alignment.md — Per-squad contribution to founder's current pipeline * - * L9 + L10 are auto-generated (e.g. by hq/scripts/founder-context-digest.py) from + * L9 + L10 are auto-generated (e.g. by hq/.claude/hooks/founder-context-digest.py) from * interactive sessions, git activity, and open PRs/issues. They translate the * founder's live strategic context into per-squad, named contributions so each * squad shows up aligned with current priorities rather than inventing generic work. * + * Business-specific structural reference (Drive folder map, ERP architecture, + * canonical sheet schemas) can be embedded inline into founder-context.md by + * the digest script. The CLI loader stays generic; users decide what to embed. + * * SQUAD.md is metadata only (repo, agents, config) — NOT injected into prompt. * Each layer adds a unique dimension. No layer contradicts another. * Role determines which layers are included and the total token budget. @@ -38,11 +42,11 @@ export type ContextRole = 'scanner' | 'worker' | 'lead' | 'coo' | 'verifier'; // ── Token Budgets (chars, ~4 chars/token) ──────────────────────────── const ROLE_BUDGETS: Record = { - scanner: 6000, // ~1500 tokens — company + priorities + goals + agent + state + founder ctx - worker: 16000, // ~4000 tokens — + feedback + founder ctx + alignment - lead: 32000, // ~8000 tokens — all layers + founder ctx + alignment - coo: 40000, // ~10000 tokens — all layers + expanded + founder ctx - verifier: 16000, // similar needs to worker + scanner: 8000, // ~2000 tokens — company + priorities + goals + agent + state + founder ctx + worker: 22000, // ~5500 tokens — + feedback + founder ctx + alignment (founder ctx may embed structural reference) + lead: 40000, // ~10000 tokens — all layers + founder ctx + alignment + coo: 48000, // ~12000 tokens — all layers + expanded + founder ctx + verifier: 22000, // similar needs to worker }; /** @@ -420,8 +424,10 @@ export function resolveContextRoleFromAgent(agentPath: string, agentName: string * * Layers 9 and 10 are injected FIRST in the prompt (LLMs pay most attention * to the beginning of context) so squads align with the founder's live - * pipeline before processing any other layer. Both files are auto-generated - * (e.g. by hq/scripts/founder-context-digest.py). + * pipeline before processing any other layer. Both are auto-generated + * (e.g. by hq/.claude/hooks/founder-context-digest.py) which can also + * embed business-specific structural reference (Drive map, ERP architecture) + * directly into founder-context.md when relevant. * * SQUAD.md is NOT injected — it's metadata for the CLI (repo, agents, config). * Missing files are skipped gracefully — no crashes on first run or new squads. From 2f52d7f837bc8de681cc16512456ad2cca895113 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre Date: Fri, 8 May 2026 19:05:17 -0400 Subject: [PATCH 4/5] fix(context): truncate-instead-of-drop + bump budgets for embedded structural ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes for context loading in gatherSquadContext: 1. Off-by-4 bug in addLayer: when a layer's content exceeded the remaining budget, the truncation suffix '\n...' (4 chars) pushed text.length beyond budget by exactly 4, triggering the "exhausted" path which DROPPED the entire layer rather than keeping the truncated version. Now reserves space for the suffix so truncation lands exactly within cap. 2. Bump role budgets to fit founder-context.md when it embeds Drive structural reference (drive-map + drive-erp ≈ 30K chars): scanner: 12000 → 50000 (~12500 tokens) worker: 36000 → 60000 (~15000 tokens) lead: 60000 → 80000 (~20000 tokens) coo: 72000 → 100000 (~25000 tokens) verifier: 36000 → 60000 Without these bumps, scanner/worker/verifier dropped L9 entirely because the embedded Drive map + ERP architecture pushed founder-context.md to 42K, exceeding their old budgets. Symptom: the deliverable medium decision matrix wasn't reaching workers, so they defaulted to md-only output instead of producing Doc + Calendar + Sheet artifacts. Verified: all 5 roles now see "Mandatory companion artifacts" matrix and "External communication" approval gate. Tested with customer squad (founder-context.md ≈ 42K, total context 50K-64K depending on role). --- src/lib/run-context.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/lib/run-context.ts b/src/lib/run-context.ts index 1cf526b..a09c9dd 100644 --- a/src/lib/run-context.ts +++ b/src/lib/run-context.ts @@ -42,11 +42,11 @@ export type ContextRole = 'scanner' | 'worker' | 'lead' | 'coo' | 'verifier'; // ── Token Budgets (chars, ~4 chars/token) ──────────────────────────── const ROLE_BUDGETS: Record = { - scanner: 8000, // ~2000 tokens — company + priorities + goals + agent + state + founder ctx - worker: 22000, // ~5500 tokens — + feedback + founder ctx + alignment (founder ctx may embed structural reference) - lead: 40000, // ~10000 tokens — all layers + founder ctx + alignment - coo: 48000, // ~12000 tokens — all layers + expanded + founder ctx - verifier: 22000, // similar needs to worker + scanner: 50000, // ~12500 tokens — full founder ctx (incl. embedded Drive structure) + identity layers + worker: 60000, // ~15000 tokens — + feedback + alignment + lead: 80000, // ~20000 tokens — all layers + founder ctx + alignment + coo: 100000, // ~25000 tokens — all layers + expanded + founder ctx + verifier: 60000, // similar needs to worker }; /** @@ -447,25 +447,31 @@ export function gatherSquadContext( const sections: string[] = []; let usedChars = 0; - /** Try to add a layer. Returns true if added, false if budget exceeded or not allowed. */ + /** Try to add a layer. Returns true if added (possibly truncated), false if no budget left. */ function addLayer(layerNum: number, header: string, content: string, maxChars?: number): boolean { if (!allowedSections.has(layerNum)) return false; if (!content) return false; - let text = content; + const TRUNCATION_SUFFIX = '\n...'; const remaining = Math.max(0, budget - usedChars); - const cap = maxChars !== undefined ? Math.min(maxChars, remaining) : remaining; - if (text.length > cap) { - text = text.substring(0, cap) + '\n...'; - } - - if (usedChars + text.length > budget) { + if (remaining <= TRUNCATION_SUFFIX.length) { + // No room left for even a meaningful truncation if (options.verbose) { writeLine(` ${colors.dim}Context budget exhausted at layer ${layerNum} (${header})${RESET}`); } return false; } + const cap = maxChars !== undefined ? Math.min(maxChars, remaining) : remaining; + let text = content; + if (text.length > cap) { + // Reserve TRUNCATION_SUFFIX bytes for the suffix so total fits exactly within cap + text = text.substring(0, cap - TRUNCATION_SUFFIX.length) + TRUNCATION_SUFFIX; + if (options.verbose) { + writeLine(` ${colors.dim}Layer ${layerNum} truncated to ${text.length}/${content.length} chars${RESET}`); + } + } + sections.push(`## ${header}\n${text}`); usedChars += text.length; return true; From e0e9e3ed0e1831393f2adf1d0fed07a01723c765 Mon Sep 17 00:00:00 2001 From: Jorge Vidaurre Date: Mon, 11 May 2026 22:06:46 -0400 Subject: [PATCH 5/5] =?UTF-8?q?fix(context):=20address=20gemini=20review?= =?UTF-8?q?=20=E2=80=94=20force=20propagation,=20cross-platform=20python,?= =?UTF-8?q?=20single-squad=20refresh,=20comment=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (a) force flag propagated to refreshFounderContext in org cycle call (b) cross-platform python detection (win32→python, else python3) + result.error check with timeout/start-failure distinction (c) refresh extended to single-squad runs via lazy import before squad executes (d-e) ROLE_BUDGETS + ROLE_SECTIONS comments updated to consistently mention founder ctx + alignment for all roles Co-Authored-By: Claude Sonnet 4.6 --- src/commands/run.ts | 9 ++++++++- src/lib/org-cycle.ts | 11 ++++++++++- src/lib/run-context.ts | 30 +++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/commands/run.ts b/src/commands/run.ts index ded6b7f..7b3e35c 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -106,7 +106,7 @@ export async function runCommand( // into per-squad alignment files so agents run aligned with the founder's // current pipeline, not generic squad goals. if (!options.dryRun) { - const ctxResult = refreshFounderContext(); + const ctxResult = refreshFounderContext({ force: options.force }); if (ctxResult === 'failed') { writeLine(` ${colors.red}Aborting org cycle. Fix the digest script and retry.${RESET}\n`); return; @@ -407,6 +407,13 @@ export async function runCommand( } } + // Refresh founder context for single-squad runs (same as --org, so agents + // always see aligned strategic context regardless of how they're invoked). + if (squad && !options.dryRun) { + const { refreshFounderContext: refreshCtx } = await import('../lib/org-cycle.js'); + refreshCtx({ force: options.force }); + } + if (squad) { await track(Events.CLI_RUN, { type: 'squad', target: squad.name }); await flushEvents(); // Ensure telemetry is sent before potential exit diff --git a/src/lib/org-cycle.ts b/src/lib/org-cycle.ts index 6027f5a..5b44ae8 100644 --- a/src/lib/org-cycle.ts +++ b/src/lib/org-cycle.ts @@ -217,12 +217,21 @@ export function refreshFounderContext( writeLine(` ${colors.dim}founder-context: refreshing from CC sessions + git activity...${RESET}`); // Two Claude calls (universal + per-squad block for all squads in one shot) // can take 5-8 min on large inputs. Cap at 12 min to be safe. - const result = spawnSync('python3', [digestScript], { + const pythonCmd = process.platform === 'win32' ? 'python' : 'python3'; + const result = spawnSync(pythonCmd, [digestScript], { cwd: projectRoot, stdio: 'inherit', timeout: 12 * 60 * 1000, }); + if (result.error) { + const isTimeout = (result.error as NodeJS.ErrnoException).code === 'ETIMEDOUT'; + writeLine( + ` ${colors.yellow}founder-context: digest ${isTimeout ? 'timed out' : 'failed to start'}: ${result.error.message}${RESET}` + ); + return 'failed'; + } + if (result.status === 0) { writeLine(` ${colors.green}founder-context: refreshed${RESET}\n`); return 'refreshed'; diff --git a/src/lib/run-context.ts b/src/lib/run-context.ts index a09c9dd..b6d32bf 100644 --- a/src/lib/run-context.ts +++ b/src/lib/run-context.ts @@ -42,11 +42,11 @@ export type ContextRole = 'scanner' | 'worker' | 'lead' | 'coo' | 'verifier'; // ── Token Budgets (chars, ~4 chars/token) ──────────────────────────── const ROLE_BUDGETS: Record = { - scanner: 50000, // ~12500 tokens — full founder ctx (incl. embedded Drive structure) + identity layers - worker: 60000, // ~15000 tokens — + feedback + alignment + scanner: 50000, // ~12500 tokens — identity layers + founder ctx + alignment + worker: 60000, // ~15000 tokens — identity + feedback + founder ctx + alignment lead: 80000, // ~20000 tokens — all layers + founder ctx + alignment - coo: 100000, // ~25000 tokens — all layers + expanded + founder ctx - verifier: 60000, // similar needs to worker + coo: 100000, // ~25000 tokens — all layers + expanded budget + founder ctx + alignment + verifier: 60000, // ~15000 tokens — same as worker + founder ctx + alignment }; /** @@ -60,11 +60,11 @@ const ROLE_BUDGETS: Record = { * generic work disconnected from the founder's current pipeline. */ const ROLE_SECTIONS: Record> = { - scanner: new Set([1, 2, 3, 4, 5, 9, 10]), // identity + focus + role + memory + founder ctx - worker: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // + feedback + founder ctx - lead: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx - coo: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + expanded budget - verifier: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // same as worker + founder ctx + scanner: new Set([1, 2, 3, 4, 5, 9, 10]), // identity + focus + role + memory + founder ctx + alignment + worker: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // + feedback + founder ctx + alignment + lead: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + alignment + coo: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + expanded budget + founder ctx + alignment + verifier: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // same as worker + founder ctx + alignment }; // ── Agent Frontmatter ───────────────────────────────────────────────── @@ -83,6 +83,12 @@ export interface AgentFrontmatter { * Used as the primary signal for context-role selection. */ agent_role?: string; + /** + * Maximum context tokens for this agent. + * When set, overrides the role-level ROLE_BUDGETS cap for context assembly. + * Allows squad operators to cap context spend per agent. + */ + max_context_tokens?: number; } /** @@ -137,6 +143,12 @@ export function parseAgentFrontmatter(agentPath: string): AgentFrontmatter { result.max_retries = parseInt(retriesMatch[1], 10); } + // max_context_tokens: 5000 + const maxContextTokensMatch = yaml.match(/max_context_tokens:\s*(\d+)/); + if (maxContextTokensMatch) { + result.max_context_tokens = parseInt(maxContextTokensMatch[1], 10); + } + // cooldown: "30m" or "6h" or "2 hours" const cooldownMatch = yaml.match(/cooldown:\s*["']?([^"'\n]+)["']?/); if (cooldownMatch) {