TodoMiddleware

org.llm4s.agent.harness.TodoMiddleware
See theTodoMiddleware companion object
@Experimental
final class TodoMiddleware(val maxTodos: Int, val maxTodoChars: Int, val id: MiddlewareId) extends AgentMiddleware

A plan for the agent: a write_todos tool that replaces the thread's todo list, and a prompt section that shows the list to the model on every model step (design ยง4.30).

  • The tool takes the whole list - content, status (pending, in_progress, done) and, to keep an item, its id - and stores it in TodoMiddleware.key. An item without an id keeps the id of an unclaimed todo with the same content, or takes a fresh one; an id is never reused within a thread. A list of more than maxTodos items, an item whose content is empty or over maxTodoChars code points, an id given twice, or an id the list does not have, is refused as a tool error the model sees. The tool locks todos, so two calls in one model message run in turn, the second seeing the first's list.
  • The sections: todos-guide, cacheable, on how to use the tool; and todos, the current plan, while it is not empty. Both are bounded by the limits whatever the stored list holds: at most maxTodos todos are shown, each cut to maxTodoChars code points.
  • Events: each change emits a durable HarnessEvents.TodosUpdated (ids and statuses) and a live HarnessEvents.TodosProgress (the whole list).

The plan guides the model only: it never routes, filters tools, or changes a permission. write_todos is a tool like any other, so a PermissionMiddleware decides it by its default resource, invoke write_todos. An output Block restores the list to its value before the turn.

The key, and the lock schedule the tool's lock needs, are registered only by an agent family with a TodoMiddleware: adding one to an existing agent changes its graph's structure.

Value parameters

maxTodoChars

the most code points one todo's content may hold; at least 1

maxTodos

the most todos one list may hold; at least 1

Attributes

Companion
object
Graph
Supertypes
class Object
trait Matchable
class Any

Members list

Value members

Concrete methods

override def promptSections(context: ModelContext): Result[Vector[PromptSection]]

Content for the system prompt of each model request: collected before every model step from the committed state, in stack order, and never stored. Each middleware bounds its own; the agent bounds them all together (maxPromptSectionTokens). Left fails the run.

Content for the system prompt of each model request: collected before every model step from the committed state, in stack order, and never stored. Each middleware bounds its own; the agent bounds them all together (maxPromptSectionTokens). Left fails the run.

Attributes

Definition Classes
override def tools: Vector[AgentTool[_]]

Tools this middleware contributes; they join the loop's tool set and are validated like any other.

Tools this middleware contributes; they join the loop's tool set and are validated like any other.

Attributes

Definition Classes

Inherited methods

def afterAgent(answer: String, context: ModelContext): Result[BoundaryResult]

Sees the run's final answer: returns it, possibly changed, with an update to this middleware's writes, or Left to block the run.

Sees the run's final answer: returns it, possibly changed, with an update to this middleware's writes, or Left to block the run.

Attributes

Inherited from:
AgentMiddleware
def beforeAgent(input: String, context: ModelContext): Result[BoundaryResult]

Sees the run's input before anything else runs: returns it, possibly changed, with an update to this middleware's writes that the turn's first model call sees, or Left to block the run.

Sees the run's input before anything else runs: returns it, possibly changed, with an update to this middleware's writes that the turn's first model call sees, or Left to block the run.

Attributes

Inherited from:
AgentMiddleware

Middleware this one must run inside of (be wrapped by); each must be registered in the same stack.

Middleware this one must run inside of (be wrapped by); each must be registered in the same stack.

Attributes

Inherited from:
AgentMiddleware

Middleware this one must run outside of (wrap); each must be registered in the same stack.

Middleware this one must run outside of (wrap); each must be registered in the same stack.

Attributes

Inherited from:
AgentMiddleware

Wraps one model call. A wrapper may rewrite the request (inject a note, filter tools), call next more than once (retry, fallback), transform its response, add an update to its writes (ModelResponse.addUpdate), or return Left, which fails the run. Only an AgentMiddleware.Asking wrapper suspends, by asking its question. Every call next makes is counted in the thread's usage, whichever response the wrapper returns.

Wraps one model call. A wrapper may rewrite the request (inject a note, filter tools), call next more than once (retry, fallback), transform its response, add an update to its writes (ModelResponse.addUpdate), or return Left, which fails the run. Only an AgentMiddleware.Asking wrapper suspends, by asking its question. Every call next makes is counted in the thread's usage, whichever response the wrapper returns.

Filtering ModelRequest.tools shapes what the model is offered and is not a permission control: a tool the model calls anyway still runs through wrapToolCall, where denial belongs.

Attributes

Inherited from:
AgentMiddleware
def wrapToolCall(request: ToolCallRequest, context: ToolContext)(next: () => ToolOutcome): ToolOutcome

Wraps one tool call, after its arguments were validated. A wrapper denies with ToolOutcome.Error("Denied: ..."), asks for approval with NeedsApproval(reason) (unless context.approved), fails the run with Fatal, and short-circuits by not calling next. It may call next more than once (retry) and transform the outcome next returns.

Wraps one tool call, after its arguments were validated. A wrapper denies with ToolOutcome.Error("Denied: ..."), asks for approval with NeedsApproval(reason) (unless context.approved), fails the run with Fatal, and short-circuits by not calling next. It may call next more than once (retry) and transform the outcome next returns.

Attributes

Inherited from:
AgentMiddleware
def writes: Set[StateKey[_, _]]

The state keys this middleware's hooks may write: an update from beforeAgent, wrapModelCall or afterAgent, or one wrapToolCall adds to a Success. A hook's write to any other key fails the run.

The state keys this middleware's hooks may write: an update from beforeAgent, wrapModelCall or afterAgent, or one wrapToolCall adds to a Success. A hook's write to any other key fails the run.

Attributes

Inherited from:
AgentMiddleware

Concrete fields

This middleware's identifier, unique in its stack; must match [a-zA-Z0-9_-]{1,64}.

This middleware's identifier, unique in its stack; must match [a-zA-Z0-9_-]{1,64}.

Attributes

val maxTodoChars: Int
val maxTodos: Int