Skip to content

AgentScript Composition Examples

AgentScript

function makeTodos()
goal: "Create a composable todo service with retry and timeout capabilities"
backstory: "Used to fetch todos from an external API with error handling"
// Define the client with composition
const client = HttpClient
|> filterStatusOk
|> prependUrl("https://jsonplaceholder.typicode.com")
// Define list function with retry and concurrency
function list(ids: Iterable<number>): Array<unknown>
goal: "Fetch multiple todos by their IDs concurrently"
requires:
ids != null: "IDs must be provided"
concurrency: "inherit"
return:
forEach(ids, (id) => findById(id) |> retry(3))
errors:
HttpClientError: "Failed to fetch todos"
TimeoutException: "Request timed out"
// Define findById function with timeout
function findById(id: number): unknown
goal: "Fetch a single todo by ID"
requires:
id > 0: "ID must be positive"
return:
client.get(`/todos/${id}`)
|> getJson
|> scoped
|> timeout("1 second")
errors:
HttpClientError: "Failed to fetch todo"
TimeoutException: "Request timed out"
return { list, findById }
tests:
success:
// Test successful todo fetching
list([1, 2]) -> [{ id: 1, ... }, { id: 2, ... }]
errors:
// Test timeout behavior
findById(999) -> TimeoutException
performance:
// Test concurrent fetching performance
list([1..10]) -> expect < 2s

TypeScript (Effect)

const makeTodos = Effect.gen(function*() {
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.filterStatusOk,
HttpClient.mapRequest(HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"))
)
// interuption and concurrency can be configured with
// composition
const list = (
ids: Iterable<number>
): Effect.Effect<
Array<unknown>,
HttpClientError.HttpClientError | Cause.TimeoutException
> =>
Effect.forEach(ids, (id) => findById(id).pipe(Effect.retry({ times: 3 })), {
concurrency: "inherit"
})
const findById = (
id: number
): Effect.Effect<
unknown,
HttpClientError.HttpClientError | Cause.TimeoutException
> =>
client.get(`/todos/${id}`).pipe(
Effect.andThen((response) => response.json),
Effect.scoped,
Effect.timeout("1 second")
)
return { list, findById } as const
})
class Todos extends Effect.Tag("Todos")<Todos, Effect.Effect.Success<typeof makeTodos>>() {
static Live = Layer.effect(Todos, makeTodos).pipe(
Layer.provide(FetchHttpClient.layer)
)
}
const main = Todos.list(Array.range(1, 10)).pipe(
Effect.withConcurrency(3),
Effect.timeout("10 seconds"),
Effect.andThen((todos) => Console.log("Got todos", todos))
)

TypeScript (Plain)

// configuration has to be added to the type signature
const getTodos = (
ids: Iterable<number>,
concurrency: number,
signal?: AbortSignal,
): Promise<any[]> =>
forEach(ids, concurrency, (id) =>
withRetries(3, () => getTodo(id, signal)),
)
const getTodo = (
id: number,
signal?: AbortSignal,
): Promise<any> =>
abortable(
(signal) =>
jsonOk(() =>
fetch(
`https://jsonplaceholder.typicode.com/todos/${id}`,
{ signal },
),
),
mergeAbortSignal(
AbortSignal.timeout(1000),
signal,
),
)
async function main() {
const ids = Array.from(
{ length: 10 },
(_, i) => i + 1,
)
const todos = await getTodos(
ids,
3,
AbortSignal.timeout(10000)
)
console.log("Got todos", todos)
}
// ... helper functions ...

The AgentScript version demonstrates several key features:

  1. Composition using the pipe operator (|>) for transforming the HTTP client and request processing
  2. Embedded contracts with requires blocks for input validation
  3. Concurrency control through the concurrency directive
  4. Error handling with explicit error types
  5. Timeouts integrated directly into the function definitions
  6. Built-in testing for success cases, errors, and performance
  7. Natural language context through goal and backstory fields

The AgentScript syntax is more concise while maintaining all the functionality of the Effect version, with added benefits of inline testing and contract programming. The transpiler would generate the appropriate TypeScript code with all the necessary Effect.js constructs.