Lloyal Labs
Engineering AI's contact with reality.
Lookup /
Reference
August
2026

Lookup

Signatures, enumerations, and floors — the facts you look up mid-task

It admits a fact only when you would look it up mid-task and could not derive it: a signature, an enumeration, a named constant, a threshold, a floor. Everything explaining why stays in the guides; the CLI surface stays in the harness.dev README. Each entry names the file it came from — read that file when the two disagree.

Lookup material.
Not a guide.
Floors

Runtime and hardware

Node 24 or newer, declared by the CLI and by every scaffolded project. 16 GB of RAM or unified memory is the working recommendation: the recommended trunk model is 2.6 GB of weights, the research template adds a 630 MB reranker, and the remainder covers KV at the recommended 32k context plus the OS and whichever surface is running.

ModelRoleSizeContext
qwen3.5-4b · Q4_K_Mllm2.6 GB32768
qwen3-reranker-0.6b-q8reranker630 MB

packages/rig/src/models.ts

Concurrent agents do not multiply this. They share one context, so cost tracks KV fullness, not agent count — four agents is not four times the model.

Retrieval

Retrieval and scoring

One cross-encoder serves four roles. Three different queries are in play — the per-call tool query, the per-agent task, and the original research query — and conflating them produces wrong scores.

TypeScript
interface EntailmentScorer {
  scoreEntailmentBatch(texts: string[]): Promise<number[]>;
  scoreRelevanceBatch(texts: string[], localQuery: string): Promise<number[]>;
  scoreSimilarityBatch(reference: string, texts: string[]): Promise<number[]>;
  shouldProceed(score: number): boolean;
}

interface ScorerReranker {
  scoreBatch(query: string, texts: string[]): Promise<number[]>;
}
MethodScores againstUsed at
scoreEntailmentBatchthe original querycontent prefill boundaries
scoreRelevanceBatchmin(local, original)exploit mode, when pressure tightens focus
scoreSimilarityBatchan arbitrary referenceecho detection at delegation
shouldProceedthe floorthe gate itself

The score is a logit-difflogit(yes) − logit(no), unbounded, with the sign carrying the meaning. Source._entailmentFloor defaults to 0: a hit passes when the cross-encoder leans yes. Raise it for noise-heavy corpora, lower it for sparse ones.

TypeScript
abstract class Source<TCtx = unknown, TChunk = unknown> {
  abstract readonly name: string;
  abstract get tools(): Tool[];
  protected _reranker: ScorerReranker | null;
  protected _entailmentFloor: number;          // default 0
  createScorer(originalQuery: string): EntailmentScorer;
  promptData(): Record<string, unknown>;
  getChunks(): TChunk[];
}

promptData() is what the per-spawn skill template reads, so an agent learns what kind of source it holds before it searches.

packages/agents/src/source.ts

Authority

The gate for consequential actions runs at DISPATCH, inside the agent runtime — below any application or interface code that could misreport it.

TypeScript
interface GrantStore {
  has(toolName: string): Operation<boolean>;
  grant(toolName: string): Operation<void>;
  revoke(toolName: string): Operation<void>;
}

GrantStoreCtx  // createContext<GrantStore>('lloyal.grantStore')

Fail-closed. A tool marked protected is denied unless the session holds a grant, and no grant store configured means every protected tool is denied. Credentials never enter the model's context — the model can trigger a gated call, never see or replay the secret.

packages/agents/src/grant-store.ts · context.ts

Diagnosis

Trace events

Pass a writer to initAgents to capture the inference graph as newline-delimited JSON. With none passed, NullTraceWriter makes every trace call a no-op at zero cost. Events form a tree through parentTraceId; the TraceParent context connects inner pools to the tool dispatch that spawned them without manual wiring.

EventCaptures
scope:open / closenamed boundaries with duration — pools, tools, spines
prompt:formatthe exact prompt the model saw, and the task before formatting
agent:turnraw output, parsed content, parsed tool calls per turn
tool:dispatchargs, toolkit position, explore flag, pressure
tool:result / error / retryresult, prefill token count, duration, transient parking
tool:authRejectprotected-tool denial with full lineage
pool:open / close / agentDropagent count, pressure snapshot, findings, drop reason
pool:recovery*recovery extraction — every attempt ends in exactly one outcome
spine:extendorchestrator extensions, delta tokens, position after
branch:create / prefill / pruneKV lifecycle with token counts and roles
entailment:*scoring decisions at search, delegation, and content boundaries

packages/agents/src/trace-types.ts — the full event union

Diagnosis

Six failure modes

Start from the symptom; the trace answers the rest.

SymptomStart from
Agents not using toolstool:dispatch — is anything dispatched at all?
Early termination — reporting too soonagent:turn — what did the model actually emit?
Agents killed by pressurepool:agentDrop — the drop reason
Recovery extraction failspool:recovery* — every attempt ends in one outcome
Synthesis ignores research findingsspine:extend — did the findings reach the spine?
Plan produces poor sub-questionsprompt:format — the prompt the planner saw
Imports

Where things live

Wrong import paths were the single most common staleness in older material. These are the current homes.

SymbolPackage
withSpine · agentPool · dag@lloyal-labs/lloyal-agents
Source · EntailmentScorer · ScorerReranker@lloyal-labs/lloyal-agents
GrantStore · GrantStoreCtx@lloyal-labs/lloyal-agents
composePrompt · renderPrompt · renderTemplate@lloyal-labs/lloyal-agents
renderSpine@lloyal-labs/rig
reportTool@lloyal-labs/rig

Prompt templates live in prompts/ beside the harness — .eta for templates with conditionals, loaded as raw strings at startup and rendered at call time.

Elsewhere

Continue