Skip to main content

PAWC Core: shared SDK for workflow models, state, layout, and orchestration

Project description

pawc-kit

pawc-kit is a Python library for multi-phase execution and review workflows: execution graphs from native config.yaml, discovery graphs from DiscoveryConfig, filesystem-backed default adapters, and a stable LLM integration layer (backends, structured output, prompt assembly, LLM roles). Sync and async engines and sessions are both first-class.

The supported public surface is:

  • pawc_kit (slim: version, utc_now, config loaders, WorkflowSession, AsyncWorkflowSession)
  • pawc_kit.config
  • pawc_kit.contracts
  • pawc_kit.workflow
  • pawc_kit.ports
  • pawc_kit.adapters (built-in adapters; submodules like adapters.fs remain valid)
  • pawc_kit.context
  • pawc_kit.llm

Example YAML by schema (RootConfig, DiscoveryConfig, RoleConfig) lives under templates/ — see templates/README.md. Field-by-field native config.yaml reference: docs/workflow-config-reference.md. How the library is layered (contracts → ports → workflow → adapters): docs/architecture.md. For the HTTP control plane and server-side deployment, see the pawc-server repository (docs/architecture.md, templates/README.md).

Install

Using uv:

uv sync --dev

Core dependencies include semver for SemVer 2.0 validation of skill/role and session version fields.

Optional extras:

  • OpenTelemetry — metrics and tracing: uv sync --dev --extra otel or pip install -e .[otel]
  • Semantic compressionsemantic-text-splitter for chunk-based prompt compression: uv sync --dev --extra semantic or pip install -e .[semantic]

Requires Python 3.12+.

Releases

The repo includes python-semantic-release configuration for SemVer bumps, CHANGELOG.md, and v* release tags based on conventional commits (see CONTRIBUTING.md).

Quick Start (config-driven)

The recommended way to run a workflow is via WorkflowSession, which reads config.yaml and builds the workflow graph, engine policy, and directory layout from it. Only role bindings and optional runtime objects (observer, clock) are supplied in code.

config.yaml

skill:
  name: my-skill
  version: "1.0.0"
state_directory: sessions

workflow:
  phases:
    - phase_id: work
      role_id: worker
      kind: executor
      on_complete: [review]
    - phase_id: review
      role_id: reviewer
      kind: review
      can_request_changes_from: [work]
  confidence_threshold: 85
  max_iterations: 10
  max_feedback_rounds: 3
  run_directory: sessions/execution
  state_filename: state.json

observability:
  observer: otel           # "otel" | "logging" | "none" (default: none)
  meter_name: pawc_kit.workflow   # for otel; ignored when observer != "otel"
  tracer_name: pawc_kit.workflow   # for otel; ignored when observer != "otel"
  logger_name: pawc_kit.workflow  # for logging; ignored when observer != "logging"

Python

from pawc_kit import WorkflowSession

session = WorkflowSession.from_config("config.yaml")
session.register_role("worker", my_worker)
session.register_role("reviewer", my_reviewer)

state = session.run(session_id="run-001")
print(state.status)

Any workflow.* value can be overridden in code when needed:

session = WorkflowSession.from_config(
    "config.yaml",
    confidence_threshold=90,   # override config value
    run_directory="sessions/custom",
)

WorkflowSession.run() is resumable — calling it again with the same session_id picks up where it left off.

Async equivalent (from_config is synchronous; run is async):

from pawc_kit import AsyncWorkflowSession

session = AsyncWorkflowSession.from_config("config.yaml")
session.register_role("worker", my_async_worker)
session.register_role("reviewer", my_async_reviewer)
state = await session.run(session_id="run-001")

Low-level engine usage

For full control over stores and paths, use WorkflowEngine directly:

from pathlib import Path
from pawc_kit.adapters.fs import FsArtifactStore, FsStateStore
from pawc_kit.workflow import WorkflowEngine

run_dir = Path(".tmp") / "demo-run"
engine = WorkflowEngine(graph, FsStateStore(run_dir), FsArtifactStore(run_dir))
engine.register_role("worker", my_worker)
engine.register_role("reviewer", my_reviewer)

