go/docs/messaging.md
Snider 2d52b83f60 docs: rewrite documentation suite against AX spec
Codex-authored docs covering primitives, commands, messaging,
lifecycle, subsystems, and getting started — all using the current
DTO/Options/Result API with concrete usage examples.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-21 10:05:04 +00:00

3.7 KiB

title description
Messaging ACTION, QUERY, QUERYALL, PERFORM, and async task flow.

Messaging

CoreGO uses one message bus for broadcasts, lookups, and work dispatch.

Message Types

type Message any
type Query any
type Task any

Your own structs define the protocol.

type repositoryIndexed struct {
	Name string
}

type repositoryCountQuery struct{}

type syncRepositoryTask struct {
	Name string
}

ACTION

ACTION is a broadcast.

c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
	switch m := msg.(type) {
	case repositoryIndexed:
		core.Info("repository indexed", "name", m.Name)
		return core.Result{OK: true}
	}
	return core.Result{OK: true}
})

r := c.ACTION(repositoryIndexed{Name: "core-go"})

Behavior

  • all registered action handlers are called in their current registration order
  • if a handler returns OK:false, dispatch stops and that Result is returned
  • if no handler fails, ACTION returns Result{OK:true}

QUERY

QUERY is first-match request-response.

c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
	switch q.(type) {
	case repositoryCountQuery:
		return core.Result{Value: 42, OK: true}
	}
	return core.Result{}
})

r := c.QUERY(repositoryCountQuery{})

Behavior

  • handlers run until one returns OK:true
  • the first successful result wins
  • if nothing handles the query, CoreGO returns an empty Result

QUERYALL

QUERYALL collects every successful non-nil response.

r := c.QUERYALL(repositoryCountQuery{})
results := r.Value.([]any)

Behavior

  • every query handler is called
  • only OK:true results with non-nil Value are collected
  • the call itself returns OK:true even when the result list is empty

PERFORM

PERFORM dispatches a task to the first handler that accepts it.

c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
	switch task := t.(type) {
	case syncRepositoryTask:
		return core.Result{Value: "synced " + task.Name, OK: true}
	}
	return core.Result{}
})

r := c.PERFORM(syncRepositoryTask{Name: "core-go"})

Behavior

  • handlers run until one returns OK:true
  • the first successful result wins
  • if nothing handles the task, CoreGO returns an empty Result

PerformAsync

PerformAsync runs a task in a background goroutine and returns a generated task identifier.

r := c.PerformAsync(syncRepositoryTask{Name: "core-go"})
taskID := r.Value.(string)

Generated Events

Async execution emits three action messages:

Message When
ActionTaskStarted just before background execution begins
ActionTaskProgress whenever Progress is called
ActionTaskCompleted after the task finishes or panics

Example listener:

c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
	switch m := msg.(type) {
	case core.ActionTaskCompleted:
		core.Info("task completed", "task", m.TaskIdentifier, "err", m.Error)
	}
	return core.Result{OK: true}
})

Progress Updates

c.Progress(taskID, 0.5, "indexing commits", syncRepositoryTask{Name: "core-go"})

That broadcasts ActionTaskProgress.

TaskWithIdentifier

Tasks that implement TaskWithIdentifier receive the generated ID before dispatch.

type trackedTask struct {
	ID   string
	Name string
}

func (t *trackedTask) SetTaskIdentifier(id string) { t.ID = id }
func (t *trackedTask) GetTaskIdentifier() string   { return t.ID }

Shutdown Interaction

When shutdown has started, PerformAsync returns an empty Result instead of scheduling more work.

This is why ServiceShutdown can safely drain the outstanding background tasks before stopping services.