Show a profile-grounding indicator on the planning screen

The planning view renders a quiet line for the standing-profile state
(loading/grounded/absent/unreadable) with a "change" affordance to
repoint the file via POST /knowledge/path. The profile text never
reaches the browser — only status, path, and size. Also updates the
README Status section to describe the M6 knowledge port.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 08:08:55 -04:00
parent b1b807590c
commit 5342da2755
3 changed files with 43 additions and 0 deletions
+11
View File
@@ -23,6 +23,17 @@ go test ./...
## Status ## Status
M6 (knowledge port): the planning coach now sharpens intents against who you
actually are. A single profile file (`~/.antidrift/knowledge.md`, overridable
via `ANTIDRIFT_KNOWLEDGE_FILE`) holds your standing context — priorities,
values, what counts as good work — and the daemon loads it asynchronously on
entering planning, mirroring the tasks fetch. The cached text grounds the AI
coach prompt only; the drift judge and nudge are untouched, and the profile text
never crosses the wire. A subtle planning-screen indicator shows whether the
profile loaded and from where, with a "change" affordance to repoint at another
file. It degrades gracefully — a missing, blank, or unreadable file just leaves
the coach ungrounded, and planning still works.
M3.5 (semantic nudge): the drift interceptor catches the *wrong app*, but not M3.5 (semantic nudge): the drift interceptor catches the *wrong app*, but not
the *wrong work inside a right app*. M3.5 adds a third, ambient AI role that — the *wrong work inside a right app*. M3.5 adds a third, ambient AI role that —
only while you are on-task in an allowed app — periodically reads your recent only while you are on-task in an allowed app — periodically reads your recent
+7
View File
@@ -116,3 +116,10 @@ input:focus { outline: 0; border-color: var(--accent); }
cursor: pointer; text-align: left; cursor: pointer; text-align: left;
} }
.task-chip:hover { border-color: var(--accent); color: var(--accent); } .task-chip:hover { border-color: var(--accent); color: var(--accent); }
/* Planning: standing-profile grounding indicator */
.knowline { opacity: 0.85; }
.link {
background: none; border: none; padding: 0; font: inherit; font-size: 12px;
color: var(--accent); cursor: pointer; text-decoration: underline;
}
+25
View File
@@ -157,12 +157,35 @@ function updatePlanningTasks(tasks) {
}); });
} }
// updatePlanningKnowledge renders the standing-profile grounding indicator and
// a small "change" affordance to repoint the file. idle/nil renders nothing;
// pending/ready/absent/error each show one quiet line. The profile text never
// reaches the browser — only status, path, and size.
function updatePlanningKnowledge(k) {
const el = document.getElementById('knowBand');
if (!el) return;
if (!k || k.status === 'idle') { el.innerHTML = ''; return; }
const base = (k.path || '').split('/').pop() || k.path || '';
let line;
if (k.status === 'pending') line = 'loading profile…';
else if (k.status === 'ready') line = `grounded by ${base} · ${k.chars} chars`;
else if (k.status === 'absent') line = `no profile (${k.path || 'unset'})`;
else line = 'profile unreadable';
el.innerHTML =
`<span class="knowline meta">${line}</span> <button type="button" class="link" id="knowChange">change</button>`;
document.getElementById('knowChange').onclick = () => {
const next = prompt('Profile file path (blank = default):', k.path || '');
if (next !== null) post('/knowledge/path', { path: next.trim() });
};
}
function render(state) { function render(state) {
setStateAttr(state); setStateAttr(state);
const rs = state.runtime_state; const rs = state.runtime_state;
if (rs === 'planning' && renderedState === 'planning') { if (rs === 'planning' && renderedState === 'planning') {
updatePlanningCoach(state.coach); updatePlanningCoach(state.coach);
updatePlanningTasks(state.tasks); updatePlanningTasks(state.tasks);
updatePlanningKnowledge(state.knowledge);
return; return;
} }
if (rs === 'active' && renderedState === 'active') { if (rs === 'active' && renderedState === 'active') {
@@ -189,6 +212,7 @@ function render(state) {
<div id="coachStatus" class="meta"></div> <div id="coachStatus" class="meta"></div>
</div> </div>
<div class="band" id="tasksBand"></div> <div class="band" id="tasksBand"></div>
<div class="band" id="knowBand"></div>
<div class="band"> <div class="band">
<label>Next action</label><input id="na" placeholder="What exactly will you do?"> <label>Next action</label><input id="na" placeholder="What exactly will you do?">
<label>Success condition</label><input id="sc" placeholder="How do you know it's done?"> <label>Success condition</label><input id="sc" placeholder="How do you know it's done?">
@@ -214,6 +238,7 @@ function render(state) {
}; };
updatePlanningCoach(state.coach); updatePlanningCoach(state.coach);
updatePlanningTasks(state.tasks); updatePlanningTasks(state.tasks);
updatePlanningKnowledge(state.knowledge);
} else if (rs === 'active') { } else if (rs === 'active') {
const c = state.commitment || {}; const c = state.commitment || {};