Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,18 @@ export {
type ReflexionKind,
type ReflexionMetadata,
} from './reflexion.js';
export type { SearchResult, GetObservationsOptions, Observation, Session } from './types.js';
export {
MAX_OPEN_PROPOSALS_PER_SCOUT,
SCOUT_PROPOSAL_ERROR_CODES,
type AgentRole,
type GetObservationsOptions,
type Observation,
type SearchResult,
type Session,
type ScoutProposalErrorCode,
type TaskProposalStatus,
type TaskThreadProposalFields,
} from './types.js';
export { createSessionId } from './ids.js';
export { inferIdeFromSessionId } from './infer-ide.js';
export {
Expand Down
27 changes: 25 additions & 2 deletions packages/core/src/response-thresholds.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Storage } from '@colony/storage';
import type { AgentRole } from './types.js';

/**
* Named capability dimensions. Intentionally small — five is the upper
Expand All @@ -17,10 +18,17 @@ export interface AgentCapabilities {

export interface AgentProfile {
agent: string;
role: AgentRole;
openProposalCount: number;
capabilities: AgentCapabilities;
updated_at: number;
}

interface AgentProfileStorageExtras {
role?: AgentRole | null;
open_proposal_count?: number | null;
}

/**
* Default profile for agents that haven't registered capabilities yet.
* All dimensions at 0.5 means "no preference either way" — the agent
Expand Down Expand Up @@ -121,6 +129,8 @@ export function loadProfile(storage: Storage, agent: string): AgentProfile {
if (!row) {
return {
agent,
role: 'executor',
openProposalCount: 0,
capabilities: { ...DEFAULT_CAPABILITIES },
updated_at: 0,
};
Expand All @@ -132,7 +142,14 @@ export function loadProfile(storage: Storage, agent: string): AgentProfile {
} catch {
caps = { ...DEFAULT_CAPABILITIES };
}
return { agent, capabilities: caps, updated_at: row.updated_at };
const extras = row as typeof row & AgentProfileStorageExtras;
return {
agent,
role: extras.role ?? 'executor',
openProposalCount: extras.open_proposal_count ?? 0,
capabilities: caps,
updated_at: row.updated_at,
};
}

/** Write a full or partial capability profile for an agent. */
Expand All @@ -148,5 +165,11 @@ export function saveProfile(
capabilities: JSON.stringify(merged),
updated_at: Date.now(),
});
return { agent, capabilities: merged, updated_at: Date.now() };
return {
agent,
role: current.role,
openProposalCount: current.openProposalCount,
capabilities: merged,
updated_at: Date.now(),
};
}
2 changes: 2 additions & 0 deletions packages/core/src/task-thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
loadProfile,
rankCandidates,
} from './response-thresholds.js';
import { SCOUT_PROPOSAL_ERROR_CODES } from './types.js';
import {
type WorkingHandoffNoteInput,
type WorkingHandoffNoteResult,
Expand Down Expand Up @@ -101,6 +102,7 @@ export const TASK_THREAD_ERROR_CODES = {
CLAIM_BATON_CONFLICT: 'CLAIM_BATON_CONFLICT',
INVALID_CLAIM_PATH: 'INVALID_CLAIM_PATH',
PROTECTED_BRANCH_CLAIM_REJECTED: 'PROTECTED_BRANCH_CLAIM_REJECTED',
...SCOUT_PROPOSAL_ERROR_CODES,
INTERNAL_ERROR: 'INTERNAL_ERROR',
} as const;

Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
export type AgentRole = 'scout' | 'executor' | 'queen';

export const MAX_OPEN_PROPOSALS_PER_SCOUT = 3;

export type TaskProposalStatus = 'proposed' | 'approved' | 'archived';

export interface TaskThreadProposalFields {
proposalStatus?: TaskProposalStatus | null;
approvedBy?: string | null;
observationEvidenceIds?: number[];
}

export const SCOUT_PROPOSAL_ERROR_CODES = {
PROPOSAL_MISSING_EVIDENCE: 'PROPOSAL_MISSING_EVIDENCE',
PROPOSAL_CAP_EXCEEDED: 'PROPOSAL_CAP_EXCEEDED',
EXECUTOR_CANNOT_PROPOSE: 'EXECUTOR_CANNOT_PROPOSE',
SCOUT_NO_CLAIM: 'SCOUT_NO_CLAIM',
} as const;

export type ScoutProposalErrorCode =
(typeof SCOUT_PROPOSAL_ERROR_CODES)[keyof typeof SCOUT_PROPOSAL_ERROR_CODES];

export interface Observation {
id: number;
session_id: string;
Expand Down
4 changes: 4 additions & 0 deletions packages/core/test/response-thresholds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ describe('rankCandidates', () => {
describe('loadProfile / saveProfile', () => {
it('loadProfile returns the default capabilities for unknown agents', () => {
const profile = loadProfile(store.storage, 'unknown');
expect(profile.role).toBe('executor');
expect(profile.openProposalCount).toBe(0);
expect(profile.capabilities).toEqual(DEFAULT_CAPABILITIES);
expect(profile.updated_at).toBe(0);
});
Expand All @@ -120,6 +122,8 @@ describe('loadProfile / saveProfile', () => {
saveProfile(store.storage, 'claude', { ui_work: 0.9, api_work: 0.4 });
saveProfile(store.storage, 'claude', { ui_work: 0.95 });
const profile = loadProfile(store.storage, 'claude');
expect(profile.role).toBe('executor');
expect(profile.openProposalCount).toBe(0);
expect(profile.capabilities.ui_work).toBe(0.95);
// Other values preserved from previous save.
expect(profile.capabilities.api_work).toBe(0.4);
Expand Down
Loading