The way event handlers receive parameters looks strange at first, but it's a combination of a few common Perl features.

The @_ parameter list.

Perl functions receive their parameter values in a special list, @_. This is faster and shorter than passing parameters by hash or hashref.

POE passes a lot of standard parameters to each event handler. They tell about useful things common to every event.

They make @_ a pain to use directly. Event handlers rarely have to use every parameter passed to them, so programs often skipped some by assigning them to undef.

sub old_event_handler {
  my ($kernel, $heap, undef, undef, @args) = @_;

  # ...
}

When array slices came along, it became more convenient to pull parameters out of @_ by their offsets.

sub old_event_handler {
  my $kernel = $_[0];
  my $heap   = $_[1];
  my @args   = @_[3 .. $#_];

  # ...
}

It's even more convenient to combine them into a single array slice.

sub old_event_handler {
  my ($kernel, $heap, @args) = @_[0, 1, 3 .. $#_];

  # ...
}

Using magic numbers like this leads to errors that are difficult to see. For example, @args really starts at $_[4].

Constants for parameter offsets.

That leads us to the next common Perl feature. Symbolic constants. POE::Session exports them so people don't need to remember where @args and things are in @_.

The constants also let POE change its list of standard parameters without breaking existing programs. Hardcoded parameter offsets would cause all sorts of trouble.

Here's a more readable (and correct) version of a previous event handler.

sub contemporary_event_handler {
  my $kernel = $_[KERNEL];
  my $heap   = $_[HEAP];
  my @args   = @_[ARG0 .. $#_];

  # ...
}

Here they are combined into one array slice. It's still correct.

sub contemporary_event_handler {
  my ($kernel, $heap, @args) = @_[KERNEL, HEAP, ARG0 .. $#_];

  # ...
}

It's also possible to use $_[KERNEL] and friends directly. And the code would be correct even then. :)

Summary

Benefits.

Passing values in @_ is faster than using a hash or hash reference.

The @_ offset constants supply names for parameters: $_[HEAP] is easier to read than $_[17]. It's also less to type than %param = @_; $param{heap}.

POE can add new parameters or rearrange existing ones without breaking existing programs. The constants it exports will always point to the correct parameters.

Programs will break at compile time if POE removes obsolete parameters. This is much better than breaking at runtime when $params{foo} is no longer defined.

Unlike typos in hash keys, POE's parameter constants are spell-checked at compile time. While tied hashes could provide runtime parameter checking, it would be even slower than plain hashes.

Non-standard event parameters, ones supplied with post() and the like, are at the end of the parameter list. @_[ARG0..$#_] is guaranteed to be all of them, in order.

Drawbacks.

The calling convention looks weird.

ARG0..$#_ are not descriptive of the additional parameters POE provides with some events.

ARG0..$#_ are still dependent on position.