# Perceptic Workflow SDK

Python SDK for Perceptic Workflow definitions. This package provides cross-language Temporal workflow types that are compatible with the Java workflow definitions in perceptic-core-client.

## Installation

```bash
uv add perceptic-workflow-sdk
```

## Usage

### Runtime Input Contract

Workflows started through Perceptic Core receive plain JSON input data.

- The first `@workflow.run` argument should be a JSON-compatible structure (`dict[str, Any]` by default).
- Perceptic Core does not wrap inputs in an SDK envelope type.
- Consumers are responsible for interpreting and validating the JSON shape they expect.

### Workflow Events

```python
from perceptic_workflow import (
    WorkflowEvent,
    InfoEvent,
    CheckpointEvent,
    UserInputEvent,
    UserInputRequestEvent,
    WorkflowCheckpointStatus,
)

# Create events
info_event = InfoEvent(
    event_id=1,
    type="progress",
    payload={"step": "processing"},
    timestamp=datetime.now()
)
```

### Implementing Workflows

The SDK provides a mixin class for implementing Perceptic-compatible workflows:

```python
from typing import Any
from pydantic import BaseModel
from temporalio import workflow
from perceptic_workflow import PercepticWorkflow, WorkflowEvent


class ResearchInput(BaseModel):
    query: str
    context: str

@workflow.defn
class MyWorkflow(PercepticWorkflow):
    def __init__(self):
        self._events: list[WorkflowEvent] = []
        self._paused = False

    @workflow.run
    async def run(self, inputs: dict[str, Any]) -> dict:
        request = ResearchInput.model_validate(inputs)

        return {
            "query": request.query,
            "context": request.context,
        }

    @workflow.query(name=PercepticWorkflow.QUERY_GET_STATUS)
    def get_status(self) -> WorkflowStatus:
        return WorkflowStatus(status="running", description="Waiting for input" if self._paused else "Running")

    @workflow.query(name=PercepticWorkflow.QUERY_GET_EVENTS)
    def get_events(self, after_event_id: int | None = None) -> dict[str, Any]:
        if after_event_id is None:
            return {"events": self._events}
        return {"events": [e for e in self._events if e["eventId"] > after_event_id]}
```

### Decorator Validation

`PercepticWorkflow` no longer performs runtime validation of Temporal handler
decorators during class creation. This avoids import-time failures for abstract
workflow bases and mixin-based inheritance patterns.

Handler contract enforcement is expected to happen through linting and type-check
tooling in your development pipeline:

```bash
uv run ty check
uv run workflow-typecheck
```

#### `workflow-typecheck` implementation notes

`workflow-typecheck` is implemented by
`src/perceptic_workflow/_checks/workflow_decorator_check.py` and performs a static
AST scan of Python classes that inherit from `PercepticWorkflow` (or from a
class that already does). For the known handler methods, it enforces:

- the method has a Temporal handler decorator (`@workflow.query` or
  `@workflow.update`)
- the decorator kind matches the expected contract
- the `name=` argument matches the `PercepticWorkflow` handler constant

Current diagnostics:

- `PWF001`: expected Temporal decorator is missing
- `PWF002`: wrong decorator kind (`query` vs `update`)
- `PWF003`: wrong or missing handler `name=...`

How to extend the check:

- Add or update required handlers in `REQUIRED_HANDLERS` when a new Perceptic
  handler method is added or an existing one changes.
- Add base class names to `MIXIN_COMPATIBLE_BASES` if additional workflow base
  classes should be recognized as Perceptic-compatible roots.
- Extend `_extract_name_kwarg` / `_is_workflow_decorator_call` if we support
  additional decorator call shapes or name expressions.

When this check must be updated:

- A `PercepticWorkflow` handler method is renamed, added, removed, or changes
  from query to update (or vice versa).
- Any `PercepticWorkflow.<HANDLER_CONSTANT>` used in handler decorators is
  renamed or replaced.
- We introduce a new inheritance pattern for workflow mixins or a new canonical
  workflow base class.
- We change how Temporal decorators are authored (for example, different
  decorator access paths or non-current `name=` expression style).

How to edit safely:

1. Update the contract mappings first (`REQUIRED_HANDLERS`,
   `MIXIN_COMPATIBLE_BASES`).
2. Keep diagnostics specific and actionable (`PWF001`-`PWF003` style).
3. Add or update tests in `tests/test_workflow_typecheck_regression.py` for the
   new/changed behavior.
4. Validate with:
   - `uv run workflow-typecheck`
   - `uv run pytest tests/test_workflow_typecheck_regression.py`
   - `uv run ty check`

### Troubleshooting

- `ValidationError` during workflow activation usually means your `@workflow.run` signature expects the wrong input shape.
- Use a JSON-compatible first workflow argument (for example `dict[str, Any]`), then validate/coerce into your domain model inside `run`.
- For run-start requests, send `inputs` only; Perceptic Core forwards that JSON payload directly.

## Compatibility

This package is designed to be compatible with:

- perceptic-core-client (Java)
- perceptic-core-workflow-runtime (Java)

The workflow definitions and event types are generated from the same JSON Schema sources to ensure cross-language compatibility.
