mahler
Version:
A automated task composer and HTN based planner for building autonomous system agents
212 lines (211 loc) • 9.83 kB
TypeScript
import { Lens } from '../lens';
import type { AnyOp, Update } from '../operation';
import type { PathType, Root } from '../path';
import { Path } from '../path';
import { View } from '../view';
import type { TaskArgs, TaskOp } from './context';
import { Context } from './context';
import type { Action, Instruction, Method } from './instructions';
import { MethodExpansion } from './instructions';
import type { ActionTaskProps, ContextWithSystem, MethodTaskProps } from './props';
interface TaskSpec<TState = unknown, TPath extends PathType = Root, TOp extends AnyOp = Update> {
/**
* A unique identifier for the task. This is automatically generated
* when constructing he task, and is not user configurable.
*/
readonly id: string;
/**
* The path to the element that this task applies to
*/
readonly lens: Path<TPath>;
/**
* The operation that this task applies to
*/
readonly op: TOp;
/**
* A descriptor for this task. The descriptor can be either a string or a Context
* instance. The description does not receive the current state to allow actions to
* be compared by their description (useful for testing and debugging).
*/
readonly description: string | ((c: Context<TState, TPath, TOp>) => string);
/**
* A condition that must be met for the task to be chosen by the planner or executed by
* an agent.
*/
readonly condition: (s: Lens<TState, TPath>, ctx: ContextWithSystem<TState, TPath, TOp>) => boolean;
}
/**
* An action task defines a primitive operation that can be chosen by a planner and
* executed by an agent. Action tasks can be used to perform composite behaviors via
* methods.
*
* Action tasks can be created via the Task constructor as follows
*
* ```typescript
* const plusOne = Task.from({
* // A condition for chosing/executing the task
* condition: (state: number, { target }) => state < target,
* // The effect of the action is increasing the system
* // counter by 1. This will be used during planning
* effect: (state: View<number>) => ++state._,
* // The actual action that will be executed
* action: async (state: View<number>) => {
* ++state._;
* },
* // An optional description. Useful for testing
* description: '+1',
* });
* ```
*
* An ActionTask is also a function that binds the task to a specific context. This allows
* the taks to be used in methods.
*
* ```ts
* const plusTwo = Task.from<number>({
* // We want this method to be chosen only if the difference between the current
* // state and the target is bigger than one
* condition: (state, { target }) => target - state > 1,
* // The method returns two instances of the plusOne task bound to the given target
* method: (_, { target }) => [plusOne({ target }), plusOne({ target })],
* description: '+2',
* });
* ```
*/
export interface ActionTask<TState = unknown, TPath extends PathType = Root, TOp extends AnyOp = Update> extends TaskSpec<TState, TPath, TOp> {
/**
* The effect on the state that the task performs.
*
* The effect function will only be ran if the condition is met and developers
* can trust that the condition will be checked beforehand.
*
* The effect function receives a view to the state, which allows
* it to mutate the state. The effect function should not return
* anything.
*
* If the task operation is `create`, the task constructor will add as
* condition that the property pointed by the lens is undefined. The value will
* be created before calling the effect function provided by the user.
*
* If the task operation is `delete`, the task constructor will add as a condition
* that the property pointed by the lens is not undefined. The value will be deleted
* automatically after the effect function provided by the user.
*
* @param view - A view to the state pointed by the lens.
* @param ctx - The calling context for the task. It includes any lens properties and the system object
*/
readonly effect: (view: View<TState, TPath, TOp>, ctx: ContextWithSystem<TState, TPath, TOp>) => void;
/**
* TThe actual action the task performs.
*
* The action function will only be ran if the condition is met and developers
* can trust that the condition will be checked beforehand.
*
* The action function receives a view to the state, which allows
* it to mutate the state. The action function should not return
* anything.
*
* If the task operation is `create`, the task constructor will add as
* condition that the property pointed by the lens is undefined. The value will
* be created before calling the action function provided by the user.
*
* If the task operation is `delete`, the task constructor will add as a condition
* that the property pointed by the lens is not undefined. The value will be deleted
* automatically after the action functions provided by the user.
*
* @param view - A view to the state pointed by the lens.
* @param ctx - The calling context for the task. It includes any lens properties and the system object
*/
readonly action: (view: View<TState, TPath, TOp>, ctx: ContextWithSystem<TState, TPath, TOp>) => Promise<void>;
/**
* The task function binds the task to a context
*
* Grounding the task converts the specification into an instruction, that is,
* something that can be evaluated by the planner. It does this by
* contextualizing the task for a specific target.
*
* ActionTask --- ground --> Action
* MethodTask --- ground --> Method
*/
(ctx: TaskArgs<TState, TPath, TOp>): Action<TState, TPath, TOp>;
}
/**
* A method task defines a composite operation that can be chosen by a planner.
*
* A method can guide the planner towards following a certain path by providing
* a sequence of operations to be used if conditions are suitable.
*
* Method tasks can be created via the Task constructor as follows
*
* ```ts
* const plusTwo = Task.from<number>({
* // We want this method to be chosen only if the difference between the current
* // state and the target is bigger than one
* condition: (state, { target }) => target - state > 1,
* // The method returns two instances of the plusOne task bound to the given target
* method: (_, { target }) => [plusOne({ target }), plusOne({ target })],
* description: '+2',
* });
* ```
*
* Methods can also reference methods for hierarchical task definition.
*/
export interface MethodTask<TState = unknown, TPath extends PathType = Root, TOp extends AnyOp = Update> extends TaskSpec<TState, TPath, TOp> {
/**
* The method expansion. The default is 'detect', meaning the planner will try to execute
* the instructions returned by the method in parallel and go back to sequential expansion
* if conflicts are detected. If sequential is chosen, the planner will jump straight to
* sequential expansion. This is a workaround to handle those cases where detection may fail
* due to instructios that read data handled by a parallel branch.
*/
readonly expansion: MethodExpansion;
/**
* The method to be called when the task is executed.
*
* The method should return a list of instructions to be used by the planner.
* It should never modify the state object.
*
* if the method returns an empty list, this means there are no
* further instructions that can be applied
*/
readonly method: (s: Lens<TState, TPath>, c: ContextWithSystem<TState, TPath, TOp>) => Instruction<TState> | Array<Instruction<TState>>;
/**
* The task function grounds the task
*
* Grounding the task converts the specification into an instruction, that is,
* something that can be evaluated by the planner. It does this by
* contextualizing the task for a specific target.
*
* ActionTask --- ground --> Action
* MethodTask --- ground --> Method
*/
(ctx: TaskArgs<TState, TPath, TOp>): Method<TState, TPath, TOp>;
}
/**
* Check if a task is a method
*/
declare function isMethodTask<TState = unknown, TPath extends PathType = Root, TOp extends TaskOp = 'update'>(t: Task<TState, TPath, TOp>): t is MethodTask<TState, TPath, TOp>;
/**
* Check if a task or an instruction is an action
*/
declare function isActionTask<TState = unknown, TPath extends PathType = Root, TOp extends TaskOp = 'update'>(t: Task<TState, TPath, TOp>): t is ActionTask<TState, TPath, TOp>;
/**
* A task is base unit of knowledge of an autonomous agent.
*/
export type Task<TState = unknown, TPath extends PathType = Root, TOp extends TaskOp = 'update'> = ActionTask<TState, TPath, TOp> | MethodTask<TState, TPath, TOp>;
/**
* Construct a new task (action or method) from a task specification
*/
declare function from<TState = unknown, TPath extends PathType = Root, TOp extends AnyOp = Update>(t: MethodTaskProps<TState, TPath, TOp>): MethodTask<TState, TPath, TOp>;
declare function from<TState = unknown, TPath extends PathType = Root, TOp extends AnyOp = Update>(t: ActionTaskProps<TState, TPath, TOp>): ActionTask<TState, TPath, TOp>;
interface TaskBuilder<TState> {
from<TPath extends PathType = Root, TOp extends TaskOp = 'update'>(t: ActionTaskProps<TState, TPath, TOp>): ActionTask<TState, TPath, TOp>;
from<TPath extends PathType = Root, TOp extends TaskOp = 'update'>(t: MethodTaskProps<TState, TPath, TOp>): MethodTask<TState, TPath, TOp>;
}
declare function of<TState>(): TaskBuilder<TState>;
export declare const Task: {
of: typeof of;
from: typeof from;
isMethod: typeof isMethodTask;
isAction: typeof isActionTask;
};
export {};