state = engine.run(
    session_id="session-1",
    skill_name="demo-skill",
    skill_version="0.1.0",
)

WorkflowEngine keeps the active session state in memory and only persists at commit points:

  • run start
  • phase transition
  • iteration commit
  • review commit
  • finalize

You can pass a ContextPack into session.run(context_pack=pack) or engine.run(context_pack=pack). The engine scopes it per phase via PhaseDefinition.context_sources and injects it into ExecutionContext.context and ReviewContext.context for roles.

Stable API

Config Infrastructure

  • load_yaml_config, load_root_config, load_role_config
  • RootConfig, SkillConfig, ContextConfig, EfficiencyConfig, RoleConfig
  • ContextInjectionConfig, CompressionConfig, ChunkPolicyConfig
  • WorkflowSession, AsyncWorkflowSession
  • LayoutManager
  • ContextPack, load_context_pack, accessible_packs
  • check_quality_gates, validate_composition

Contracts

  • SessionState, IterationEntry, ReviewEntry, ArtifactRef
  • DecisionPayload, HandoffContext, handoff artifact types (HandoffArtifact, …)
  • Discovery: DiscoveryConfig, DiscoveryPhaseConfig, QuestionEntry, …
  • Events: RunStarted, RunCompleted, PhaseStarted, IterationCommitted, ReviewCommitted, … plus event_to_dict / event_from_dict helpers
  • Errors: PawcError, LLMError, ConcurrencyError, …

Workflow

  • PhaseDefinition, PhaseGraph (including discovery-shaped graphs when built from DiscoveryConfig)
  • ExecutionContext, ReviewContext
  • ExecutionResult, ReviewDecision, ReviewResult
  • WorkflowEngine, AsyncWorkflowEngine
  • Executor, Reviewer, AsyncExecutor, AsyncReviewer

Ports

  • StateStore, AsyncStateStore
  • ArtifactStore, AsyncArtifactStore
  • WorkflowObserver, AsyncWorkflowObserver
  • Clock, AsyncClock
  • ContextCompressor — pluggable text compression for prompt injection

Filesystem Adapters

  • FsStateStore, AsyncFsStateStore
  • FsArtifactStore, AsyncFsArtifactStore

Generic Config Loading

The config/ subpackage provides a generic YAML loader so that no consumer needs to implement its own config processing:

from pydantic import BaseModel
from pawc_kit import load_yaml_config

class RunnerConfig(BaseModel):
    max_retries: int = 3

# Flat config
cfg = load_yaml_config("my-config.yaml", RunnerConfig)

# Keyed config (e.g. runner-config.yaml with top-level "runner:" key)
cfg = load_yaml_config("runner-config.yaml", RunnerConfig, root_key="runner")

Convenience wrappers for PAWC-defined config types:

from pawc_kit import load_root_config, load_role_config

root = load_root_config("config.yaml")           # -> RootConfig
role = load_role_config("worker/config.yaml")     # -> RoleConfig

LLM Integrations

LLM helpers are available under the stable namespace:

from pawc_kit.llm import (
    MockBackend,
    LLMExecutorRole,
    LLMReviewerRole,
    StructuredOutput,
    AsyncStructuredOutput,
)

Backends implement LLMBackend / AsyncLLMBackend (complete(..., max_tokens: int | None = None)). StructuredOutput / AsyncStructuredOutput call the backend, parse JSON into Pydantic models (trying multiple fenced blocks / extractions), and retry on validation failure; async structured output supports optional exponential backoff between retries (retry_delay on AsyncStructuredOutput).

Context injection and compression

Context pack data (request files, discovery handoff, child packs) is injected into executor and reviewer prompts according to RootConfig.context_injection (ContextInjectionConfig). You control:

  • What to includeinclude_request_files, include_discovery, include_children
  • Filteringfile_allowlist, file_blocklist, max_file_chars, discovery_sections
  • Compressioncompression.mode: "simple" (default regex-based MarkdownCompressor), "semantic" (chunk → classify → policy → reassemble, requires pawc-kit[semantic]), or "none" (PassthroughCompressor)

