NAME

Chorus::Expert - Orchestrator for one or more Chorus::Engine agents working on a shared task.

VERSION

2.01

DESCRIPTION

Chorus::Expert does three things:

  1. Registers one or more Chorus::Engine agents.

  2. Provides every agent with a shared Chorus::Frame called BOARD, used for inter-agent communication and to carry the input to the pipeline.

  3. Runs a do/until loop over the agents until one of them signals SOLVED or FAILED.

SYNOPSIS

use Chorus::Expert;
use Chorus::Engine;

my $agent1 = Chorus::Engine->new(_IDENT => 'Enrich');
$agent1->addrule( ... );

my $agent2 = Chorus::Engine->new(_IDENT => 'Validate');
$agent2->addrule( ... );

my $xprt = Chorus::Expert->new();
$xprt->register($agent1, $agent2);

my $ok = $xprt->process($input);   # 1 = solved, undef = failed

METHODS

new

Creates a new Chorus::Expert instance with an empty agent list and a fresh shared BOARD frame.

my $xprt = Chorus::Expert->new();

Note -- arguments passed to new() are currently ignored. To override _MAX_ITER, assign directly after construction:

my $xprt = Chorus::Expert->new();
$xprt->{_MAX_ITER} = 50_000;   # default is 10,000

register

Registers one or more agents. Each agent receives:

  • BOARD -- the shared frame, accessible as $agent->BOARD.

  • EXPERT -- a back-reference to this expert instance.

$xprt->register($agent1, $agent2, $agent3);

Agents are stored in registration order, which determines the order in which process() calls their loop() method.

The termination agent (the one that calls solved()) should be registered last.

debug

Enables verbose output to STDERR for the main process loop.

$xprt->debug(1);   # enable
$xprt->debug(0);   # disable

process

Runs the pipeline.

my $ok = $xprt->process();           # no input
my $ok = $xprt->process($something); # $something available as $agent->BOARD->INPUT

The main loop iterates over all registered agents in order, calling loop() on each one, until BOARD-{SOLVED}> or BOARD-{FAILED}> is set. It respects _REPLAY and _REPLAY_ALL signals from the agents.

An agent tagged with _LOCK_UNTIL_STABLE is skipped when any earlier agent in the current iteration has already succeeded (_SUCCES is true). This allows priority-based sequencing without explicit coupling.

If _MAX_ITER full iterations complete without termination, a warning is emitted and process() returns undef.

Returns 1 if SOLVED, undef if FAILED or if _MAX_ITER is exceeded.

_LOCK_UNTIL_STABLE

An optional flag set directly on an agent frame:

$agent->{_LOCK_UNTIL_STABLE} = 'Y';

When _LOCK_UNTIL_STABLE is set on agent N, process() skips that agent in the current iteration if any earlier agent has already succeeded (_SUCCES is true on that agent). This implements priority-based sequencing: earlier agents are given another full pass before the locked agent is allowed to run.

Typical use: a "global cleanup" or "conformity check" agent that should only run once all upstream agents have stabilised for the current cycle.

_REPLAY and _REPLAY_ALL

These flags are set by the corresponding engine methods and are handled transparently by process().

_REPLAY

Set by $agent->replay(). process() re-runs loop() on the same agent immediately (inner do/while loop), without advancing to the next agent.

_REPLAY_ALL

Set by $agent->replay_all(). process() restarts the outer agent loop from the beginning — all agents are iterated again from agent 1.

Both flags are automatically deleted by process() before the re-run, so they fire exactly once per call.

BOARD

Every agent registered with register() receives a reference to a shared Chorus::Frame called BOARD. Access it from inside any rule:

my $board = $agent->BOARD;

Reserved slots

SOLVED

Set to 'Y' by $agent->solved(). Causes process() to return 1 immediately after the current loop() call finishes. Deleted by process() before returning.

FAILED

Set to 'Y' by $agent->failed(). Causes process() to return undef immediately. Deleted by process() before returning.

INPUT

Set by process($input) before the main loop starts. Holds the raw input value passed to the pipeline.

my $input = $agent->BOARD->INPUT;

Custom slots for inter-agent communication

Any other slot can be freely written and read by agents to exchange state that does not belong to individual domain frames:

# In agent 1's _APPLY:
$agent->BOARD->set('phase', 'enrichment');

# In agent 2's _APPLY:
my $phase = $agent->BOARD->phase;    # 'enrichment'

Use BOARD for pipeline-level flags and counters. Domain knowledge (facts about specific objects) belongs on Chorus::Frame instances, not on the BOARD.

AUTHOR

Christophe Ivorra

BUGS

new() ignores its arguments. Parameters passed to Chorus::Expert-new()> (including _MAX_ITER) are silently discarded. Always set _MAX_ITER by direct assignment immediately after construction:

my $xprt = Chorus::Expert->new();
$xprt->{_MAX_ITER} = 50_000;   # mandatory for long pipelines

The default is 10,000 iterations. For a pipeline of N frames × M total rules, a safe heuristic is N × M × safety_margin (typically ×10).

Please report other bugs via the CPAN request tracker: http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Chorus

SUPPORT

perldoc Chorus::Expert

SEE ALSO

Chorus::Frame, Chorus::Engine

LICENSE AND COPYRIGHT

Copyright 2013 Christophe Ivorra.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.