Extract repo activity projector

This commit is contained in:
Meta Vens 2026-07-16 04:13:21 +01:00
commit be3659e632
6 changed files with 31 additions and 40 deletions

View file

@ -0,0 +1,21 @@
import type { RepoActivityObservation } from "./repo-activity-source.js";
export function projectRepoActivityObservation(observation: RepoActivityObservation): string {
if (observation.status === "unconfigured") return "- This Persona has no source repository configured; no repo activity was requested.";
if (observation.status === "unavailable") return [
`- Recent ${observation.sourceRepoName} activity could not be read for this turn.`,
observation.detail ? `- Reader error: ${collapse(observation.detail, 500)}` : "- Reader error: no diagnostic output.",
"- Do not claim current repo state from stale memory; use source/history tools before making fresh claims.",
].join("\n");
if (observation.status === "malformed") return [
`- Recent ${observation.sourceRepoName} activity output was not parseable.`,
`- Raw output: ${collapse(observation.raw, 500)}`,
"- Do not claim current repo state from stale memory; use source/history tools before making fresh claims.",
].join("\n");
return observation.digest || `- No recent ${observation.sourceRepoName} activity was reported.`;
}
function collapse(value: string, maxLength: number): string {
const normalized = value.replace(/\s+/g, " ").trim();
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
}

View file

@ -217,6 +217,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- Global Codex doctrine discovery now crosses `global-agent-doctrine-source.ts`, which reports the first readable `AGENTS.md` authority or a typed unavailable observation with attempted paths and errors. `persona-turn-assembler.ts` alone lowers that observation into prompt text, so source failure cannot silently become invented doctrine and the shell no longer crawls user-home paths.
- Persona Mind assembly now crosses `persona-memory-turn-coordinator.ts`. It sequences the existing typed state source, curiosity source/projector, state-packet projector, optional text-projection actuator, semantic memory source, and `persona-memory-recall-projector.ts` through narrow injected ports. Queued live turns and `--assemble-prompt` inspection now receive the same projected Mind and semantic-recall pair; the old shell accidentally omitted semantic recall from live turns. The coordinator does not select initiative, queue work, or mutate canonical Persona state.
- Stored interaction-profile acquisition now enters through `readStoredPersonaHumanPronounGuidance` in `persona-social-context-source.ts`. The adapter owns opening the configured state provider and delegates visible-human selection, pronoun evidence precedence, and guaranteed close behavior to the existing source. The heartbeat shell no longer opens profile storage or manufactures a reader callback.
- Repo-activity observation lowering now lives in pure `repo-activity-projector.ts`; `repo-activity-source.ts` retains exporter execution and parsing. Both live and inspection assembly use the same projector for `ok`, `unconfigured`, `unavailable`, and `malformed` evidence, and the shell's dead text helpers were deleted.
- Direct Face `UPDATE REQUEST` and `BIFROST TOPIC` packets are ignored while `REPO_FACE_BIFROST_ENABLED=false`. Do not debug missing Bifrost jobs as a live failure unless that flag has been deliberately enabled again.
- Live model-facing prompt prose has started moving out of executable code. The shared loader is `packages/shared/src/prompt-template.ts`; extracted prompt surfaces live under `prompts/`, including repo Face turn/reviewer prompts, provider wrapper prompts, situational social read prompts, source-grounding reminders, consensus-feed prompts, and repo Face model-smoke scenarios. Code should project data and select prompt names; prompt files should own behavioral prose.
- Identity collapse has started. Active bot, worker, MCP repo-identity tools, and Face CTB scheduler paths now load through the canonical Face registry and treat repo Discord identities as compatibility projections. Next bounded move is to finish removing remaining direct conceptual dependence on repo identities, then split the Face turn loop into scheduler, context assembler, prompt renderer, candidate parser, policy gate, and transport adapter.

View file

