feat(family): the promotion engine - triggers, the three criteria, the decision log and undo (milestone 463 step 3, #4989)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Failing after 1m5s
CI & Build / Python tests (push) Successful in 1m58s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Failing after 1m5s
CI & Build / Python tests (push) Successful in 1m58s
CI & Build / Build & push image (push) Skipped
The agent promotes a family idea when all three criteria hold, and no person approves it. The criteria are product text: services/family.py states them, and the promote tool's docstring names every criterion the service enforces.
- Criteria: each one vetoes on its own when its reasoning is blank. Platform terms also needs an applies-when and a platform scope; proven also needs named evidence. A veto keeps the idea a candidate and is logged, so it becomes precedent.
- Precedent: every promotion stores the decisions on the nearest ideas by meaning, plus any the caller names.
- Promotion sets canon, the applicability test and the platform scope, and opens an unassessed ledger row for each member project the promoter can write. Re-promotion moves the version past every version the idea has held.
- Retire and undo: undo reverses only the latest idea-level decision, restores its recorded before-state, and logs itself with the undone decision as its precedent. Settled here: leaving canon closes the unassessed rows but keeps the judged ones, which read as needing a recheck after a re-promotion.
- Triggers open evaluations but never promote:
- a cross-project lineage citation ("matching #N") on a note or task write;
- a same-meaning record in another project on a shared platform, on create, at 0.80 (measured: the known pattern's builds scored 0.79-0.82, an unrelated project's best match 0.65);
- a milestone closing on a platform.
Each fails open and rides the response as family_hint.
- Doors: seven MCP tools, /api/family REST endpoints, and a Family page (nav, /family) showing the criteria, the ideas, and the decision log with undo and retire.
- utils/recordHref.ts holds the one copy of "where a record opens", now shared with LessonDetailView.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ import { DEAD_WEIGHT_ADVICE } from "@/utils/deadWeight";
|
||||
import { useToastStore } from "@/stores/toast";
|
||||
import { renderMarkdown } from "@/utils/markdown";
|
||||
import { canWriteRecord } from "@/utils/permission";
|
||||
import { recordHref } from "@/utils/recordHref";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -59,14 +60,6 @@ const insightHtml = computed(() =>
|
||||
lesson.value?.insight ? renderMarkdown(lesson.value.insight) : "",
|
||||
);
|
||||
|
||||
/** Where each source opens. A task and a note live at different routes, and a
|
||||
* link that guesses wrong is worse than one that is plain. */
|
||||
function sourceHref(rec: { id: number; is_task?: boolean; note_type?: string }) {
|
||||
if (rec.is_task) return `/tasks/${rec.id}`;
|
||||
if (rec.note_type === "snippet") return `/snippets/${rec.id}`;
|
||||
if (rec.note_type === "lesson") return `/lessons/${rec.id}`;
|
||||
return `/notes/${rec.id}`;
|
||||
}
|
||||
|
||||
const canWrite = computed(() => canWriteRecord(lesson.value?.permission));
|
||||
|
||||
@@ -193,7 +186,7 @@ onMounted(load);
|
||||
<h2 class="ld-panel-label">Learned from</h2>
|
||||
<ul class="ld-sources">
|
||||
<li v-for="rec in lesson.learned_from_records" :key="rec.id">
|
||||
<router-link :to="sourceHref(rec)" class="ld-link">
|
||||
<router-link :to="recordHref(rec)" class="ld-link">
|
||||
{{ rec.title }}
|
||||
</router-link>
|
||||
<span v-if="rec.status" class="ld-source-status">{{ rec.status }}</span>
|
||||
|
||||
Reference in New Issue
Block a user