AgentWorkshop.Workshop (AgentWorkshop v0.3.0)

Copy Markdown View Source

Multi-agent IEx API for coordinating LLM CLI sessions.

A naming and coordination layer over backend-specific session processes. Each agent is a supervised process addressed by name. All functions are designed to be called interactively after import AgentWorkshop.Workshop.

Quick start

# 1. Set backend and config (do this first -- affects all new agents)
configure(backend: MyApp.ClaudeBackend,
  backend_config: %{working_dir: "~/projects/myapp"},
  context: "Elixir project. Run mix test before committing.")

# 2. Create agents with roles
agent(:impl, "You write clean, tested code.", max_turns: 15)
agent(:reviewer, "You review code. Do not modify files.",
  model: "opus", allowed_tools: ["Read", "Bash"])

# 3. Talk to them
ask(:impl, "Implement caching for the user lookup")
pipe(:impl, :reviewer, "Review for correctness")

# 4. Check on things
status()       # dashboard table
total_cost()   # how much you've spent

Sync vs async

Two ways to send messages:

  • ask/2 -- blocks until the agent responds, prints the result
  • cast/2 -- returns immediately, agent works in the background

Use await/1 or await_all/0 to collect async results. Use status/0 to see who's busy.

Coordination

  • pipe/3 -- wait for one agent, forward its result to another
  • fan/2 -- send the same message to multiple agents in parallel

ask/2 and pipe/3 return the agent name on success, so they compose with Elixir's pipe operator:

ask(:impl, "implement caching")
|> pipe(:reviewer, "review for edge cases")
|> pipe(:tests, "write tests for this")

Inspecting agents

  • status/0 -- dashboard of all agents
  • info/1 -- detailed map for one agent (model, session_id, cost, ...)
  • result/1 -- last response text (or pass :full for the full result map)
  • history/1 -- print the full conversation
  • cost/0 -- itemized costs; cost/1 -- single agent; total_cost/0 -- sum

Lifecycle

  • load/0 -- load .workshop.exs (auto-loaded on startup if present)
  • load/1 -- load a specific setup file
  • reset/1 -- clear an agent's conversation (keeps role and config)
  • dismiss/1 -- remove an agent entirely
  • reset_all/0 -- stop everything, clear config

Architecture

The OTP Application owns the full lifecycle: ETS tables, registries, supervisors, and the EventLog. Workshop is the public IEx facade -- it reads and writes shared state but does not start or supervise any infrastructure. Call configure/1 to set your backend; everything else is already running.

