1. Conclusions and boundaries
This refactoring adds no third Agent abstraction and does not wrap the session Job API in another public function. Public semantics remain in agent(); persistence and branching belong to the execution environment, not the Workflow authoring API.
Shared execution entry
agent() and configured Agent calls enter the same autonomous tool loop.
Ordinary Agentic Functions do not call it and permit only one llm(); the full classification contract is in Programming model and categories. Within a Workflow, agent() runs in the current Runtime and forms parent-child Call relationships.
Internal session adapter
run_agent_turn() serves only Chat, CLI, background Jobs and genuinely independent sub-agents. It creates session branches, claims workers, restores Jobs and moves session heads; Programs must not import it directly.
2. Current implementation: verified facts
agent() Already uses the current Runtimeopenprogram/agentic_programming/agent.py calls runtime.exec(..., execution_kind="agent"), making it naturally suitable for a Workflow DAG.
run_agent_turn() is a persistent Job entryIt creates/resumes session Jobs and manages worker claims, branches, heads, archives and chain depth.
create_workflow authoring runs through _run_planner_turn() → agent() and belongs to the current Workflow’s public Agent Call; run_agent_turn() remains exclusive to Chat/CLI/Jobs.
agent() already supports structured resultsPublic agent() Already passes response_format through to the current Runtime and returns validated JSON values unchanged; the Workflow planner still parses JSON text.
| Location | Current responsibility | Problem | Target |
|---|---|---|---|
agentic_programming.agent | Tool loop inside the current Runtime | Incomplete parameters and unclear DAG labels | Extend into the sole public Agent contract |
agent.sub_agent_run | Session branches and durable Jobs | Called directly by internal Workflow planning | Retain only as an internal lifecycle adapter |
| Workflow management and entries | search_workflows, create_workflow, revise_workflow, and workflows/auto_workflow.py | The old composite entry mixed distinct side effects | The first three remain reusable management functions; the full automatic process resides in workflows/ and stays independently registered |
Runtime.exec | Shared lower-level LLM/tool loop | Public parameters are not fully exposed | Remain the sole Agent-loop core without public session semantics |
3. Target architecture: two adapter entries, one execution core
Differences exist only in lifecycle before/after execution: Programs reuse the current Runtime; sessions create durable Jobs first. Model loops, tool selection and error semantics remain identical.
4. Fixed hierarchy of four execution primitives
Workflow is the highest layer and may directly choose llm(), agent(), goal() or ordinary Python Functions.goal() builds on agent(); agent() builds on llm(). These are dependencies; a Workflow need not traverse every layer.
| Primitive | Public semantics | Persistent session? | DAG record |
|---|---|---|---|
llm() | Exactly one logical model request; structured output supported | No; inherits current Runtime | One LLM Call |
agent() | Model/tool loop within one task; returns final text or structured results | No; inherits current Runtime | One Agent Call containing LLM and Function Calls |
goal() | Attempt with an Agent, then independently judge goal satisfaction. Programs isolate pre-call history by default; /goal explicitly supplies the current session view | Yes; writes the same GoalState in its owning session | Goal → Agent → Goal judge |
run_agent_turn() | Internal session lifecycle adapter, not a Program primitive | Yes | Independent Agent Call associated with session/branch |
Target public signature
def agent(
prompt: str | list[dict],
*,
description: str = "",
agent_id: str = "",
model: str = "",
effort: str = "",
tools: Sequence[str] | None = None,
response_format: dict | JsonSchemaOutput | None = None,
max_iterations: int = 20,
timeout_s: float | None = None,
) -> str | dict | list | int | float | bool | None:
...
description affects only visible labels and Call metadata; agent_id reuses the existing Agents-page profile resolver; explicit model/tools overrides corresponding profile fields. The public signature adds no session_id, branch_from, advance_head, archive_when_done or run_in_background.
5. Target Self Programming Workflow process
Public behavior is split into search_workflows, create_workflow, revise_workflow and the user-triggered entry auto_workflow(task). Chat Agents directly call the first three capabilities or a specific Workflow as needed; only a manually requested complete flow enters auto_workflow. This section describes only their use of public agent(); the complete contract is defined by Discovery, creation and automatic execution.
create_workflow author and deterministic validator generate/publish the multi-file Python package; explicit edits are independently owned by revise_workflow .| Stage | Allowed capabilities | Explicit output | Observability |
|---|---|---|---|
| Search and selection | Read-only catalog and candidate metadata | Pinned revision or create decision | Search function and one selection-Agent node |
| Validation and publication | Pure Python validation and file operations | Revision, dependency inventory and validation diagnostics | Function Call node |
| Creation retry | Same unpublished candidate, explicit diagnostics and bounded rounds | New candidate passing gates | create_workflow Agent nodes under |
| Execution | Programs declared by the Workflow | File deliverables or explicit return_result | Complete nested DAG |
| Summary | Run events and deliverable metadata | Process summary without copying the complete body | Conclusion area |
6. DAG and chat display contract
Primary acceptance is not merely a function returning JSON: users must see how a Workflow selected and created work, which Programs actually ran and where an error occurred.
| UI level | Default display | Expanded display |
|---|---|---|
| Top-level Workflow | Name, state, duration, step count and complete Conclusion | Selection/authoring Agents, validation, actual Workflow and summary |
| Agent | Description label, state and iteration count | Model-output summaries and actual Program calls; no hidden chain-of-thought |
| Program Call | Name, argument summary and state | Input, output, errors and side-panel details |
| sub-agent session | Separate navigable row | Switch to the child session; returning preserves the same row rather than another card system |
7. Failures, recovery and state consistency
| Failure point | Handling rule | User-visible state |
|---|---|---|
| Provider/model lacks structured output | After capability detection, use text JSON + local schema validation; never overwrite an internal LLM error with Workflow completed | Corresponding Agent/LLM Call error; top-level Workflow failed |
| Candidate Workflow validation fails | Pass deterministic diagnostics to bounded public agent() repair; fail at the limit | Each repair is an expandable Agent node |
| Worker restart | Only session/Job paths use durable recovery; in-Program Agents terminate with the parent Call and follow the parent Workflow retry policy | Do not retain old running states indefinitely |
| User cancellation | Propagate one cancellation token to Runtime, provider and tools; terminate child calls before closing the parent Workflow | Consistent cancelled/interrupted states, never completed |
| Tool or child Workflow failure | Preserve original error payload, caller and partial artifacts; summary must not claim completion | Failed nodes and Conclusion agree |
| Historical run recovery | Old SINGLE/code.py state serves only historical recovery; every new task must use project selection/creation | Old records remain readable; new runs create no legacy project |
8. Staged migration plan
Each stage first adds tests reproducing the current gap, then modifies the corresponding layer. Never change public API, session lifecycle, Workflow layout and chat visual styles in one stage.
Freeze execution contracts
- Record current
agent(),run_agent_turn()and Runtime call graphs. - Add architecture tests: Programs must not import
agent.sub_agent_run. - Add real registered-schema tests fixing public parameters of the four Workflow management functions.
Complete public agent()
- Pass through
response_format, content blocks, description and Agent profile. - Reuse profile/tool resolvers without a new configuration format.
- Add an Agent wrapper Call for stable ownership of LLM/tool nodes.
Separate Workflow public behaviors
search_workflowsremains deterministic and read-only.auto_workflowselection Agent chooses reuse/create only; no file writes or automatic revision.- Authoring, writing and pre-publication validation belong to
create_workflowandrevise_workflow.
Restrict the session adapter
run_agent_turn()remains internal, supporting branches, Jobs, recovery and archive.- Confirm dispatcher ultimately uses the same Agent-loop core as public agent.
- Remove duplicate model defaults, tool resolution and error normalization.
Migrate all Programs
- Scan
openprogram/programs/for internal Agent API and direct-loop calls. - Workflows and Applications consistently use public
agent(); ordinary Agentic Functions permit only onellm(). - Retain the vanilla sub-agent tool as the product entry for the session adapter.
DAG and chat display
- Backend events add execution_kind, label, caller, iteration and artifact metadata.
- Frontend renders the same Runtime hierarchy without inferring a separate structure.
- Conclusion, footer and collapse behavior reuse existing shared components.
Compatibility cleanup and release
- Read-only recovery for old runs; prohibit legacy SINGLE for new runs.
- Delete unused planner helpers and duplicate test fixtures.
- Update design navigation, API documentation and Programs dependency graphs.
Controlled rollout
- Run focused component tests first, then required complete suites.
- Accept one real reuse, create, repair and cancel session each.
- Retain the old recovery reader for one compatibility period; never write the old format again.
9. Compatibility, retention and removals
| Object | Treatment | Reason | |
|---|---|---|---|
agent() | Retain and extend; old arguments continue working | Already the correct public primitive within ambient Runtime | |
run_agent_turn() | Retain as internal; prohibit Programs imports | Chat/CLI/background genuinely need durable sessions | |
_run_planner_turn() | Workflow selection/authoring calls public agent() | Planning inside Programs no longer imports run_agent_turn | Retain as a agent(tools=PLANNER_TOOLS) thin wrapper; removable later |
_request_project_decision() | Replace with actual tool behavior of a single orchestration Agent | Selection JSON followed by authoring creates two Agent semantics and a disconnected DAG | |
legacy SINGLE / code.py | Read-only recovery; prohibit creation | Preserve historical records without creating unmaintainable single-file Workflows | |
| Workflow project package | Continue standard Python packages and explicit Agent method or function exports | Support multi-file maintenance, Git revisions, static dependency graphs and direct Python calls | |
| Agents profile | Reuse existing Agents configuration/resolver | Avoid a second model/tool configuration inside Programs |
10. Acceptance matrix
API and registration
- Registry contains only
search_workflows,create_workflow,revise_workflowandauto_workflow; category directories are not registered. agent()has stable model/tools/profile override order.- Structured output validates with providers supporting and lacking native schemas.
Call structure
- Selection belongs to
auto_workflow; authoring and pre-publication retries belong tocreate_workfloworrevise_workflow. - All Programs called by a Workflow appear in the DAG.
- Direct/manual Workflows are not incorrectly attached to the prior assistant through predecessor.
Lifecycle
- Parent cancellation terminates Agent, LLM and tools.
- Session Job restart recovers only the session path.
- New runs never generate SINGLE; old runs resume or return an explicit incompatibility error.
UI
- Collapsed state shows Workflow title, Conclusion and footer.
- Expand progressively, not all children at once.
- App/Web/TUI read the same default profile and worker at 18100.
Suggested focused checks
pytest -q tests/component/agent/test_agentic_workflow.py
pytest -q tests/unit/programs/dag/test_runtime_exec_dag.py
pytest -q tests/unit/agent/workflows/test_agentic_programming_goal.py
pytest -q tests/component/agent/jobs/test_async_job_recovery.py
pytest -q tests/unit/agent/jobs/test_spawn_caller_invariant.py
pytest -q tests/component/webui/abilities/test_programs_explorer.py
npm --prefix web run check:chat-ui
npx --prefix web tsc --noEmit
python -m scripts.docs_site.checklinks
git diff --check
11. Framework comparison and decisions
Compare only capabilities directly relevant to this architectural decision. OpenProgram introduces no new third-party orchestration runtime.
| Reference | Adopted | Not adopted |
|---|---|---|
| OpenAI Agents SDK · Running agents | Runner owns the model/tool loop of one logical turn; max turns, sessions and hooks are separate. | Do not use Runner as a second OpenProgram runtime; retain current Runtime/dispatcher. |
| OpenAI Agents SDK · Tools | Python function tools and agent-as-tool are explicit calls; parents can observe child execution. | Do not implicitly convert Workflows into handoffs; they remain Python packages. |
| OpenAI Agents SDK · Orchestration | Distinguish a manager calling a child Agent from handing control to another Agent. | Inside a Program, agent() does not automatically create session branches; only product-level sub-agent actions switch sessions. |
| LangGraph · Workflows and agents | Workflows are predefined code paths; Agents dynamically choose tools locally. | No new graph DSL; static call graphs derive from Python imports and explicit entry identities . |
| LangGraph · Persistence | Persistence, threads, checkpoints and interruption recovery should be explicit lifecycle capabilities. | Do not force every in-Program Agent into a persistent thread; only the session adapter uses durable Jobs. |
12. Implementation status
llm(), agent(), goal()All three already use ambient Runtime; retain the hierarchy direction.
response_format, tools, model, effort, timeout and execution_kind capabilities already exist.
agent()workflow._run_planner_turn() calls openprogram.agentic_programming.agent()(restricted PLANNER_TOOLS); auto selection has a AUTO_DECISION_ATTEMPTS limit. The authoring design owns the four registered entries and source-classification contract.
agent() still uses the session adapterlegacy code.py In the execution path, _agent_function() remains bound to the session turn; project-snapshot execution already uses public agent().
Component tests cover reuse/create/revise/pin/resume; selection-Agent nodes, Conclusion and reload behavior in the default App remain unverified.
agent() Structured-output passthroughPublic agent() Accepts a bare JSON Schema or JsonSchemaOutput, returning Runtime-validated objects, arrays, scalars or null unchanged. Without structured-output requests, retain text and legacy {"text": ...} compatibility; Workflow-planner migration is outside this implementation.