Python workflows combine two sources:
- the public
terse_sdk package
- the generated helpers in
src/terse_generated.py
Most Python users should start with the shared CLI:
terse init my-project --language python
The CLI scaffolds src/main.py, installs dependencies with uv, and generates src/terse_generated.py from your connected integrations.
Project structure
my-project/
├── src/
│ ├── main.py # Workflow definitions
│ └── terse_generated.py # Generated helpers (never edit by hand)
├── pyproject.toml
├── .python-version
└── .env
Use src/main.py to register workflows. Re-run terse generate whenever your connected integrations or available resources change.
Core SDK imports
Import these from terse_sdk:
Terse — the runtime object used to register workflows
CronJobInputEvent and other typed event models
SdkAgentStreamEventFinalOutput if you want to stream and inspect final output explicitly
Import these from terse_generated:
TerseAgent — the generated subclass with typed agent.tools
Schedule, Attio, Snowflake — generated trigger and skill helpers
Terse
The runtime entry point. Create one app instance in src/main.py.
from terse_sdk import Terse
app = Terse()
@app.job(...)
Registers a workflow in the Python runtime.
| Field | Type | Description |
|---|
name | str | Unique workflow identifier. Used to match workflows across deploys. |
triggers | list[TriggerConfig] | One or more triggers that start the workflow. |
skills | list[SkillConfig] | Capabilities available to the agent during the run. |
tool_approvals | list[str] | Tool names that require human approval before execution. |
filter | Callable[[event], bool] | Optional function to skip the run for specific events. |
webhook_url | str | Optional URL to notify after each run completes. |
| decorated function | Callable[[event, agent], None] | The workflow handler. |
from terse_sdk import CronJobInputEvent, Terse
from terse_generated import Schedule, TerseAgent
app = Terse()
@app.job(
name="weekly-pipeline-digest",
triggers=[Schedule.cron("0 9 * * 1")],
skills=[],
)
def run_job(event: CronJobInputEvent, agent: TerseAgent) -> None:
summary = agent.run_and_wait(
f"Summarize the most important pipeline changes this week. Context: {event.formatted_content}",
event,
)
print(summary)
Use workflow in prose if you are describing the product concept. The Python SDK API itself uses @app.job(...).
TerseAgent
Annotate handlers with TerseAgent from terse_generated, not terse_sdk, so agent.tools is typed when generated helpers exist.
run(prompt, event=None)
Streams agent events from the backend.
from terse_sdk import SdkAgentStreamEventFinalOutput
for stream_event in agent.run("Summarize the account.", event):
if isinstance(stream_event, SdkAgentStreamEventFinalOutput):
print(stream_event.final_output)
Use run when you want streamed output or intermediate events.
run_and_wait(prompt, event=None)
Runs the agent to completion and returns the final output string, or None if no final output event arrives.
summary = agent.run_and_wait(
f"Summarize this event in 3 bullets. Context: {event.formatted_content}",
event,
)
Use run_and_wait for most workflows where you want the final answer before continuing.
Calls a named tool directly, bypassing the model.
result = agent.execute_tool(
"snowflakeExecuteQuery",
{"query": "select current_date()"},
)
Use execute_tool when you know the exact tool name and parameters in advance.
Generated helpers attach deterministic wrappers under agent.tools.*.
result = agent.tools.snowflake.execute_query(
query="select current_date()",
)
Use agent.tools.* when a generated wrapper exists. It is usually clearer and safer than calling execute_tool(...) by raw name.
Event context
Python event models expose prompt-ready context as event.formatted_content.
prompt = (
"Summarize this event and recommend one next step. "
f"Context: {event.formatted_content}"
)
When you write prompts, include event.formatted_content so the agent sees the full serialized event context.
Generated helpers
src/terse_generated.py is created by terse generate. Do not edit it by hand.
Today, Python generation focuses on:
Schedule.cron(...)
Attio.skill(...)
Snowflake.skill()
- deterministic wrappers on
agent.tools.attio
- deterministic wrappers on
agent.tools.snowflake
- generated
TerseAgent, GeneratedTools, create_tools(), and attach_tools()
Re-run terse generate when your integration context changes.
Do not edit src/terse_generated.py by hand.
Current Python surface
The Python CLI/runtime path is real and supported, but the generated helper surface is narrower than TypeScript today.
Python projects currently get first-class generated helpers for:
- scheduled workflows via
Schedule
- Attio skills and deterministic Attio tool wrappers
- Snowflake skills and deterministic Snowflake tool wrappers
If a helper is not present in src/terse_generated.py, do not invent it. Re-run terse generate first, then check what the generated file actually exports.
Filtering events
Use filter to skip runs for events that do not match your criteria. Return True to run, False to skip.
from terse_sdk import CronJobInputEvent, Terse
from terse_generated import Schedule, TerseAgent
app = Terse()
def weekday_only(event: CronJobInputEvent) -> bool:
return "Sat" not in event.formatted_content and "Sun" not in event.formatted_content
@app.job(
name="weekday-digest",
triggers=[Schedule.cron("0 9 * * *")],
skills=[],
filter=weekday_only,
)
def run_job(event: CronJobInputEvent, agent: TerseAgent) -> None:
summary = agent.run_and_wait(f"Summarize the workday context: {event.formatted_content}", event)
print(summary)
List tool names in tool_approvals to require human approval before those tools execute. During local testing, the CLI prompts in the terminal. In production, approval requests surface in the Terse app under Notifications.
from terse_sdk import CronJobInputEvent, Terse
from terse_generated import Schedule, Snowflake, TerseAgent
app = Terse()
@app.job(
name="warehouse-health-check",
triggers=[Schedule.cron("0 9 * * 1")],
skills=[Snowflake.skill()],
tool_approvals=["snowflakeExecuteQuery"],
)
def run_job(event: CronJobInputEvent, agent: TerseAgent) -> None:
result = agent.tools.snowflake.execute_query(
query="select current_date() as today",
)
print(result)
Use tool_approvals when a workflow writes to production systems during early development or when compliance requires a human in the loop.
Local workflow
terse init my-project --language python
cd my-project
terse integrate
terse generate
# edit src/main.py
terse test
terse deploy
Python projects use uv for dependency management and local execution under the hood, but you still use the same terse CLI commands after scaffold.