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.configpawc_kit.contractspawc_kit.workflowpawc_kit.portspawc_kit.adapters(built-in adapters; submodules likeadapters.fsremain valid)pawc_kit.contextpawc_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 otelorpip install -e .[otel] - Semantic compression — semantic-text-splitter for chunk-based prompt compression:
uv sync --dev --extra semanticorpip 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_configRootConfig,SkillConfig,ContextConfig,EfficiencyConfig,RoleConfigContextInjectionConfig,CompressionConfig,ChunkPolicyConfigWorkflowSession,AsyncWorkflowSessionLayoutManagerContextPack,load_context_pack,accessible_packscheck_quality_gates,validate_composition
Contracts
SessionState,IterationEntry,ReviewEntry,ArtifactRefDecisionPayload,HandoffContext, handoff artifact types (HandoffArtifact, …)- Discovery:
DiscoveryConfig,DiscoveryPhaseConfig,QuestionEntry, … - Events:
RunStarted,RunCompleted,PhaseStarted,IterationCommitted,ReviewCommitted, … plusevent_to_dict/event_from_dicthelpers - Errors:
PawcError,LLMError,ConcurrencyError, …
Workflow
PhaseDefinition,PhaseGraph(including discovery-shaped graphs when built fromDiscoveryConfig)ExecutionContext,ReviewContextExecutionResult,ReviewDecision,ReviewResultWorkflowEngine,AsyncWorkflowEngineExecutor,Reviewer,AsyncExecutor,AsyncReviewer
Ports
StateStore,AsyncStateStoreArtifactStore,AsyncArtifactStoreWorkflowObserver,AsyncWorkflowObserverClock,AsyncClockContextCompressor— pluggable text compression for prompt injection
Filesystem Adapters
FsStateStore,AsyncFsStateStoreFsArtifactStore,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 include —
include_request_files,include_discovery,include_children - Filtering —
file_allowlist,file_blocklist,max_file_chars,discovery_sections - Compression —
compression.mode:"simple"(default regex-basedMarkdownCompressor),"semantic"(chunk → classify → policy → reassemble, requirespawc-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:
- Explicit
observer=SomeObserver()kwarg wins. - Explicit
observer=Nonesuppresses the observer (even if config saysotel). - 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.workflowto request lifecycle logs atDEBUG,INFO,WARNING, orERROR - configure
pawc_kit.llmto include structured-output retry and failure logs - the library installs a
NullHandleronpawc_kitand never callsbasicConfig()
Default logging adapter level mapping:
INFO:RunStarted,RunResumed, successfulRunCompletedDEBUG:PhaseStarted,PhaseTransitioned,IterationCommitted, approvedReviewCommittedWARNING:ReviewCommittedwithREQUEST_CHANGES, abandonedRunCompletedERROR: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): run → phase → iteration / 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_idis never included in metric labels. - Traces:
session_idis 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.
PhaseStartedbeforeRunStarted), child spans are still recorded as roots; duplicateRunStartedfor the same session ends the previous run span before opening a new one. AsyncOpenTelemetryWorkflowObserverdelegates 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.
contracts— pure data models and error typesports— abstract interfaces (state, artifacts, observers, clock)workflow— engine, phase graph, role protocolsadapters— filesystem and observability implementationsconfig— YAML config loading and validation infrastructurellm— LLM backend integration (roles, structured output, prompts, context injection, compressors:MarkdownCompressor,SemanticCompressor,PassthroughCompressor)
Top-level modules bridge config with the engine:
session.py—WorkflowSessionorchestrator (config -> layout -> stores -> engine)context.py— context pack loading, resolution, scopinglayout.py— run directory structure managementvalidators.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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c92a83167e3ad7ac442e0c0f26204d4f5e5240bc26a4978bb91ec35743054324
|
|
| MD5 |
b6ded9774a07094bdae81b54cd211b1f
|
|
| BLAKE2b-256 |
1521f39ebe00b920cefba156a33a6e7fdc52d3d2124033912a7a62d964476cee
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d525d81ac2554d660ed91408e89ed3363fb545a2bb91559da6b4acfd135164a3
|
|
| MD5 |
b31ac1d0baa269ea29db490a1229e449
|
|
| BLAKE2b-256 |
aa0344df3b5a73758ae4cfc4a4599f5cae9eb27905d65af2e892631a9a80e3c2
|