With mode: "semantic", per-chunk-type policies are configurable in YAML (compression.policies): heading, paragraph, list, code, table, diagram, each with action (keep / truncate / collapse / strip) and optional limits (max_sentences, max_items, max_lines, max_rows). The semantic pipeline uses semantic-text-splitter as the boundary oracle, then a heuristic classifier and your policies.

Implementations: MarkdownCompressor, PassthroughCompressor, SemanticCompressor; all implement the ContextCompressor protocol. The prompt builder resolves the compressor from config when building request and discovery sections; an explicit compressor argument overrides.

Observability

pawc_kit emits immutable workflow events through the WorkflowObserver port. Built-in adapters are available for standard-library logging and OpenTelemetry.

Config-driven observer selection

Set observability.observer in config.yaml to auto-construct an observer without writing any wiring code. The session resolves the observer with the following precedence:

  1. Explicit observer=SomeObserver() kwarg wins.
  2. Explicit observer=None suppresses the observer (even if config says otel).
  3. Omitted kwarg -- auto-constructed from config.observability (default: "none").
observability:
  observer: otel     # "otel" | "logging" | "none"

The otel value requires pawc-kit[otel]. An explicit observer= kwarg on WorkflowSession / AsyncWorkflowSession always takes precedence.

Logging adapter

import logging

from pawc_kit.adapters import LoggingWorkflowObserver

logging.basicConfig(level=logging.INFO)
logging.getLogger("pawc_kit.workflow").setLevel(logging.DEBUG)

observer = LoggingWorkflowObserver()

Level selection follows normal Python library conventions:

  • configure pawc_kit.workflow to request lifecycle logs at DEBUG, INFO, WARNING, or ERROR
  • configure pawc_kit.llm to include structured-output retry and failure logs
  • the library installs a NullHandler on pawc_kit and never calls basicConfig()

Default logging adapter level mapping:

  • INFO: RunStarted, RunResumed, successful RunCompleted
  • DEBUG: PhaseStarted, PhaseTransitioned, IterationCommitted, approved ReviewCommitted
  • WARNING: ReviewCommitted with REQUEST_CHANGES, abandoned RunCompleted
  • ERROR: RunFailed

OpenTelemetry adapter

Requires pawc-kit[otel]. Handles all 8 workflow event types with metrics (counters and histograms) and traces (nested spans).

from pawc_kit.adapters import OpenTelemetryWorkflowObserver

observer = OpenTelemetryWorkflowObserver(
    meter_name="pawc_kit.workflow",
    tracer_name="pawc_kit.workflow",
)

Span hierarchy (parent → child): runphaseiteration / review.

Span name Created on Ended on Typical attributes
pawc.workflow.run RunStarted, RunResumed RunCompleted, RunFailed session_id, phase_id, resumed, skill_name (when RunStarted); on completion: run.status, run.feedback_loops or error.type / error.message
pawc.workflow.phase PhaseStarted PhaseTransitioned, run end session_id, phase_id, role_id, phase_kind
pawc.workflow.iteration IterationCommitted same event (duration from event timestamps) session_id, phase_id, iteration, confidence_score
pawc.workflow.review ReviewCommitted same event (duration from event timestamps) session_id, phase_id, review, decision, confidence_score

Metrics emitted:

Instrument Type Attributes Event
pawc.workflow.runs counter phase_id, outcome RunStarted, RunCompleted
pawc.workflow.run_failures counter phase_id RunFailed
pawc.workflow.iterations counter phase_id, outcome IterationCommitted
pawc.workflow.reviews counter phase_id, outcome ReviewCommitted
pawc.workflow.phases counter phase_id, phase_kind PhaseStarted
pawc.workflow.transitions counter from_phase, to_phase PhaseTransitioned
pawc.workflow.resumes counter phase_id, phase_kind RunResumed
pawc.workflow.run.duration.seconds histogram outcome RunCompleted
pawc.workflow.iteration.duration.seconds histogram phase_id IterationCommitted
pawc.workflow.review.duration.seconds histogram phase_id ReviewCommitted