@ -79,8 +79,8 @@ import {
} from "../apps/persona-scheduler/dist/control-source.js";
import {
readRepoActivity,
type RepoActivityObservation,
} from "../apps/persona-scheduler/dist/repo-activity-source.js";
import { projectRepoActivityObservation } from "../apps/persona-scheduler/dist/repo-activity-projector.js";
async function main(): Promise<void> {
const config = loadConfig();
@ -417,7 +417,7 @@ async function queueRepoFaceTurn(input: {
});
const repoActivitySurface = identity.identityKind === "native_persona"
? projectNativePersonaBody(identity)
: renderRepoActivityObservation(readRepoActivity({ identity, storageRoot: input.config.storageRoot }));
: projectRepoActivityObservation(readRepoActivity({ identity, storageRoot: input.config.storageRoot }));
const globalAgentDoctrine = await readGlobalAgentDoctrine({ codexHome: process.env.CODEX_HOME, userProfile: process.env.USERPROFILE });
const assembly = assemblePersonaTurn({
identity,
@ -547,7 +547,7 @@ async function assembleRepoFaceTurnPrompt(input: {
stateObservation: personaStateObservation,
projectedMemoryOverride,
});
const repoActivitySurface = renderRepoActivityObservation(
const repoActivitySurface = projectRepoActivityObservation(
readRepoActivity({ identity, storageRoot: input.config.storageRoot }),
);
const globalAgentDoctrine = await readGlobalAgentDoctrine({ codexHome: process.env.CODEX_HOME, userProfile: process.env.USERPROFILE });
@ -592,27 +592,6 @@ async function assembleRepoFaceTurnPrompt(input: {
};
}
function renderRepoActivityObservation(observation: RepoActivityObservation): string {
if (observation.status === "unconfigured") {
return "- This Persona has no source repository configured; no repo activity was requested.";
}
if (observation.status === "unavailable") {
return [
`- Recent ${observation.sourceRepoName} activity could not be read for this turn.`,
observation.detail ? `- Reader error: ${collapseWhitespace(observation.detail, 500)}` : "- Reader error: no diagnostic output.",
"- Do not claim current repo state from stale memory; use source/history tools before making fresh claims.",
].join("\n");
}
if (observation.status === "malformed") {
return [
`- Recent ${observation.sourceRepoName} activity output was not parseable.`,
`- Raw output: ${collapseWhitespace(observation.raw, 500)}`,
"- Do not claim current repo state from stale memory; use source/history tools before making fresh claims.",
].join("\n");
}
return observation.digest || `- No recent ${observation.sourceRepoName} activity was reported.`;
}
async function readOptionalMemorySurface(path: string | undefined): Promise<string | undefined> {
if (!path) {
return undefined;
@ -656,25 +635,10 @@ function buildInspectionParticipant(
};
}
function collapseWhitespace(value: string, maxLength?: number): string {
const normalized = value.replace(/\s+/g, " ").trim();
return maxLength && normalized.length > maxLength
? `${normalized.slice(0, maxLength - 3)}...`
: normalized;
}
function normalizeKey(value: string): string {
return value.trim().toLowerCase();
}
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, Number(value.toFixed(3))));
}
function round3(value: number): number {
return Math.round(value * 1000) / 1000;
}
function readArgValue(name: string): string | undefined {
const index = process.argv.indexOf(name);
if (index < 0) {

View file

@ -63,6 +63,7 @@ import {
readSwarmControlState,
} from "../apps/persona-scheduler/dist/control-source.js";
import { readRepoActivity } from "../apps/persona-scheduler/dist/repo-activity-source.js";
import { projectRepoActivityObservation } from "../apps/persona-scheduler/dist/repo-activity-projector.js";
import { readPersonaStateObservation } from "../apps/persona-scheduler/dist/persona-state-source.js";
import { readPersonaMemoryRecall } from "../apps/persona-scheduler/dist/persona-memory-context-source.js";
import { coordinatePersonaMemoryTurn } from "../apps/persona-scheduler/dist/persona-memory-turn-coordinator.js";
@ -858,6 +859,8 @@ const repoActivity = readRepoActivity({
},
});
assert.deepEqual(repoActivity, { status: "ok", sourceRepoName: "AetheriaLore", digest: "- Fresh repo motion." }, "repo activity crosses the source boundary as facts, not prompt prose");
assert.equal(projectRepoActivityObservation(repoActivity), "- Fresh repo motion.", "repo activity prompt lowering belongs to the pure projector");
assert.match(projectRepoActivityObservation({ status: "unavailable", sourceRepoName: "AetheriaLore", detail: "exporter asleep" }), /could not be read[\s\S]*exporter asleep[\s\S]*Do not claim current repo state/, "repo activity failure remains explicit without moving exporter mechanics into the projector");
assert.ok(repoActivityArgs.includes("--read-only"), "Persona turns cannot advance the repo activity cursor while observing body context");
assert.deepEqual(repoActivityArgs.slice(-4), ["--hours", "96", "--max-commits", "5"], "the source owns one bounded activity window");
const failedRepoActivity = readRepoActivity({

View file

@ -435,3 +435,4 @@
{"ts":"2026-07-16T04:45:00Z","type":"implementation","status":"ok","note":"Global Codex doctrine discovery moved into global-agent-doctrine-source.ts with typed provenance and unavailable evidence. persona-turn-assembler.ts now owns failure-to-prompt projection; the shell no longer crawls CODEX_HOME/USERPROFILE/home paths or authors fallback doctrine. Focused smoke proves candidate deduplication, first-readable authority, and explicit failure projection."}
{"ts":"2026-07-16T05:05:00Z","type":"architecture","status":"ok","note":"Persona Mind assembly moved into persona-memory-turn-coordinator.ts with injected state, curiosity, projection, and recall ports; recall text moved into persona-memory-recall-projector.ts. This exposed and repaired a live/inspection divergence: queued turns previously omitted semantic Persona recall while inspection included it. Both paths now consume the same projected Mind plus recall pair; shell fell to 718 lines and typecheck/initiative/migration smokes pass."}
{"ts":"2026-07-16T05:15:00Z","type":"implementation","status":"ok","note":"Configured interaction-profile acquisition moved into persona-social-context-source.ts as readStoredPersonaHumanPronounGuidance. The shell no longer opens state storage or builds a profile-reader adapter; focused smoke proves existing pronoun policy is reused and provider close semantics survive the concrete adapter."}
{"ts":"2026-07-16T05:20:00Z","type":"implementation","status":"ok","note":"Repo activity prompt lowering moved into pure repo-activity-projector.ts while repo-activity-source.ts retains exporter execution/parsing. Live and inspection paths share the projector; unavailable evidence smoke preserves failure detail and anti-stale-claim guidance. Three dead shell text/number helpers were deleted."}

View file

@ -76,6 +76,7 @@ active_subgoals:
- `global-agent-doctrine-source.ts` owns read-only candidate discovery and acquisition for global Codex `AGENTS.md` doctrine. It returns typed `ok` or `unavailable` evidence with provenance and errors; `persona-turn-assembler.ts` owns the prompt-facing failure projection. The shell cannot search home directories or invent fallback doctrine.
- `persona-memory-turn-coordinator.ts` owns sequencing, not truth: it carries one typed Persona-state observation through curiosity evidence/projection, deterministic state-packet projection, optional text projection, and semantic recall, returning one projected Mind plus one recall surface. Each source/projector retains its own invariant and injected port. Live queued turns and inspection turns share this coordinator, so semantic recall can no longer exist only in inspection. `persona-memory-recall-projector.ts` alone lowers recall hits or unavailable evidence into prompt text.
- `persona-social-context-source.ts` owns both the injected interaction-profile reader contract and the concrete configured-storage adapter. The adapter opens the provider and delegates visible-human selection, evidence precedence, and guaranteed close semantics to the source; the orchestration shell cannot open profile storage or duplicate pronoun policy.
- `repo-activity-projector.ts` is the pure prompt-facing owner for typed repo-activity observations. `repo-activity-source.ts` alone executes and parses the exporter; live and inspection turns share the projector and the shell cannot reinterpret failures or raw exporter output.
- `bifrost-governance-source.ts` owns the remaining Bifrost digest CLI adapter and converts command or parse failures into typed error topics. Persona prompt composition receives governance facts without knowing Bifrost process mechanics; `scripts/run-repo-face-heartbeats.ts` may not invoke the Bifrost CLI directly.
- `initiative-engine.ts` is the only writer of scheduler state fields and history. Reconciliation, controls, pause/disabled skips, stale-turn recovery witnesses, semantic pressure, attention consumption, queue transitions, and tick finalization all cross named engine commit primitives. Queue/context actuators return results and cannot patch participants or history directly.
- `turn-actuator.ts` owns lowering an assembled Persona prompt and evidence bundle into the canonical worker `ContextBundle` and approved `repo-face-rumination` job. It owns the worker actor, command, request ID, approval defaults, and `createJob` call, then returns only a queue receipt. The orchestration shell chooses no queue mechanics and the actuator cannot read Persona or initiative state.
@ -289,4 +290,4 @@ rejected_paths:
current_status:
summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live. Repo Face/persona heartbeat jobs run through the direct `openai_api` provider configured for MiMo `mimo-v2.5-pro`, with the expensive state-projector pass still disabled. The 2026-07-04 transport repair replaced the old Bifrost HTTP/token bridge shortcut with typed Bifrost CultMesh Discord command and receipt documents advertised in the Bifrost provider store at `cultmesh://asgard.starfire.bifrost/commands/discord-post`. VoidBot stores the Bifrost target as a CultMesh URI, writes command docs to `E:/Projects/Bifrost/.bifrost/provider-store.cc`, optionally pumps Bifrost's command processor, and waits for typed receipts before completing repo Face Discord delivery. Bifrost's provider advertisement exposes `discord.post` command metadata for Odin/Eve agents, and a bounded Weksa live-fire completed visibly. Eve live state now runs end to end through CultLib dev retained publications, Hermodr ordered SSE, Eve component bindings, and VoidBot typed control state. Receipts acknowledge intent only. Heat initial load, provider-driven change, and browser-command change were visibly verified without reload through the same subscription.
next_action: Keep the Yggdrasil swarm paused. Move the remaining repo-activity observation-to-text lowering from `scripts/run-repo-face-heartbeats.ts` into a pure projector beside `repo-activity-source.ts`, then make live and inspection assembly share it. Do not move exporter execution into the projector and do not unpause without an explicit controlled-live-fire decision.
next_action: Keep the Yggdrasil swarm paused. Move inspection-only participant synthesis from `scripts/run-repo-face-heartbeats.ts` into a pure scheduler inspection fixture/factory, then audit the remaining shell for any authority beyond CLI parsing, top-level orchestration, neutral participant projection, and source/actuator calls. Do not unpause without an explicit controlled-live-fire decision.