Engine¶
Agent orchestration, execution loops, task decomposition, routing, and parallel execution.
Agent Engine¶
agent_engine
¶
Agent engine -- top-level orchestrator.
Ties together prompt construction, execution context, execution loop,
tool invocation, and budget tracking into a single run() entry point.
PersonalityTrimNotifier
¶
PersonalityTrimNotifier = Callable[[PersonalityTrimPayload], Awaitable[None]]
Async callback invoked when an agent's personality section is trimmed.
PersonalityTrimPayload
¶
Bases: TypedDict
Structured payload forwarded to :data:PersonalityTrimNotifier callbacks.
AgentEngine
¶
AgentEngine(
*,
provider,
execution_loop=None,
tool_registry=None,
cost_tracker=None,
recovery_strategy=_DEFAULT_RECOVERY_STRATEGY,
shutdown_checker=None,
error_taxonomy_config=None,
classification_sinks=(),
evolution_service=None,
policy_engine=None,
budget_enforcer=None,
security_config=None,
security_config_provider=None,
approval_store=None,
review_gate=None,
review_pipeline=None,
artifact_probe=None,
clarification_enabled=True,
scoping_enabled=True,
parked_context_repo=None,
cost_forecast_repo=None,
approval_gate=None,
mcp_self_consumer=None,
task_engine=None,
checkpoint_repo=None,
heartbeat_repo=None,
checkpoint_config=None,
coordinator=None,
stagnation_detector=None,
step_classifier=None,
steering_inbox=None,
auto_loop_config=None,
openhands_loop_config=None,
openhands_loop_deps=None,
compaction_callback=None,
provider_registry=None,
provider_configs=None,
model_resolver=None,
tool_invocation_tracker=None,
memory_injection_strategy_provider=None,
ontology_injection_strategy=None,
procedural_memory_config=None,
capture_strategy=None,
memory_backend=None,
distillation_capture_enabled=False,
config_resolver=None,
personality_trim_notifier=None,
coordination_metrics_collector=None,
audit_log=None,
project_repo=None,
agent_middleware_chain=None,
event_reader=None,
event_stream_hub=None,
interrupt_store=None,
approval_interrupt_timeout_seconds=None,
external_api_runtime=None,
forge_tools_runtime=None,
chat_tools_runtime=None,
brain_tool_factory_provider=None,
knowledge_tool_factory_provider=None,
docs_tool_factory_provider=None,
research_tool_factory_provider=None,
structure_map_tool_factory_provider=None,
capability=None,
agent_registry=None,
flight_recorder_sink=None,
agent_state_repository_provider=None,
clock=None,
)
Bases: AgentEngineChatActionMixin, AgentEngineContextMixin, AgentEngineErrorsMixin, AgentEngineFactoriesMixin, AgentEngineLoopFactoriesMixin, AgentEnginePostExecMixin, AgentEngineRecoveryMixin, AgentEngineResumeMixin, AgentEngineRunMixin, AgentEngineStakesErrorsMixin
Top-level orchestrator for agent execution.
Source code in src/synthorg/engine/agent_engine.py
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 | |
has_mcp_self_consumer
property
¶
Whether trust-scoped SynthOrg MCP tools are wired into agents.
Gates the direct-MCP conversational actor: with no self-consumer
an acting agent has no MCP tools, so /meta/chat/act 503s.
set_autonomy_resolution
¶
Bind the one resolver every dispatch path asks for autonomy.
Called by the boot path once the worker execution service exists. Until it is bound, a caller that supplies no autonomy runs degraded, which is what a coordinated wave did permanently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resolution
|
AutonomyResolution
|
The single owner of "what autonomy governs this
run", asked whenever :meth: |
required |
Source code in src/synthorg/engine/agent_engine.py
coordinate
async
¶
Delegate to the multi-agent coordinator.
Returns:
| Name | Type | Description |
|---|---|---|
The |
CoordinationResultWithAttribution
|
class: |
CoordinationResultWithAttribution
|
coordinator's |
Raises:
| Type | Description |
|---|---|
ExecutionStateError
|
If no coordinator was configured. |
Source code in src/synthorg/engine/agent_engine.py
project_background_failure
async
¶
Project a terminal RUN_ERROR for a run that failed before the loop.
A backgrounded conversational run can fail in the pipeline spine (project resolution, decomposition, assignment) before the execution loop ever runs to publish its own terminal frame, leaving a dashboard subscribed to the task's SSE stream hung on "Working". Called by the worker's background wrapper on such a failure so the operator sees the run end. No-op when no event-stream hub is wired.
Source code in src/synthorg/engine/agent_engine.py
run
async
¶
run(
*,
identity,
task,
completion_config=None,
max_turns=None,
memory_messages=(),
timeout_seconds=None,
effective_autonomy=None,
resume_execution_id=None,
)
Execute an agent on a task.
Returns:
| Name | Type | Description |
|---|---|---|
The |
AgentRunResult
|
class: |
AgentRunResult
|
tracking, post-execution transitions, and recovery / |
|
AgentRunResult
|
checkpoint resume applied. |
Raises:
| Type | Description |
|---|---|
MemoryError
|
Re-raised after logging from the explicit log-and-raise critical-error path (the engine surfaces non-recoverable interpreter signals to the worker). |
RecursionError
|
Same path as |
ProjectNotFoundError
|
From project validation when the task references a missing project. |
Source code in src/synthorg/engine/agent_engine.py
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 | |
Execution Loop Protocol¶
loop_protocol
¶
Execution loop protocol and supporting models.
Defines the ExecutionLoop protocol that the agent engine calls to
run a task, along with ExecutionResult, TerminationReason, and the
BudgetChecker and ShutdownChecker type aliases. TurnRecord is
imported from synthorg.execution.turn (the engine-free leaf) and
re-exported here for callers.
BudgetChecker
module-attribute
¶
BudgetChecker = Callable[[AgentContext], bool]
Callback that returns True when the budget is exhausted.
ShutdownChecker
module-attribute
¶
Callback that returns True when a graceful shutdown has been requested.
TaskCancellationChecker
module-attribute
¶
Async callback that returns True when the running task has been cancelled
or superseded externally (e.g. by a steering supersession or a cockpit kill).
Consulted at the top-of-turn safe boundary so the agent halts cleanly instead of running an obsolete task to completion. The task's terminal DB status is the durable cross-process signal (the operator cancels in the API process; the agent runs in the worker process).
TurnObserver
module-attribute
¶
TurnObserver = Callable[[TurnProgress], Awaitable[None]]
Async progress callback invoked with a :class:TurnProgress. Two calling
conventions share this shape:
- ReAct loop: fires after each continuing turn with the tool names that turn requested; the terminal turn (which ends the loop) returns before the hook, so no observation marks it.
- OpenHands loop: fires as each event arrives off the harness stream, with a one-element tuple naming the tool the event used, or empty when the event named none.
Purely observational: it never affects control flow, and an observer raising
must not corrupt the run. Used to surface incremental progress on a streamed
chat action and to keep the live-activity state current; None disables
it.
TerminationReason
¶
Bases: StrEnum
Why the execution loop terminated.
NO_OP
class-attribute
instance-attribute
¶
A task-backed run that finished without calling any tool, so it
produced no artifacts. A silent no-op success is a failure: the run
is routed to FAILED unless an explicit no-op justification was
recorded (see engine.task_sync).
ExecutionResult
pydantic-model
¶
Bases: BaseModel
Result returned by an execution loop.
Attributes:
| Name | Type | Description |
|---|---|---|
context |
AgentContext
|
Final agent context after execution. |
termination_reason |
TerminationReason
|
Why the loop stopped. |
turns |
tuple[TurnRecord, ...]
|
Per-turn metadata records. |
total_tool_calls |
int
|
Total tool calls across all turns (computed). |
error_message |
str | None
|
Error description when termination_reason is ERROR. |
metadata |
dict[str, object]
|
Forward-compatible dict for future loop types.
Note: |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
context(AgentContext) -
termination_reason(TerminationReason) -
turns(tuple[TurnRecord, ...]) -
quality_signals(tuple[StepQualitySignal, ...]) -
error_message(str | None) -
error_type(str | None) -
metadata(dict[str, object])
Validators:
-
_deep_copy_metadata -
_validate_error_message
quality_signals
pydantic-field
¶
Per-step quality signals produced during the loop
TurnProgress
¶
Bases: NamedTuple
What a loop reports about one turn while the run is still going.
The context is carried because everything an operator wants to know
about a run in flight (how many turns, how much spend, when it last did
anything) lives on it and nowhere else until the run finishes, so a
report without it can say only that a turn happened.
The context carries the run's whole conversation, which is
agent-authored and holds tool results from outside the system. It is
fenced where it is STORED, not here, so an observer that puts any of it
into a prompt (a narration call, a summary, an LLM-scored dashboard)
owes it a wrap_untrusted at that boundary, exactly as the review
gate's own inputs do. The observers shipped today read scalars only
(turn count, spend, timestamps, tool names), so none of them needs one.
Attributes:
| Name | Type | Description |
|---|---|---|
turn_number |
int
|
1-based index of the turn just observed. |
tool_names |
tuple[str, ...]
|
Short labels for what that turn did. |
context |
AgentContext
|
The run's context as it stands after the turn. Untrusted content: see above before putting any of it in a prompt. |
ExecutionLoop
¶
Bases: Protocol
Protocol for agent execution loops.
The agent engine calls execute to run a task through the loop.
Implementations decide the control flow but all return an
ExecutionResult with a TerminationReason.
execute
async
¶
execute(
*,
context,
provider,
tool_invoker=None,
budget_checker=None,
shutdown_checker=None,
completion_config=None,
task_cancellation_checker=None,
turn_observer=None,
streaming_enabled=False,
)
Run the execution loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
AgentContext
|
Initial agent context with conversation and identity. |
required |
provider
|
CompletionProvider
|
LLM completion provider. |
required |
tool_invoker
|
ToolInvokerProtocol | None
|
Optional tool invoker for tool execution. |
None
|
budget_checker
|
BudgetChecker | None
|
Optional callback; returns |
None
|
shutdown_checker
|
ShutdownChecker | None
|
Optional callback; returns |
None
|
completion_config
|
CompletionConfig | None
|
Optional per-execution override for temperature/max_tokens (defaults to identity's model config). |
None
|
task_cancellation_checker
|
TaskCancellationChecker | None
|
Optional async callback; returns
|
None
|
turn_observer
|
TurnObserver | None
|
Optional per-run progress callback; used to
project live execution progress onto the AG-UI stream and to
keep the live-activity state current. Awaited once per turn
with a single :class: |
None
|
streaming_enabled
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
ExecutionResult
|
Execution result with final context and termination reason. |
Source code in src/synthorg/engine/loop_protocol.py
get_loop_type
¶
Return the loop type identifier (e.g. "react").
Returns:
| Type | Description |
|---|---|
str
|
The loop's type discriminator string. |
make_budget_checker
¶
Create a budget checker if the task carries either bound.
The returned callable returns True when accumulated cost meets the
task's money limit OR accumulated tokens meet its token ceiling. The
token half matters because money measures nothing against a provider
that bills by flat subscription, where the cost bound can never fire.
Returns:
| Name | Type | Description |
|---|---|---|
A |
BudgetChecker | None
|
class: |
BudgetChecker | None
|
|
Source code in src/synthorg/engine/loop_protocol.py
ReAct Loop¶
react_loop
¶
ReAct execution loop -- think, act, observe.
Implements the ExecutionLoop protocol using the ReAct pattern:
check shutdown -> check budget -> call LLM -> record turn ->
check for LLM errors -> update context -> handle completion or
(check shutdown -> execute tools) -> repeat.
ReactLoop
¶
ReactLoop(
checkpoint_callback=None,
*,
approval_gate=None,
stagnation_detector=None,
compaction_callback=None,
steering_inbox=None,
step_classifier=None,
turn_observer=None,
clock=None,
)
ReAct execution loop: reason, act, observe.
The loop checks for shutdown, checks the budget, calls the LLM, checks for termination conditions, executes any requested tools, feeds results back, and repeats until the LLM signals completion, the turn limit is reached, the budget is exhausted, a shutdown is requested, or an error occurs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
checkpoint_callback
|
CheckpointCallback | None
|
Optional async callback invoked after each completed turn; the callback itself decides whether to persist. |
None
|
approval_gate
|
ApprovalGate | None
|
Optional gate that checks for pending escalations
after tool execution and parks the agent when approval is
required. |
None
|
stagnation_detector
|
StagnationDetector | None
|
Optional detector that checks for
repetitive tool-call patterns and intervenes with
corrective prompts or early termination. |
None
|
compaction_callback
|
CompactionCallback | None
|
Optional async callback invoked at turn
boundaries to compress older conversation turns when the
context fill level is high. |
None
|
step_classifier
|
StepQualityClassifier | None
|
Optional step-quality classifier. ReAct is
turn-based with no step boundary, so a single whole-run
signal is emitted at natural termination; |
None
|
steering_inbox
|
SteeringInbox | None
|
Optional inbox polled at turn boundaries for
mid-run steering messages; |
None
|
turn_observer
|
TurnObserver | None
|
Optional async callback invoked after each
continuing turn with the tools it requested; |
None
|
Source code in src/synthorg/engine/react_loop.py
get_loop_type
¶
execute
async
¶
execute(
*,
context,
provider,
tool_invoker=None,
budget_checker=None,
shutdown_checker=None,
completion_config=None,
task_cancellation_checker=None,
turn_observer=None,
streaming_enabled=False,
)
Run the ReAct loop until termination.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
AgentContext
|
Initial agent context with conversation. |
required |
provider
|
CompletionProvider
|
LLM completion provider. |
required |
tool_invoker
|
ToolInvokerProtocol | None
|
Optional tool invoker for tool execution. |
None
|
budget_checker
|
BudgetChecker | None
|
Optional budget exhaustion callback. |
None
|
shutdown_checker
|
ShutdownChecker | None
|
Optional callback; returns |
None
|
completion_config
|
CompletionConfig | None
|
Optional per-execution config override. |
None
|
task_cancellation_checker
|
TaskCancellationChecker | None
|
Optional async callback; returns
|
None
|
turn_observer
|
TurnObserver | None
|
Optional per-run progress callback; when given, it takes precedence over the construction-time observer so a per-execution stream (e.g. AG-UI task progress) can be wired without rebuilding the shared loop. |
None
|
streaming_enabled
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
ExecutionResult
|
Execution result with final context and termination info. |
Raises:
| Type | Description |
|---|---|
MemoryError
|
Re-raised unconditionally (non-recoverable). |
RecursionError
|
Re-raised unconditionally (non-recoverable). |
Source code in src/synthorg/engine/react_loop.py
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | |
OpenHands Loop¶
loop
¶
The OpenHands adapter: the bundled ExecutionLoop.
Drives an OpenHands conversation through the injected factory, maps its
event stream to TurnRecords, and consults the budget / shutdown /
cancellation checkers at each turn boundary (after recording a turn event),
stopping the run (via the sink's False return) when any trips.
Completion honours the same NO_OP / artifacts_expected rule as the
native loops. All logic is independent of the SDK, which lives behind the
conversation factory.
OpenHandsLoop
¶
Runs a task through the OpenHands coding agent as an ExecutionLoop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
OpenHandsLoopConfig
|
Frozen, settings-driven behaviour. |
required |
deps
|
OpenHandsLoopDeps
|
Injected collaborators (conversation factory, signer, URLs, clock). |
required |
Source code in src/synthorg/engine/openhands/loop.py
get_loop_type
¶
execute
async
¶
execute(
*,
context,
provider,
tool_invoker=None,
budget_checker=None,
shutdown_checker=None,
completion_config=None,
task_cancellation_checker=None,
turn_observer=None,
streaming_enabled=False,
)
Run the task through OpenHands and return an ExecutionResult.
provider / tool_invoker / streaming_enabled are unused:
OpenHands runs its own LLM (through the gateway, which owns its own
streaming + cost) and its own tools (native + credentialed-MCP).
completion_config is not: its sampling half travels into the run
spec, because the harness choosing its own temperature while the native
loop is handed one is a difference between the loops that nobody chose.
Returns:
| Type | Description |
|---|---|
ExecutionResult
|
The terminal :class: |
Source code in src/synthorg/engine/openhands/loop.py
Execution Context¶
context
¶
Agent execution context.
Wraps an AgentIdentity (frozen config) with evolving runtime state
(conversation, cost, turn count, task execution) using
model_copy(update=...) for cheap, immutable state transitions.
AgentContext
pydantic-model
¶
Bases: BaseModel
Frozen runtime context for agent execution.
All state evolution happens via model_copy(update=...).
The context tracks the conversation, accumulated cost, and
optionally a TaskExecution for task-bound agent runs.
Attributes:
| Name | Type | Description |
|---|---|---|
execution_id |
NotBlankStr
|
Unique identifier for this execution run. |
identity |
AgentIdentity
|
Frozen agent identity configuration. |
task_execution |
TaskExecution | None
|
Current task execution state (if any). |
conversation |
tuple[ChatMessage, ...]
|
Accumulated chat messages. |
accumulated_cost |
TokenUsage
|
Running token usage and cost totals. |
turn_count |
int
|
Number of LLM turns completed. |
max_turns |
int
|
Hard limit on turns before the engine stops. |
started_at |
AwareDatetime
|
When this execution began. |
context_fill_tokens |
int
|
Estimated tokens currently in the full context (system prompt + conversation + tool defs). |
context_capacity_tokens |
int | None
|
Model's max context window tokens,
or |
compression_metadata |
CompressionMetadata | None
|
Metadata about conversation compression, set when compaction has occurred. |
async_task_state |
AsyncTaskStateChannel
|
Dedicated state channel for tracked async
tasks. Separate from |
loaded_tools |
frozenset[str]
|
Tool names with L2 bodies active in context. |
loaded_resources |
frozenset[tuple[str, str]]
|
|
tool_load_order |
tuple[str, ...]
|
Insertion-ordered tool names for FIFO auto-unload under budget pressure. |
Config:
frozen:Trueallow_inf_nan:False
Fields:
-
execution_id(NotBlankStr) -
identity(AgentIdentity) -
task_execution(TaskExecution | None) -
conversation(tuple[ChatMessage, ...]) -
accumulated_cost(TokenUsage) -
turn_count(int) -
max_turns(int) -
turn_extensions_remaining(int) -
turn_extensions_granted(int) -
max_unresolved_tool_turns(int) -
cost_ceiling(float | None) -
token_ceiling(int | None) -
started_at(AwareDatetime) -
context_fill_tokens(int) -
context_capacity_tokens(int | None) -
compression_metadata(CompressionMetadata | None) -
async_task_state(AsyncTaskStateChannel) -
loaded_tools(frozenset[str]) -
loaded_resources(frozenset[tuple[str, str]]) -
tool_load_order(tuple[str, ...]) -
adopted_steering_ids(frozenset[NotBlankStr])
Validators:
-
_validate_disclosure_consistency
accumulated_cost
pydantic-field
¶
accumulated_cost = ZERO_TOKEN_USAGE
Running cost totals across all turns
turn_extensions_remaining
pydantic-field
¶
Further turn budgets this run may grant itself
turn_extensions_granted
pydantic-field
¶
Further turn budgets this run has already taken
max_unresolved_tool_turns
pydantic-field
¶
Consecutive turns resolving to no tool before the run stops
cost_ceiling
pydantic-field
¶
Optional per-session cost ceiling; the chat-action loop halts once accumulated cost meets it. Carried on the context so the bound survives a park/resume round-trip.
token_ceiling
pydantic-field
¶
Optional per-session token ceiling, the companion to cost_ceiling: money measures nothing against a provider that bills by flat subscription, where the cost bound can never fire, and tokens are counted on every provider. Carried on the context for the same reason, so the bound survives a park/resume round-trip.
context_capacity_tokens
pydantic-field
¶
Model's max context window tokens
compression_metadata
pydantic-field
¶
Compression metadata when compacted
async_task_state
pydantic-field
¶
Async task tracking state (survives compaction and context reset)
loaded_resources
pydantic-field
¶
(tool_name, resource_id) pairs with L3 active
adopted_steering_ids
pydantic-field
¶
Steering directive entry ids already adopted by this run
context_fill_percent
property
¶
Percentage of context window currently filled.
Returns None when context capacity is unknown.
has_turns_remaining
property
¶
Whether the agent has turns remaining before hitting max_turns.
from_identity
classmethod
¶
from_identity(
identity,
*,
task=None,
max_turns=DEFAULT_MAX_TURNS,
turn_extensions=0,
max_unresolved_tool_turns=DEFAULT_MAX_UNRESOLVED_TOOL_TURNS,
context_capacity_tokens=None,
cost_ceiling=None,
token_ceiling=None,
)
Create a fresh execution context from an agent identity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identity
|
AgentIdentity
|
The frozen agent identity card. |
required |
task
|
Task | None
|
Optional task to bind to this execution. |
None
|
max_turns
|
int
|
Maximum number of LLM turns allowed. |
DEFAULT_MAX_TURNS
|
turn_extensions
|
int
|
How many further turn budgets the run may grant itself before parking for a human. Zero, the default, ends the run at the first ceiling: extensions are task-run policy, and a bounded session (decomposition, a review panellist, a chat action) sets its own cap and never asked to exceed it. Only the task-run path passes the operator's configured value. |
0
|
max_unresolved_tool_turns
|
int
|
How many consecutive turns the run may spend asking only for tools that are not registered before it is stopped. Zero never stops it early. |
DEFAULT_MAX_UNRESOLVED_TOOL_TURNS
|
context_capacity_tokens
|
int | None
|
Model's max context window
tokens, or |
None
|
cost_ceiling
|
float | None
|
Optional per-session cost ceiling. Passed through
the constructor (not a post-hoc |
None
|
token_ceiling
|
int | None
|
Optional per-session token ceiling, the bound that still applies where money measures nothing. |
None
|
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Source code in src/synthorg/engine/context.py
with_message
¶
Append a single message to the conversation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
ChatMessage
|
The chat message to append. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Source code in src/synthorg/engine/context.py
with_steering_adopted
¶
Mark a mid-flight steering directive as adopted by this run.
Adoption is context-local and travels with the checkpointed context, so every concurrent agent on a project adopts a directive independently and a resumed run never re-adopts one it already consumed. Idempotent: re-adopting is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directive_id
|
NotBlankStr
|
The project-brain entry id of the directive. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
AgentContext
|
instance when it was already adopted. |
Source code in src/synthorg/engine/context.py
with_turn_completed
¶
Record a completed turn.
Increments turn count, appends the response message, and accumulates cost on both the context and the task execution (if present).
The turn count and the cost advance whether or not there is a message: a wordless turn still happened and was still billed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
usage
|
TokenUsage
|
Token usage from this turn's LLM call. |
required |
response_msg
|
ChatMessage | None
|
The assistant's response message, or |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Raises:
| Type | Description |
|---|---|
MaxTurnsExceededError
|
If |
Source code in src/synthorg/engine/context.py
with_context_fill
¶
Update the estimated context fill level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fill_tokens
|
int
|
New estimated fill in tokens. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/synthorg/engine/context.py
with_async_task_state
¶
Replace the async task state channel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
AsyncTaskStateChannel
|
New state channel. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Source code in src/synthorg/engine/context.py
with_compression
¶
Replace conversation with a compressed version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metadata
|
CompressionMetadata
|
Compression metadata to attach. |
required |
compressed_conversation
|
tuple[ChatMessage, ...]
|
The compressed message tuple. |
required |
fill_tokens
|
int
|
Updated fill estimate after compression. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/synthorg/engine/context.py
with_task_transition
¶
Transition the task execution status.
Delegates to
:meth:~synthorg.engine.task_execution.TaskExecution.with_transition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
TaskStatus
|
The desired target status. |
required |
reason
|
str
|
Optional reason for the transition. |
''
|
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Raises:
| Type | Description |
|---|---|
ExecutionStateError
|
If no task execution is set. |
ValueError
|
If the transition is invalid (from
|
Source code in src/synthorg/engine/context.py
to_snapshot
¶
Create a compact snapshot for reporting and logging.
Returns:
| Type | Description |
|---|---|
AgentContextSnapshot
|
Frozen |
Source code in src/synthorg/engine/context.py
with_tool_loaded
¶
Mark a tool's L2 body as loaded.
Idempotent: loading an already-loaded tool is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_name
|
str
|
Name of the tool to load. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Source code in src/synthorg/engine/context.py
with_tool_unloaded
¶
Mark a tool's L2 body as unloaded.
Also removes any L3 resources for the unloaded tool. Idempotent: unloading an already-unloaded tool is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_name
|
str
|
Name of the tool to unload. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Source code in src/synthorg/engine/context.py
with_resource_loaded
¶
Mark an L3 resource as fetched.
Idempotent: loading an already-loaded resource is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_name
|
str
|
Name of the tool owning the resource. |
required |
resource_id
|
str
|
Identifier of the resource. |
required |
Returns:
| Type | Description |
|---|---|
AgentContext
|
New |
Source code in src/synthorg/engine/context.py
Prompt Builder¶
prompt
¶
System prompt construction from agent identity and context.
Translates agent configuration (personality, skills, authority, role) into contextually rich system prompts that shape agent behavior during LLM calls.
Non-inferable principle: System prompts should contain only information
that agents cannot discover by reading the codebase or environment. Full
tool definitions are delivered via the LLM provider's API tools
parameter. However, lightweight L1 metadata (name, category, cost tier,
one-line description) IS injected into the system prompt so agents can
discover what tools exist and decide which to load via load_tool().
Example::
from synthorg.engine.prompt import build_system_prompt
prompt = build_system_prompt(agent=agent_identity, task=task)
prompt.content # rendered system prompt string
SystemPrompt
pydantic-model
¶
Bases: BaseModel
Immutable result of system prompt construction.
Attributes:
| Name | Type | Description |
|---|---|---|
content |
str
|
Full rendered prompt text. |
template_version |
str
|
Version of the template that produced this prompt. |
estimated_tokens |
int
|
Token estimate of the prompt content. |
sections |
tuple[str, ...]
|
Names of sections included in the prompt. |
metadata |
dict[str, str]
|
Agent identity metadata (agent_id, name, role, department, level, and optionally profile_capability). |
personality_trim_info |
PersonalityTrimInfo | None
|
Populated when personality section was trimmed to fit the profile's token budget. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
content(str) -
template_version(str) -
estimated_tokens(int) -
sections(tuple[str, ...]) -
metadata(dict[str, str]) -
personality_trim_info(PersonalityTrimInfo | None)
Validators:
-
_deep_copy_metadata
personality_trim_info
pydantic-field
¶
Populated when personality section was trimmed
build_system_prompt
¶
build_system_prompt(
*,
agent,
role=None,
task=None,
available_tools=(),
l1_summaries=(),
company=None,
org_policies=(),
max_tokens=None,
custom_template=None,
token_estimator=None,
effective_autonomy=None,
context_budget_indicator=None,
currency=DEFAULT_CURRENCY,
capability=None,
personality_trimming_enabled=True,
max_personality_tokens_override=None,
strategy_config=None,
async_task_state=None,
)
Build a system prompt from agent identity and optional context.
When max_tokens is provided and the prompt exceeds it, optional
sections are progressively trimmed (strategy, company, task,
org_policies).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
AgentIdentity
|
Agent identity containing personality, skills, authority. |
required |
role
|
Role | None
|
Optional role with description and responsibilities. |
None
|
task
|
Task | None
|
Optional task context injected into the prompt. |
None
|
available_tools
|
tuple[ToolDefinition, ...]
|
Tool definitions populated into template context for custom templates only; the default template omits tools per D22 (non-inferable principle). |
()
|
l1_summaries
|
tuple[ToolL1Metadata, ...]
|
L1 metadata for system prompt injection. Lightweight tool summaries rendered in the Available Tools section of the default template. |
()
|
company
|
Company | None
|
Opt-in. Non-inferable principle recommends omitting unless agents need org-level context they cannot discover. |
None
|
org_policies
|
tuple[str, ...]
|
Company-wide policy texts to inject into prompt. |
()
|
max_tokens
|
int | None
|
Token budget; sections are trimmed if exceeded. |
None
|
custom_template
|
str | None
|
Optional Jinja2 template string override. |
None
|
token_estimator
|
PromptTokenEstimator | None
|
Custom token estimator (defaults to char/4). |
None
|
effective_autonomy
|
EffectiveAutonomy | None
|
Resolved autonomy for the current run. |
None
|
context_budget_indicator
|
str | None
|
Formatted context budget indicator string to inject into the prompt. |
None
|
currency
|
CurrencyCode
|
ISO 4217 currency code for budget displays. Validated
against the allowlist in |
DEFAULT_CURRENCY
|
capability
|
CapabilityLevel | None
|
Capability rung for prompt profile selection.
|
None
|
personality_trimming_enabled
|
bool
|
When |
True
|
max_personality_tokens_override
|
int | None
|
When set to a positive value,
overrides the profile's |
None
|
strategy_config
|
StrategyConfig | None
|
Strategy and trendslop mitigation config.
When provided and the agent qualifies (C-suite/VP/Director
or has explicit |
None
|
async_task_state
|
AsyncTaskStateChannel | None
|
Optional async task state channel.
When non-empty, appends an |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Immutable |
SystemPrompt
|
class: |
Raises:
| Type | Description |
|---|---|
PromptBuildError
|
If prompt construction fails. |
Source code in src/synthorg/engine/prompt.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 | |
build_error_prompt
¶
Return the existing system prompt or a minimal error placeholder.
Used by the engine when the execution pipeline fails and a
SystemPrompt was never built (or was partially built).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identity
|
AgentIdentity
|
Agent identity for metadata. |
required |
agent_id
|
str
|
String agent identifier. |
required |
system_prompt
|
SystemPrompt | None
|
Previously built prompt, or |
required |
Returns:
| Type | Description |
|---|---|
SystemPrompt
|
The existing prompt if available, else a minimal placeholder. |
Source code in src/synthorg/engine/prompt.py
Task Execution¶
task_execution
¶
Runtime task execution state.
Wraps the frozen Task config model with evolving execution state
(status, cost, turn count) using model_copy(update=...) for cheap,
immutable state transitions.
StatusTransition
pydantic-model
¶
Bases: BaseModel
Frozen audit record for a single status transition.
Attributes:
| Name | Type | Description |
|---|---|---|
from_status |
TaskStatus
|
Status before the transition. |
to_status |
TaskStatus
|
Status after the transition. |
timestamp |
AwareDatetime
|
When the transition occurred (timezone-aware). |
reason |
str
|
Optional human-readable reason for the transition. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
from_status(TaskStatus) -
to_status(TaskStatus) -
timestamp(AwareDatetime) -
reason(str)
TaskExecution
pydantic-model
¶
Bases: BaseModel
Frozen runtime wrapper around a Task for execution tracking.
All state evolution happens via model_copy(update=...).
Transitions are validated explicitly via
:func:~synthorg.core.task_transitions.validate_transition before
the copy is made.
Attributes:
| Name | Type | Description |
|---|---|---|
task |
Task
|
Original frozen task definition. |
status |
TaskStatus
|
Current execution status (starts from |
transition_log |
tuple[StatusTransition, ...]
|
Audit trail of status transitions. |
accumulated_cost |
TokenUsage
|
Running token usage and cost totals. |
turn_count |
int
|
Number of LLM turns completed. |
retry_count |
int
|
Number of previous failure-reassignment cycles. |
started_at |
AwareDatetime | None
|
Set by |
completed_at |
AwareDatetime | None
|
When execution reached a terminal state. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
task(Task) -
status(TaskStatus) -
transition_log(tuple[StatusTransition, ...]) -
accumulated_cost(TokenUsage) -
turn_count(int) -
retry_count(int) -
started_at(AwareDatetime | None) -
completed_at(AwareDatetime | None)
from_task
classmethod
¶
Create a fresh execution from a task definition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Task
|
The frozen task to wrap. |
required |
retry_count
|
int
|
Number of previous failure-reassignment cycles. |
0
|
Returns:
| Type | Description |
|---|---|
TaskExecution
|
New |
Source code in src/synthorg/engine/task_execution.py
with_transition
¶
Validate and apply a status transition.
Returns:
| Type | Description |
|---|---|
TaskExecution
|
A new :class: |
TaskExecution
|
applied (Pydantic copy-on-write). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the transition is invalid. |
Source code in src/synthorg/engine/task_execution.py
with_cost
¶
Accumulate token usage and increment turn count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
usage
|
TokenUsage
|
Token usage from a single LLM call. |
required |
Returns:
| Type | Description |
|---|---|
TaskExecution
|
New |
Raises:
| Type | Description |
|---|---|
ExecutionStateError
|
If execution is in a terminal state. |
Source code in src/synthorg/engine/task_execution.py
Parallel Execution¶
parallel
¶
Parallel agent execution orchestrator.
Coordinates multiple AgentEngine.run() calls in parallel using
structured concurrency (asyncio.TaskGroup), with error isolation,
concurrency limits, resource locking, and progress tracking.
Inspired by the ToolInvoker.invoke_all() pattern from
tools/invoker.py (TaskGroup + Semaphore + guarded
execution), extended with fail-fast, progress tracking, and
CancelledError handling.
ProgressCallback
module-attribute
¶
Synchronous callback invoked on progress updates.
Called directly (not awaited) from the executor's event loop; must not block. Async functions will produce un-awaited coroutines.
ParallelExecutor
¶
ParallelExecutor(
*,
engine,
shutdown_manager=None,
resource_lock=None,
progress_callback=None,
clock=None,
)
Orchestrates concurrent agent execution.
Composition over inheritance -- takes an AgentEngine and
coordinates concurrent run() calls.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
AgentEngine
|
Agent execution engine. |
required |
shutdown_manager
|
ShutdownManager | None
|
Optional shutdown manager for task registration. |
None
|
resource_lock
|
ResourceLock | None
|
Optional resource lock for exclusive file access.
Defaults to one |
None
|
progress_callback
|
ProgressCallback | None
|
Optional synchronous callback invoked on progress updates. |
None
|
Source code in src/synthorg/engine/parallel.py
execute_group
async
¶
Execute a parallel group of agent assignments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
ParallelExecutionGroup
|
The execution group to run. |
required |
Returns:
| Type | Description |
|---|---|
ParallelExecutionResult
|
Result with all agent outcomes. Under |
ParallelExecutionResult
|
agent failure cancels the remaining assignments; the failure |
ParallelExecutionResult
|
and the cancellations are recorded as outcomes and logged, not |
ParallelExecutionResult
|
raised, so callers detect them via |
ParallelExecutionResult
|
per-agent outcomes. |
Raises:
| Type | Description |
|---|---|
ResourceConflictError
|
If resource claims conflict between assignments. |
MemoryError
|
Propagated directly (single fatal) so the interpreter-fatal reaches the top of the stack unmasked. |
RecursionError
|
Propagated directly (single fatal), as above. |
ExceptionGroup
|
When more than one fatal error occurred; its members are the original MemoryError/RecursionError instances. |
ParallelExecutionError
|
Only when resource-lock release fails and no other error is pending to carry the note (never wraps a fatal). |
Source code in src/synthorg/engine/parallel.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | |
Run Result¶
run_result
¶
Agent run result model.
Frozen Pydantic model wrapping ExecutionResult with outer metadata
from the engine layer (system prompt, wall-clock duration, agent/task IDs).
AgentRunResult
pydantic-model
¶
Bases: BaseModel
Immutable result of a complete agent engine run.
Wraps the ExecutionResult from the loop with engine-level
metadata: system prompt, wall-clock duration, and agent/task IDs.
Attributes:
| Name | Type | Description |
|---|---|---|
execution_result |
ExecutionResult
|
Outcome from the execution loop. |
system_prompt |
SystemPrompt
|
System prompt used for this run. |
duration_seconds |
float
|
Wall-clock run time in seconds. |
agent_id |
NotBlankStr
|
Agent identifier (string form of UUID). |
task_id |
NotBlankStr | None
|
Task identifier (always set currently; |
Config:
frozen:Trueallow_inf_nan:False
Fields:
-
execution_result(ExecutionResult) -
system_prompt(SystemPrompt) -
duration_seconds(float) -
agent_id(NotBlankStr) -
task_id(NotBlankStr | None) -
produced_artifacts(tuple[Artifact, ...]) -
bound_model(ModelConfig | None) -
currency(CurrencyCode)
bound_model
pydantic-field
¶
The (provider, model) pair the run actually committed to, after stakes routing and any budget ceiling have spoken. This is not always the pair the agent carries on the roster, so a caller recording what produced an output reads it here rather than off the identity it dispatched. None on the paths that terminate before a binding is committed.
currency
pydantic-field
¶
ISO 4217 currency that denominates total_cost. Populated by the engine from the active BudgetConfig.currency so cross-agent aggregations (e.g. ParallelExecutionResult.total_cost) can enforce the same-currency invariant before summing. Required so constructor sites cannot silently mis-label a non-default run as DEFAULT_CURRENCY.
is_awaiting_human
property
¶
True when the run parked on an escalation.
The single owner of "is this run a human wait", so every consumer reads the same answer. A parked run is neither a success nor a failure: the task is alive, an approval is pending, and the run resumes from its parked context once the human decides. Counting it as a failure fails the wave, skips the merge, tears down the workspace the resume needs, and kills the plan while its approval is still open.
completion_summary
property
¶
Extract the last assistant message content as a work summary.
Walks the conversation in reverse to find the most recent
assistant message with non-empty text content. Tool-call-only
assistant messages (content is None or empty) are skipped.
Returns:
| Type | Description |
|---|---|
str | None
|
The content string, or |
Metrics¶
metrics
¶
Task completion metrics model.
Proxy overhead metrics for an agent run, computed from
AgentRunResult data per docs/design/coordination-metrics.md.
TaskCompletionMetrics
pydantic-model
¶
Bases: BaseModel
Proxy overhead metrics for an agent run.
See docs/design/coordination-metrics.md.
Computed from AgentRunResult after execution to surface
orchestration overhead indicators (turns, tokens, cost, duration).
Attributes:
| Name | Type | Description |
|---|---|---|
task_id |
NotBlankStr | None
|
Task identifier ( |
agent_id |
NotBlankStr
|
Agent identifier (string form of UUID). |
turns_per_task |
int
|
Number of LLM turns to complete the task. |
tokens_per_task |
int
|
Total tokens consumed (input + output). |
cost_per_task |
float
|
Total cost for the task in the configured currency. |
duration_seconds |
float
|
Wall-clock execution time in seconds. |
prompt_tokens |
int
|
Estimated system prompt tokens (per-call estimate
from |
prompt_token_ratio |
float
|
Per-call ratio of prompt tokens to total tokens
(overhead indicator, derived via |
accuracy_effort_ratio |
float | None
|
Accuracy-effort ratio from step-level
quality signals ( |
Config:
frozen:Trueallow_inf_nan:False
Fields:
-
task_id(NotBlankStr | None) -
agent_id(NotBlankStr) -
turns_per_task(int) -
tokens_per_task(int) -
cost_per_task(float) -
duration_seconds(float) -
prompt_tokens(int) -
accuracy_effort_ratio(float | None)
Validators:
-
_cap_prompt_tokens
accuracy_effort_ratio
pydantic-field
¶
Accuracy-effort ratio from step-level quality signals (None when quality signals are unavailable)
prompt_token_ratio
property
¶
Per-call ratio of prompt tokens to total tokens (overhead indicator).
For multi-turn runs the actual overhead is higher because the system prompt is resent on every turn.
from_run_result
classmethod
¶
Build metrics from an agent run result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
AgentRunResult
|
The |
required |
Returns:
| Type | Description |
|---|---|
TaskCompletionMetrics
|
New |
TaskCompletionMetrics
|
the result's execution context and metadata. |
Source code in src/synthorg/engine/metrics.py
Errors¶
errors
¶
Engine-layer error hierarchy.
EngineError
¶
Bases: DomainError
Base exception for all engine-layer errors.
Inherits from :class:DomainError so the prefix-vs-category
validator runs on every subclass; a typo in a subclass
error_code whose first digit no longer matches the declared
error_category is rejected at class-definition time.
Class Attributes
status_code: Default HTTP status for API exposure (500). error_code: Default RFC 9457 error code. error_category: Default RFC 9457 error category. retryable: Whether the client should retry the request. default_message: Generic 5xx-safe message used by exception handlers.
Source code in src/synthorg/core/domain_errors.py
PromptBuildError
¶
Bases: EngineError
Raised when system prompt construction fails.
Source code in src/synthorg/core/domain_errors.py
ExecutionStateError
¶
Bases: EngineError
Raised when an execution state transition is invalid.
Source code in src/synthorg/core/domain_errors.py
MaxTurnsExceededError
¶
Bases: EngineError
Raised when turn_count reaches max_turns during execution.
Enforced by AgentContext.with_turn_completed when the hard turn
limit has been reached.
Source code in src/synthorg/core/domain_errors.py
LoopExecutionError
¶
Bases: EngineError
Non-recoverable execution loop error for the engine layer.
The execution loop returns TerminationReason.ERROR internally.
This exception is available for the engine layer above the loop to
convert that result into a raised error when appropriate.
Source code in src/synthorg/core/domain_errors.py
ParallelExecutionError
¶
Bases: EngineError
Raised when a parallel execution group encounters a fatal error.
Source code in src/synthorg/core/domain_errors.py
ResourceConflictError
¶
Bases: EngineError
Raised when resource claims conflict between assignments.
Source code in src/synthorg/core/domain_errors.py
DecompositionError
¶
Bases: EngineError
Base exception for task decomposition failures.
Source code in src/synthorg/core/domain_errors.py
DecompositionBudgetExhaustedError
¶
Bases: DecompositionError
Raised when the model hit its token ceiling before writing content.
Distinct from a parse failure, and deliberately not retried: the next
attempt truncates at the same place. A reasoning model spends completion
tokens on its own reasoning before any content, so a budget sized for the
answer alone returns an empty string that reaches the JSON parser and is
reported as malformed JSON. The fix is a larger max_output_tokens,
which is not what a parse error tells anyone to do.
Source code in src/synthorg/core/domain_errors.py
DecompositionTimeoutError
¶
Bases: DecompositionError
Raised when a decomposition outran one of its wall-clock ceilings.
Distinct from every other decomposition failure, and for the same reason
:class:DecompositionBudgetExhaustedError is: the ceiling is unchanged on
the next attempt, so a retry buys the same outcome at full price. That
price is the whole ceiling, which is what makes the distinction worth a
type rather than a log line: a caller that retries a parse failure is
paying for a fresh roll of the dice, while one that retries a timeout is
paying the ceiling twice to reach the same place.
Source code in src/synthorg/core/domain_errors.py
PlanReviewUnavailableError
¶
Bases: EngineError
Raised when a seated review panel could not review at all.
Distinct from a quiet panel: every seated reviewer's provider failed, so the plan carries no quality signal for a reason that is an outage, not a judgement. Parking it would present an unreviewed plan as an unobjectionable one, so plan preparation fails instead.
Source code in src/synthorg/core/domain_errors.py
DecompositionCycleError
¶
Bases: DecompositionError
Raised when a dependency cycle is detected in the subtask graph.
Source code in src/synthorg/core/domain_errors.py
DecompositionDepthError
¶
Bases: DecompositionError
Raised when decomposition exceeds the maximum nesting depth.
Source code in src/synthorg/core/domain_errors.py
DecompositionSubtaskLimitError
¶
Bases: DecompositionError
Raised when a plan carries more subtasks than the caller allowed.
Every strategy refuses an over-limit plan rather than substituting a smaller one: the request named the ceiling, and quietly returning a thinner plan the operator never saw is a worse answer wearing a success.
Both numbers are attributes, not only prose, so a caller can offer to raise the ceiling to the number actually produced without parsing the message. Composing the message here also keeps the three strategies from wording the same refusal differently.
Source code in src/synthorg/engine/errors.py
RetrospectiveError
¶
Bases: EngineError
Base exception for objective-retrospective capture failures.
Source code in src/synthorg/core/domain_errors.py
RetrospectiveParseError
¶
Bases: RetrospectiveError
Raised when a submitted retrospective cannot be parsed.
Source code in src/synthorg/core/domain_errors.py
InitiativeEvaluationError
¶
Bases: EngineError
Base exception for initiative-evaluation failures.
Source code in src/synthorg/core/domain_errors.py
InitiativeEvaluationParseError
¶
Bases: InitiativeEvaluationError
Raised when a submitted evaluation cannot be parsed.
Source code in src/synthorg/core/domain_errors.py
PlanReviewError
¶
Bases: EngineError
Base exception for stakeholder plan-review failures.
Source code in src/synthorg/core/domain_errors.py
PlanReviewParseError
¶
Bases: PlanReviewError
Raised when a panellist's submitted review cannot be parsed.
Source code in src/synthorg/core/domain_errors.py
PlanReviewCategoryGuidanceError
¶
Bases: PlanReviewError
Raised when a finding category carries no reviewer-facing meaning.
The brief and the tool schema render the vocabulary from one mapping, so a category present in the enum and absent from that mapping would reach a reviewer as a bare name. A reviewer shown a name it was never told the sense of proposes its own, which is the behaviour the vocabulary exists to remove, so the render fails rather than shipping a half-explained list.
Source code in src/synthorg/core/domain_errors.py
TaskRoutingError
¶
Bases: EngineError
Raised when task routing to an agent fails.
Source code in src/synthorg/core/domain_errors.py
TaskAssignmentError
¶
Bases: EngineError
Raised when task assignment fails.
Source code in src/synthorg/core/domain_errors.py
NoEligibleAgentError
¶
Bases: TaskAssignmentError
Raised when no eligible agent is found for assignment.
Source code in src/synthorg/core/domain_errors.py
RecoveryConfigError
¶
Bases: EngineError
Configuration cannot satisfy the selected recovery strategy.
Typical cause: EngineRecoveryConfig.strategy == CHECKPOINT but
no :class:CheckpointRepository was wired through to the factory.
Source code in src/synthorg/core/domain_errors.py
RecoveryCheckpointMissingError
¶
Bases: EngineError
A resumable recovery result carries no checkpoint to resume from.
The strategy answered can_resume true and then supplied no
checkpoint JSON, so the two halves of its own answer disagree. Typed
rather than a bare RuntimeError because the recovery boundary
catches broadly: an untyped breach degrades into one warning line
indistinguishable from any other failure the resume path hit.
Source code in src/synthorg/core/domain_errors.py
ParkedContextRepoMissingError
¶
Bases: EngineError
A context was parked with nowhere to persist it.
Raised rather than returning quietly, because a park that stores nothing is a run reported PARKED that no resume can ever find: the approval waits for a decision, the decision looks up a parked context that was never written, and the run's only remaining exit is a manual cancellation nobody knows to perform. Every caller already has a honest fallback for a failed park (a hard-ceiling crossing stops the run as BUDGET_EXHAUSTED, a tool escalation denies), so failing loud costs a real behaviour and buys back a reachable one.
Source code in src/synthorg/core/domain_errors.py
ProjectNotFoundError
¶
Bases: EngineError
Referenced project does not exist.
The single not-found error for a missing project, raised from both
the engine lookup and work-pipeline intake paths. The project_id
attribute is for structured logs only and must NOT be surfaced to
clients; the wire message stays the generic default_message.
Source code in src/synthorg/engine/errors.py
ProjectRepositoryNotConfiguredError
¶
Bases: EngineError
Task declares a project but no project repository is configured.
Fail-loud precondition: with no project repository wired the engine
cannot resolve the task's project or enforce its budget, so it must
not run the agent unvalidated. Raised into the engine's fatal-error
boundary so the task terminates FAILED with the surfaced reason. The
project_id attribute is for structured logs only.
Source code in src/synthorg/engine/errors.py
WorkspaceError
¶
Bases: EngineError
Base exception for workspace isolation failures.
Source code in src/synthorg/core/domain_errors.py
WorkspaceSetupError
¶
Bases: WorkspaceError
Raised when workspace creation fails.
Source code in src/synthorg/core/domain_errors.py
WorkspaceMergeError
¶
Bases: WorkspaceError
Raised when workspace merge fails.
Source code in src/synthorg/core/domain_errors.py
WorkspaceCleanupError
¶
Bases: WorkspaceError
Raised when workspace teardown fails.
Source code in src/synthorg/core/domain_errors.py
WorkspaceLimitError
¶
Bases: WorkspaceError
Raised when maximum concurrent workspaces reached.
Source code in src/synthorg/core/domain_errors.py
WorkspacePushError
¶
Bases: WorkspaceError
Raised when the coordinator-owned push to the git backend fails.
Distinct from :class:WorkspaceMergeError (local git merge state)
so callers can tell a forge/remote push rejection apart from a
local textual merge conflict.
Source code in src/synthorg/core/domain_errors.py
ProjectWorkspaceError
¶
Bases: EngineError
Base exception for persistent project-workspace failures.
Source code in src/synthorg/core/domain_errors.py
ProjectWorkspaceNotProvisionedError
¶
Bases: ProjectWorkspaceError
Raised when a project workspace is required but not yet provisioned.
The wire message stays generic to avoid leaking identifiers; the
project_id attribute is for structured logs only and must NOT be
surfaced to clients.
Source code in src/synthorg/engine/errors.py
GitBackendError
¶
Bases: EngineError
Base exception for pluggable git-backend failures.
Source code in src/synthorg/core/domain_errors.py
GitBackendConfigError
¶
Bases: GitBackendError
Raised when git-backend configuration is invalid for the strategy.
Fail-fast at factory construction (e.g. LOCAL_PATH selected but
no local_repo_path, or EXTERNAL_REMOTE without its connection
catalog / secret-backend dependency).
Source code in src/synthorg/core/domain_errors.py
GitBackendProvisionError
¶
Bases: GitBackendError
Raised when the git backend fails to provision a repository.
Source code in src/synthorg/core/domain_errors.py
GitBackendSeedError
¶
Bases: GitBackendError
Raised when the git backend fails to seed an existing source.
Seeding is the one-shot import of an existing repository (clone of a remote URL or copy of a local path) into a freshly provisioned workspace. Distinct from provisioning (which creates an empty repo): a seed onto a workspace that already holds a git history fails here, and the brownfield intake service maps that to its own typed error.
Source code in src/synthorg/core/domain_errors.py
GitBackendPushError
¶
Bases: GitBackendError
Raised when the git backend fails to push a branch.
Source code in src/synthorg/core/domain_errors.py
GitBackendFetchError
¶
Bases: GitBackendError
Raised when the git backend fails to fetch from the remote.
Source code in src/synthorg/core/domain_errors.py
GitBackendRemoteMissingError
¶
Bases: GitBackendError
Raised when a push targets a forge repo that does not exist yet.
Distinct from a transient push failure: the operator's credential is valid but the addressed repository has never been created. The external-remote backend catches this to trigger lazy forge-API repo provisioning (create-then-retry-once); it is NOT retried by the transient-I/O retry handler.
Source code in src/synthorg/core/domain_errors.py
GitBackendRateLimitError
¶
Bases: GitBackendError
Raised when a forge rate-limits a git or forge-API operation.
Retryable via the transient-I/O backoff handler. retry_after
carries the server-advertised cooldown (seconds) when present, for
observability; the backoff itself is exponential (Pattern A).
Source code in src/synthorg/engine/errors.py
GitBackendForgeApiError
¶
Bases: GitBackendError
Raised when a forge REST API call fails (non-auth).
Retryable: forge-API 5xx / connection failures are transient.
Source code in src/synthorg/core/domain_errors.py
GitBackendForgeAuthError
¶
Bases: GitBackendForgeApiError
Raised on 401/403 forge-API responses (invalid/expired token).
Non-retryable: a fresh credential is required, not a backoff.
Source code in src/synthorg/core/domain_errors.py
ProjectEnvironmentError
¶
Bases: EngineError
Base exception for reproducible per-project environment failures.
Named ProjectEnvironmentError (not EnvironmentError) to avoid
shadowing the built-in EnvironmentError alias of OSError;
mirrors the :class:ProjectWorkspaceError sibling.
Source code in src/synthorg/core/domain_errors.py
EnvironmentConfigError
¶
Bases: ProjectEnvironmentError
Raised when environment configuration is invalid for the strategy.
Fail-fast at factory construction (e.g. a strategy selected without
the runtime dependency it requires, mirroring
:class:GitBackendConfigError).
Source code in src/synthorg/core/domain_errors.py
EnvironmentProvisionError
¶
Bases: ProjectEnvironmentError
Raised when an environment strategy fails to provision.
Source code in src/synthorg/core/domain_errors.py
EnvironmentDockerBuildError
¶
Bases: EnvironmentProvisionError
Raised when the devcontainer image build fails.
Source code in src/synthorg/core/domain_errors.py
EnvironmentBackendUnavailableError
¶
Bases: ProjectEnvironmentError
Raised when a declaration needs a sandbox backend that is not active.
Loud, never silent: e.g. a DEVCONTAINER declaration on a project
whose build/test categories resolve to the subprocess backend cannot
build a sealed image, so provisioning fails rather than degrading to
an unfaithful host-only run.
Source code in src/synthorg/core/domain_errors.py
TaskEngineError
¶
Bases: EngineError
Base exception for all task engine errors.
Source code in src/synthorg/core/domain_errors.py
TaskEngineNotRunningError
¶
Bases: TaskEngineError
Raised when a mutation is submitted to a stopped task engine.
Source code in src/synthorg/core/domain_errors.py
TaskEngineQueueFullError
¶
Bases: TaskEngineError
Raised when the task engine queue is at capacity.
Source code in src/synthorg/core/domain_errors.py
TaskMutationError
¶
Bases: TaskEngineError
Raised when a task mutation fails (not found, validation, etc.).
Source code in src/synthorg/core/domain_errors.py
TaskNotFoundError
¶
Bases: TaskMutationError, NotFoundError
Raised when a task is not found during mutation.
Multi-inherits :class:TaskMutationError (engine-layer family
catch) and :class:NotFoundError (API-layer
:func:require_resource_or_404 accepts as error_class).
Source code in src/synthorg/core/domain_errors.py
TaskVersionConflictError
¶
Bases: TaskMutationError
Raised when optimistic concurrency version does not match.
Source code in src/synthorg/core/domain_errors.py
TaskOrphanedPlanError
¶
Bases: TaskEngineError
A task names a plan that no longer exists.
Filing it would leave live work under nothing: its plan id resolves to no row, so the rollup that would notice the work never reaches it. The complement of the plan delete's own guard, which refuses to remove a plan while live tasks exist.
Source code in src/synthorg/core/domain_errors.py
TaskInternalError
¶
Bases: TaskEngineError
Raised when a task mutation fails due to an internal engine error.
Sibling of :class:TaskMutationError, not a subtype, so a broad
except TaskMutationError handler does not accidentally catch
internal engine faults. Inherits the default 500 / ENGINE_ERROR /
INTERNAL metadata from :class:TaskEngineError.
Source code in src/synthorg/core/domain_errors.py
DelegationRoundLimitError
¶
Bases: EngineError
Hard abort when delegation rounds exceed 2x the soft cap.
Attributes:
| Name | Type | Description |
|---|---|---|
current_round |
int
|
The round number that triggered the abort. |
soft_limit |
int
|
The configured soft cap on delegation rounds. |
Source code in src/synthorg/engine/errors.py
CoordinationError
¶
Bases: EngineError
Base exception for multi-agent coordination failures.
Source code in src/synthorg/core/domain_errors.py
CoordinationConfigError
¶
Bases: CoordinationError
Coordinator configuration is invalid at startup.
Source code in src/synthorg/core/domain_errors.py
CoordinationPhaseError
¶
Bases: CoordinationError
Raised when a coordination pipeline phase fails.
Carries the failing phase name and all phase results accumulated up to and including the failure, enabling partial-result inspection.
Attributes:
| Name | Type | Description |
|---|---|---|
phase |
str
|
Name of the phase that failed. |
partial_phases |
tuple[CoordinationPhaseResult, ...]
|
Phase results accumulated before and including this failure. |
Source code in src/synthorg/engine/errors.py
RuntimeServicesBuildError
¶
Bases: EngineError
Raised when the boot/reinit runtime-services build fails.
Wraps the underlying failure from build_runtime_services (provider
registry, tool registry, agent engine, or coordinator factory) so the
boot hook and the /setup/complete controller see a typed domain
error instead of a raw exception. The original cause is preserved via
raise ... from exc.
Source code in src/synthorg/core/domain_errors.py
WorkflowExecutionError
¶
Bases: EngineError
Base exception for workflow execution failures.
Source code in src/synthorg/core/domain_errors.py
WorkflowDefinitionInvalidError
¶
Bases: WorkflowExecutionError
Raised when a workflow definition fails validation at activation time.
422 + WORKFLOW_DEFINITION_INVALID: a definition that fails
activation-time structural checks is a caller-side validation failure
surfaced after the request reached the engine, not an internal fault.
Distinct from :class:WorkflowDefinitionValidationError (the
create/update path, WORKFLOW_DEFINITION_VALIDATION_FAILED) so a
client can tell an activation-time rejection from a create/update one;
both stay in the 422 VALIDATION category.
Source code in src/synthorg/core/domain_errors.py
WorkflowConditionEvalError
¶
Bases: WorkflowExecutionError
Raised when a condition expression cannot be evaluated.
422 + WORKFLOW_CONDITION_EVAL_FAILED: a condition expression that
fails evaluation is authored by the caller as part of the workflow
definition, so the failure is a request-shape problem rather than an
engine fault.
Source code in src/synthorg/core/domain_errors.py
WorkflowExecutionNotFoundError
¶
Bases: WorkflowExecutionError, NotFoundError
Raised when a workflow execution instance is not found.
Multi-inherits :class:WorkflowExecutionError (engine-layer
family catch) and :class:NotFoundError
(:func:require_resource_or_404 accepts as error_class).
Source code in src/synthorg/core/domain_errors.py
SubworkflowNotFoundError
¶
Bases: WorkflowExecutionError
Raised when a referenced subworkflow version cannot be resolved.
Attributes:
| Name | Type | Description |
|---|---|---|
subworkflow_id |
NotBlankStr
|
The subworkflow identifier. |
version |
NotBlankStr
|
The semver pin that failed to resolve. |
Source code in src/synthorg/engine/errors.py
SubworkflowCycleError
¶
Bases: WorkflowExecutionError
Raised when the subworkflow reference graph contains a cycle.
Attributes:
| Name | Type | Description |
|---|---|---|
cycle_path |
tuple[tuple[str, str], ...]
|
Ordered |
Source code in src/synthorg/engine/errors.py
SubworkflowDepthExceededError
¶
Bases: WorkflowExecutionError
Raised when runtime subworkflow nesting exceeds the configured limit.
Attributes:
| Name | Type | Description |
|---|---|---|
depth |
int
|
The depth at which the limit was exceeded. |
max_depth |
int
|
The configured maximum. |
Source code in src/synthorg/engine/errors.py
SubworkflowIOError
¶
Bases: WorkflowExecutionError
Raised when subworkflow input or output binding is invalid.
Covers missing required inputs, unknown inputs, unknown outputs, type mismatches, and invalid binding expressions. The 422 mapping treats binding mismatches as caller-side validation failures so the centralised RFC 9457 dispatch surfaces a structured envelope without controller-level translation.
Source code in src/synthorg/core/domain_errors.py
WorkflowTypeInvalidError
¶
Bases: WorkflowExecutionError
Raised when a request specifies an unknown workflow_type value.
Uses WORKFLOW_TYPE_INVALID and 400: the value did not parse
against the WorkflowType enum at the API boundary, a request-shape
failure distinct from the workflow-definition validation codes.
Source code in src/synthorg/core/domain_errors.py
WorkflowDefinitionValidationError
¶
Bases: WorkflowExecutionError
Raised when a workflow definition fails structural checks.
The default message is intentionally generic so Pydantic validation
detail does not leak to API clients; callers may still chain the
underlying exception with raise … from exc for the structured
log emitted by the centralised handler.
Source code in src/synthorg/core/domain_errors.py
WorkflowYamlExportError
¶
Bases: WorkflowExecutionError
Raised when YAML serialisation of a workflow definition fails.
Maps to 422 (Unprocessable Entity) on /workflows/{id}/export:
the request itself is well-formed, but the persisted definition
cannot be serialised to YAML -- a content-level failure rather
than a request-syntax problem.
Source code in src/synthorg/core/domain_errors.py
KanbanInvalidMoveError
¶
Bases: EngineError
Raised when a requested Kanban column move is not a legal transition.
Maps to 400: the target column is unreachable from the card's current
column under VALID_COLUMN_TRANSITIONS (e.g. a jump that skips the
board's flow), a request-shape failure surfaced by the board service.
Source code in src/synthorg/core/domain_errors.py
KanbanWipLimitError
¶
Bases: EngineError
Raised when a move would push a column past its enforced WIP limit.
Maps to 409 (conflict): the move is legal but the target column is at capacity and WIP enforcement is on, so the board rejects it until a slot frees. Advisory mode never raises this.
Source code in src/synthorg/core/domain_errors.py
SprintError
¶
Bases: EngineError
Base for agile-sprint service failures.
Source code in src/synthorg/core/domain_errors.py
SprintNotFoundError
¶
Bases: SprintError, NotFoundError
Raised when a sprint id resolves to no persisted row.
Maps to 404: the requested sprint does not exist.
Source code in src/synthorg/core/domain_errors.py
SprintBacklogFullError
¶
Bases: SprintError, ConflictError
Raised when adding a task would exceed max_tasks_per_sprint.
Maps to 409 (conflict): the sprint backlog is at capacity, so the task belongs in a later sprint until a slot frees.
Source code in src/synthorg/core/domain_errors.py
SprintTransitionConflictError
¶
Bases: SprintError, ConflictError
Raised when a sprint is not in the state a lifecycle hop requires.
Maps to 409 (conflict). Fires from two places: an upfront status
check (e.g. add_task / start_sprint on a non-PLANNING
sprint, or advancing a terminal sprint), and the transition_if
CAS returning a mismatch when a concurrent advance moved the row out
of the expected from state before this hop landed.
Source code in src/synthorg/core/domain_errors.py
SprintTaskNotInBacklogError
¶
Bases: SprintError, ValidationError
Raised when work is requested on a task outside the active sprint.
Maps to 400: the board move targets a task that is not in the active sprint's backlog, so the sprint gate rejects pulling it into flow.
Source code in src/synthorg/core/domain_errors.py
WorkflowExecutionAlreadyTerminalError
¶
Bases: VersionConflictError
Raised when cancel targets an execution already in a terminal status.
Distinct from :class:synthorg.core.domain_errors.VersionConflictError
(4002) so API clients can discriminate "the execution finished before
you cancelled" (no retry will succeed) from a row-level optimistic-
concurrency race where the caller can re-read and try again. Both
map to 409 + CONFLICT so the HTTP envelope shape is unchanged;
only the error_code differs.
Source code in src/synthorg/core/domain_errors.py
SelfReviewError
¶
Bases: EngineError
Raised when an agent attempts to review their own work.
Structurally prevents an agent from acting as reviewer on a task they executed, enforcing separation of duties at the approval gate.
The exception message is deliberately generic ("Self-review is not
permitted") to avoid leaking internal agent/task identifiers across
authorization boundaries when the message is surfaced via an HTTP
error response. The task_id and agent_id attributes are
available for structured logs but must NOT be passed to user-facing
error responses.
Attributes:
| Name | Type | Description |
|---|---|---|
task_id |
NotBlankStr
|
The task identifier the self-review was attempted on. |
agent_id |
NotBlankStr
|
The agent identifier that is both executor and reviewer. |
Source code in src/synthorg/engine/errors.py
Task Decomposition¶
protocol
¶
Decomposition strategy protocol.
DecompositionStrategy
¶
Bases: Protocol
Protocol for task decomposition strategies.
Implementations produce a DecompositionPlan from a parent task
and a decomposition context. The plan describes subtask definitions
and their dependency relationships.
decompose
async
¶
Decompose a task into subtasks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Task
|
The parent task to decompose. |
required |
context
|
DecompositionContext
|
Decomposition constraints (max subtasks, depth). |
required |
Returns:
| Type | Description |
|---|---|
DecompositionPlan
|
A decomposition plan with subtask definitions. |
Source code in src/synthorg/engine/decomposition/protocol.py
get_strategy_name
¶
plans_any_task
¶
Whether this strategy can plan a task it was not constructed for.
Recursion decomposes a CHILD task, which the caller never named, so a strategy holding one operator-supplied plan for one parent cannot serve it: asked about the child, it refuses, and the refusal fails the whole decomposition rather than the one subtask. Declared per strategy rather than inferred, because "can you plan something I have not shown you" is a claim about the implementation that no caller can test without asking.
Source code in src/synthorg/engine/decomposition/protocol.py
WorkspaceInventory
¶
Bases: Protocol
Protocol answering what a project's workspace currently holds.
Narrow on purpose. Decomposition needs one fact about the workspace and has no business reaching the provisioning service that owns it: a planner must never provision, re-provision or otherwise touch the tree it is being told about.
describe_inventory
async
¶
Describe the project's workspace contents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project_id
|
NotBlankStr
|
The project being planned for. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A phrase naming what the workspace holds, worded so an empty one |
str
|
reads as "there is nothing there" rather than "unknown". |
Source code in src/synthorg/engine/decomposition/protocol.py
models
¶
Decomposition domain models.
Frozen Pydantic models for subtask definitions, decomposition plans and the
decomposition tree. The context a decomposition runs under lives in
:mod:synthorg.engine.decomposition.context, and what its execution adds up to
is a different question again, in
:mod:synthorg.engine.decomposition.status_rollup.
SubtaskDefinition
pydantic-model
¶
Bases: BaseModel
Definition of a single subtask within a decomposition plan.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
NotBlankStr
|
Unique subtask identifier (within this decomposition). |
title |
NotBlankStr
|
Short subtask title. |
description |
NotBlankStr
|
Detailed subtask description. |
dependencies |
tuple[NotBlankStr, ...]
|
IDs of other subtasks this one depends on. |
estimated_complexity |
Complexity
|
Complexity estimate for routing. |
stakes |
Stakes
|
Stakes level for capability-based agent selection. |
required_skills |
tuple[NotBlankStr, ...]
|
Skill IDs needed for routing. |
required_tags |
tuple[NotBlankStr, ...]
|
Tags needed for multi-faceted routing match. When set, the routing scorer awards a small bonus to agents whose matched-skill tags cover every required tag. Empty tuple disables the tag-match tier. |
required_role |
NotBlankStr | None
|
Optional role name for routing. |
expected_artifacts |
tuple[NotBlankStr, ...]
|
Deliverables this subtask must produce. These
project onto the dispatched task's |
acceptance_criteria |
tuple[NotBlankStr, ...]
|
Per-subtask criteria that define "done" for it. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
id(NotBlankStr) -
title(NotBlankStr) -
description(NotBlankStr) -
dependencies(tuple[NotBlankStr, ...]) -
estimated_complexity(Complexity) -
stakes(Stakes) -
required_skills(tuple[NotBlankStr, ...]) -
required_tags(tuple[NotBlankStr, ...]) -
required_role(NotBlankStr | None) -
expected_artifacts(tuple[NotBlankStr, ...]) -
acceptance_criteria(tuple[NotBlankStr, ...]) -
satisfies(tuple[NotBlankStr, ...]) -
kind(PlanItemKind) -
options(tuple[PlanOption, ...])
Validators:
-
_validate_subtask
estimated_complexity
pydantic-field
¶
estimated_complexity = Complexity.MEDIUM
Complexity estimate for routing
kind
pydantic-field
¶
Whether this subtask is work to execute or a decision point
DecompositionPlan
pydantic-model
¶
Bases: BaseModel
Plan describing how a parent task is decomposed into subtasks.
Validates subtask collection integrity at construction:
non-empty, unique IDs, valid dependency references, and a declared
deliverable per WORK subtask.
Cycle detection is handled by DependencyGraph.validate()
in the service layer.
Attributes:
| Name | Type | Description |
|---|---|---|
parent_task_id |
NotBlankStr
|
ID of the task being decomposed. |
subtasks |
tuple[SubtaskDefinition, ...]
|
Ordered subtask definitions. |
task_structure |
TaskStructure
|
Structure the planner declared, or |
coordination_topology |
CoordinationTopology
|
Selected coordination topology. |
planning_strategy |
NotBlankStr | None
|
Which planner produced this plan. Blank means the strategy did not say; a fallback always says, so the approval gate can show the operator that what they are being asked to approve is a single-shot substitute rather than the researched plan the owner was asked for. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
parent_task_id(NotBlankStr) -
subtasks(tuple[SubtaskDefinition, ...]) -
task_structure(TaskStructure) -
coordination_topology(CoordinationTopology) -
open_questions(tuple[NotBlankStr, ...]) -
assumptions(tuple[NotBlankStr, ...]) -
planning_strategy(NotBlankStr | None)
Validators:
-
_validate_subtasks
task_structure
pydantic-field
¶
task_structure = TaskStructure.AUTO
Structure the planner declared; AUTO means it declared nothing and the classifier heuristic decides
coordination_topology
pydantic-field
¶
coordination_topology = CoordinationTopology.AUTO
Selected coordination topology
open_questions
pydantic-field
¶
Unresolved questions the planner surfaced for the human
planning_strategy
pydantic-field
¶
Which planner produced this plan; set when a fallback produced it so the substitution is visible on the durable plan
DecompositionResult
pydantic-model
¶
Bases: BaseModel
Result of a complete task decomposition.
One level of a decomposition. A subtask the atomicity policy judged
oversized is decomposed again, and its own result hangs off children,
so the whole shape is a tree rather than a list.
children defaults to empty, which is exactly what a non-recursive
decomposition produces, so every reader that predates recursion sees the
flat result it always saw.
Attributes:
| Name | Type | Description |
|---|---|---|
plan |
DecompositionPlan
|
The decomposition plan that was executed. |
created_tasks |
tuple[Task, ...]
|
Task objects created from subtask definitions. |
dependency_edges |
tuple[tuple[NotBlankStr, NotBlankStr], ...]
|
Directed edges (from_id, to_id) in the DAG. |
depth |
int
|
This level's nesting depth, |
children |
tuple[DecompositionResult, ...]
|
The decomposition of each subtask at this level that was
split further, in no particular relation to |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
plan(DecompositionPlan) -
created_tasks(tuple[Task, ...]) -
dependency_edges(tuple[tuple[NotBlankStr, NotBlankStr], ...]) -
depth(int) -
children(tuple[DecompositionResult, ...])
Validators:
-
_validate_plan_task_consistency
split_task_ids
property
¶
Ids of this level's tasks that were decomposed further.
Returns:
| Type | Description |
|---|---|
frozenset[str]
|
The parent task id of each child decomposition. |
leaf_tasks
property
¶
Every task in the tree that nothing below it replaced.
This is what gets dispatched: a task that was split is a container for the work below it, and running it as well would do that work twice.
Returns:
| Type | Description |
|---|---|
tuple[Task, ...]
|
The leaves, this level's first, then each child's in order. |
all_tasks
property
¶
Every task in the tree, split containers included.
Returns:
| Type | Description |
|---|---|
tuple[Task, ...]
|
This level's tasks, then each child's, recursively. |
max_depth_reached
property
¶
The deepest level this tree actually reached.
The measured counterpart of DecompositionContext.max_depth, which
is only a ceiling: a planner that never produced an oversized subtask
stops well short of it.
Returns:
| Type | Description |
|---|---|
int
|
|
service
¶
Decomposition service.
Orchestrates strategy, classifier, DAG validation, and task creation to decompose a parent task into executable subtasks.
DecompositionService
¶
DecompositionService(
strategy,
classifier,
stakes_assessor=None,
*,
config_resolver=None,
workspace_inventory=None,
)
Service orchestrating task decomposition.
Composes a decomposition strategy with a structure classifier, DAG validator, and task factory to produce executable subtasks.
Source code in src/synthorg/engine/decomposition/service.py
decompose_task
async
¶
Decompose a task into subtasks.
- Call strategy.decompose().
- Resolve the task structure: the planner's own declaration stands; only a plan that declared none falls to the classifier.
- Validate DAG via DependencyGraph.
- Create Task objects from SubtaskDefinitions.
- Return DecompositionResult.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Task
|
The parent task to decompose. |
required |
context
|
DecompositionContext
|
Decomposition constraints. |
required |
Returns:
| Type | Description |
|---|---|
DecompositionResult
|
Decomposition result with created tasks and dependency edges. |
Raises:
| Type | Description |
|---|---|
DecompositionTimeoutError
|
When any one planning session outruns
|
DecompositionError
|
When something inside timed out on its own without either ceiling firing, which IS worth retrying, and for every other decomposition failure. |
Source code in src/synthorg/engine/decomposition/service.py
set_config_resolver
¶
Adopt the resolver the ceiling is read through.
A setter rather than a constructor argument because the coordinator factory that builds this service is already at its approved argument count, and threading one more through it would widen a signature the repository pins. The resolver is handed over right after the coordinator is assembled, before anything can decompose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resolver
|
ConfigResolverProtocol
|
The live settings resolver. |
required |
Source code in src/synthorg/engine/decomposition/service.py
rollup_status
¶
Compute status rollup for a parent task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parent_task_id
|
NotBlankStr
|
The parent task identifier. |
required |
subtask_statuses
|
tuple[TaskStatus, ...]
|
Statuses of all subtasks. |
required |
Returns:
| Type | Description |
|---|---|
SubtaskStatusRollup
|
Aggregated status rollup. |
Source code in src/synthorg/engine/decomposition/service.py
Task Routing¶
models
¶
Task routing domain models.
Frozen Pydantic models for routing candidates, decisions, results, and topology configuration.
RoutingCandidate
pydantic-model
¶
Bases: BaseModel
A candidate agent for a subtask with scoring details.
Attributes:
| Name | Type | Description |
|---|---|---|
agent_identity |
AgentIdentity
|
The candidate agent. |
score |
float
|
Match score between 0.0 and 1.0. |
matched_skills |
tuple[NotBlankStr, ...]
|
Skills that matched the subtask requirements. |
reason |
NotBlankStr
|
Human-readable explanation of the score. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
agent_identity(AgentIdentity) -
score(float) -
matched_skills(tuple[NotBlankStr, ...]) -
reason(NotBlankStr)
RoutingDecision
pydantic-model
¶
Bases: BaseModel
Routing decision for a single subtask.
Attributes:
| Name | Type | Description |
|---|---|---|
subtask_id |
NotBlankStr
|
ID of the subtask being routed. |
selected_candidate |
RoutingCandidate
|
The chosen agent candidate. |
alternatives |
tuple[RoutingCandidate, ...]
|
Other candidates considered. |
topology |
CoordinationTopology
|
Coordination topology for this subtask. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
subtask_id(NotBlankStr) -
selected_candidate(RoutingCandidate) -
alternatives(tuple[RoutingCandidate, ...]) -
topology(CoordinationTopology)
Validators:
-
_validate_selected_not_in_alternatives
RoutingResult
pydantic-model
¶
Bases: BaseModel
Result of routing all subtasks in a decomposition.
Attributes:
| Name | Type | Description |
|---|---|---|
parent_task_id |
NotBlankStr
|
ID of the parent task. |
decisions |
tuple[RoutingDecision, ...]
|
Routing decisions for routable subtasks. |
unroutable |
tuple[NotBlankStr, ...]
|
IDs of subtasks with no matching agent. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
parent_task_id(NotBlankStr) -
decisions(tuple[RoutingDecision, ...]) -
unroutable(tuple[NotBlankStr, ...])
Validators:
-
_validate_unique_subtask_ids
AutoTopologyConfig
pydantic-model
¶
Bases: BaseModel
Configuration for automatic topology selection.
Attributes:
| Name | Type | Description |
|---|---|---|
sequential_override |
CoordinationTopology
|
Topology for sequential structures. |
parallel_default |
CoordinationTopology
|
Topology for parallel structures. |
mixed_default |
CoordinationTopology
|
Topology for mixed structures. |
parallel_artifact_threshold |
int
|
Artifact count above which parallel tasks use decentralized topology. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
sequential_override(CoordinationTopology) -
parallel_default(CoordinationTopology) -
mixed_default(CoordinationTopology) -
parallel_artifact_threshold(int)
Validators:
-
_validate_no_auto_defaults
sequential_override
pydantic-field
¶
sequential_override = CoordinationTopology.SAS
Topology for sequential structures
parallel_default
pydantic-field
¶
parallel_default = CoordinationTopology.CENTRALIZED
Topology for parallel structures
mixed_default
pydantic-field
¶
mixed_default = CoordinationTopology.CONTEXT_DEPENDENT
Topology for mixed structures
parallel_artifact_threshold
pydantic-field
¶
Artifact count threshold for decentralized topology
service
¶
Task routing service.
Routes decomposed subtasks to appropriate agents: the capability ladder narrows the pool to the band that best fits what each subtask demands, the scorer ranks within it, and the topology is selected once for the wave.
The ladder runs here rather than only at dispatch because assignment and dispatch must reach the same verdict. Routing a subtask to an agent the dispatch will then refuse is the two-owner shape: the quieter authority wins and the operator sees a parked task with no assignment reason.
TaskRoutingService
¶
Routes subtasks to agents by capability fit, then by score.
For each subtask in a decomposition result, narrows the available agents to the band that best fits the capability the subtask demands, scores that band, and selects the best match. Subtasks with no viable candidate are reported as unroutable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scorer
|
AgentTaskScorer
|
Ranks candidates within whichever capability band answers. |
required |
topology_selector
|
TopologySelector
|
Chooses the wave's coordination topology. |
required |
capability
|
CapabilityPolicy | None
|
The org's one capability policy, shared with the solo
assignment path and with dispatch. |
None
|
Source code in src/synthorg/engine/routing/service.py
route
¶
Route all subtasks to appropriate agents.
For each subtask: 1. Score all available agents. 2. Select the best candidate (highest score >= min_score). 3. Select topology from parent task override or plan structure. 4. Report unroutable subtasks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
decomposition_result
|
DecompositionResult
|
The decomposition to route. |
required |
available_agents
|
tuple[AgentIdentity, ...]
|
Pool of agents to consider. |
required |
parent_task
|
Task
|
The parent task (for topology selection). |
required |
Returns:
| Type | Description |
|---|---|
RoutingResult
|
Routing result with decisions and unroutable subtask IDs. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the topology cannot be resolved from the parent task's override and plan structure. |
Source code in src/synthorg/engine/routing/service.py
Task Assignment¶
protocol
¶
Task assignment strategy protocol.
Defines the pluggable interface for assignment strategies.
TaskAssignmentStrategy
¶
Bases: Protocol
Protocol for task assignment strategies.
Implementations must be synchronous (pure computation, no I/O)
and return an AssignmentResult with the selected agent and
ranked alternatives. TaskAssignmentService calls assign()
synchronously -- async implementations will NOT work correctly.
Error signaling contract:
ManualAssignmentStrategyraisesNoEligibleAgentErrorwhen the designated agent is not found or not ACTIVE, andTaskAssignmentErrorwhentask.assigned_toisNone.- Scoring-based strategies (the
ScoringBasedAssignmentStrategycompositions for role-based, load-balanced, cost-optimized, hierarchical, and auction) returnAssignmentResult(selected=None, ...)when no agent meets the minimum score threshold.
TaskAssignmentService propagates both patterns: it re-raises
TaskAssignmentError (including its subclass
NoEligibleAgentError) and logs a warning when
result.selected is None, returning the result to the
caller for handling.
assign
¶
Assign a task to an agent based on the strategy's algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
AssignmentRequest
|
The assignment request with task and agent pool. |
required |
Returns:
| Type | Description |
|---|---|
AssignmentResult
|
Assignment result with selected agent and alternatives. |
AssignmentResult
|
|
AssignmentResult
|
found (scoring strategies) -- callers must check this. |
Raises:
| Type | Description |
|---|---|
TaskAssignmentError
|
When preconditions are violated
(e.g. missing |
NoEligibleAgentError
|
When the designated agent cannot be found or is not ACTIVE (manual strategy only). |
Source code in src/synthorg/engine/assignment/protocol.py
models
¶
Task assignment domain models.
Frozen Pydantic models for assignment requests, results, agent workloads, and assignment candidates.
AgentWorkload
pydantic-model
¶
Bases: BaseModel
Snapshot of an agent's current workload.
Attributes:
| Name | Type | Description |
|---|---|---|
agent_id |
NotBlankStr
|
Unique agent identifier. |
active_task_count |
int
|
Number of tasks currently in progress. |
total_cost |
float
|
Total cost incurred by this agent in the configured currency. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
agent_id(NotBlankStr) -
active_task_count(int) -
total_cost(float)
AssignmentCandidate
pydantic-model
¶
Bases: BaseModel
A candidate agent for task assignment with scoring details.
Attributes:
| Name | Type | Description |
|---|---|---|
agent_identity |
AgentIdentity
|
The candidate agent. |
score |
float
|
Match score between 0.0 and 1.0. |
matched_skills |
tuple[NotBlankStr, ...]
|
Skills that matched the assignment requirements. |
reason |
NotBlankStr
|
Human-readable explanation of the score. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
agent_identity(AgentIdentity) -
score(float) -
matched_skills(tuple[NotBlankStr, ...]) -
reason(NotBlankStr)
AssignmentRequest
pydantic-model
¶
Bases: BaseModel
Request for task assignment to an agent.
The required_skills and required_role fields live here
(not on Task) so that scoring strategies can evaluate agent-task
fit without modifying the Task model.
Attributes:
| Name | Type | Description |
|---|---|---|
task |
Task
|
The task to assign. |
available_agents |
tuple[AgentIdentity, ...]
|
Pool of agents to consider (must be non-empty, unique by agent id). |
workloads |
tuple[AgentWorkload, ...]
|
Current workload snapshots per agent (unique by agent_id). |
min_score |
float
|
Minimum score threshold for eligibility. |
required_skills |
tuple[NotBlankStr, ...]
|
Skill names needed for scoring. |
required_role |
NotBlankStr | None
|
Optional role name for scoring. |
stakes |
Stakes
|
How consequential the task is, gating the low-confidence
band and the capability floor. Derived from |
max_concurrent_tasks |
int | None
|
Maximum concurrent tasks per agent.
Agents at or above this limit are excluded from scoring.
|
required_capability |
CapabilityLevel | None
|
The rung this work demands, from the stakes
floor raised by substantial complexity. Not a hard filter: it is
the target of the capability ladder, which prefers an exact match,
then the nearest rung above, then (where the stakes allow) the
nearest rung below. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
task(Task) -
available_agents(tuple[AgentIdentity, ...]) -
workloads(tuple[AgentWorkload, ...]) -
min_score(float) -
low_confidence_score(float) -
required_skills(tuple[NotBlankStr, ...]) -
required_role(NotBlankStr | None) -
max_concurrent_tasks(int | None) -
required_capability(CapabilityLevel | None)
Validators:
-
_validate_collections
low_confidence_score
pydantic-field
¶
Score below which a winning fit is treated as low-confidence. The fit is still assigned (never a hard-fail), but flagged: high/critical stakes log an operator-facing escalation for review, low/normal only flag. Clamped to at least min_score via effective_low_confidence_score.
max_concurrent_tasks
pydantic-field
¶
Maximum concurrent tasks per agent. Agents at or above this limit are excluded from scoring. None = no limit.
required_capability
pydantic-field
¶
Capability rung this work demands. None imposes no requirement.
stakes
property
¶
Read the task's own stakes.
Derived rather than carried so there is one owner. As a field it
defaulted to NORMAL with nothing tying it to the task, so a
caller could hand a critical task to assignment under a normal
floor and a normal low-confidence band, and neither the floor nor
the escalation would say the stakes it read were not the task's.
Returns:
| Type | Description |
|---|---|
Stakes
|
The task's assessed stakes. |
effective_low_confidence_score
property
¶
The low-confidence band, clamped to at least min_score.
A raised eligibility floor above the configured band collapses the marginal zone (there is nothing between the two), so the band never sits below the floor.
Returns:
| Type | Description |
|---|---|
float
|
|
AssignmentResult
pydantic-model
¶
Bases: BaseModel
Result of a task assignment operation.
Attributes:
| Name | Type | Description |
|---|---|---|
task_id |
NotBlankStr
|
ID of the task that was assigned. |
strategy_used |
NotBlankStr
|
Name of the strategy that produced this result. |
selected |
AssignmentCandidate | None
|
The selected candidate (None if no viable agent). |
alternatives |
tuple[AssignmentCandidate, ...]
|
Other candidates considered, ranked by score. |
reason |
NotBlankStr
|
Human-readable explanation of the assignment decision. |
low_confidence |
bool
|
Whether the selected candidate cleared eligibility but scored below the low-confidence band (a marginal fit that was assigned anyway and flagged; high/critical stakes additionally log an operator-facing escalation for review). |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
task_id(NotBlankStr) -
strategy_used(NotBlankStr) -
selected(AssignmentCandidate | None) -
low_confidence(bool) -
alternatives(tuple[AssignmentCandidate, ...]) -
reason(NotBlankStr)
Validators:
-
_validate_selected_not_in_alternatives
low_confidence
pydantic-field
¶
Selected candidate scored below the low-confidence band
service
¶
Task assignment service.
Orchestrates task assignment by delegating to a pluggable
TaskAssignmentStrategy with logging and validation.
TaskAssignmentService
¶
Orchestrates task assignment via a pluggable strategy.
Validates task status and stamps the capability the work demands onto the request before delegating to the strategy. Does NOT mutate the task -- callers are responsible for any subsequent status transitions.
The requirement is derived here rather than by each caller so every
assignment asks for the same rung: an agent is a fixed
(role, personality, model) unit, and the answer to work that needs
more capability is a different agent, so which agents are eligible must
not depend on which caller assembled the request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
strategy
|
TaskAssignmentStrategy
|
The assignment strategy to delegate to. |
required |
capability
|
CapabilityPolicy | None
|
The org's one capability policy. |
None
|
Source code in src/synthorg/engine/assignment/service.py
assign
¶
Assign a task to an agent using the configured strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
AssignmentRequest
|
The assignment request. Its |
required |
Returns:
| Type | Description |
|---|---|
AssignmentResult
|
Assignment result from the strategy. |
Raises:
| Type | Description |
|---|---|
TaskAssignmentError
|
If the task status is not eligible for assignment. |
Source code in src/synthorg/engine/assignment/service.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
Error Classification¶
models
¶
Classification result models for the error taxonomy pipeline.
Defines severity levels, individual error findings, and aggregated classification results produced by the detection pipeline.
ErrorSeverity
¶
Bases: StrEnum
Severity level for a detected coordination error.
ErrorFinding
pydantic-model
¶
Bases: BaseModel
A single coordination error detected during classification.
Attributes:
| Name | Type | Description |
|---|---|---|
category |
ErrorCategory
|
The error category from the taxonomy. |
severity |
ErrorSeverity
|
Severity level of the finding. |
description |
NotBlankStr
|
Human-readable description of the error. |
evidence |
tuple[NotBlankStr, ...]
|
Supporting evidence extracted from the conversation. |
turn_range |
tuple[int, int] | None
|
(start, end) 0-based index range where the error
was observed, or |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
category(ErrorCategory) -
severity(ErrorSeverity) -
description(NotBlankStr) -
evidence(tuple[NotBlankStr, ...]) -
turn_range(tuple[int, int] | None)
Validators:
-
_validate_turn_range
turn_range
pydantic-field
¶
0-based index range (start, end) where error was observed. For conversation-based detectors this is the message index in the conversation tuple; for turn-based detectors this is the index into the turns tuple.
ClassificationResult
pydantic-model
¶
Bases: BaseModel
Aggregated result from the error classification pipeline.
Attributes:
| Name | Type | Description |
|---|---|---|
execution_id |
NotBlankStr
|
Unique identifier for the execution run. |
agent_id |
NotBlankStr
|
Agent that was executing. |
task_id |
NotBlankStr
|
Task being executed. |
categories_checked |
tuple[ErrorCategory, ...]
|
Which error categories were checked. |
findings |
tuple[ErrorFinding, ...]
|
All detected error findings. |
classified_at |
AwareDatetime
|
Timestamp when classification completed. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
execution_id(NotBlankStr) -
agent_id(NotBlankStr) -
task_id(NotBlankStr) -
categories_checked(tuple[ErrorCategory, ...]) -
findings(tuple[ErrorFinding, ...]) -
classified_at(AwareDatetime)
Validators:
-
_validate_findings_match_categories
pipeline
¶
Error classification pipeline.
Orchestrates the detection of coordination errors from an execution
result using the configured error taxonomy. Detectors are discovered
dynamically from the ErrorTaxonomyConfig.detectors dict and
dispatched via the Detector protocol. The pipeline never raises
exceptions -- all errors are caught and logged.
classify_execution_errors
async
¶
classify_execution_errors(
execution_result,
agent_id,
task_id,
*,
config,
task_repo=None,
provider=None,
sinks=(),
)
Classify coordination errors from an execution result.
Discovers detectors from config.detectors, loads
scope-appropriate context, runs detectors sequentially
(concurrency happens inside CompositeDetector), and
dispatches results to registered sinks.
Rate limiting is handled by the BaseCompletionProvider
internally; semantic detectors do not take a separate rate limiter
because a second limiter on the same shared instance would
double-throttle.
Returns None when the taxonomy is disabled. Never raises;
all exceptions except MemoryError/RecursionError are
caught and logged as CLASSIFICATION_ERROR.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
execution_result
|
ExecutionResult
|
The completed execution result to analyse. |
required |
agent_id
|
NotBlankStr
|
Agent that executed the task. |
required |
task_id
|
NotBlankStr
|
Task that was executed. |
required |
config
|
ErrorTaxonomyConfig
|
Error taxonomy configuration. |
required |
task_repo
|
TaskRepository | None
|
Optional task repository for TASK_TREE scope. |
None
|
provider
|
CompletionProvider | None
|
Optional LLM provider for semantic detectors. |
None
|
sinks
|
tuple[ClassificationSink, ...]
|
Downstream consumers to notify after classification. |
()
|
Returns:
| Type | Description |
|---|---|
ClassificationResult | None
|
Classification result with findings, or |
Source code in src/synthorg/engine/classification/pipeline.py
Workspace Isolation¶
protocol
¶
Workspace isolation strategy protocol.
WorkspaceIsolationStrategy
¶
Bases: Protocol
Protocol for workspace isolation strategies.
Implementations provide the ability to create, merge, and tear down isolated workspaces for concurrent agent execution.
setup_workspace
async
¶
Create an isolated workspace for an agent task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
WorkspaceRequest
|
Workspace creation request. |
required |
Returns:
| Type | Description |
|---|---|
Workspace
|
The created workspace. |
Raises:
| Type | Description |
|---|---|
WorkspaceLimitError
|
When max concurrent worktrees reached. |
WorkspaceSetupError
|
When git operations fail. |
Source code in src/synthorg/engine/workspace/protocol.py
teardown_workspace
async
¶
Remove an isolated workspace and clean up resources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workspace
|
Workspace
|
The workspace to tear down. |
required |
Raises:
| Type | Description |
|---|---|
WorkspaceCleanupError
|
When git cleanup operations fail. |
Source code in src/synthorg/engine/workspace/protocol.py
merge_workspace
async
¶
Merge a workspace branch back into the base branch.
Merge conflicts are returned as a MergeResult with
success=False rather than raised as exceptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workspace
|
Workspace
|
The workspace to merge. |
required |
Returns:
| Type | Description |
|---|---|
MergeResult
|
The merge result with conflict details if any. |
Raises:
| Type | Description |
|---|---|
WorkspaceMergeError
|
When checkout or merge abort fails. |
Source code in src/synthorg/engine/workspace/protocol.py
list_active_workspaces
async
¶
Return all currently active workspaces.
Returns:
| Type | Description |
|---|---|
tuple[Workspace, ...]
|
Tuple of active workspaces. |
get_strategy_type
¶
models
¶
Workspace isolation domain models.
WorkspaceRequest
pydantic-model
¶
Bases: BaseModel
Request to create an isolated workspace for an agent task.
Attributes:
| Name | Type | Description |
|---|---|---|
task_id |
NotBlankStr
|
Identifier of the task requiring isolation. |
agent_id |
NotBlankStr
|
Identifier of the agent that will work in the workspace. |
base_branch |
NotBlankStr
|
Git branch to branch from. |
file_scope |
tuple[NotBlankStr, ...]
|
Optional file path hints for the workspace. |
project_id |
NotBlankStr | None
|
Owning project. Selects the per-project repo tree
( |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
task_id(NotBlankStr) -
agent_id(NotBlankStr) -
base_branch(NotBlankStr) -
file_scope(tuple[NotBlankStr, ...]) -
project_id(NotBlankStr | None)
Workspace
pydantic-model
¶
Bases: BaseModel
An active isolated workspace backed by a git worktree.
Attributes:
| Name | Type | Description |
|---|---|---|
workspace_id |
NotBlankStr
|
Unique identifier for this workspace. |
task_id |
NotBlankStr
|
Task this workspace serves. |
agent_id |
NotBlankStr
|
Agent operating in this workspace. |
branch_name |
NotBlankStr
|
Git branch created for this workspace. |
worktree_path |
NotBlankStr
|
Filesystem path to the worktree directory. |
base_branch |
NotBlankStr
|
Branch this workspace was created from. |
created_at |
datetime
|
Timestamp of workspace creation. |
project_id |
NotBlankStr | None
|
Owning project. Selects the per-project repo tree
for merge / teardown so they run in the same repo the
worktree was created from; |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
workspace_id(NotBlankStr) -
task_id(NotBlankStr) -
agent_id(NotBlankStr) -
branch_name(NotBlankStr) -
worktree_path(NotBlankStr) -
base_branch(NotBlankStr) -
created_at(datetime) -
project_id(NotBlankStr | None)
MergeConflict
pydantic-model
¶
Bases: BaseModel
A single merge conflict detected during workspace merge.
Attributes:
| Name | Type | Description |
|---|---|---|
file_path |
NotBlankStr
|
Path of the conflicting file. |
conflict_type |
ConflictType
|
Type of conflict (e.g. textual, semantic). |
ours_content |
str
|
Content from the base branch side. |
theirs_content |
str
|
Content from the workspace branch side. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
file_path(NotBlankStr) -
conflict_type(ConflictType) -
ours_content(str) -
theirs_content(str) -
description(str)
Validators:
-
_validate_semantic_description
MergeResult
pydantic-model
¶
Bases: BaseModel
Result of merging a single workspace branch back.
Attributes:
| Name | Type | Description |
|---|---|---|
workspace_id |
NotBlankStr
|
Workspace that was merged. |
branch_name |
NotBlankStr
|
Branch that was merged. |
success |
bool
|
Whether the merge completed without conflicts. |
conflicts |
tuple[MergeConflict, ...]
|
Any textual conflicts encountered during merge. |
escalation |
ConflictEscalation | None
|
Escalation strategy applied, if any. |
merged_commit_sha |
NotBlankStr | None
|
SHA of the merge commit, if successful. |
duration_seconds |
float
|
Time taken for the merge operation. |
semantic_conflicts |
tuple[MergeConflict, ...]
|
Semantic conflicts detected after merge. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
workspace_id(NotBlankStr) -
branch_name(NotBlankStr) -
success(bool) -
conflicts(tuple[MergeConflict, ...]) -
escalation(ConflictEscalation | None) -
merged_commit_sha(NotBlankStr | None) -
duration_seconds(float) -
semantic_conflicts(tuple[MergeConflict, ...])
Validators:
-
_validate_success_consistency
semantic_conflicts
pydantic-field
¶
Semantic conflicts detected after successful merge
WorkspaceGroupResult
pydantic-model
¶
Bases: BaseModel
Aggregated result of merging a group of workspaces.
Attributes:
| Name | Type | Description |
|---|---|---|
group_id |
NotBlankStr
|
Identifier for this merge group. |
merge_results |
tuple[MergeResult, ...]
|
Individual merge results for each workspace. |
duration_seconds |
float
|
Total time for the group merge operation. |
Config:
frozen:Trueallow_inf_nan:Falseextra:forbid
Fields:
-
group_id(NotBlankStr) -
merge_results(tuple[MergeResult, ...]) -
duration_seconds(float)
total_semantic_conflicts
property
¶
Sum of semantic conflicts from all merge results.
service
¶
Workspace isolation service.
High-level service that coordinates workspace lifecycle: setup, merge, and teardown for groups of agent workspaces.
WorkspaceIsolationService
¶
WorkspaceIsolationService(
*, strategy, config, git_backend=None, default_branch=_DEFAULT_BRANCH, clock=None
)
Service for managing workspace isolation lifecycle.
Coordinates creating, merging, and tearing down workspaces for groups of concurrent agent tasks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
strategy
|
WorkspaceIsolationStrategy
|
Workspace isolation strategy implementation. |
required |
config
|
WorkspaceIsolationConfig
|
Workspace isolation configuration. |
required |
Source code in src/synthorg/engine/workspace/service.py
setup_group
async
¶
Create workspaces for a group of agent tasks.
Rolls back all already-created workspaces if any setup fails.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requests
|
tuple[WorkspaceRequest, ...]
|
Workspace creation requests. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Workspace, ...]
|
Tuple of created workspaces. |
Raises:
| Type | Description |
|---|---|
WorkspaceLimitError
|
When max concurrent worktrees reached. |
WorkspaceSetupError
|
When git operations fail. |
Source code in src/synthorg/engine/workspace/service.py
merge_group
async
¶
Merge all workspaces and return aggregated result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workspaces
|
tuple[Workspace, ...]
|
Workspaces to merge. |
required |
Returns:
| Type | Description |
|---|---|
WorkspaceGroupResult
|
Aggregated merge result for the group. |
Raises:
| Type | Description |
|---|---|
WorkspaceMergeError
|
When a merge operation fails fatally. |
Source code in src/synthorg/engine/workspace/service.py
merge_workspace_with_push
async
¶
Merge workspace then push the default branch, serialised.
When no git backend is wired the merge still runs (via the strategy) but nothing is pushed -- this keeps the call site uniform whether or not durable backing is configured.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workspace
|
Workspace
|
The agent workspace to merge back. |
required |
project_id
|
NotBlankStr
|
Owning project (selects the serial queue). |
required |
repo_root
|
Path
|
Project working tree the push runs from. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
The |
MergeResult
|
class: |
Raises:
| Type | Description |
|---|---|
WorkspaceMergeError
|
The merge failed fatally. |
WorkspacePushError
|
The backend push failed. |
Source code in src/synthorg/engine/workspace/service.py
shutdown
async
¶
Stop every per-project push queue (best-effort, all attempted).
Source code in src/synthorg/engine/workspace/service.py
teardown_group
async
¶
Tear down all workspaces in a group.
Uses best-effort teardown: attempts all workspaces even if some fail, then raises a combined error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workspaces
|
tuple[Workspace, ...]
|
Workspaces to tear down. |
required |
Raises:
| Type | Description |
|---|---|
WorkspaceCleanupError
|
When any teardown operation fails. |
Source code in src/synthorg/engine/workspace/service.py
enums
¶
Workspace merge domain enumerations.
MergeOrder
¶
Bases: StrEnum
Order in which workspace branches are merged back.
Determines the sequence of merge operations when multiple agent workspaces are being merged into the base branch.
ConflictEscalation
¶
Bases: StrEnum
Strategy for handling merge conflicts during workspace merges.
Controls whether merging stops for human review or continues with an automated review agent flagging conflicts.
ConflictType
¶
Bases: StrEnum
Type of merge conflict detected during workspace merges.
Workflow Enums¶
enums
¶
Workflow subsystem enumerations.
WorkflowType
¶
Bases: StrEnum
Workflow type for organizing task execution.
Matches the four workflow types defined in the Engine design page (docs/design/engine.md, Workflow Types section).
WorkflowNodeType
¶
Bases: StrEnum
Node type in a visual workflow definition.
Each node represents a step or control-flow element in the visual workflow editor.
WorkflowValueType
¶
Bases: StrEnum
Typed value kinds for workflow I/O declarations.
Used by :class:WorkflowIODeclaration to enforce typed contracts
on subworkflow inputs and outputs at save time and at runtime.
WorkflowEdgeType
¶
Bases: StrEnum
Edge type connecting nodes in a visual workflow definition.
Encodes the relationship semantics between workflow nodes.
WorkflowExecutionStatus
¶
Bases: StrEnum
Lifecycle status of a workflow execution instance.
Tracks the overall progress of an activated workflow definition from creation through completion or cancellation.
WorkflowNodeExecutionStatus
¶
Bases: StrEnum
Per-node execution status within a workflow execution.
Tracks whether each node in the workflow graph has been processed, skipped (conditional branch not taken), or resulted in a concrete task.
Agent Runtime Status¶
ExecutionStatus
¶
Bases: StrEnum
Runtime execution status of an agent.
Tracks whether an agent is currently executing, paused (e.g. waiting
for approval), or idle. Used by AgentRuntimeState for dashboard
queries and graceful-shutdown discovery.
Recovery Failure Category¶
FailureCategory
¶
Bases: StrEnum
Machine-readable failure classification for recovery results.
Used by RecoveryResult to provide structured failure diagnosis
that enables smarter checkpoint reconciliation and task reassignment
routing. UNKNOWN is the honest default for error messages that
cannot be confidently classified -- it is explicit rather than a
silent TOOL_FAILURE lie.
Review Decision Outcome¶
DecisionOutcome
¶
Bases: StrEnum
Outcome of a review gate decision.
Used by DecisionRecord for the auditable decisions drop-box.
Operator Intervention¶
enums
¶
Operator intervention domain enumerations.
InterventionKind
¶
Bases: StrEnum
Operator intervention applied from the mission-control cockpit.
PAUSE and KILL reuse the task lifecycle seams (transition to
INTERRUPTED / cancel to CANCELLED). HINT and REDIRECT route
through the steering directive: both post an INFO_REQUEST
interrupt the engine consumes at the next safe turn boundary, so the
operator's text reaches the running agent without corrupting state.