Feature

Primitives

Gimbal supplies runtime primitives, not named tactics. Eight of them cover most multi-agent work.

Signatures and edge cases live in Godoc: go doc -all github.com/tylergannon/gimbal. This page says what each primitive is for.

Scopes

Scope(ctx, name, body) runs body in a named child scope. When body returns, the scope’s services stop, its sessions close through their adapters, and its context is cancelled. A scope is the unit of ownership, of context, and of the map: every sheet in the console is one.

err := gimbal.Scope(ctx, "research", func(ctx context.Context) error {
	researcher := gimbal.NewSession(ctx, roleResearch, env.WorkDir)
	// researcher is closed when this function returns
	return nil
})

Scoped context delivery

Set and SetJSON record a value in the current scope, once. Generate appends the scope’s rendered context to the prompt, so every agent turn in that scope, and in its children, receives the facts without the workflow threading them through. Prompts stay constants; data stays data.

gimbal.Set(ctx, "promise", promise)
gimbal.Set(ctx, "repository", env.WorkDir)
// every turn below this line is sent both values

A key is set once per scope; revision is shadowing in a child scope. WithScopeTemplate reshapes the rendered context for one call, with a template that is itself a constant.

an outcome scope, selected
An outcome scope selected in the current console, listing context set here and inherited from outer scopes.
An outcome scope in the console: one value set here and four inherited from outer scopes.

Promise loop

PromiseLoop(ctx, name, goal, planner) opens planner-directed dispatch toward a goal. The planner keeps a revisable backlog and selects each task from the evidence so far. You range over loop.Tasks; each task arrives with its own scope, a description, a definition of done, and optional validation. Check loop.Err() afterward.

loop := gimbal.PromiseLoop(ctx, "implementation", promise, planner)
for taskCtx, task := range loop.Tasks {
	// do the task, record what happened in taskCtx, break when done
}
if err := loop.Err(); err != nil {
	return err
}

What the worker and validator record in the task’s scope is what the planner sees when it picks the next task. An operator can message the loop while it runs; the message waits for the planner’s next decision.

Supervision

WithSupervisor(session, instruction) attaches a second agent to a running turn. Jev screens available worker thinking for likely concerns; a selected supervisor then reviews the evidence and can steer an objection into the live turn. The runtime retains one logical supervisor for the worker and joins supervision before Generate returns. A Jev screen and a supervisor’s advice are not independent validation of the completed outcome.

report, err := worker.Generate[gimbal.Text](taskCtx, implementationPrompt,
	gimbal.WithSupervisor(coach, scopePrompt, gimbal.WithInterval(2*time.Minute)))

Supervision is advisory. It can catch scope drift while it is cheap to correct, and it never gates completion. It attaches to a planner’s turns too.

a watcher, selected
A watcher selected on the current console map, with its role and model visible.

Statically mapped graph

go generate reads the workflow function’s source into a typed workflow.Graph literal: sessions, agent calls with their prompts, loops, groups, conditions, commands, and supervisors, each with its file and line. The literal registers itself by name.

At run time, events are joined to that graph by name. The console therefore shows the whole workflow before the first turn, marks what has not started, and links every node to the line that made it. There is no graph file to write and none to drift.

Observable concurrency

Group is errgroup-style concurrency whose branches are scopes: each Go gets a name, a lifetime, and a place on the map, and Wait joins them. Iterate yields each item of a finite slice with a fresh scope, in order, closing that item’s sessions before the next.

group := gimbal.Group(ctx, "drafts")
group.Go("draft", func(ctx context.Context) error { /* ... */ return nil })
group.Go("draft", func(ctx context.Context) error { /* ... */ return nil })
return group.Wait()

Evidence and services

Check runs a command and records its exit code, stdout, and stderr in the current scope for the next agent turn to assess. A nonzero exit is evidence, not an error. RunCommand returns the same values to Go instead.

Service starts a foreground command, such as a dev server, and makes the current scope own it. It stops when the scope ends. If it exits early, the scope fails.

gimbal.Service(ctx, "dev-server", env.WorkDir, "pnpm dev")
gimbal.Check(ctx, "tests", env.WorkDir, "go", "test", "./...")

Interviews

Interview(ctx, name, session, purpose) lets an agent question a person. Each question is a typed session turn; the run blocks until the console delivers an answer, and an empty answer ends the interview. The transcript comes back as a value.

Next Workflow roles