mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
refactor(x): finish turn-spine adoption for headless surfaces
Background-task and live-note transcript views move from fetch-once to the shared useAgentRunTranscript hook (useTurn over the turns:events spine, with a one-shot legacy runs:fetch fallback for pre-migration run ids), so an in-flight run's transcript now streams live. The headless runner drains its execution stream — delivery rides the turn event bus, and an unconsumed HotStream buffered every durable event until settle. Deletes the caller-less runHeadlessTurn duplicate (HeadlessAgentRunner is the implementation; session-design.md now says so) and the orphaned web-search skill file that was never registered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
248bb0d1a1
commit
f916cb1d70
12 changed files with 132 additions and 180 deletions
|
|
@ -376,7 +376,10 @@ which governs mutation of live logs, not their removal.
|
|||
## 11. Headless standalone turns
|
||||
|
||||
A helper covers the non-session callers (background tasks, live notes,
|
||||
knowledge pipelines, scheduled agents):
|
||||
knowledge pipelines, scheduled agents). Implemented as
|
||||
`HeadlessAgentRunner` in `agents/headless.ts` (start/run handle with
|
||||
turn id, reduced state, and final assistant text); the shape below is
|
||||
the contract it fulfils:
|
||||
|
||||
```ts
|
||||
function runHeadlessTurn(input: {
|
||||
|
|
|
|||
|
|
@ -163,6 +163,18 @@ export class HeadlessAgentRunner implements IHeadlessAgentRunner {
|
|||
const execution = this.turnRuntime.advanceTurn(turnId, undefined, {
|
||||
signal: options.signal,
|
||||
});
|
||||
// Drain the execution stream: live delivery rides the turn event bus,
|
||||
// and an unconsumed HotStream would buffer every durable event in
|
||||
// memory until the turn settles.
|
||||
void (async () => {
|
||||
try {
|
||||
for await (const event of execution.events) {
|
||||
void event;
|
||||
}
|
||||
} catch {
|
||||
// Infrastructure failures surface through the outcome.
|
||||
}
|
||||
})();
|
||||
const done = execution.outcome.then(async (outcome) => {
|
||||
const state = reduceTurn(
|
||||
(await this.turnRuntime.getTurn(turnId)).events,
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
export const skill = String.raw`
|
||||
# Web Search Skill
|
||||
|
||||
You have access to two search tools for finding information on the internet. Choose the right one based on the user's intent.
|
||||
|
||||
## Tools
|
||||
|
||||
### web-search (Brave Search)
|
||||
Quick, general-purpose web search. Returns titles, URLs, and short descriptions.
|
||||
|
||||
**Best for:**
|
||||
- Quick lookups for things that change ("current price of Bitcoin", "weather in SF")
|
||||
- Current events and breaking news
|
||||
- Finding a specific website or page
|
||||
- Simple questions with direct answers
|
||||
- Checking a fact or date
|
||||
|
||||
### research-search (Exa Search)
|
||||
Deep, research-oriented search. Returns full article text, highlights, and metadata (author, published date).
|
||||
|
||||
**Best for:**
|
||||
- Exploring a topic in depth ("what are the latest advances in CRISPR")
|
||||
- Finding articles, blog posts, papers, and quality sources
|
||||
- Discovering companies, people, or organizations
|
||||
- Research where you need rich context, not just links
|
||||
- When the user says "research", "find articles about", "look into", "deep dive"
|
||||
|
||||
**Category filter:** Use the category parameter when the user's intent clearly maps to one: company, research paper, news, tweet, personal site, financial report, people.
|
||||
|
||||
## How Many Searches to Do
|
||||
|
||||
**CRITICAL: Always start with exactly ONE search call.** Pick the single best tool (\`web-search\` or \`research-search\`) and make one request. Wait for the result before deciding if more searches are needed.
|
||||
|
||||
**NEVER call multiple search tools simultaneously.** No parallel web-search + research-search. No firing off two web-searches at once. Always sequential: one search at a time.
|
||||
|
||||
Only make a follow-up search if:
|
||||
- The first search returned truly uninformative or irrelevant results
|
||||
- The query has clearly distinct sub-topics that the first search couldn't cover (e.g., "compare X and Y" after getting results for X only)
|
||||
- The user explicitly asks you to dig deeper
|
||||
|
||||
One good search is almost always enough. Default to one and stop.
|
||||
|
||||
## Choosing Between the Two
|
||||
|
||||
If both tools are attached, prefer:
|
||||
- \`web-search\` when the user wants a quick answer or specific link
|
||||
- \`research-search\` when the user wants to learn, explore, or gather sources
|
||||
|
||||
If only one is attached, use whichever is available.
|
||||
`;
|
||||
|
||||
export default skill;
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import type { z } from "zod";
|
||||
import type { UserMessage } from "@x/shared/dist/message.js";
|
||||
import type {
|
||||
ConversationMessage,
|
||||
RequestedAgent,
|
||||
} from "@x/shared/dist/turns.js";
|
||||
import type { ITurnRuntime, TurnOutcome } from "../turns/api.js";
|
||||
|
||||
// Standalone turns for non-session callers (background tasks, live notes,
|
||||
// knowledge pipelines, scheduled agents): sessionId null, automatic
|
||||
// permissions, no human. Never appears in the session index; callers keep
|
||||
// the turnId if they need history.
|
||||
export async function runHeadlessTurn(
|
||||
turnRuntime: ITurnRuntime,
|
||||
input: {
|
||||
agent: z.infer<typeof RequestedAgent>;
|
||||
context?: Array<z.infer<typeof ConversationMessage>>;
|
||||
input: z.infer<typeof UserMessage>;
|
||||
maxModelCalls?: number;
|
||||
signal?: AbortSignal;
|
||||
},
|
||||
): Promise<{ turnId: string; outcome: TurnOutcome }> {
|
||||
const turnId = await turnRuntime.createTurn({
|
||||
agent: input.agent,
|
||||
sessionId: null,
|
||||
context: input.context ?? [],
|
||||
input: input.input,
|
||||
config: {
|
||||
autoPermission: true,
|
||||
humanAvailable: false,
|
||||
...(input.maxModelCalls === undefined
|
||||
? {}
|
||||
: { maxModelCalls: input.maxModelCalls }),
|
||||
},
|
||||
});
|
||||
const execution = turnRuntime.advanceTurn(turnId, undefined, {
|
||||
signal: input.signal,
|
||||
});
|
||||
const outcome = await execution.outcome;
|
||||
return { turnId, outcome };
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
export * from "./api.js";
|
||||
export * from "./bus.js";
|
||||
export * from "./fs-repo.js";
|
||||
export * from "./headless.js";
|
||||
export * from "./in-memory-session-repo.js";
|
||||
export * from "./repo.js";
|
||||
export * from "./session-index.js";
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import type {
|
|||
import { TurnInputError } from "../turns/api.js";
|
||||
import { HotStream } from "../turns/stream.js";
|
||||
import { TurnNotSettledError } from "./api.js";
|
||||
import { runHeadlessTurn } from "./headless.js";
|
||||
import { InMemorySessionRepo } from "./in-memory-session-repo.js";
|
||||
import type { ISessionRepo } from "./repo.js";
|
||||
import { SessionsImpl } from "./sessions.js";
|
||||
|
|
@ -823,26 +822,6 @@ describe("startup scan (13.6)", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("headless standalone turns (13.8)", () => {
|
||||
it("runs a standalone turn with sessionId null, auto permission, no human", async () => {
|
||||
const fake = new FakeTurnRuntime();
|
||||
const { turnId, outcome } = await runHeadlessTurn(fake, {
|
||||
agent: { agentId: "background-task-agent" },
|
||||
input: user("summarize"),
|
||||
maxModelCalls: 3,
|
||||
});
|
||||
expect(fake.createTurnInputs[0]).toEqual({
|
||||
agent: { agentId: "background-task-agent" },
|
||||
sessionId: null,
|
||||
context: [],
|
||||
input: user("summarize"),
|
||||
config: { autoPermission: true, humanAvailable: false, maxModelCalls: 3 },
|
||||
});
|
||||
expect(outcome.status).toBe("completed");
|
||||
expect(fake.advanceCalls).toEqual([{ turnId, input: undefined }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("active-skill carry-forward", () => {
|
||||
const skillTool = {
|
||||
toolId: "builtin:file-writeText",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue