Skip to content

Commit 838ef51

Browse files
committed
Merge PR HarnessMD#243 into release/0.4.6-rc
2 parents 3b27530 + 9edf34e commit 838ef51

7 files changed

Lines changed: 168 additions & 18 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ All notable changes to this project are documented here. The format is based on
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- **The ASK ME card renders markdown.** Questions arrived with their asterisks and backticks on
12+
screen, because the card printed the raw text. It now renders the same way the file preview does:
13+
emphasis, bullets, `code`, tables, and links that open in the browser instead of navigating the
14+
app. The task detail's Q&A trail renders too, answers included. A single newline is still a line
15+
break, so a question written as plain text looks exactly as it did before. Raw HTML is still shown
16+
as text rather than parsed, which is what keeps agent-written markdown safe to display.
17+
- **Agents are told to format what they ask you.** The orchestrator prompt and `PROTOCOL.md` now
18+
ask for a bold lead line, backticks around paths and commands, and bullets whenever a question has
19+
more than one option.
20+
921
## [0.4.5] — 2026-08-22
1022

1123
**The release that fixes the things you trusted and were quietly wrong.** Cost reporting was off

‎src/main/hive.ts‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,7 @@ export class HiveManager {
13781378
: '';
13791379
const godLine = meta.isGod
13801380
? 'You are the GOD / ORCHESTRATOR of this hive — your job is to ORCHESTRATE, not to implement: maintain live situational awareness and delegate the work. (1) AWARENESS — always know what is going on: keep an accurate picture of every agent (active vs archived/idle), the task board, and all in-flight work; drain your inbox continually and triage every other agent\'s requests, answering clarifications so the team runs autonomously. (2) DELEGATE — decompose work and fan it out to the hive agents via their inboxes (route messages and assign owners; do not do their jobs); do NOT take on grunt implementation yourself. Stay aware of who is already on the floor and delegate OPPORTUNISTICALLY: BEFORE you spawn anything, CHECK THE LIVE ROSTER (active agents in registry.json + their state in fleet.json) and prefer routing to an EXISTING agent that fits — above all when the request names one ("ask Pam to…", "have Jim…"), route to that agent instead of reflexively creating a new one. Reuse an idle or already-running agent whose role matches; only spawn a fresh agent when no existing one is a sensible fit, and say that you checked. One capable owner beats a duplicate. (3) OWN ONLY THE IMPORTANT, high-leverage things — task decomposition, dispatch decisions, sign-offs, conflict resolution, branch integration, and final QA — and remain the sole scribe of board.md. You are otherwise fully autonomous — there is NO separate approval queue. For the genuinely critical (destructive actions, spending real money, scope changes, unresolvable conflicts), ask the human directly in your own session and let the tool-permission prompt gate the action; the human approves natively, including remotely from their phone via /remote-control. Keep the team unblocked. When you DISPATCH a task, write it as a 4-part contract so the agent can run autonomously: (1) OBJECTIVE — the concrete goal; (2) OUTPUT — the expected deliverable/format; (3) TOOLS — what to use or avoid, and any references to read instead of re-deriving; (4) BOUNDARIES — scope limits + the definition of done. Pass references (file paths, message ids, board sections), not pasted content — keep dispatches short.'
1381-
+ ` MONITOR the floor by reading ${inRoot('fleet.json')} (live per-agent tokens, cost, status, last tool, breaker level, inbox backlog) and ${inRoot('registry.json')} — note that running 'claude agents' will NOT list your hive's sibling agents. A full Claude Code command reference is at ${inRoot('COMMANDS.md')} (slash commands act ONLY on your own session; CLI commands run in your shell and can target the fleet). You periodically receive scheduler / "Heartbeat" standup requests — on each, review every agent via fleet.json, re-engage anyone stalled, over-budget, or breaker-armed, and keep board.md and tasks.json accurate. In tasks.json, ALWAYS set each task's "assignee" to the worker's agent id the moment you dispatch it, and NEVER clear it on status changes — a done card must still say who did the work (the human reads the board by who-did-what). HUMAN FEEDBACK is first-class in the ledger: when a task can only proceed with the human's input — a QUESTION to answer OR an ACTION only the human can perform (create an account, approve a purchase, provide credentials/screenshots, test on their device) — set its status to "blocked" and append the concrete ask to the card's "humanQA" array (push {"q":"...","askedAt":"<iso>"}; phrase actions as clear to-dos; keep every past entry — the history documents the card's decisions). The harness surfaces open questions on the office floor's ASK ME board; the human's answer lands in the same entry ("a") AND arrives as an inbox message to you — read it, act on it, and unblock the card so work continues. Do NOT park human questions in separate files (no HumanQuestion.md) and never sit waiting on the human in your own session. Steward the token budget.`
1381+
+ ` MONITOR the floor by reading ${inRoot('fleet.json')} (live per-agent tokens, cost, status, last tool, breaker level, inbox backlog) and ${inRoot('registry.json')} — note that running 'claude agents' will NOT list your hive's sibling agents. A full Claude Code command reference is at ${inRoot('COMMANDS.md')} (slash commands act ONLY on your own session; CLI commands run in your shell and can target the fleet). You periodically receive scheduler / "Heartbeat" standup requests — on each, review every agent via fleet.json, re-engage anyone stalled, over-budget, or breaker-armed, and keep board.md and tasks.json accurate. In tasks.json, ALWAYS set each task's "assignee" to the worker's agent id the moment you dispatch it, and NEVER clear it on status changes — a done card must still say who did the work (the human reads the board by who-did-what). HUMAN FEEDBACK is first-class in the ledger: when a task can only proceed with the human's input — a QUESTION to answer OR an ACTION only the human can perform (create an account, approve a purchase, provide credentials/screenshots, test on their device) — set its status to "blocked" and append the concrete ask to the card's "humanQA" array (push {"q":"...","askedAt":"<iso>"}; phrase actions as clear to-dos; keep every past entry — the history documents the card's decisions). WRITE THE ASK SHORT AND IN MARKDOWN. The human reads it on a CARD, not in a terminal, so an ask longer than a short paragraph plus its options (roughly 700 characters) is a report, not a question — cut the narrative, keep the decision. Open with ONE **bold** sentence saying exactly what you need from them; put paths, commands, values and identifiers in \`backticks\`; give each option or step its own "-" bullet or "1." number; leave a blank line between paragraphs (a single newline is a line break, so each option stays on its own line). When the ask originates in another agent's report, REWRITE it into that shape — never paste the report body in as the question, and never make the human read the investigation to find the decision. The harness surfaces open questions on the office floor's ASK ME board; the human's answer lands in the same entry ("a") AND arrives as an inbox message to you — read it, act on it, and unblock the card so work continues. Do NOT park human questions in separate files (no HumanQuestion.md) and never sit waiting on the human in your own session. Steward the token budget.`
13821382
: meta.isAssistant
13831383
? `You are ${godNameForPrompt}'s PREP ASSISTANT. You will be handed short, possibly vague instructions (each begins with "ENRICH TASK:"). For each one: (1) figure out which project it concerns and cd into the most relevant repo — you start in ${godNameForPrompt}'s home directory; (2) gather concrete context READ-ONLY (exact file paths, current state, relevant code, conventions, active branch, gotchas) — NEVER modify, create, or delete files; (3) rewrite the instruction into ONE clear, self-contained prompt that ${godNameForPrompt} can execute autonomously, preserving the user's original intent without inventing scope. Then deliver it: write ONE message JSON into your outbox with "to":"god", "act":"request", a short subject, and the finished prompt as the body. Do NOT perform the task yourself — your only output is the improved prompt sent to ${godNameForPrompt}.`
13841384
: 'For anything ambiguous, cross-cutting, or needing sign-off, address a message to "god".';
@@ -2505,6 +2505,32 @@ There are two shared surfaces, both in the hive root:
25052505
- \`tasks.json\` — the structured task ledger (a kanban: \`todo / doing / blocked / done\`, with title,
25062506
assignee, priority, deps). Keep the task you're working reflected in its status.
25072507
2508+
## Asking the human (the ASK ME card)
2509+
When a card can only move with the human — a question to answer, or an action only they can do
2510+
(create an account, approve a spend, hand over credentials, test on their device) — the god sets the
2511+
card \`"status": "blocked"\` and appends the ask to its \`humanQA\` array:
2512+
2513+
\`\`\`json
2514+
{ "q": "the ask, in markdown", "askedAt": "<iso timestamp>" }
2515+
\`\`\`
2516+
2517+
The harness shows the open ask on the ASK ME board and in the ASK ME tab, and the human's reply lands
2518+
in the same entry as \`"a"\` plus an inbox message to god. Every past entry stays on the card — that
2519+
trail is the decision history.
2520+
2521+
**Write the ask short, and in markdown.** The card renders it, so plain-text asterisks and backticks
2522+
show up literally, and a card is not a terminal — an ask longer than a short paragraph plus its
2523+
options (roughly 700 characters) is a report, not a question. Cut the narrative and keep the decision:
2524+
- open with ONE **bold** sentence saying exactly what you need from them;
2525+
- \`backticks\` for paths, commands, values, and identifiers;
2526+
- \`-\` bullets or \`1.\` numbering for every option or step;
2527+
- a blank line between paragraphs; a single newline is rendered as a line break, so each option
2528+
stays on its own line.
2529+
2530+
When the ask originates in another agent's report, REWRITE it into that shape. Never paste the report
2531+
body in as the question, and never make the human read the investigation to find the decision. Do NOT park human questions in separate files (no \`HumanQuestion.md\`),
2532+
and never sit idle waiting for a reply — move on to other work and pick the answer up when it arrives.
2533+
25082534
## Guardrails: circuit breaker & token budgets
25092535
A circuit breaker watches every agent for runaway behavior (looping on the same tool, error storms,
25102536
overspending). It escalates gently: \`steer\` → \`constrain\` → \`stop\`. If a \`Circuit breaker: steer\`

‎src/renderer/src/components/AskMeTab.tsx‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
22
import { PixelButton } from './PixelButton';
33
import { PixelBadge } from './PixelBadge';
44
import { useStore } from '@/store/store';
5+
import { MarkdownPreview } from '@/markdown/MarkdownPreview';
56
import { type HiveTask, type HumanQA, openQuestion, waitsOnHuman } from './TasksKanban';
67

78
/**
@@ -206,9 +207,13 @@ export function AskMeTab() {
206207
</div>
207208

208209
<div style={{ padding: 9, display: 'flex', flexDirection: 'column', gap: 8 }}>
209-
{/* the question */}
210-
<div style={{ fontSize: 15, lineHeight: '19px', color: 'var(--cth-ink-900)', whiteSpace: 'pre-wrap' }}>
211-
{open.q}
210+
{/* The question, rendered as markdown. The god writes these with
211+
emphasis, lists, `code` and links; as plain text the asterisks
212+
and backticks were on screen literally. The card variant keeps
213+
this card's mono face and turns a single newline into a break, so
214+
a question with no markdown in it looks exactly as it did. */}
215+
<div style={{ fontSize: 15, lineHeight: '19px', color: 'var(--cth-ink-900)' }}>
216+
<MarkdownPreview source={open.q} variant="card" />
212217
</div>
213218

214219
{/* answer box */}
@@ -217,7 +222,7 @@ export function AskMeTab() {
217222
onChange={(e) => setAnswerDraft(t.id, e.target.value)}
218223
onKeyDown={(e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) void sendAnswer(t); }}
219224
rows={3}
220-
placeholder="Your answer — or 'done', with the result… (Ctrl+Enter to send)"
225+
placeholder="Your answer — or 'done', with the result… (markdown ok · Ctrl+Enter to send)"
221226
style={{
222227
width: '100%', boxSizing: 'border-box', padding: '6px 8px', resize: 'vertical',
223228
background: 'var(--cth-paper-100)', border: 'none',

‎src/renderer/src/components/TasksKanban.tsx‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PixelButton } from './PixelButton';
44
import { PixelBadge } from './PixelBadge';
55
import { Icon } from './Icon';
66
import { useStore } from '@/store/store';
7+
import { MarkdownPreview } from '@/markdown/MarkdownPreview';
78

89
/** A card on the task kanban. Mirrors HiveTask in the main/preload process —
910
* re-declared locally so the renderer doesn't reach into the preload package
@@ -349,7 +350,9 @@ export function TaskDetail({ task, all, assigneeName, onMove, onAssign, onClose
349350
{task.description?.trim() || <span style={{ color: 'var(--cth-ink-300)' }}>(no description on this card)</span>}
350351
</div>
351352

352-
{/* The human Q&A trail — every decision documented on the card */}
353+
{/* The human Q&A trail — every decision documented on the card.
354+
Rendered as markdown (card variant), matching the ASK ME tab the
355+
"view earlier answers" link arrives from. */}
353356
{(task.humanQA?.length ?? 0) > 0 && (
354357
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
355358
<div style={{ fontFamily: 'var(--cth-font-display)', fontSize: 8, color: 'var(--cth-ink-500)' }}>
@@ -358,21 +361,27 @@ export function TaskDetail({ task, all, assigneeName, onMove, onAssign, onClose
358361
{task.humanQA!.map((e, i) => (
359362
<div key={i} style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
360363
<div style={{
361-
padding: '5px 7px', background: 'var(--cth-lilac-light, #ece2f5)',
364+
display: 'flex', gap: 6, padding: '5px 7px',
365+
background: 'var(--cth-lilac-light, #ece2f5)',
362366
boxShadow: 'inset 0 0 0 1px var(--cth-ink-300)',
363-
fontSize: 12, lineHeight: '17px', color: 'var(--cth-ink-900)', whiteSpace: 'pre-wrap'
367+
fontSize: 12, lineHeight: '17px', color: 'var(--cth-ink-900)'
364368
}}>
365-
<span style={{ fontFamily: 'var(--cth-font-display)', fontSize: 8, marginRight: 6 }}>Q</span>
366-
{e.q}
369+
<span style={{ fontFamily: 'var(--cth-font-display)', fontSize: 8, flexShrink: 0, marginTop: 2 }}>Q</span>
370+
<div style={{ flex: 1, minWidth: 0 }}>
371+
<MarkdownPreview source={e.q} variant="card" />
372+
</div>
367373
</div>
368374
{e.a ? (
369375
<div style={{
370-
padding: '5px 7px', background: 'var(--cth-mint-light, #d9eed9)',
376+
display: 'flex', gap: 6, padding: '5px 7px',
377+
background: 'var(--cth-mint-light, #d9eed9)',
371378
boxShadow: 'inset 0 0 0 1px var(--cth-ink-300)',
372-
fontSize: 12, lineHeight: '17px', color: 'var(--cth-ink-900)', whiteSpace: 'pre-wrap'
379+
fontSize: 12, lineHeight: '17px', color: 'var(--cth-ink-900)'
373380
}}>
374-
<span style={{ fontFamily: 'var(--cth-font-display)', fontSize: 8, marginRight: 6 }}>A</span>
375-
{e.a}
381+
<span style={{ fontFamily: 'var(--cth-font-display)', fontSize: 8, flexShrink: 0, marginTop: 2 }}>A</span>
382+
<div style={{ flex: 1, minWidth: 0 }}>
383+
<MarkdownPreview source={e.a} variant="card" />
384+
</div>
376385
</div>
377386
) : (
378387
<div style={{ fontSize: 11, color: 'var(--cth-coral)', fontFamily: 'var(--cth-font-display)' }}>

‎src/renderer/src/design/global.css‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,37 @@ canvas, img, svg { image-rendering: pixelated; image-rendering: crisp-edges; }
261261
background: var(--cth-cream-200); box-shadow: inset 0 0 0 1px var(--cth-ink-100);
262262
color: var(--cth-ink-500); border-radius: 2px;
263263
}
264+
265+
/* ─── Markdown inside a card (v0.4.6) ────────────────────────────────────────
266+
The same renderer, hosted in a surface that already has its own type: the
267+
ASK ME question and the task detail's Q&A trail. It inherits the host's face
268+
and size (the mono face those cards set, not the UI face) and drops the document
269+
chrome — no 72ch measure, no page padding — so a one-line question is still
270+
one line. Every size below is relative, so the host sets the scale. */
271+
.cth-md-preview.cth-md-card {
272+
font-family: inherit; font-size: inherit; line-height: inherit;
273+
max-width: none; padding: 0;
274+
/* A long path or URL in an agent's question wraps rather than widening the
275+
card: the ASK ME column is narrow and does not scroll sideways. */
276+
overflow-wrap: anywhere;
277+
}
278+
.cth-md-card > :first-child { margin-top: 0; }
279+
.cth-md-card > :last-child { margin-bottom: 0; }
280+
.cth-md-card p { margin: 1.1em 0; }
281+
.cth-md-card h1, .cth-md-card h2, .cth-md-card h3,
282+
.cth-md-card h4, .cth-md-card h5 {
283+
margin: 0.8em 0 0.3em; border-bottom: none; padding-bottom: 0;
284+
}
285+
.cth-md-card h1 { font-size: 1.25em; }
286+
.cth-md-card h2 { font-size: 1.15em; }
287+
.cth-md-card h3, .cth-md-card h4, .cth-md-card h5 { font-size: 1.05em; }
288+
.cth-md-card code { font-size: 0.9em; padding: 0 3px; }
289+
.cth-md-card pre { margin: 0.5em 0; padding: 6px 8px; }
290+
/* 22px, not less: the list marker is painted OUTSIDE the padding box, and a
291+
tighter indent clips "1." against the card's own 9px edge. */
292+
.cth-md-card ul, .cth-md-card ol { padding-left: 22px; margin: 0.4em 0; }
293+
.cth-md-card li { margin: 1px 0; }
294+
.cth-md-card blockquote { margin: 0.5em 0; padding-left: 10px; }
295+
.cth-md-card hr { margin: 0.8em 0; }
296+
.cth-md-card table { margin: 0.5em 0; }
297+
.cth-md-card th, .cth-md-card td { padding: 3px 7px; font-size: 0.9em; }

0 commit comments

Comments
 (0)