temporal
Skill190 starsChanged 6 months agoMITView on GitHub
Use when building or debugging the Agentex ADK temporal agent type — structuring workflows and activities, handling signal routing, managing state across replays, or diagnosing workflow failures and retry exhaustion. For standalone Temporal workers outside of Agentex, use general-temporal.
- Reads credentials
We split this skill into the rules it gives an agent
3 rules. 0 are common, 1 is distinctive, and 2 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 22
Read in this order to build a complete mental model of any agent:
- Very specific: line 100
All Temporal agents extend BaseWorkflow.
- Very specific: line 255
Never set manually for normal runs — agentex agents run --manifest manifest.yaml injects them from manifest.yaml:
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Temporal Workflows (Agentex)
Temporal is the durability layer for async Agentex agents. Every step is recorded as an immutable event; if the worker crashes, Temporal replays history to resume exactly where it left off.
When to Activate
- Building or editing a Temporal-based agent (
manifest.yamlhastemporal.enabled: true) - Debugging workflow failures, retries, or signal handling
- Adding activities or custom I/O to an existing workflow
- Questions about failure recovery, event replay, or state persistence
- Implementing the state machine pattern inside a workflow
Project File Reading Order
Read in this order to build a complete mental model of any agent:
manifest.yaml— agent name, workflow name, queue name, env varsproject/models.py— state shape stored in MongoDB between turnsproject/activities.py— real I/O (HTTP, DB, file); the only place non-deterministic work livesproject/acp.py— 5-line config wiring ACP → Temporal (no handlers needed here)project/workflow.py—on_task_create(startup) +on_task_event_send(each user turn)project/run_worker.py— wires activities + workflow + starts the worker process
Core Concepts
Event Sourcing / Replay
Temporal records every decision as an immutable event before executing it:
Event 1: WorkflowStarted
Event 2: ActivityScheduled (scrape_url, url1)
Event 3: ActivityCompleted (scrape_url, url1) → "scraped text"
Event 4: SignalReceived (RECEIVE_EVENT)
Event 5: ActivityScheduled (scrape_url, url2)
← worker crashes hereOn restart, Temporal replays events 1–4. For completed activities it returns the recorded result (no real I/O). Execution resumes at Event 5 for real.
Determinism rule: Workflow code must produce the same decisions on every replay.
- ✅ Call activities for all I/O, random values, current time
- ❌ Never use
random,time.time(),httpx, file reads directly in workflow code - ❌ Never
importI/O libraries at module level inside workflow files — useworkflow.unsafe.imports_passed_through()if unavoidable
Workflow vs Activity
| Workflow | Activity | |
| Purpose | Orchestration logic, state decisions | Real I/O (HTTP, DB, LLM calls) |
| I/O allowed | ❌ No — must be deterministic | ✅ Yes |
| Retried by Temporal | Workflow tasks retried on exception | Yes, via RetryPolicy |
| Runs in | Worker process (sandboxed) | Worker process (unrestricted) |
ACP Entry Point (acp.py)
For Temporal agents, acp.py is just configuration. No handlers are registered manually — Temporal routes everything automatically.
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path(__file__).parent / ".env")
from agentex.lib.sdk.fastacp.fastacp import FastACP
from agentex.lib.types.fastacp import TemporalACPConfig
acp = FastACP.create(
acp_type="async",
config=TemporalACPConfig(
type="temporal",
temporal_address=os.getenv("TEMPORAL_ADDRESS", "localhost:7233"),
),
)ACP → Temporal mapping:
| ACP RPC call | Temporal action |
task/create | Starts a new workflow execution |
event/send | Sends RECEIVE_EVENT signal to the running workflow |
task/cancel | Cancels the workflow execution directly |
Workflow Structure (workflow.py)
All Temporal agents extend BaseWorkflow. Two methods to implement:
from typing import override
from temporalio import workflow
from agentex.lib.core.temporal.types.workflow import SignalName
from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow
from agentex.lib.environment_variables import EnvironmentVariables
from agentex.lib.types.acp import CreateTaskParams, SendEventParams
environment_variables = EnvironmentVariables.refresh()
@workflow.defn(name=environment_variables.WORKFLOW_NAME)
class MyWorkflow(BaseWorkflow):
def __init__(self):
super().__init__(display_name="My Agent")
self._done = False # set True to exit; usually stays False (cancelled externally)
@workflow.run
@override
async def on_task_create(self, params: CreateTaskParams) -> None:
# Called ONCE when the task is created.
# Initialize state, send opening message, then block.
await adk.state.create(task_id=params.task.id, agent_id=params.agent.id, state=MyState.initial())
await adk.messages.create(task_id=params.task.id, content=TextContent(author="agent", content="Ready!"))
await workflow.wait_condition(lambda: self._done) # keeps workflow alive
@workflow.signal(name=SignalName.RECEIVE_EVENT)
@override
async def on_task_event_send(self, params: SendEventParams) -> None:
# Called on EVERY user message. Runs as a Temporal signal handler.
# All logic for responding to user input lives here.
...wait_condition is mandatory in on_task_create. Without it the workflow exits immediately after startup and can no longer receive signals.
Activities (activities.py)
Activities are the only place with real I/O. Group them in a class, register the bound instance in run_worker.py.
import httpx
from pydantic import BaseModel
from temporalio import activity
SCRAPE_URL_ACTIVITY = "scrape_url" # string name must match workflow.execute_activity() call
class ScrapeURLParams(BaseModel):
url: str # serialized to JSON by Temporal when dispatching to the worker
class ScraperActivities:
@activity.defn(name=SCRAPE_URL_ACTIVITY)
async def scrape_url(self, params: ScrapeURLParams) -> str:
async with httpx.AsyncClient(follow_redirects=True, timeout=30) as client:
response = await client.get(params.url)
response.raise_for_status() # non-2xx → exception → Temporal retries
return response.text[:8000]Calling an activity from the workflow:
from datetime import timedelta
from temporalio.common import RetryPolicy
result: str = await workflow.execute_activity(
SCRAPE_URL_ACTIVITY,
ScrapeURLParams(url=url),
start_to_close_timeout=timedelta(minutes=2), # must finish within this window
retry_policy=RetryPolicy(maximum_attempts=2), # 2 total attempts before raising
)State Management
State is a Pydantic model stored in MongoDB, keyed by (task_id, agent_id). Load → mutate in-memory → save.
# models.py
from agentex.lib.utils.model_utils import BaseModel
class MyState(BaseModel):
turn: int = 0
pending_urls: list[str] = []
@classmethod
def initial(cls) -> "MyState":
return cls()# Inside on_task_create
await adk.state.create(task_id=task_id, agent_id=agent_id, state=MyState.initial())
# Inside on_task_event_send
task_state = await adk.state.get_by_task_and_agent(task_id=task_id, agent_id=agent_id)
state = MyState.model_validate(task_state.state) # deserialize
state.turn += 1 # mutate in-memory
await adk.state.update( # persist
state_id=task_state.id,
task_id=task_id,
agent_id=agent_id,
state=state,
)Important: adk.state.update inside a workflow executes as a Temporal activity. If the worker crashes before it runs, MongoDB retains the old state and the replay re-runs the handler from scratch using the old state — no corruption occurs.
Worker Entry Point (run_worker.py)
import asyncio
from dotenv import load_dotenv
from pathlib import Path
load_dotenv(Path(__file__).parent / ".env")
from agentex.lib.core.temporal.activities import get_all_activities
from agentex.lib.core.temporal.workers.worker import AgentexWorker
from agentex.lib.environment_variables import EnvironmentVariables
from project.activities import ScraperActivities
from project.workflow import MyWorkflow
env = EnvironmentVariables.refresh()
async def main():
scraper = ScraperActivities()
worker = AgentexWorker(task_queue=env.WORKFLOW_TASK_QUEUE, health_check_port=8084)
await worker.run(
activities=[*get_all_activities(), scraper.scrape_url],
workflow=MyWorkflow,
)
if __name__ == "__main__":
asyncio.run(main())get_all_activities()— built-in ADK activities (messages, state, tracing). Must always be included.ScraperActivities()— instantiated here soscraper.scrape_urlis a bound method.WORKFLOW_TASK_QUEUE— injected byagentex agents runfrommanifest.yaml(agent.temporal.workflows[0].queue_name).
Environment Variables
Never set manually for normal runs — agentex agents run --manifest manifest.yaml injects them from manifest.yaml:
| Env var | Source in manifest |
WORKFLOW_NAME | agent.temporal.workflows[0].name |
WORKFLOW_TASK_QUEUE | agent.temporal.workflows[0].queue_name |
AGENT_NAME | agent.name |
OPENAI_API_KEY etc. | agent.env.* |
A project/.env file is only needed when running acp.py or run_worker.py directly without the CLI.
Failure Handling
What Temporal handles automatically
| Failure | Temporal behaviour |
| Worker process crash | Replays event history on next available worker; resumes from last checkpoint |
| Activity timeout | Retries per RetryPolicy; raises ActivityError into workflow after max attempts |
| Workflow task exception | Retries the workflow task; workflow moves to FAILED after repeated failures |
What the code must handle explicitly
Activity failure (after all retries): wrap workflow.execute_activity in try/except:
try:
page_text = await workflow.execute_activity(
SCRAPE_URL_ACTIVITY, ScrapeURLParams(url=u),
start_to_close_timeout=timedelta(minutes=2),
retry_policy=RetryPolicy(maximum_attempts=2),
)
scraped_pages.append((u, page_text))
except Exception as e:
await adk.messages.create(task_id=task_id,
content=TextContent(author="agent", content=f"Failed to scrape `{u}`: {e}"))
# continue loop — one bad URL doesn't abort the batchState load failure (MongoDB down): unhandled → workflow FAILED:
try:
task_state = await adk.state.get_by_task_and_agent(task_id=task_id, agent_id=agent_id)
state = MyState.model_validate(task_state.state)
except Exception as e:
await adk.messages.create(task_id=task_id,
content=TextContent(author="agent", content=f"Failed to load state: {e}. Try again."))
returnLLM call failure (OpenAI/litellm down): unhandled → workflow FAILED:
try:
chat_completion = await adk.providers.litellm.chat_completion(llm_config=..., trace_id=task_id)
except Exception as e:
await adk.messages.create(task_id=task_id,
content=TextContent(author="agent", content=f"Summarization failed: {e}. Please resend URLs."))
await adk.state.update(state_id=task_state.id, task_id=task_id, agent_id=agent_id, state=state)
returnFailure handling pattern:
- Wrap
workflow.execute_activityintry/except— continue or message user on failure - Wrap
adk.state.get_by_task_and_agent— return early and message user on failure - Wrap
adk.providers.litellm.chat_completion— save state before returning on failure - Save state before every
returnso the next signal loads clean data
State Machine Pattern
For workflows with multiple distinct phases, use agentex.lib.sdk.state_machine:
from agentex.lib.sdk.state_machine.state import State
self.state_machine = MyStateMachine(
initial_state=MyPhase.WAITING,
states=[
State(name=MyPhase.WAITING, workflow=WaitingWorkflow()),
State(name=MyPhase.PROCESSING, workflow=ProcessingWorkflow()),
State(name=MyPhase.DONE, workflow=DoneWorkflow()),
],
state_machine_data=MyData(),
trace_transitions=True,
)
# In on_task_create:
await self.state_machine.run()
# In on_task_event_send — trigger transitions:
await self.state_machine.transition(MyPhase.PROCESSING)See state_machine/project/ in the repo for a full deep-research example.
ADK Providers
from agentex.lib import adk
from agentex.lib.types.llm_messages import LLMConfig, SystemMessage, UserMessage
# Non-streaming LLM call (litellm)
result = await adk.providers.litellm.chat_completion(
llm_config=LLMConfig(
model="gpt-4o-mini",
messages=[SystemMessage(content="You are helpful."), UserMessage(content="Summarize this.")],
),
trace_id=task_id,
)
summary = result.choices[0].message.content or ""
# Streaming LLM — auto-sends chunks to the UI
await adk.providers.litellm.chat_completion_stream_auto_send(
task_id=task_id,
llm_config=LLMConfig(model="gpt-4o-mini", messages=messages, stream=True),
trace_id=task_id,
)
# OpenAI Agents SDK (with tools + MCP)
run_result = await adk.providers.openai.run_agent_streamed_auto_send(
task_id=task_id,
trace_id=task_id,
input_list=conversation_history,
tools=[MY_FUNCTION_TOOL],
agent_name="Assistant",
agent_instructions="You are helpful.",
model="gpt-4o-mini",
)
final_history = run_result.final_input_list # updated conversation for next turnTracing
# Span as context manager (auto-closes)
async with adk.tracing.span(trace_id=task_id, name="Turn 1", input=state) as span:
await adk.messages.create(..., trace_id=task_id, parent_span_id=span.id)
result = await adk.providers.litellm.chat_completion(..., trace_id=task_id)
span.output = result
# Manual span (must call end() yourself)
span = await adk.tracing.start_span(trace_id=task_id, name="Turn 1", input={...})
# ... work ...
await adk.tracing.end_span(span_id=span.id, output={...})Running Locally
# From the agent directory (e.g. url-summarizer-temporal/)
export ENVIRONMENT=development
agentex agents run --manifest manifest.yaml
# Debug mode — attach VS Code debugger on port 5679
agentex agents run --manifest manifest.yaml --debug-worker --debug-port 5679Temporal UI (inspect workflow history, signals, failures): http://localhost:8080
Red Flags
- I/O directly in workflow code —
httpx, database queries, or LLM calls in a workflow function break determinism; on replay Temporal returns the recorded result instead of re-executing, so the actual network call never happens and the code path diverges; all I/O must be in activities random,time.time(), ordatetime.now()in a workflow — these return different values on every replay, causing divergence; useworkflow.now()for timestamps and pass randomness through activity return valueson_task_createwithoutawait workflow.wait_condition(lambda: self._done)— without this the workflow function returns immediately after startup, the workflow execution completes, and all subsequentRECEIVE_EVENTsignals are dropped because there is no running workflow to receive themworkflow.execute_activitywithouttry/except— when an activity exhausts its retry policy Temporal raisesActivityErrorinto the workflow; unhandled, this puts the workflow into FAILED state and the user never receives an error message; always catch and notify- Not saving state before
returnin a signal handler — returning fromon_task_event_sendwithout callingadk.state.updateleaves MongoDB with the state from the previous turn; the next signal handler loads stale data and the agent loses its context - Not including
get_all_activities()inrun_worker.py— ADK built-in activities handleadk.messages,adk.state, and tracing; omitting them causes everyadk.*call to fail at runtime with "activity not registered on this worker" - Importing I/O libraries at module level in workflow files — Temporal's sandbox isolates workflow execution;
import httpxat the top of a workflow file either fails in sandboxed mode or subtly breaks determinism; useworkflow.unsafe.imports_passed_through()if you must import, or move the import into the activity file
Checklist
- Workflow code has zero I/O — all HTTP, DB, and LLM calls are in activities
- No
random,time.time(), or I/O imports at module level in workflow files on_task_createends withawait workflow.wait_condition(lambda: self._done)- Activities use
@activity.defn(name=CONSTANT)with string constant matchingexecute_activity()call start_to_close_timeoutandretry_policyset on everyexecute_activitycallworkflow.execute_activitywrapped intry/exceptto handle exhausted retriesadk.state.get_by_task_and_agentwrapped — unhandled exception → workflow FAILEDadk.providers.litellm.chat_completionwrapped — state saved before returning on failure- State saved before every
returninside signal handlers so next signal loads clean data get_all_activities()included alongside custom activities inrun_worker.pyWORKFLOW_NAMEandWORKFLOW_TASK_QUEUEare injected by the CLI — not set manually
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- Kshitijpalsinghtomar/depth-skills★ 4same name
- thatjuan/agent-skills★ 1same name
- nilssonr/claude-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository65 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- accessibilitySkill
- agentexSkill
- ai-engineerSkill
- angularSkill
- api-designSkill
- authSkill
- awsSkill
- azure-service-busSkill
- azureSkill
- cachingSkill
- ci-cdSkill
- claude-apiSkill
- claude-codeSkill
- coding-standardsSkill
- complex-doc-ragSkill
- containerizationSkill
- database-designSkill
- data-pipelinesSkill
- deployment-strategiesSkill
- development-workflowSkill
- dockerSkill
- error-handlingSkill
- event-drivenSkill
- fastapiSkill
- feature-flagsSkill
- frontendSkill
- general-temporalSkill
- github-issuesSkill
- goSkill
- graphqlSkill
- incident-responseSkill
- infrastructure-as-codeSkill
- integration-testingSkill
- langgraphSkill
- memory-mapSkill
- microservicesSkill
- mongodbSkill
- nextjsSkill
- observabilitySkill
- openai-agentsSkill
- performanceSkill
- performance-testingSkill
- postgresqlSkill
- promptbaseSkill
- pydanticSkill
- pythonSkill
- reactSkill
- redisSkill
- requirements-planningSkill
- securitySkill
- smoke-testingSkill
- solution-testingSkill
- spellbook-setupSkill
- sqlalchemySkill
- system-designSkill
- tailwindSkill
- technical-documentationSkill
- test-strategySkill
- typescriptSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