Application supervisor (started automatically)
  |-- TableManager (owns ETS tables)
  |-- :persistent_term (global config -- no process needed)
  |-- Registry (PubSub, Scheduler, BoardWorker)
  |-- EventLog
  |-- DynamicSupervisor (agent sessions)
  `-- Task.Supervisor (async cast tasks)

Workshop (this module -- IEx helpers, no supervision duties)

Summary

Functions

List all agent names, sorted alphabetically.

Send a message and wait for the response. Prints the result when done.

Wait for an async agent to finish. Prints the result when done.

Wait for all busy agents to finish. Prints each result as it arrives.

Show the work board. Optionally filter by status or type.

Create a board worker — an agent that polls the board for work.

Show budget info. With no argument, shows global. With an agent name, shows per-agent.

Cancel a recurring schedule for an agent.

Cancel a work item, removing it from active consideration.

Send a message asynchronously. Returns :ok immediately.

Claim a work item for an agent.

Clear all recorded events from the event log.

Mark a work item as done. Unblocks any dependents.

Set global defaults inherited by all agents. Call this first.

Show itemized costs for all agents, then print the total.

Show cost for a single agent. Returns the cost as a float.

Start the LiveView dashboard.

Remove an agent entirely. Stops its process and discards all state.

Show recent events.

Run an agent prompt on a recurring interval.

Mark a work item as failed with an optional error message.

Send the same message to multiple agents in parallel (all via cast/2).

Get a value from the shared store.

Get a value from the shared store, returning default if the key is missing.

Get uncommitted changes as a diff string.

Get a structured summary of the current git state.

Print the conversation history for an agent.

Get detailed info about an agent as a map.

Load a Workshop setup file (.exs).

Set the log level for Workshop messages.

Start the Workshop MCP server.

Wait for from to finish, then send its last result to to.

Define a reusable agent profile (template).

List available profiles.

Put a value in the shared store.

Reset an agent's conversation. Keeps the role, model, and all config.

Stop all agents and clear global config. The nuclear option.

Reset all budget tracking.

Reset a workflow -- removes its work items and resets to :defined.

Get the last response from an agent.

Run a workflow by expanding its stages into work board items.

List active schedules.

Mark a work item as in progress.

Print a status dashboard showing all agents.

Stop the Workshop and clean up all state. Used for teardown in tests.

Print all entries in the shared store to the console.

Delete a key from the shared store.

List all keys currently in the shared store.

Get the total cost across all agents as a single number.

Stop printing live events.

Start printing live events to the console.

Add a work item to the board.

Create a work item from an agent's last result.

Get details of a specific work item.

Print a summary of all active board workers.

Define a declarative workflow -- a sequence of stages that expand into work board items with dependencies.

Show workflow progress -- each stage with its current status.

List all defined workflows.

Functions

agent(name, role_or_opts \\ nil, opts \\ [])

@spec agent(atom(), String.t() | nil, keyword()) :: :ok

Start a named agent.

The role string is agent-specific context that composes with the global context from configure/1. If both are set, the effective system prompt is context <> "\n\n" <> role. Agent-specific options override globals.

Calling agent/3 with an existing name replaces that agent.

Examples

# Full agent with role and options
agent(:impl, "You write clean, well-tested code.", max_turns: 15)

# Read-only reviewer using opus
agent(:reviewer, "You review code. Do not modify files.",
  model: "opus", allowed_tools: ["Read", "Bash"])

# Minimal -- inherits everything from configure()
agent(:scratch)

# Options without a role
agent(:fast, model: "haiku", max_turns: 3)

Permissions

Workshop defaults to permission_mode: :auto so agents can work non-interactively. Override per-agent or globally via configure/1:

# Global: all agents use bypass
configure(permission_mode: :bypass_permissions)

# Per-agent: restrict the reviewer to default
agent(:reviewer, "Review only.", permission_mode: :default)

Valid modes: :default, :accept_edits, :bypass_permissions, :dont_ask, :plan, :auto.

Options

Core: :model, :max_turns, :permission_mode, :max_budget_usd, :effort

Permissions: :allowed_tools, :disallowed_tools, :dangerously_skip_permissions

Backend pass-through (Claude CLI): :worktree, :mcp_config, :add_dir, :settings, :append_system_prompt, :no_session_persistence

Workshop-specific: :workshop_tools, :skill, :max_cost_usd, :timeout

agents()

@spec agents() :: [atom()]

List all agent names, sorted alphabetically.

Example

agents()    # => [:impl, :reviewer, :tests]

ask(name, prompt)

@spec ask(atom(), String.t()) :: atom() | {:error, term()}

Send a message and wait for the response. Prints the result when done.

This is the primary way to talk to an agent. Each call continues the agent's conversation (the session ID is threaded automatically).

If the agent has a pending async task from cast/2, waits for it first.

Returns the agent name on success, enabling |> chaining with pipe/3:

ask(:impl, "implement caching")
|> pipe(:reviewer, "review for edge cases")

Examples

ask(:impl, "What modules are in this project?")
ask(:impl, "Now add tests for the retry module")   # continues conversation
ask(:impl, "What files did you change?")            # still the same session

await(name, timeout \\ :infinity)

@spec await(atom(), timeout()) :: :ok | {:error, term()}

Wait for an async agent to finish. Prints the result when done.

If the agent is already idle, prints a note and returns :ok. Pass a timeout in milliseconds to avoid blocking forever.

Examples

cast(:impl, "Work on this")
# ... later ...
await(:impl)              # block until done
await(:impl, 30_000)      # give up after 30 seconds

await_all(timeout \\ :infinity)

@spec await_all(timeout()) :: :ok

Wait for all busy agents to finish. Prints each result as it arrives.

Example

cast(:impl, "Implement feature")
cast(:tests, "Write tests")
await_all()    # blocks until both are done

board(filters \\ [])

@spec board(keyword()) :: [AgentWorkshop.Work.t()]

Show the work board. Optionally filter by status or type.

Examples

board()                    # full board
board(status: :ready)      # items ready to pick up
board(type: :code)         # only code tasks

board_worker(name, work_type, opts \\ [])

@spec board_worker(atom(), atom(), keyword()) :: :ok

Create a board worker — an agent that polls the board for work.

Combines a profile, an agent, and a poll loop. The worker claims ready items matching its work type, executes them, and marks them complete.

Options

  • :profile - (required) profile name to create the agent from
  • :interval - poll interval in ms (default: 60_000 / 1 min)
  • :worktree - when true, passes worktree: true to the agent's query opts, enabling the Claude CLI --worktree flag for isolated parallel execution

Examples

board_worker(:coder_1, :code, profile: :coder, interval: :timer.minutes(1))
board_worker(:reviewer_1, :review, profile: :reviewer, interval: :timer.minutes(2))

# Post work — workers pick it up automatically
work(:feature, "Implement feature X", type: :code, spec: "...")

budget(target \\ :global)

@spec budget(atom() | :global) :: map()

Show budget info. With no argument, shows global. With an agent name, shows per-agent.

Examples

budget()          # global budget info
budget(:impl)     # per-agent budget

cancel(name)

@spec cancel(atom()) :: :ok

Cancel a recurring schedule for an agent.

Stops the agent from executing its scheduled prompt. The agent itself remains available for manual interaction. No-op if the agent has no active schedule.

Example

iex> every(:monitor, "Check CI status", interval: :timer.minutes(5))
iex> cancel(:monitor)
:ok

cancel_work(id)

@spec cancel_work(atom()) :: :ok | {:error, term()}

Cancel a work item, removing it from active consideration.

Transitions the item to :cancelled status. Unlike fail_work/2, this indicates the work was intentionally abandoned rather than attempted and failed.

Example

iex> work(:cache, "Implement LRU cache", type: :code)
iex> cancel_work(:cache)
:ok

cast(name, prompt)

@spec cast(atom(), String.t()) :: :ok | {:error, :queue_full}

Send a message asynchronously. Returns :ok immediately.

The agent works in the background while you do other things. Use status/0 to check progress, await/1 to collect the result.

If the agent is already working, the message is queued (up to 5 deep). Queued messages are processed in order after the current task finishes.

Examples

cast(:impl, "Implement the caching layer from issue #12")
cast(:tests, "Add property-based tests for lib/myapp/encoder.ex")

# Queue a follow-up while :impl is still working
cast(:impl, "Also add the cache invalidation")

# Go think about something else...
status()        # see who's done (shows queue depth)
await(:impl)    # block until current + queued work finishes
await_all()     # wait for everyone

claim_work(id, agent_name)

@spec claim_work(atom(), atom()) :: :ok | {:error, term()}

Claim a work item for an agent.

Example

claim(:cache, :impl)

clear_events()

@spec clear_events() :: :ok

Clear all recorded events from the event log.

After calling this, events/0 will show no entries until new events occur.

Example

iex> clear_events()
:ok

complete_work(id, result \\ nil)

@spec complete_work(atom(), String.t() | nil) :: :ok | {:error, term()}

Mark a work item as done. Unblocks any dependents.

Examples

complete_work(:cache)
complete_work(:cache, "Implemented with GenServer-backed LRU")

configure(opts \\ [])

@spec configure(keyword()) :: :ok

Set global defaults inherited by all agents. Call this first.

Changes affect new agents only; existing agents keep their config.

Examples

# Minimal
configure(backend: MyApp.ClaudeBackend, backend_config: %{working_dir: "."})

# Full setup
configure(
  backend: MyApp.ClaudeBackend,
  backend_config: %{working_dir: "~/projects/myapp"},
  model: "sonnet",
  max_turns: 10,
  context: """
  Elixir project using Phoenix 1.7.
  Run mix test before considering any task complete.
  Use conventional commits.
  """
)

Permissions

Workshop defaults to permission_mode: :auto so agents can work non-interactively (the CLI default of :default would hang waiting for approval that never comes). Override here to change for all agents:

configure(permission_mode: :bypass_permissions)

Options

Backend options:

  • :backend -- module implementing AgentWorkshop.Backend (required on first call)
  • :backend_config -- backend-specific configuration (passed to start_session/2)

Query options: :model, :max_turns, :permission_mode, :max_budget_usd, :effort

Special:

  • :context -- global system prompt prepended to every agent's role. The effective system prompt for each agent is context <> "\n\n" <> role.
  • :persistence -- true (use .agent_workshop/ in cwd), a path string, or false to disable. Saves work board and store to disk on change, reloads on next start.

cost()

@spec cost() :: float()

Show itemized costs for all agents, then print the total.

Example

cost()
# :impl: $0.12 (3 turns)
# :reviewer: $0.24 (1 turn)
# Total: $0.36

cost(name)

@spec cost(atom()) :: float()

Show cost for a single agent. Returns the cost as a float.

Example

cost(:impl)    # => :impl: $0.12 across 3 turns

dashboard(opts \\ [])

@spec dashboard(keyword()) :: :ok | {:error, term()}

Start the LiveView dashboard.

Options

  • :port - HTTP port (default 4223)

Examples

dashboard()              # start on port 4223
dashboard(port: 8080)    # custom port

dismiss(name)

@spec dismiss(atom()) :: :ok

Remove an agent entirely. Stops its process and discards all state.

No-op if the agent doesn't exist. Use reset/1 instead if you want to keep the agent but clear its conversation.

events(opts \\ [])

@spec events(keyword()) :: [map()]

Show recent events.

Options

  • :last - number of events to show (default 20)

Examples

events()            # last 20
events(last: 50)    # last 50

every(name, prompt, opts)

@spec every(atom(), String.t(), keyword()) :: :ok

Run an agent prompt on a recurring interval.

Examples

every(:monitor, "Check CI status", interval: :timer.minutes(5))
every(:sweeper, "Clean up stale branches", interval: :timer.hours(1))

fail_work(id, error \\ nil)

@spec fail_work(atom(), String.t() | nil) :: :ok | {:error, term()}

Mark a work item as failed with an optional error message.

Transitions the item to :failed status. Unlike cancel_work/1, this indicates the work was attempted but did not succeed.

Examples

iex> fail_work(:cache, "Tests failed: 3 assertions")
:ok

iex> fail_work(:cache)
:ok

fan(message, agent_names)

@spec fan(String.t(), [atom()]) :: :ok

Send the same message to multiple agents in parallel (all via cast/2).

Good for getting multiple perspectives on the same question.

Example

fan("What issues do you see in lib/myapp/retry.ex?", [:impl, :reviewer])
# both agents work concurrently
await_all()
result(:impl)       # impl's take
result(:reviewer)   # reviewer's take

from_profile(profile_name, agent_name, overrides \\ [])

@spec from_profile(atom(), atom(), keyword()) :: :ok

Create an agent from a profile.

Examples

profile(:coder, "You write code.", max_turns: 15)
from_profile(:coder, :coder_bug_42)
from_profile(:coder, :coder_feature_7, max_turns: 25)  # override opts

get(key)

Get a value from the shared store.

Returns nil if the key does not exist. See get/2 to supply a default.

Examples

iex> put(:spec, "LRU cache with TTL")
iex> get(:spec)
"LRU cache with TTL"

iex> get(:nonexistent)
nil

get(key, default)

Get a value from the shared store, returning default if the key is missing.

Examples

iex> get(:missing, "fallback")
"fallback"

iex> put(:count, 5)
iex> get(:count, 0)
5

git_diff(opts \\ [])

@spec git_diff(keyword()) :: {:ok, String.t()} | {:error, term()}

Get uncommitted changes as a diff string.

git_summary(opts \\ [])

@spec git_summary(keyword()) :: {:ok, map()} | {:error, term()}

Get a structured summary of the current git state.

Returns branch, status, recent commits, and file changes. Requires the optional git dependency.

Example

git_summary()
# => %{branch: "main", dirty: true, staged: 1, ...}

history(name, opts \\ [])

@spec history(
  atom(),
  keyword()
) :: :ok

Print the conversation history for an agent.

Shows each turn with its cost and result text. Use :last to limit output for long conversations.

Examples

history(:impl)           # full conversation
history(:impl, last: 3)  # only the last 3 turns

info(name)

@spec info(atom()) :: map()

Get detailed info about an agent as a map.

Returns model, session_id, role, status, cost, turns, and config.

Example

info(:impl)
# => %{name: :impl, status: :idle, model: "sonnet", session_id: "abc-123",
#       cost: 0.04, turns: 1, role: "You write clean code."}

load(path \\ ".workshop.exs")

@spec load(String.t()) :: :ok | {:error, :not_found}

Load a Workshop setup file (.exs).

The file is evaluated with import AgentWorkshop.Workshop in scope, so it can call configure/1, agent/3, etc. directly.

With no argument, looks for .workshop.exs in the current directory.

Example file (.workshop.exs)

configure(backend: MyApp.ClaudeBackend,
  backend_config: %{working_dir: "."},
  model: "sonnet",
  context: "Elixir project. Run mix test before committing.")

agent(:impl, "You write clean code.", max_turns: 15)
agent(:reviewer, "Review only.", model: "opus",
  allowed_tools: ["Read", "Bash"])

Examples

load()                        # loads .workshop.exs
load("setups/review.exs")     # loads a specific file

log_level(level)

@spec log_level(Logger.level()) :: :ok

Set the log level for Workshop messages.

Useful for quieting debug noise during interactive sessions.

Examples

log_level(:warning)    # quiet -- only warnings and errors
log_level(:info)       # default
log_level(:debug)      # verbose -- see send/receive for every message

mcp_server(opts \\ [])

@spec mcp_server(keyword() | boolean()) :: :ok | {:error, term()}

Start the Workshop MCP server.

Exposes all Workshop functions as MCP tools over HTTP. Requires anubis_mcp, bandit, and plug deps.

Can also be triggered via configure(mcp: [port: 4222]).

Examples

mcp_server()                # start on default port 4222
mcp_server(port: 8080)      # custom port

pipe(from, to, message \\ nil)

@spec pipe(atom() | {:error, term()}, atom(), String.t() | nil) ::
  atom() | {:error, term()}

Wait for from to finish, then send its last result to to.

The message argument frames what to should do with the result. The forwarded text is appended after your message. Without a message, a default framing is used.

This is synchronous -- it blocks until both agents are done.

Returns the target agent name on success, so pipes chain:

ask(:impl, "implement caching")
|> pipe(:reviewer, "review for edge cases")
|> pipe(:tests, "write tests for this")

Examples

# Implement, then review
ask(:impl, "Implement caching for user lookup")
pipe(:impl, :reviewer, "Review for cache invalidation edge cases")

# Or after an async cast
cast(:impl, "Implement the retry logic")
# ... do other stuff ...
pipe(:impl, :tests)   # awaits :impl, sends result to :tests

profile(name, role \\ nil, opts \\ [])

@spec profile(atom(), String.t() | nil, keyword()) :: :ok

Define a reusable agent profile (template).

Examples

profile(:coder, "You write clean code.", max_turns: 15)
profile(:reviewer, "Review only.", model: "opus", allowed_tools: ["Read", "Bash"])

profiles()

@spec profiles() :: [{atom(), map()}]

List available profiles.

put(key, value)

Put a value in the shared store.

Keys can be any term. Use tuples for namespacing.

Examples

put(:spec, "LRU cache with TTL support")
put({:impl, :notes}, "Chose GenServer over Agent")

reset(name)

@spec reset(atom()) :: :ok

Reset an agent's conversation. Keeps the role, model, and all config.

The agent gets a fresh session -- like calling agent/3 again with the same arguments. Cost tracking is also reset to zero.

Example

ask(:impl, "Write some code")
# ... not happy with the direction ...
reset(:impl)
ask(:impl, "Try a different approach")   # fresh conversation

reset_all()

@spec reset_all() :: :ok

Stop all agents and clear global config. The nuclear option.

After this, you'll need to call configure/1 and agent/3 again.

reset_budget()

@spec reset_budget() :: :ok

Reset all budget tracking.

Clears both global and per-agent budget limits and spent totals. After calling this, agents can spend without budget restrictions until new limits are set via configure(max_cost_usd: ...) or per-agent agent(:name, "role", max_cost_usd: ...).

Example

iex> reset_budget()
:ok

reset_workflow(name)

@spec reset_workflow(atom()) :: :ok | {:error, term()}

Reset a workflow -- removes its work items and resets to :defined.

Use this to re-run a workflow or clear a failed run.

Example

reset_workflow(:feature)
run_workflow(:feature)

result(name, mode \\ :text)

@spec result(atom(), :text | :full) :: String.t() | map() | nil

Get the last response from an agent.

Returns the response text by default. Pass :full to get the complete result map (includes cost, session_id, duration, etc.).

Examples

result(:impl)          # => "Here is the implementation..."
result(:impl, :full)   # => %{result: "...", cost_usd: 0.04, ...}

run_workflow(name)

@spec run_workflow(atom()) :: :ok | {:error, term()}

Run a workflow by expanding its stages into work board items.

Board workers will automatically pick up and execute the stages in dependency order.

Example

run_workflow(:feature)

schedules()

@spec schedules() :: [map()]

List active schedules.

start_work(id)

@spec start_work(atom()) :: :ok | {:error, term()}

Mark a work item as in progress.

Transitions a work item from :ready (or :claimed) to :in_progress. Typically called after an agent has claimed the item with claim_work/2.

Example

iex> work(:cache, "Implement LRU cache", type: :code)
iex> claim_work(:cache, :impl)
iex> start_work(:cache)
:ok

status()

@spec status() :: [map()]

Print a status dashboard showing all agents.

Example output

 agent     | status  | task                                 | cost  | turns
-----------+---------+--------------------------------------+-------+------
 :impl     | working | Implement the caching layer from ... | $0.08 | 3
 :reviewer | idle    |                                      | $0.00 | 0
 :tests    | idle    |                                      | $0.04 | 2

stop()

@spec stop() :: :ok

Stop the Workshop and clean up all state. Used for teardown in tests.

Dismisses all agents, clears all ETS tables. The Application supervisor remains running -- call configure/1 to set up again.

store()

Print all entries in the shared store to the console.

Each entry is displayed as key: value. Prints "Store is empty." when the store has no entries.

Example

iex> put(:spec, "LRU cache")
iex> put(:status, :draft)
iex> store()
  :spec: "LRU cache"
  :status: :draft
:ok

store_delete(key)

Delete a key from the shared store.

No-op if the key does not exist.

Examples

iex> put(:scratch, "temp value")
iex> store_delete(:scratch)
:ok
iex> get(:scratch)
nil

store_keys()

List all keys currently in the shared store.

Returns an empty list when the store has no entries.

Examples

iex> put(:spec, "cache design")
iex> put(:notes, "use GenServer")
iex> store_keys()
[:notes, :spec]

iex> store_keys()
[]

total_cost()

@spec total_cost() :: float()

Get the total cost across all agents as a single number.

unwatch()

@spec unwatch() :: :ok

Stop printing live events.

watch()

@spec watch() :: :ok

Start printing live events to the console.

Shows agent creation/dismissal, ask/cast completions with cost, work board changes, errors, and more.

Example

watch()
cast(:impl, "do something")
# [agent] impl created
# [cast] impl complete ($0.04) — Here is the implementation...

work(id, title, opts \\ [])

@spec work(atom(), String.t(), keyword()) :: :ok

Add a work item to the board.

Options

  • :type - work type (:code, :review, :test, :docs, :deploy, :triage, :custom)
  • :spec - detailed specification
  • :priority - 1 (highest) to 5 (lowest), default 3
  • :depends_on - list of work item IDs that must complete first

Examples

work(:cache, "Implement LRU cache",
  type: :code, priority: 1,
  spec: "LRU cache with configurable max size and TTL per entry")

work(:cache_review, "Review cache implementation",
  type: :review, depends_on: [:cache])

work_from_result(agent_name, work_id, opts \\ [])

@spec work_from_result(atom(), atom(), keyword()) :: :ok

Create a work item from an agent's last result.

Uses result(agent_name) as the spec for the new work item. Useful for turning an agent's analysis, plan, or review into actionable board items.

Examples

ask(:arch, "Review the architecture of this project")
work_from_result(:arch, :refactor, type: :code, title: "Address architecture findings")

ask(:planner, "Break this feature into tasks")
work_from_result(:planner, :feature_impl, type: :code)

work_item(id)

@spec work_item(atom()) :: AgentWorkshop.Work.t() | nil

Get details of a specific work item.

Returns the full work item struct including status, type, priority, dependencies, claimed agent, and result. Returns nil if the item does not exist.

Examples

iex> work(:cache, "Implement LRU cache", type: :code, priority: 1)
iex> item = work_item(:cache)
iex> item.title
"Implement LRU cache"
iex> item.status
:ready

iex> work_item(:nonexistent)
nil

workers()

@spec workers() :: [map()]

Print a summary of all active board workers.

Board workers are agents that automatically poll the work board for items matching their work type, claim them, execute them, and mark them complete. Shows each worker's type, completed count, and current item (if any). Prints "No board workers." when none are running.

Example

iex> workers()
  :coder_1: code, 3 completed
  :reviewer_1: review, 1 completed (working on :cache_review)
:ok

workflow(name, stages)

@spec workflow(atom(), list()) :: :ok | {:error, term()}

Define a declarative workflow -- a sequence of stages that expand into work board items with dependencies.

Each stage is a tuple: {stage_name, agent, title} or {stage_name, agent, title, opts}.

Stage options

  • :from - data source: a file path (string), a stage name (atom), or a list of stage names (fan-in)
  • :type - work board type (default: :custom)
  • :priority - priority 1-5 (default: 3)

Example

workflow(:feature, [
  {:plan, :planner, "Break this into tasks", from: "specs/feature.md"},
  {:implement, :coder, "Implement the plan", from: :plan},
  {:test, :tester, "Write tests", from: :implement},
  {:review, :reviewer, "Review everything", from: [:implement, :test]}
])

workflow_status(name)

@spec workflow_status(atom()) ::
  {AgentWorkshop.Workflow.t(), [AgentWorkshop.Work.t() | nil]}
  | {:error, term()}

Show workflow progress -- each stage with its current status.

Example

workflow_status(:feature)

workflows()

@spec workflows() :: [AgentWorkshop.Workflow.t()]

List all defined workflows.

Example

workflows()