Q40: tasks choose a writing mode; house style applies to conversational work only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 07:28:47 -04:00
1 parent 645ce686bb
commit 7be072f8c5
18 files changed
+119 -41

No files matched your search

@@ -0,0 +1,7 @@
export const taskWritingModeSchema = `
alter table tasks add column writing_mode text not null default 'conversational'
check (writing_mode in ('conversational', 'technical', 'structured'));
insert into schema_migrations(version, name, applied_at)
values (66, 'task_writing_mode', datetime('now'));
`;
+2
View File
@@ -64,6 +64,7 @@ import { providerContextWindowSchema } from "./migrations/062-provider-context-w
import { knowledgeScopeSchema } from "./migrations/063-knowledge-scope";
import { knowledgeRevisionsSchema } from "./migrations/064-knowledge-revisions";
import { documentVersionsSchema } from "./migrations/065-document-versions";
import { taskWritingModeSchema } from "./migrations/066-task-writing-mode";
interface Migration {
version: number;
@@ -137,6 +138,7 @@ const MIGRATIONS: Migration[] = [
{ version: 63, name: "knowledge_scope", sql: knowledgeScopeSchema },
{ version: 64, name: "knowledge_revisions", sql: knowledgeRevisionsSchema },
{ version: 65, name: "document_versions", sql: documentVersionsSchema },
{ version: 66, name: "task_writing_mode", sql: taskWritingModeSchema },
];
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
+1 -1
View File
@@ -30,7 +30,7 @@ export function listSkillsTool(deps: AgentToolDeps, ctx: AgentToolContext): stri
const summary = skillSummary(skill.manifest_json);
if (forced.has(skill.slug)) {
const language = skill.slug === "humanizer-zh" ? "Chinese" : "English";
return `- ${skill.name} (${skill.slug})${summary}: house style — carried automatically by every agent when the app language is ${language}`;
return `- ${skill.name} (${skill.slug})${summary}: house style — carried automatically by every agent on conversational tasks when the app language is ${language}`;
}
const holders = bindings
.filter((binding) => binding.skill_id === skill.id)
+9 -2
View File
@@ -1,5 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, type RiskLevel } from "../../shared/domain";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, WRITING_MODE, type RiskLevel, type WritingMode } from "../../shared/domain";
import { ORGANIZATION_DEPARTMENTS } from "./org-governance-service";
import type { OrganizationService } from "./organization-service";
import { buildTaskHandoff, HANDOFF_TOOL_PROPERTIES } from "./agent-task-handoff";
@@ -15,7 +15,7 @@ import { KNOWLEDGE_TOOLS, recordKnowledgeTool, reviseKnowledgeTool, searchKnowle
import { DOCUMENT_TOOLS, DOCUMENT_TYPES, listDocumentsTool, readDocumentTool, updateDocumentTool } from "./agent-document-tools";
import { sendMessageTool } from "./agent-send-message";
import { withSavepoint } from "../database/connection";
import { optionalString, optionalStringArray, requireString } from "./agent-tool-args";
import { optionalEnum, optionalString, optionalStringArray, requireString } from "./agent-tool-args";
import type { JsonRecord } from "./provider-http";
import type { ToolCall, ToolDefinition } from "./provider-tool-turn";
@@ -85,6 +85,11 @@ export const AGENT_TOOLS: ToolDefinition[] = [
type: "boolean",
description: "Set true for engineering work that must change code or run commands (build, test) in the workspace. The assignee then gets run_command; the operator approves such tasks before they execute.",
},
writing_mode: {
type: "string",
enum: ["conversational", "technical", "structured"],
description: "How the output reads. conversational (default): plain human prose in house style. technical: specs, API docs, code reviews; precise, no house style. structured: output must match an exact format (JSON, table, template).",
},
...HANDOFF_TOOL_PROPERTIES,
},
required: ["title", "description"],
@@ -337,6 +342,7 @@ function createTask(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord
const assignee = assigneeName ? requireRunnableAgentByName(deps.db, ctx.orgId, assigneeName) : null;
const handoff = buildTaskHandoff(deps.db, ctx.orgId, { taskId: ctx.taskId, threadId: ctx.sourceThreadId }, args);
const parentTaskId = delegationParent(deps.db, ctx);
const writingMode = optionalEnum(args.writing_mode, "writing_mode", Object.values(WRITING_MODE), WRITING_MODE.CONVERSATIONAL) as WritingMode;
const create = () => {
const taskId = deps.organization.createTask({
orgId: ctx.orgId,
@@ -350,6 +356,7 @@ function createTask(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord
workspaceId: ctx.workspace?.id ?? null,
priority: TASK_PRIORITY.NORMAL,
requiresFilesystem: args.requires_filesystem === true,
writingMode,
});
deps.db.prepare("update tasks set handoff_json = ?, parent_task_id = ? where id = ?")
.run(JSON.stringify(handoff), parentTaskId, taskId);
+4 -3
View File
@@ -1,5 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import { APPROVAL_STATE, RISK_LEVEL, TASK_STATUS, type ApprovalState } from "../../shared/domain";
import { APPROVAL_STATE, RISK_LEVEL, TASK_STATUS, WRITING_MODE, type ApprovalState } from "../../shared/domain";
import { createId } from "../../shared/ids";
import type { CreateAgentInput, CreateGoalInput, CreateOrganizationInput, CreateProjectInput, CreateTaskInput, CreateWorkspaceInput, DeleteTaskInput, UpdateAgentInput, UpdateEmployeeProfileInput, UpdateTaskInput } from "../../shared/commands";
import { AuditService } from "./audit-service";
@@ -234,9 +234,9 @@ export class OrganizationService {
this.db.prepare(`
insert into tasks(
id, org_id, title, description, status, assignee_agent_id, risk,
project_id, goal_id, workspace_id, priority, requires_filesystem, origin, created_at, updated_at
project_id, goal_id, workspace_id, priority, requires_filesystem, writing_mode, origin, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
id,
input.orgId,
@@ -250,6 +250,7 @@ export class OrganizationService {
input.workspaceId,
input.priority,
input.requiresFilesystem ? 1 : 0,
input.writingMode ?? WRITING_MODE.CONVERSATIONAL,
input.origin ?? null,
now,
now,
+3 -3
View File
@@ -244,8 +244,8 @@ export class SkillService {
* agent carries the forced house-style skill once the catalogue holds a
* language variant, plus whatever bindings the CEO granted.
*/
hasRuntimeSkills(orgId: string, agentId: string): boolean {
if (this.forcedSkillRows(orgId).length > 0) return true;
hasRuntimeSkills(orgId: string, agentId: string, houseStyle: boolean): boolean {
if (houseStyle && this.forcedSkillRows(orgId).length > 0) return true;
return Boolean(this.db.prepare(`
select sb.id from skill_bindings sb join skill_definitions sd on sd.id = sb.skill_id and sd.status = 'active'
where sb.org_id = ? and sb.agent_id = ? and sb.enabled = 1 limit 1
@@ -294,7 +294,7 @@ export class SkillService {
* prompt instead of silently overflowing the model's context.
*/
resolveRuntimeSnapshot(input: ResolveAgentSkillsInput): string {
const forcedRows = this.forcedSkillRows(input.orgId);
const forcedRows = input.houseStyle ? this.forcedSkillRows(input.orgId) : [];
const boundRows = this.db.prepare(`
select
sb.skill_id as skill_id,
+5 -5
View File
@@ -1,6 +1,6 @@
import type { DatabaseSync } from "node:sqlite";
import type { RunTaskInput } from "../../shared/commands";
import { TASK_STATUS } from "../../shared/domain";
import { TASK_STATUS, WRITING_MODE } from "../../shared/domain";
import { isTransientProviderFailure } from "../../shared/provider-runtime-domain";
import { createId } from "../../shared/ids";
import { AuditService } from "./audit-service";
@@ -148,7 +148,7 @@ export class TaskExecutionService {
let providerRequestId: string | null = null;
let skillRuntimeSnapshot: SkillRuntimeSnapshotRow | null = null;
try {
skillRuntimeSnapshot = this.resolveSkillRuntimeSnapshot(orgId, agent.id, runId, skillBudgetTokens(provider.context_window_tokens), `${task.title}\n${task.description}`, { providerKey: provider.provider_key, model: provider.model });
skillRuntimeSnapshot = this.resolveSkillRuntimeSnapshot(orgId, agent.id, runId, skillBudgetTokens(provider.context_window_tokens), `${task.title}\n${task.description}`, { providerKey: provider.provider_key, model: provider.model }, task.writing_mode === WRITING_MODE.CONVERSATIONAL);
// Watermark for reply delivery and mid-run message polling.
const runStartedAt = new Date().toISOString();
const chatContext = loadChatWakeContext(this.db, orgId, agent.id, isolation.secretScopeJson);
@@ -391,9 +391,9 @@ export class TaskExecutionService {
`).run(TASK_RUN_STATE.FAILED, providerRequestId, new Date().toISOString(), errorMessage, runId, orgId);
}
private resolveSkillRuntimeSnapshot(orgId: string, agentId: string, runId: string, maxTokens: number, taskText: string, runModel: RunModel): SkillRuntimeSnapshotRow | null {
if (!this.skills.hasRuntimeSkills(orgId, agentId)) return null;
const snapshotId = this.skills.resolveRuntimeSnapshot({ orgId, agentId, runId, maxTokens, taskText, runModel });
private resolveSkillRuntimeSnapshot(orgId: string, agentId: string, runId: string, maxTokens: number, taskText: string, runModel: RunModel, houseStyle: boolean): SkillRuntimeSnapshotRow | null {
if (!this.skills.hasRuntimeSkills(orgId, agentId, houseStyle)) return null;
const snapshotId = this.skills.resolveRuntimeSnapshot({ orgId, agentId, runId, maxTokens, taskText, runModel, houseStyle });
const snapshot = this.db.prepare(`
select id, compiled_prompt, tools_manifest, source_revision_ids
from skill_runtime_snapshots
+15 -1
View File
@@ -1,3 +1,4 @@
import { WRITING_MODE, type WritingMode } from "../../shared/domain";
import { canonicalMentionHandle } from "../../shared/mention-handles";
const RECORD_LEARNINGS_INSTRUCTION =
@@ -16,6 +17,7 @@ export interface TaskExecutionTaskRow {
workspace_id: string | null;
priority: string;
requires_filesystem: number;
writing_mode: WritingMode;
handoff_json: string | null;
origin: string | null;
}
@@ -132,7 +134,7 @@ function capabilityBriefing(input: AgentInstructionContext): string {
);
if (input.isCeo) {
lines.push(
"You are the CEO: you automatically carry every connected service, and access for everyone else is yours to manage — when you delegate work that needs an external service, grant it first with grant_connector and take it back with revoke_connector when done. The company also ships a built-in skill library: staff every hire with the skills their role needs by passing skills to hire_agent (several per person is normal), and adjust later with grant_skill / revoke_skill. You run only with skills granted to you too: grant yourself a playbook before you rely on it. A teammate without the grant cannot use the tool or skill, so grant before you assign. The humanizer skill is company house style — every agent, you included, carries it automatically and must write like a person, not like an AI.",
"You are the CEO: you automatically carry every connected service, and access for everyone else is yours to manage — when you delegate work that needs an external service, grant it first with grant_connector and take it back with revoke_connector when done. The company also ships a built-in skill library: staff every hire with the skills their role needs by passing skills to hire_agent (several per person is normal), and adjust later with grant_skill / revoke_skill. You run only with skills granted to you too: grant yourself a playbook before you rely on it. A teammate without the grant cannot use the tool or skill, so grant before you assign. The humanizer skill is company house style — every agent, you included, carries it on conversational work and must write like a person, not like an AI. For specs and API docs pass writing_mode technical to create_task; for output in an exact format pass structured; both skip house style.",
);
} else if (input.connectorNames.length === 0) {
lines.push(
@@ -160,6 +162,17 @@ function roleVoice(input: AgentInstructionContext): string {
return "You own your craft. Ask the question early instead of guessing, do the work properly, and when you are stuck for more than a few minutes say so in the thread instead of spinning in place.";
}
/** The writing rule for technical and structured tasks, which run without house style. */
export function writingModeLines(mode: WritingMode): string[] {
if (mode === WRITING_MODE.TECHNICAL) {
return ["", "Technical writing: this task's output is a spec or technical document. Be exact and complete: define terms, name types, units and edge cases, prefer lists and tables over prose, and never trade precision for tone."];
}
if (mode === WRITING_MODE.STRUCTURED) {
return ["", "Structured output: the deliverable must match the requested format exactly (JSON, table, template). No prose around it, no extra fields, no commentary inside it."];
}
return [];
}
function skillToolLines(toolsManifest: string, available: string[]): string[] {
const { requiredTools } = JSON.parse(toolsManifest) as { requiredTools: string[] };
const missing = requiredTools.filter((tool) => !available.includes(tool));
@@ -216,6 +229,7 @@ export function buildTaskPrompt(input: {
`Mounts: ${input.isolation.workspaceMountJson}`,
);
}
lines.push(...writingModeLines(input.task.writing_mode));
lines.push(...input.handoffLines);
lines.push(...input.knowledgeLines);
lines.push("", `Secret scope: ${input.isolation.secretScopeJson}`);
+4
View File
@@ -28,6 +28,10 @@ export const zh: Record<string, string> = {
"Advanced": "高级",
"Integrations": "集成密钥",
"Document": "文档",
"Writing": "写作方式",
"Conversational (house style)": "日常沟通(公司文风)",
"Technical spec": "技术规格",
"Structured output": "结构化输出",
"Applies to": "适用范围",
"Category": "类别",
"Content": "内容",
+14 -2
View File
@@ -1,8 +1,8 @@
import type { FormEvent } from "react";
import { Dialog } from "../shared/Dialog";
import { Field } from "../shared/ui";
import type { AppSnapshot, RiskLevel, TaskPriority } from "../../../shared/domain";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS } from "../../../shared/domain";
import type { AppSnapshot, RiskLevel, TaskPriority, WritingMode } from "../../../shared/domain";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, WRITING_MODE } from "../../../shared/domain";
import type { SnapshotActions } from "../app/useSnapshot";
import { t } from "../i18n/i18n";
@@ -18,6 +18,12 @@ const PRIORITY_OPTIONS: ReadonlyArray<{ value: TaskPriority; label: string }> =
{ value: TASK_PRIORITY.HIGH, label: t("High") },
];
const WRITING_OPTIONS: ReadonlyArray<{ value: WritingMode; label: string }> = [
{ value: WRITING_MODE.CONVERSATIONAL, label: t("Conversational (house style)") },
{ value: WRITING_MODE.TECHNICAL, label: t("Technical spec") },
{ value: WRITING_MODE.STRUCTURED, label: t("Structured output") },
];
function value(form: FormData, key: string): string {
return String(form.get(key) ?? "").trim();
}
@@ -59,6 +65,7 @@ export function NewTaskDialog({
workspaceId: optional(form, "workspaceId"),
priority: value(form, "priority") as TaskPriority,
requiresFilesystem: form.get("requiresFilesystem") === "on",
writingMode: value(form, "writingMode") as WritingMode,
})).then((result) => {
if (result) onClose();
});
@@ -102,6 +109,11 @@ export function NewTaskDialog({
{PRIORITY_OPTIONS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
</select>
</Field>
<Field label={t("Writing")}>
<select className="input" name="writingMode" defaultValue={WRITING_MODE.CONVERSATIONAL}>
{WRITING_OPTIONS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
</select>
</Field>
<label className="checkbox-row">
<input type="checkbox" name="requiresFilesystem" /> {t("Requires workspace access")}
</label>
+3 -1
View File
@@ -1,4 +1,4 @@
import type { ApprovalState, IdentitySource, PrincipalType, RiskLevel, RoleKey, TaskPriority, TaskStatus, UiLanguage, UiTheme } from "../domain";
import type { ApprovalState, IdentitySource, PrincipalType, RiskLevel, RoleKey, TaskPriority, TaskStatus, UiLanguage, UiTheme, WritingMode } from "../domain";
export interface SetActiveOrganizationInput {
orgId: string;
@@ -86,6 +86,8 @@ export interface CreateTaskInput {
workspaceId: string | null;
priority: TaskPriority;
requiresFilesystem: boolean;
/** Defaults to conversational. */
writingMode?: WritingMode;
/** Provenance: 'chat_wake' for mention-wake vehicles, 'goal_review' for CEO goal checkpoints, omitted for plain work. */
origin?: "chat_wake" | "goal_review";
}
+2
View File
@@ -25,6 +25,8 @@ export interface ResolveAgentSkillsInput {
taskText: string;
/** The run's provider and model, matched against each skill's compatible_models. */
runModel: { providerKey: string; model: string | null };
/** False on technical and structured tasks, which skip the house-style skill. */
houseStyle: boolean;
}
export interface RunSkillTestsInput {
+4
View File
@@ -19,11 +19,14 @@ export const TASK_STATUS = { BACKLOG: "backlog", IN_PROGRESS: "in_progress", REV
export const APPROVAL_STATE = { PENDING: "pending", APPROVED: "approved", DECLINED: "declined" } as const;
export const RISK_LEVEL = { LOW: "low", MEDIUM: "medium", HIGH: "high" } as const;
export const TASK_PRIORITY = { LOW: "low", NORMAL: "normal", HIGH: "high" } as const;
/** How a task's output should read: conversational prose carries house style; technical and structured do not. */
export const WRITING_MODE = { CONVERSATIONAL: "conversational", TECHNICAL: "technical", STRUCTURED: "structured" } as const;
export const PROVIDER_AUTH_TYPE = { API_KEY: "api_key", OAUTH_PKCE: "oauth_pkce", ACCOUNT_SUBSCRIPTION: "account_subscription", CLOUD: "cloud" } as const;
export const SOCIAL_PLATFORM = { X: "x", LINKEDIN: "linkedin", YOUTUBE: "youtube", INSTAGRAM: "instagram" } as const;
export const FILE_SCAN_STATE = { APPROVED: "approved", BLOCKED: "blocked" } as const;
export type ValueOf<T> = T[keyof T];
export type WritingMode = ValueOf<typeof WRITING_MODE>;
export type TaskStatus = ValueOf<typeof TASK_STATUS>;
export type ApprovalState = ValueOf<typeof APPROVAL_STATE>;
export type RiskLevel = ValueOf<typeof RISK_LEVEL>;
@@ -94,6 +97,7 @@ export interface TaskRecord {
priority: TaskPriority;
parentTaskId: string | null;
requiresFilesystem: boolean;
writingMode: WritingMode;
createdAt: string;
updatedAt: string;
}
+1 -1
View File
@@ -98,7 +98,7 @@ describe("collaboration integration flow", () => {
revisionId = skills.importDirectory({ orgId, directoryPath: createSkillDirectory(), versionSemver: "1.0.0" });
skillId = (connection.db.prepare("select skill_id from skill_revisions where id = ?").get(revisionId) as { skill_id: string }).skill_id;
skills.bind({ orgId, agentId, skillId, revisionId });
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: `run_${randomUUID().replaceAll("-", "")}`, maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } });
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: `run_${randomUUID().replaceAll("-", "")}`, maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true });
suiteId = (connection.db.prepare("select id from eval_suites where org_id = ? and key = ?")
.get(orgId, EVAL_SUITE_KEY.INTEGRATION) as { id: string }).id;
evalRunId = sdlc.runEvalSuite({ orgId, suiteId });
+14 -7
View File
@@ -171,7 +171,7 @@ describe("built-in skills", () => {
h.skills.bind({ orgId: h.orgId, agentId, skillId: skill.id, revisionId: skill.revision_id });
});
const prompt = (runId: string) => {
const id = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId, maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } }));
const id = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId, maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true }));
return (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(id) as { compiled_prompt: string }).compiled_prompt;
};
expect(prompt("run_before")).toContain("# Skill: Backend Architect");
@@ -265,7 +265,7 @@ describe("built-in skills", () => {
expect(hired).toContain("Unknown skills skipped: no-such-skill");
const danaId = (h.connection.db.prepare("select id from agents where org_id = ? and name = 'Dana Lee'").get(h.orgId) as { id: string }).id;
const danaSnapshot = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId: danaId, runId: "run_dana", maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } }));
const danaSnapshot = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId: danaId, runId: "run_dana", maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true }));
const danaPrompt = (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(danaSnapshot) as { compiled_prompt: string }).compiled_prompt;
expect(danaPrompt.indexOf("# Skill: humanizer")).toBe(0);
expect(danaPrompt).toContain("# Skill: Backend Architect");
@@ -274,12 +274,19 @@ describe("built-in skills", () => {
expect(danaPrompt.length).toBeLessThan(SKILL_TOKENS * 3 + 5_000);
// The CEO carries humanizer too - forced by resolution, not by binding.
expect(h.skills.hasRuntimeSkills(h.orgId, ceoId)).toBe(true);
const ceoSnapshot = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId: ceoId, runId: "run_ceo", maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } }));
expect(h.skills.hasRuntimeSkills(h.orgId, ceoId, true)).toBe(true);
const ceoSnapshot = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId: ceoId, runId: "run_ceo", maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true }));
const ceoPrompt = (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(ceoSnapshot) as { compiled_prompt: string }).compiled_prompt;
expect(ceoPrompt).toContain("# Skill: humanizer");
expect(ceoPrompt).not.toContain("# Skill: Backend Architect");
// Technical and structured work drops house style; a CEO without grants then carries nothing.
expect(h.skills.hasRuntimeSkills(h.orgId, ceoId, false)).toBe(false);
const technicalSnapshot = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId: danaId, runId: "run_dana_spec", maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: false }));
const technicalPrompt = (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(technicalSnapshot) as { compiled_prompt: string }).compiled_prompt;
expect(technicalPrompt).not.toContain("# Skill: humanizer");
expect(technicalPrompt.indexOf("# Skill: Backend Architect")).toBe(0);
// list_skills shows descriptions and marks the house-style skill.
const listing = dispatchAgentTool(deps, ceoCtx, { id: "call_2", name: "list_skills", arguments: {} });
expect(listing).toContain("humanizer (humanizer)");
@@ -300,7 +307,7 @@ describe("built-in skills", () => {
});
});
const promptFor = (runId: string) => {
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId, maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } }));
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId, maxTokens: SKILL_TOKENS, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true }));
return (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(snapshotId) as { compiled_prompt: string }).compiled_prompt;
};
@@ -346,7 +353,7 @@ describe("built-in skills", () => {
modelProviderId: null, modelId: null, budgetCents: 1000, managerAgentId: null,
});
});
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId: "run_tiny", maxTokens: 10, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } }));
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId: "run_tiny", maxTokens: 10, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true }));
const prompt = (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(snapshotId) as { compiled_prompt: string }).compiled_prompt;
expect(prompt).not.toContain("# Skill:");
expect(prompt).toContain("# Skills omitted for size");
@@ -373,7 +380,7 @@ describe("built-in skills", () => {
bindSkillSlugs({ db: h.connection.db, organization: h.organization, chat: new ChatService(h.connection.db) }, h.orgId, agentId, ["marketing-growth-hacker", "engineering-backend-architect"]);
});
const compile = (runId: string, maxTokens: number, taskText: string) => {
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId, maxTokens, taskText, runModel: { providerKey: "openai", model: "gpt-5.4" } }));
const snapshotId = h.connection.transaction(() => h.skills.resolveRuntimeSnapshot({ orgId: h.orgId, agentId, runId, maxTokens, taskText, runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true }));
return (h.connection.db.prepare("select compiled_prompt from skill_runtime_snapshots where id = ?").get(snapshotId) as { compiled_prompt: string }).compiled_prompt;
};
const parts = compile("run_full", 1_000_000, "").split("\n\n---\n\n");
+4 -4
View File
@@ -24,7 +24,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66]);
connection.close();
});
@@ -186,7 +186,7 @@ describe("domain services", () => {
skills.bind({ orgId, agentId, skillId: skill.id, revisionId });
rmSync(skillDir, { recursive: true, force: true });
skills.runTests({ orgId, revisionId });
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: "run-review-1", maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } });
skills.resolveRuntimeSnapshot({ orgId, agentId, runId: "run-review-1", maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true });
});
// Catalogue metadata ships in the snapshot; skill internals stay in the
@@ -247,10 +247,10 @@ describe("domain services", () => {
.get(orgA, "review-routes") as { id: string }).id;
expect(() => skills.bind({ orgId: orgA, agentId: agentB, skillId, revisionId })).toThrow("Agent was not found.");
skills.bind({ orgId: orgA, agentId: agentA, skillId, revisionId });
const firstResolve = skills.resolveRuntimeSnapshot({ orgId: orgA, agentId: agentA, runId: "run-review-1", maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } });
const firstResolve = skills.resolveRuntimeSnapshot({ orgId: orgA, agentId: agentA, runId: "run-review-1", maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true });
// Idempotent per run: a recovered run reuses its frozen snapshot
// instead of failing on the uniqueness guard.
expect(skills.resolveRuntimeSnapshot({ orgId: orgA, agentId: agentA, runId: "run-review-1", maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" } })).toBe(firstResolve);
expect(skills.resolveRuntimeSnapshot({ orgId: orgA, agentId: agentA, runId: "run-review-1", maxTokens: 44_800, taskText: "", runModel: { providerKey: "openai", model: "gpt-5.4" }, houseStyle: true })).toBe(firstResolve);
expect(connection.db.prepare("select count(*) as n from skill_runtime_snapshots where run_id = 'run-review-1'").get())
.toEqual({ n: 1 });
});
+1 -1
View File
@@ -42,7 +42,7 @@ describe("skill compatibility", () => {
skills.bind({ orgId, agentId, skillId: skill.id, revisionId });
});
const prompt = (runId: string, providerKey: string, model: string) => {
const id = connection.transaction(() => skills.resolveRuntimeSnapshot({ orgId, agentId, runId, maxTokens: 40_000, taskText: "", runModel: { providerKey, model } }));
const id = connection.transaction(() => skills.resolveRuntimeSnapshot({ orgId, agentId, runId, maxTokens: 40_000, taskText: "", runModel: { providerKey, model }, houseStyle: true }));
return connection.db.prepare("select compiled_prompt, source_revision_ids from skill_runtime_snapshots where id = ?").get(id) as { compiled_prompt: string; source_revision_ids: string };
};
+26 -10
View File
@@ -7,6 +7,7 @@ import { OrganizationService } from "../../src/main/services/organization-servic
import { ChatService } from "../../src/main/services/chat-service";
import { SkillService } from "../../src/main/services/skill-service";
import { AGENT_TOOLS, dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
import { writingModeLines } from "../../src/main/services/task-prompts";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
@@ -75,6 +76,21 @@ function importSkill(h: Harness, name: string, slug: string): void {
}
describe("skill grants", () => {
test("create_task records the writing mode, which decides house style and the writing rule", () => {
const h = harness();
const created = dispatchAgentTool(h.deps, h.ceoCtx, call("create_task", { title: "API spec", description: "Write the spec.", writing_mode: "technical" }));
const taskId = /task_id=(tsk_\w+)/.exec(created)![1];
expect(h.connection.db.prepare("select writing_mode from tasks where id = ?").get(taskId)).toEqual({ writing_mode: "technical" });
const plain = dispatchAgentTool(h.deps, h.ceoCtx, call("create_task", { title: "Chat", description: "Say hi." }));
expect(h.connection.db.prepare("select writing_mode from tasks where id = ?").get(/task_id=(tsk_\w+)/.exec(plain)![1])).toEqual({ writing_mode: "conversational" });
expect(dispatchAgentTool(h.deps, h.ceoCtx, call("create_task", { title: "X", description: "Y", writing_mode: "poetic" })))
.toBe('ERROR: "writing_mode" must be one of: conversational, technical, structured.');
expect(writingModeLines("conversational")).toEqual([]);
expect(writingModeLines("technical").join(" ")).toContain("Technical writing");
expect(writingModeLines("structured").join(" ")).toContain("Structured output");
h.connection.close();
});
test("the CEO runs only with granted skills, as the tool text says, and can grant itself", () => {
const h = harness();
importSkill(h, "Release Gate", "release-gate");
@@ -82,10 +98,10 @@ describe("skill grants", () => {
expect(listSkills?.description).not.toContain("every skill automatically");
expect(listSkills?.description).toContain("the CEO included");
const skills = new SkillService(h.connection.db, "");
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId)).toBe(false);
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId, true)).toBe(false);
expect(dispatchAgentTool(h.deps, h.ceoCtx, call("grant_skill", { agent_name: "CEO", skill: "release-gate" })))
.toBe("CEO now has the Release Gate skill; it joins their next run.");
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId)).toBe(true);
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId, true)).toBe(true);
h.connection.close();
});
@@ -100,11 +116,11 @@ describe("skill grants", () => {
expect(listing).toContain("Release Gate (release-gate): granted to Dana");
const skills = new SkillService(h.connection.db, "");
expect(skills.hasRuntimeSkills(h.orgId, h.danaId)).toBe(true);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId, true)).toBe(true);
const revoked = dispatchAgentTool(h.deps, h.ceoCtx, call("revoke_skill", { agent_name: "Dana", skill: "release-gate" }));
expect(revoked).toContain("no longer has the Release Gate skill");
expect(skills.hasRuntimeSkills(h.orgId, h.danaId)).toBe(false);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId, true)).toBe(false);
});
test("staff cannot hand out skills", () => {
@@ -112,7 +128,7 @@ describe("skill grants", () => {
importSkill(h, "Release Gate", "release-gate");
const denied = dispatchAgentTool(h.deps, h.danaCtx, call("grant_skill", { agent_name: "Dana", skill: "release-gate" }));
expect(denied).toContain("ERROR");
expect(new SkillService(h.connection.db, "").hasRuntimeSkills(h.orgId, h.danaId)).toBe(false);
expect(new SkillService(h.connection.db, "").hasRuntimeSkills(h.orgId, h.danaId, true)).toBe(false);
});
test("the humanizer house-style skill reaches every agent without bindings and cannot be revoked", () => {
@@ -120,18 +136,18 @@ describe("skill grants", () => {
const skills = new SkillService(h.connection.db, "");
// A non-forced skill grants nothing on its own - not even to the CEO.
importSkill(h, "Release Gate", "release-gate");
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId)).toBe(false);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId)).toBe(false);
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId, true)).toBe(false);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId, true)).toBe(false);
// Once the forced skill exists in the catalogue, everyone carries it.
importSkill(h, "humanizer", "humanizer");
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId)).toBe(true);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId)).toBe(true);
expect(skills.hasRuntimeSkills(h.orgId, h.ceoId, true)).toBe(true);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId, true)).toBe(true);
// House style is not revocable, and granting it is a no-op with feedback.
const revoked = dispatchAgentTool(h.deps, h.ceoCtx, call("revoke_skill", { agent_name: "Dana", skill: "humanizer" }));
expect(revoked).toContain("ERROR");
expect(revoked).toContain("house style");
const granted = dispatchAgentTool(h.deps, h.ceoCtx, call("grant_skill", { agent_name: "Dana", skill: "humanizer" }));
expect(granted).toContain("automatically");
expect(skills.hasRuntimeSkills(h.orgId, h.danaId)).toBe(true);
expect(skills.hasRuntimeSkills(h.orgId, h.danaId, true)).toBe(true);
});
});