Skip to content
中文

Decorators and definition pools ​

Import decorators and readers from @jangts/agent-core/support/decorators. Only standard TypeScript decorators are supported; NestJS compilation units using experimentalDecorators cannot use this syntax directly. Original object, define, constructor and bind paths remain available. No runtime dependency, reflection metadata or automatic DI is added.

Class decorators construct a definition once after static initialization and supply id. Classes own their original fields, handlers, private helpers and getters; do not repeat complete options in decorator metadata. Classes require zero-argument construction. Constructors initialize definitions without business operations. No Engine, Runner, request budget or model call is created.

Named class contracts ​

Import Node, Tool, Fragment, Workflow, Agent and Model from the decorator subpath. These contracts omit the decorator-owned id. Object-shaped contracts support implements; Agent preserves the original union and is checked by its decorator rather than requiring a class to implement a union. Getters, private fields and prototype methods retain their original receiver.

ts
import { AgentCoreEngine, defineWorkflow } from '@jangts/agent-core';
import { node, nodePool } from '@jangts/agent-core/support/decorators';
import type { Node } from '@jangts/agent-core/support/decorators';
type State = { text: string; answer?: string };
@node<State, string>('greet', { ns: 'chat' })
class Greeting implements Node<State, string> {
  readonly description = 'Greeting';
  readonly routes = [{ complete: (state: Readonly<State>) => state.answer! }];
  #prefix = 'Hello, ';
  run(state: Readonly<State>) { return { answer: this._format(state.text) }; }
  private _format(text: string) { return this.#prefix + text.trim(); }
}
const workflow = defineWorkflow<State, string>({ id: 'greeting', nodes: nodePool.getAll('chat') });
console.log(await new AgentCoreEngine({ workflow }).run({ text: 'visitor' }));

Namespaces and reads ​

Each kind has one shared reader: nodePool, toolPool, fragmentPool, workflowPool, agentPool and modelPool. Consumers do not create pools or register definitions manually. Every decorator accepts optional { ns: string | readonly string[] }, defaulting to 'default'.

ts
nodePool.get('greet', 'chat');
nodePool.getAll('chat');
nodePool.pick(['greet', 'answer'], 'chat');
nodePool.get('greet');    // default namespace
nodePool.getAll();       // only default, not every namespace
nodePool.pick(['greet']);

Ids are unique within one kind and namespace. Other namespaces and kinds may reuse an id. Names match exactly, without hierarchy, wildcard or fallback; blank names and empty namespace arrays reject. Repeated namespaces are deduplicated. An array registers the same definition in multiple namespaces without copying or reconstructing it. All destinations are checked before writing; a conflict leaves no partial registration.

get returns undefined on a miss. getAll returns a frozen registration-order snapshot, or an empty array for an unknown namespace. pick takes an id array, preserves caller order and rejects missing or repeated ids; an empty id array returns an empty snapshot. Reads accept original-definition generics, for example nodePool.get<WorkflowNode<State, string>>('greet', 'chat'). The generic describes the caller's expected contract, not a runtime type proof.

ns is external registration metadata, excluded from Core options. Returned ids remain unchanged. Combining same-id definitions from different namespaces in one Workflow or ToolRegistry still fails original uniqueness checks. App owns explicit node ordering and routes. Readers expose no constructors, registration, reset, sealing, tags or owner lookups.

Existing instance methods ​

ts
class GreetingService {
  constructor(private readonly prefix: string) {}
  @node<State, string>('reply', {
    ns: 'chat-service', description: 'Use an existing service',
    routes: [{ complete: (state: Readonly<State>) => state.answer! }],
  })
  answer(state: Readonly<State>) { return { answer: this.prefix + state.text }; }
}
const service = new GreetingService('Hello, ');
const definition = nodePool.get('reply', 'chat-service');

Standard instance initializers automatically bind and register methods. No registerFrom, manual bind, renamed handler or extra class decorator is needed. Initializers run before instance fields and constructor bodies, so they bind without invoking handlers or reading business fields. Read and execute only after successful host construction. Constructor failures do not roll back completed initializer registrations and must be treated as wiring failures. Pools retain host references; use long-lived services, not request-scoped hosts. A second instance with the same kind, namespace and id rejects without replacing the original binding.

Direct metadata callbacks also bind the host. Nested schema/routes retain their original receiver, and arrow functions retain lexical this. Only public instance methods are supported; static/private methods, top-level function decorator syntax and implicit inheritance merging are unsupported. A class-owned id rejects because its decorator supplies id.

Original consumers ​

DecoratorClass contractAdapted methodReaderOriginal consumer
nodeNode<State, Result, App>runnodePooldefineWorkflow
toolTool<Input, Output, App>executetoolPooldefineTool / defineToolCatalog
fragmentFragment<Input>formatfragmentPooldefineContextFragments
workflowWorkflow<State, Result, App>NoneworkflowPooldefineWorkflow
agentAgent<State, Result, App>NoneagentPoolEngine constructor
modelModelNonemodelPoolModelRunner constructor

agent/model ids are registration keys, not Core branch, model or transport identities. Original schema, graph, trust, authorization, result, budget and cancellation boundaries remain in force. Nodes are validated in the final graph. No public metadata-plus-free-function adapter is provided; top-level functions belong directly in original options.

Getter-defined routes ​

A routes getter may return when: state => this._ready(state) and complete: state => this._reply(state). Workflow assembly reads and snapshots the table, potentially reading the getter multiple times. Keep getters pure and independent of request state. After state patches commit, route callbacks receive the latest state and can call private helpers. The getter is not re-evaluated each run; do not store request state on shared nodes.

ESM/CJS module copies share roots within one JavaScript realm. Processes, workers and separate realms do not share roots. Independently evaluated classes registering the same kind, namespace and id reject rather than merge. Explicitly retain definition modules in the bundle: discarded side-effect-only imports cannot guarantee registration. Shared definitions must not retain request identity, signal or budget. Reading is neither execution nor authorization.

WorkflowTool and Intent ​

WorkflowTool and WorkflowNode are explicit names for the tool and node decorators. WorkflowTool supports definition classes and public instance methods. A model-only Tool provides inputSchema and resolve; an executable Tool additionally provides kind, authorize, execute and parseOutput. The same pool stores their normalized definitions.

ts
import { WorkflowTool, Intent, toolPool } from '@jangts/agent-core/support/decorators';
import { Support } from '@jangts/agent-core';

@Intent('The user wants to remember something')
@WorkflowTool('memo', { ns: 'assistant' })
class MemoTool {
  description = 'Accept a memo draft';
  inputSchema = memoSchema;
  resolve(input) { return input; }
}
const candidates = Support.defineModelTools(toolPool.getAll('assistant')).withIntent(context);

Intent accepts no argument, a description, a synchronous boolean predicate, or { description?, when?, delegates? }. It must decorate the same target as WorkflowTool and has no independent ID. Alone it registers nothing. Either decorator order works. Do not pass asIntent in WorkflowTool options or define it on the decorated class; use the separate Intent decorator. Explicit object configuration continues to support asIntent.

API reference, usage examples and developer guides.