feat(core): add agent execution context

This commit is contained in:
Yichen Jiang
2026-07-16 16:29:46 +08:00
parent 04df615dd6
commit 7bcae0cd64
93 changed files with 1272 additions and 462 deletions

View File

@@ -26,6 +26,8 @@ const FENCE = 'ts cordis-catalog'
// TODO(catalog-type-links): verify or generate link-map coverage.
export const LINK_MAP: Record<string, string> = {
Agent: 'core.md',
AgentExecution: 'core.md',
AgentExecutionService: 'core.md',
ContentBlock: 'core.md',
Message: 'core.md',
MessageSource: 'core.md',
@@ -84,13 +86,13 @@ interface ServiceEntry {
key: string
/** The service class/interface name, e.g. `LlmService`. */
type: string
/** Whether the service class is abstract (a seam interface). */
/** Whether the service declaration is abstract (a seam interface). */
abstract: boolean
/** Class-level JSDoc prose, one line per paragraph. */
/** Declaration-level JSDoc prose, one line per paragraph. */
doc: string
/** Public method signatures (bodies stripped), in source order. */
methods: string[]
/** Source pointer of the class declaration. */
/** Source pointer of the service declaration. */
source: string
}
@@ -172,8 +174,8 @@ export function collectEvents(scanRoot: string = root): EventEntry[] {
return entries
}
/** Walk every harness `interface Context` block + its service class, hard-
* erroring (aggregated) on any JSDoc-completeness violation: a class or public
/** Walk every harness `interface Context` block + its service declaration,
* hard-erroring (aggregated) on any JSDoc-completeness violation: a service or public
* method without JSDoc prose, an undocumented parameter, a stale `@param`, a
* missing `@returns` on a non-void method, or an inferred (unannotated) return
* type the pure-AST walk cannot classify.
@@ -199,18 +201,23 @@ export function collectServices(scanRoot: string = root): ServiceEntry[] {
}
}
if (keyToType.size === 0) continue
// Find each service class declared in the same file and emit an entry.
// Find each service declaration in the same file and emit an entry.
for (const [key, type] of keyToType) {
const cls = sf.statements.find(
(s): s is ts.ClassDeclaration => ts.isClassDeclaration(s) && s.name?.text === type,
const declaration = sf.statements.find(
(s): s is ts.ClassDeclaration | ts.InterfaceDeclaration =>
(ts.isClassDeclaration(s) || ts.isInterfaceDeclaration(s)) && s.name?.text === type,
)
if (!cls) continue // a Pick-mixin member (e.g. timer helpers), not a class here
const abstract = cls.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false
const clsDoc = parseJsDoc(rawJsDoc(text, cls)).doc
if (!clsDoc) violations.push(`service ctx.${key} (${pointer(rel, sf, cls)}): class ${type} has no JSDoc.`)
if (!declaration) continue // a Pick-mixin member (e.g. timer helpers), not a declaration here
const abstract = ts.isInterfaceDeclaration(declaration)
|| (declaration.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false)
const declarationDoc = parseJsDoc(rawJsDoc(text, declaration)).doc
if (!declarationDoc) {
const kind = ts.isInterfaceDeclaration(declaration) ? 'interface' : 'class'
violations.push(`service ctx.${key} (${pointer(rel, sf, declaration)}): ${kind} ${type} has no JSDoc.`)
}
const methods: string[] = []
for (const member of cls.members) {
if (!ts.isMethodDeclaration(member)) continue
for (const member of declaration.members) {
if (!ts.isMethodDeclaration(member) && !ts.isMethodSignature(member)) continue
// Only instance methods callable through `ctx.<key>` are surface;
// private, protected, and static methods are not.
const nonPublic = member.modifiers?.some(m =>
@@ -238,9 +245,9 @@ export function collectServices(scanRoot: string = root): ServiceEntry[] {
key,
type,
abstract,
doc: clsDoc,
doc: declarationDoc,
methods,
source: pointer(rel, sf, cls),
source: pointer(rel, sf, declaration),
})
}
}

View File

@@ -152,6 +152,14 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['agent-loop', 'acp', 'subagent-inprocess', 'stdio-demo', 'invariants'],
note: 'Owns live Agent handles and the create/resume factory seam.',
},
{
key: 'agentExecution',
pkg: 'agent-execution',
title: 'Agent execution context',
mode: 'core',
consumers: ['agent-loop'],
note: 'Carries the exact initiating Agent across one process-local asynchronous driver chain; explicit identities remain authoritative at external boundaries.',
},
{
key: 'agentLoop',
pkg: 'agent-loop',

View File

@@ -11,6 +11,8 @@
{ "doc": "docs/core-data-structures/core.md", "symbol": "LlmCallConfig", "source": "packages/llm/llm/src/call-config.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "Agent", "source": "packages/core/agent/src/types.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "AgentExecution", "source": "packages/core/agent-execution/src/types.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "AgentExecutionService", "source": "packages/core/agent-execution/src/index.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "HookContext", "source": "packages/core/agent/src/types.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "PromptDecision", "source": "packages/core/agent/src/types.ts" },
{ "doc": "docs/core-data-structures/core.md", "symbol": "ContinuationDecision", "source": "packages/core/agent/src/types.ts" },

View File

@@ -28,6 +28,7 @@ interface SentenceContract {
* so an absent section cannot be mistaken for forgotten documentation.
*/
const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
'packages/core/agent-execution': 'The package is model-agnostic ambient control infrastructure; model-facing consumers own any resulting request surface.',
'packages/core/scope': 'The package is a model-agnostic registration and lifecycle primitive; model-facing consumers own any context selection.',
'packages/util/brand': 'The package is a type-only primitive erased at compile time.',
}