The module docstring keeps why the kind exists, where its trigger lives, what
it inherits and why it needs no migration, without milestone, decision and
note ids or this instance's snippet counts. The body-strip docstring states
why taking the note matters instead of retelling the defect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The module docstring keeps why rules need their own usage stream and why the
ranked list is the one that is named, and drops the dated story of the
bulk surfaces starting to emit, measured decline rates and the corrected
earlier wording. Outcome docstrings describe the four states without
milestone references.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue, decision, milestone and operator-decision citations are removed;
"previously each route…" and "list_tasks once returned…" stories become the
present-tense reasons they taught. The note about the removed delete_note
now says plainly that there is deliberately none, and why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue, milestone and rule citations and dated measurement headers are
removed; calibration figures stay. Corrected the lesson-gate comment, which
said lessons are not in _REPORT_KINDS and so have no duplicate-report
backstop. They are, and the comment now says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestone, issue, note and rule-number citations are removed. The
co_surfaces docstring states why paired rules need the edge instead of
retelling the instance that taught it; the list-rules and applicable-rules
docstrings drop the retired subscription history. The preference-restore
error string no longer cites a milestone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue, decision, milestone and rule citations are removed; the snippet-mirror
docstring drops the paragraph about its own earlier wording, and the
pointer-model and freshness comments state the design rather than the plan
step that introduced it. Access notes point at services/access.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue, note and milestone citations are removed along with first-deploy
counts and the measured phantom-definition story; "button B" jargon becomes
the divergence it names. Each block keeps its present-tense reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recurring "#2663 shape" and similar issue citations become the property
they stand for (an outage that reads as a quiet window, a statistic that
cannot vary). Dated observations, milestone tags and rule numbers are
removed; the near-miss and pull-through reasoning stays in present tense.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The format-version log keeps what each version adds, because restore depends
on what an older archive can carry; dates, milestone and issue ids, and the
stories of what a missing column once cost are removed. Compatibility
comments now name the archive version instead of a milestone. Two leftover
history-phrased comments in embeddings and shape_ledger are restated as
present-tense reasons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record ids, milestone tags, dated measurements and "used to" stories are
removed; the chunker version list now says what each stamped version
contains. Corrected two comments that said a failed search leaves
report["searched"] absent (it is stamped False first), and pointed the
rule-search docstring at _RULEHINT_BAND in retrieval_pipeline instead of a
tier note in plugin_context that no longer exists. plugin_context's own
dangling "note on tiers" pointer is reworded to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record ids, milestone and step numbers, and the stories of copies drifting
apart are removed; each invariant and slot is justified in present tense.
The rule-band probe table stays as calibration. Section headers drop their
milestone tags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record ids, snippet numbers, dated measurements and the proposer's version
history are removed; each block now states the design reason in the present
tense. Calibration figures that justify a floor stay, without the instance
they were measured on. One error string drops a note id. The density
threshold comment moves above the constants it describes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record ids, milestone numbers, dated telemetry readings and rule numbers
leave the retrieval-arm comments and docstrings. Each bar keeps its
present-tense reason and the probe-score calibrations, which describe the
model on any install.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MCP tool docstrings and comments, the server's orientation notes, the plugin
skills, every hook script and the plugin README/PACKAGING drop record ids,
milestone numbers, dates and incident stories, and state each non-obvious why
as a present-tense fact. History stays in the task logs.
Corrections found on the way:
- plugin README/PACKAGING said every hook is a GET; moment, rule-opened and
reply-rules POST, so the plugin's key needs write scope. Both now say so,
name Settings -> MCP Access, and list every wired hook and endpoint.
- the contract-change hint printed "(lesson #4207)", this instance's record,
to every install. It no longer cites a record.
- tasks.py claimed there is no delete_task; there is.
- rulebooks.py cited delete_event and a design spec that no longer exist.
- scribe_after_write.sh carried an always-empty etag_q; removed.
- test_write_path_trigger pinned "| tac | scribe_defs | head -1", text that
existed only in a comment about the old code; it now pins the real tail -1
pipeline.
- docs/development.md: CI runs on Gitea Actions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
g t pushed "/", which now redirects to the dashboard, so it did what g h
does. It pushes /knowledge. The overlay gains g r (Rules) and g g (Graph),
calls g h "Dashboard", and docs/features.md lists g t.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestone 544, step 6217 (follow-up to #1778, which rewrote features.md and
api-reference.md in July but left architecture.md and configuration.md alone).
- architecture.md, configuration.md, development.md: rewritten against the
code. They still described Ollama, chat, SSE generation, voice, project
summaries, image caching, Docker secrets that the compose files don't use,
a raw-SQL migration convention the migrations no longer follow, a 27-entry
migration list, CalVer YY.MM.DD.N tags and the removed infra/ files.
- features.md: adds what shipped since July (spikes, claims, moments, rule
versions and verification, lessons, reply shapes, snippets and the shape
ledger, forge integration, family canon, design systems, retrieval tuning)
and corrects the Settings tabs and keyboard shortcuts (g g is the graph).
- api-reference.md: adds the endpoints for snippets, lessons, design
systems, family canon, platforms, shared areas, retrieval, rule versions,
relations and verification, forge connections, webhooks, scheduled
backups and the newer plugin hooks.
- api-keys-and-mcp.md: Settings tab is "MCP Access"; a plugin install
section; the tool-family table matches the tools that exist.
- design-system.md: the dated "decisions in progress" changelog (April
PR log, chat/journal/calendar surfaces) becomes a short present-tense
section; the stored design system is named as the authority.
- sso-oauth.md: the app is Scribe, not "Fabled Assistant".
- README: feature summary and doc table brought current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At the operator's request (milestone 544, step 6203):
- docs/2026-03-23-fable-mcp-{design,plan}.md, 2026-03-29-voice-s2s-design.md
and 2026-04-10-agentic-briefing-design.md were plans for work that has
since shipped or been retired (voice and briefings left in the pivot).
Plans live in Scribe milestones.
- infra/act-runner-config.yml and runner-compose.yml described one
install's CI runner (an /nfs path, the py3.12-node22 label, a
RUNNER_SETUP.md that no longer exists). The runner is the CI-Runner
repo's concern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the three compose files in line with rule 198 (milestone 544,
step 6203, at the operator's request):
- no healthcheck on the app or the database, so a slow pg_isready on
stalled storage can no longer get a working database killed;
- no deploy.restart_policy in the Swarm file, so Swarm's default
(condition: any) restarts clean exits too, instead of on-failure with
max_attempts leaving the app down;
- depends_on is plain start order; restart: unless-stopped stays on the
plain-Compose files (Swarm ignores it), and the dev app gains it.
The orchestrator used to be what retried a container that started before
Postgres was ready. That is now the app's job: alembic/env.py calls
services/db_ready.wait_for_database before migrating. It retries refused
connections, unresolved hosts, connect timeouts and "the database system
is starting up" with 1-2-4-...-10s spacing for up to 300s, then exits
non-zero; a wrong password or missing database fails at once.
Per-request recovery is the engine's pool_pre_ping.
Also fixes the dev compose database image: postgres:16-alpine has no
vector extension, so migrations could not run against it. It now uses
pgvector/pgvector:pg17 like the other two files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment-only pilot for the comment standard (rule 32, milestone 544).
Issue and rule numbers, dates, run ids and incident stories come out;
the constraint each one guarded stays as a present-tense sentence.
History removed from the files, kept here:
- Dockerfile/CI: installing from uv.lock replaced pip/uv-pip installs that
ignored the lock (#2194, the mcp 2.0.0 break on 2026-07-28); --locked
then caught pgvector missing from the lock.
- CI: plugin paths were added after #2198/#2209 reached a live install;
`integration` joined the build's needs after run 8212 published
:latest with integration red (#4958); the `[[ ]]` tag step failed
silently on dash (2a374d9); BUILD_VERSION carried the channel until
2026-08-31 (#3298).
- jq was removed from the hooks in #4107.
- prod compose: the lenient DB healthcheck followed a runc setns crash
loop; pgvector arrived with migration 0067 via a PG16->17 dump/restore.
Corrections to match the code: README names the Settings tab
"MCP Access"; ci-requirements says the Dockerfile builds on node:22
and lint covers scripts/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings -> Profile (name, role, expertise, response style, tone, interests,
working hours) is typed by its user and neither derived nor a credential, yet
_NOT_INCLUDED named it, so a restore dropped it without a word.
BACKUP_VERSION 26. Both exports carry it; the restore remaps only the user, and
a blank field stays blank rather than restoring as the page's default. The
column and import guards cover it, and the tar round-trip restores one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The column becomes token_sealed (migration 0122 seals existing rows with a
frozen copy of the v1 key), every reader opens it through connection_token,
a row this instance cannot open is skipped with a warning, and Settings asks
for that token again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The restore route takes the tar as a raw body beside the JSON download.
scribe/restore.py reads either format, refuses a database that already
has an account (a restore re-creates everything with new ids, so into a
live instance it would duplicate rather than replace), has no size
limit, and says to restart so search indexes rebuild. An integration
test restores a written tar through the real restore_full_backup and
checks every id is remapped. docs/backups.md explains the file, why it
is uncompressed, both destinations, Fabled Archives as the worked
example, and restoring.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BackupScheduleSettings.vue leads with whether the data is backed up
(last good backup, or Overdue), then what is wrong (last failure in
words, an S3 secret this SECRET_KEY cannot open, incomplete config),
then the form: daily toggle and UTC hour, folder or S3 bucket, Test
destination with unsaved values, Back up now (polls while running,
re-armed in finally), and the last runs. The secret is write-only.
formatBytes moves to utils/bytes.ts for both views. Restore from
Backup now also takes the .tar, and says to use the CLI on a 413.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
services/backup_job.py reads its config when it fires, so startup reads
nothing (#4181) and a changed hour needs no reschedule. A run is held to
a 60-minute deadline (a hung NFS mount would otherwise hold the lock and
skip every later backup), never runs twice at once, and keeps its outcome
- file, size, duration, or the reason in words - in the bounded
backup_runs setting, which no backup carries. summarise() gives the page
its verdict: overdue when on and no success for 26h.
Admin API: GET/PUT /api/admin/backups, POST /backups/check (tests unsaved
form values, 45s deadline), POST /backups/run (202, background).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
services/backup_s3.py is FabledSteward's SigV4 client ported (put + list,
never delete, so an append-only key is enough), with its tests against
AWS's published signatures. services/backup_destination.py holds the
admin config (backup_config JSON) and delivers a dated tar to a mounted
folder or a bucket, and checks either without sending a backup.
services/secrets.py seals the S3 secret with Fernet under a key derived
from SECRET_KEY; backup._SETTINGS_NOT_EXPORTED keeps it out of every
backup. cryptography becomes a direct dependency (already in the image
through pyjwt).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
export_full_backup orders every query by primary key: an UPDATE moves a
row to the end of the heap, so an unordered export shifted every byte
after an edited row. services/backup_file.py writes the payload as one
dated tar per run - manifest.json plus <section>.jsonl, sorted keys,
fixed member headers - atomically via a dot-prefixed .part file, and
reads it back into the dict restore_full_backup takes, refusing a
missing or short section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > General > Reply shapes. One menu row per shape (core, completion,
asks, plan): its title and when it arrives, how many of the operator's
preferences adjust it, and its deliveries over 30 days (in full / as a
reminder, "—" when the counts cannot be read). A verdict line leads. Opening a
row shows the shipped text, read-only because it is product, and the
preferences mounted on its moment; each opens in the rule editor, and "Adjust
this" opens a new preference already mounted on the shape's moment with a
starting trigger.
The rule editor takes an optional preset (kind, moments, trigger) and, opened
outside the Rules view, asks where a new record lives instead of dropping it.
GET /api/retrieval/reply-shapes now returns reply_shapes.overview: the
catalog plus mounted preferences (the same lookup that delivers them) and
counts from the reply_shape delivery rows. #5497 (step 5 of milestone 500).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reply shapes are server product now, delivered at their moments, so the
skill stops restating them. Gone from it: the "Every reply" list, the
per-kind tables, and "The operator's own shapes come first" (preferences
arrive beside the shape on the same moments). It opens by saying where the
shapes come from (list_reply_shapes, the delivered core's header) and keeps
the reasoning: sections chosen not filled, a settled decision acted on,
placement from the record, who decides what, the assumptions an option
carries, the completion report worked in full, and the second pass. 13.5k to
10.7k characters.
The core gains the Finding kind the skill's table carried (2,150 of 2,200).
Tests follow the content: the kind and Approval-row pins move to the shapes,
a new test holds the worked example and the completion shape to the same
sections, and the guidance-ownership registry reads the delivered shapes as a
surface, owning the preference-wins and length topics there. using-scribe
points at the delivery and list_reply_shapes. #5496.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
report_preference searched one constant string when a task closed and handed
matches back as update_task's reply_preferences. Milestone 500 step 3 delivers
the reply shapes and the preferences mounted beside them on the moments, so
the arm goes, whole (rule 22):
- services/reply_preferences.py, the REPORT_PREFERENCE RuleArm, its re-scorer
and corpus entries, and the REPORTPREF constants
- update_task's reply_preferences block and its cue; the docstring now points
at moment_rules
- the fixed_query field and the fixed_query_never_clears warning: this arm was
the only one that set it, so the concept and the cannot_decline exemption
go with it
- the two Settings fields for its floor and budget
- migration 0121 deletes its rows (logs, judgments, rule-usage events, tuning
history, settings keys), operator-approved 2026-10-09. Without it the
readout would call the source unregistered and its surfacings would count
as ambient. The unindexed rule_usage delete is bounded by created_at.
#5496 (step 4 of milestone 500).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The marker scribe_reply_check.sh writes after a completion-section hold sat in
the swept ledger directory as `<sid>.reportcheck`, and the sweep matches
`.ids` - test_every_session_file_in_a_swept_directory_follows_the_convention
caught it (run 799). Renamed `<sid>.reportcheck.ids` and added to the roster.
#5496.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Stop hook sent the finished reply twice: scribe_report_check.sh checked a
task-closing reply for the completion sections in shell and reported to
/report-check, and scribe_reply_check.sh sent the same reply to /reply-rules
for the rule hold. Now the reply goes once. When the turn closed a task the
hook adds the close count and ids, and the server runs the section check
(services/report_check, the same three patterns) beside the reply hold,
folding both into one reason.
The report_check adherence log is still written for every checked reply
(milestone 409's number). A section hold marks the session, so its rewrite is
sent back once with rewrite:true to record how it came out, and is never held.
The block reason carries the completion shape's one line and points at
list_reply_shapes rather than at the skill. #5496 (step 4 of milestone 500).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scribe_json_unescape wrote each escaped code point with sprintf("%c", n),
which writes the character only under gawk in a UTF-8 locale; gawk in the C
locale and mawk in any locale write the single byte n % 256. The server
escapes every non-ASCII character, so "·" reached the session as 0xB7, "—"
as 0x14 and an emoji as a NUL wherever a hook ran without a UTF-8 locale.
The decoder now encodes UTF-8 itself under LC_ALL=C. A new test sends what
the server sends from a bare environment, in three locales (#5495).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every turn (/api/plugin/retrieve, UserPromptSubmit): the core reply shape
leads the payload - in full the first time, as its one-line reminder
after that - followed by whatever is mounted on reply.report, under the
shared rule ledger. Fresh keys come back as shape_keys.
- The ledger is <sid>.shapes.ids in scribe-priorart (scribe_shapes_file /
_seen / _append), so the compaction sweep that clears every .ids ledger
is what brings the full core back after one.
- At a moment (/api/plugin/moment): the slice for that reply - completion
on work.finish, asks on reply.ask, plan on work.plan - ahead of the
mounted rules. reachable_tools now lists tools reaching a shaped moment
even on an install with nothing mounted.
- Scribe's own tools (attach_moment_rules): reply_shape in the response,
in full, since that door has no ledger. enter_project carries the core
for clients with no prompt hook.
- Telemetry: one AppLog row per delivery (plugin / reply_shape), each
shape with full or pointer and the door (turn, hook, mcp).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operator's line (2026-10-09), reconciling their 2026-09-21 ruling
(#4218, "you are the judge") with "not that Claude should make all
decisions": judging covers the work and its records - facts, findings,
measurements, whether work is done - and direction stays theirs:
priorities, what the thing should be, trade-offs only they can price,
anything they live with, and every act hard to undo or facing outward.
- reply_shapes: a one-line form in the core (every turn), the full text
in the asks slice (where a question gets handed back). Slice budget
1400 -> 1600 for it.
- reporting-back "You are the judge": "the operator reads the decision -
they do not make it" replaced; the line is drawn by who can see the
evidence, not by how hard the call is.
- plugin version minted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
services/reply_shapes.py is the single source of the default reply shapes:
the core (every reply, ~1,800 chars against a 2,200 budget) and three
slices - completion on work.finish, asks on reply.ask, plan on work.plan.
Read through list_reply_shapes (MCP, read-only) and GET
/api/retrieval/reply-shapes, one service behind both doors.
Nothing delivers them yet; that is step 3. The skill still carries its
copy until step 4 shrinks it to the long-form reference.
The software-only vocabulary guard moves into tests/helpers.py
(DEV_ONLY, dev_only_hits) rather than becoming a fourth copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runner's docker daemon is shared, so `--filter name=<job> | head -n1` can
pick another repo's Postgres when two jobs run side by side (Steward run 8358,
Inkwell run 8653). Scope the lookup to this job's own task prefix and require
exactly one match. Done from Inkwell #5313 with the operator's permission; the
recipe is rule 79.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lessons share one register, so any two score well above unrelated text and
a fixed similarity bar sat inside that band: every no-rule lesson
"resembled" most of the others and the named group grew with the pool.
A neighbour now counts only when it stands above the lesson's own
background (median and MAD of its similarity to every lesson it can reach,
in that register's spread), and a group is a clique: every pair clears the
bar from both sides, so one broad lesson cannot join unrelated ones. The
bar, the minimum background and the fetch sizes are stated as defaults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A multi-pass DRY audit could not answer whether it created duplication:
a pass's own shortening can leave two statements identical to a third,
and no per-pass scan sees a copy that did not exist when it ran. The
Librarian retrospective improvised this measure in a scratchpad; this is
it as a tool the DRY Pass process can name.
6-line windows over significant lines (comments, blanks and bare
punctuation dropped, strings folded), grouped by glob or extension.
Revisions are read through git archive, so nothing is checked out.
Stdlib only, so any project can run a scratch copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dated release log (a note) matched a signed-APK CI snippet at 0.83 on
shared release vocabulary and was opened as a family candidate, then came
back as a precedent. One chunk of a long narrative scores like the shape
it mentions, so a cross-kind score does not mean "the same thing made
twice". The search is filtered to the new record's kind and the loop
checks it too. Code built from an idea written elsewhere still reaches the
engine through the citation trigger and the shape ledger.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each decision stored its reason three times (reason, before.reason, after.reason)
and a promotion carried the whole criteria reasoning in evidence. Precedents
(assess, promote, get_family_adoption, get_family_idea) and get_family_idea's
history now serve FamilyDecision.to_brief(): reason once, before/after as status,
canon_version and platforms, evidence as the cited list. list_family_decisions
and the decision echoed back by a write keep to_dict. The precedent count was
already bounded at five.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 1 added family_ideas.topic_id but nothing could set it, so the note<->topic
link the milestone promised existed only as a column. set_family_topic links or
unlinks (0) a topic in a rulebook the caller owns, one idea per topic; the
version does not move. get_family_idea and get_family_adoption list the topic's
rules; list_family_ideas and the Family page name the topic. The model
docstrings no longer claim the topic's rules are platform-scoped in retrieval.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- enter_project carries a `family` key, but only when the project has
something to answer: counts of unassessed, owed and to-recheck answers,
each with the list_family_adoptions call that lists it. It shows on every
entry, never by platform touch: entry is when work is chosen, and an
unanswered idea is otherwise invisible.
- Retrieval: a widened project search (include_global_kinds) now also
reaches the canon ideas on the project's platforms. It also reaches their
references in the project's languages, or all of them when none matches.
An off-platform project gets none, and the plain project filter (the
duplicate gate) is unchanged.
- Closing a task returns `family_owed`, the owed answers filed while it was
open, and the report cue asks for them to be named.
- New plugin skill family-canon (moment work.record) covers when to
evaluate a promotion, answering in order, what counts as a reason, the
precedent reflex and the conflict order. _INSTRUCTIONS, create_note,
create_snippet, classify_shapes and reporting-back point at it.
The plugin version is minted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- classify_shapes takes idea_id: the shape is judged against the idea's
reference in its language, or its first.
- A shape classified against a canon idea's reference moves the project's
adoption row (instance -> adopted, variant -> variant). Withdrawing the
shapes that gave an answer returns it to unassessed. This runs on
classify_shapes, the sweep, confirm_proposals and the coverage refresh.
- assess and undo refuse an answer the shapes contradict. A conflict
resolution re-judges the variant shapes on both sides.
- The proposer's family arm offers a canon idea's reference on a shared
platform, in any language. It proposes only when the reference is the top
hit over every readable snippet and scores at least 0.70. The pairs this
was measured on are recorded beside _FAMILY_FLOOR. The proposer is now v6.
- The matrix cell shows the code that answers it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
assess_family_adoption took system_ids for the task it files, which made
the family tool module look like a System-tagging door and tripped the
parity registry. It is not one: the owed task is tagged to the project's
System matching the idea's canonical area, and update_task retags it.
The unit test for each conflict ground passed evidence/conditions twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- services/family_adoption.py: assess one project against one canon idea by
the four outcomes in order (exempt, variant, adopted, owed). Every outcome
needs a reason and adopted needs evidence. The engine records the precedents
itself: this idea's answers elsewhere, and this project's answers to the
nearest ideas. The same answer given twice records nothing.
- owed files a task in the OWING project, tagged to the System matching the
idea's canonical area, naming the gap and the reference for that project's
language. The task follows the answer: adopted closes it, exempt or variant
cancels it, owed again reopens it. Each move is logged on the task.
- the conflict order is enforced: every ground above the deciding one must
say why it did not decide. The losing side is folded into the idea's note as
a trap, an alternative or a condition branch, the version moves, and both
rows are answered against the revision.
- recheck is derived (row version != idea version). family.revise moves the
version when substance changes. undo covers a project's latest answer too.
- set_family_references names the reference implementations.
- MCP: get/list/assess adoption, resolve_family_conflict, revise_family_idea,
set_family_references. Web: GET /api/family/matrix.
- UI: an adoption matrix on /family (platform filter, cell detail with reason,
recheck and owed-task link) and the same matrix narrowed to one project on
its Family tab. The decision log now reads project-level decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the undone decision had no recorded before-state, the synthesized prior copied the canon idea's current applies_when and platforms into a retired row. Before that decision the record was not an idea, so the restored state now has neither. Caught by test_undoing_a_promotion_restores_the_prior_state_and_keeps_judged_rows (run 8281).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>