Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,10 @@ execution result and contract results. An agent can use this feedback to
revise the draft task and retry until the contracts pass or the loop reaches
its attempt limit.

The lowering integration example is in `examples/agent`. Its draft currently
contains an intentionally disabled OP-IR update so the contract-failure path
can be exercised; uncomment those lines to restore the successful task.
The lowering integration example lives in the Graphite repository, under
`examples/agent`. It starts from a draft task whose OP-IR update is disabled,
so the first attempt fails a contract, and runs this loop until the revised
draft passes.

### Pydantic AI task generation

Expand Down
1 change: 1 addition & 0 deletions loom/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol

from ..core import Pipeline, PipelineResult, Task
Expand Down
25 changes: 18 additions & 7 deletions loom/agent/task_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
from __future__ import annotations

import importlib.util
from collections.abc import Mapping
from pathlib import Path
from typing import Iterable
from typing import Any, Iterable
from uuid import uuid4

from pydantic import BaseModel, Field

from graphite import ContractException

from ..core import Pipeline, Task
from .agent import AgentContext
from .runner import TaskFactories, TaskFactory
Expand Down Expand Up @@ -68,12 +67,24 @@ def _selected_paths(self, context: AgentContext) -> dict[str, Path]:
return {name: context.draft[name] for name in self.tasks}

@staticmethod
def _prompt(name: str, source: str, failure: Exception | None) -> str:
def _contract_failures(failure: Exception | None) -> Mapping[str, Any] | None:
"""Return per-contract failures when ``failure`` carries them.

Contract types live outside Loom, so the shape is duck-typed rather
than imported: ``loom.agent`` stays usable without a contract package
installed.
"""
failures = getattr(failure, "failures", None)
return failures if isinstance(failures, Mapping) and failures else None

@classmethod
def _prompt(cls, name: str, source: str, failure: Exception | None) -> str:
feedback = "No execution feedback is available yet."
if isinstance(failure, ContractException):
contract_failures = cls._contract_failures(failure)
if contract_failures is not None:
feedback = "Contract failures:\n" + "\n".join(
f"- {contract}: {result.failures}"
for contract, result in failure.failures.items()
f"- {contract}: {getattr(result, 'failures', result)}"
for contract, result in contract_failures.items()
)
elif failure is not None:
feedback = f"Task/runtime exception: {type(failure).__name__}: {failure}"
Expand Down