Design notes:

  • Metrics: low-cardinality attributes only; session_id is never included in metric labels.
  • Traces: session_id is included on spans (expected for request-scoped traces and distinct from the metrics policy).
  • Threading: the observer assumes single-threaded event delivery per session (same model as the default filesystem stores); span state is not locked.
  • Defensive behavior: if events arrive without a parent span (e.g. PhaseStarted before RunStarted), child spans are still recorded as roots; duplicate RunStarted for the same session ends the previous run span before opening a new one.
  • AsyncOpenTelemetryWorkflowObserver delegates to the sync observer (OTEL SDK calls are CPU-bound)

Architecture

Narrative and diagrams: docs/architecture.md (execution vs discovery config, async vs sync, related docs).

The public architecture is split into layers:

flowchart TD
    contracts["contracts<br/>models, events, errors"]
    ports["ports<br/>abstract interfaces"]
    workflow["workflow<br/>graph, roles, engine"]
    adapters["adapters<br/>filesystem, logging, OpenTelemetry"]
    config["config<br/>YAML loading and validation"]
    llm["llm<br/>backends, prompts, roles, structured output, compressors"]
    session["session.py / async_session.py<br/>config-driven orchestration"]
    context["context.py<br/>context pack loading and scoping"]
    layout["layout.py<br/>run directory management"]
    validators["validators.py<br/>composition and quality gates"]

    contracts --> ports
    contracts --> workflow
    contracts --> adapters
    contracts --> config
    contracts --> llm
    ports --> workflow
    ports --> adapters
    workflow --> llm
    config --> session
    config --> context
    config --> llm
    workflow --> session
    workflow --> llm
    adapters --> session
    context --> workflow
    validators --> context
    validators --> llm
    layout --> session

Execution wiring follows the same direction: config/session code builds the graph and adapters, then hands execution to the workflow engine and role implementations.

  1. contracts — pure data models and error types
  2. ports — abstract interfaces (state, artifacts, observers, clock)
  3. workflow — engine, phase graph, role protocols
  4. adapters — filesystem and observability implementations
  5. config — YAML config loading and validation infrastructure
  6. llm — LLM backend integration (roles, structured output, prompts, context injection, compressors: MarkdownCompressor, SemanticCompressor, PassthroughCompressor)

Top-level modules bridge config with the engine:

  • session.pyWorkflowSession orchestrator (config -> layout -> stores -> engine)
  • context.py — context pack loading, resolution, scoping
  • layout.py — run directory structure management
  • validators.py — composition and quality-gate validation

Development

Primary local workflow:

./scripts/verify.sh   # lint (fix), test, lint-check, type-check, test

Or run individual steps: ./scripts/lint.sh, ./scripts/test.sh, ./scripts/lint-check.sh, ./scripts/type-check.sh. Use ./scripts/commit.sh "type(scope): subject" for commit message validation.

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pawc_kit-0.6.0.tar.gz (86.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pawc_kit-0.6.0-py3-none-any.whl (100.0 kB view details)

Uploaded Python 3

File details

Details for the file pawc_kit-0.6.0.tar.gz.

File metadata

  • Download URL: pawc_kit-0.6.0.tar.gz
  • Upload date:
  • Size: 86.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pawc_kit-0.6.0.tar.gz
Algorithm Hash digest
SHA256 c92a83167e3ad7ac442e0c0f26204d4f5e5240bc26a4978bb91ec35743054324
MD5 b6ded9774a07094bdae81b54cd211b1f
BLAKE2b-256 1521f39ebe00b920cefba156a33a6e7fdc52d3d2124033912a7a62d964476cee

See more details on using hashes here.

File details

Details for the file pawc_kit-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: pawc_kit-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 100.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pawc_kit-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d525d81ac2554d660ed91408e89ed3363fb545a2bb91559da6b4acfd135164a3
MD5 b31ac1d0baa269ea29db490a1229e449
BLAKE2b-256 aa0344df3b5a73758ae4cfc4a4599f5cae9eb27905d65af2e892631a9a80e3c2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page