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
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 75
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-7acaeb315af90255109ae17afc71e32a8e5851bb8a956a2a284cb4d344dfab51.yml
openapi_spec_hash: 3044e94b48d60311b6048e8df88e7552
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/sgp/agentex-sdk-ee0c521f0612c31b874bd595b90cd9209545bab603983552a1a7a87f38ed931e.yml
openapi_spec_hash: 917a1ffe9e353bed2740524dec786ed2
config_hash: 593e89b291976a5e84e4c3c3f8324354
4 changes: 4 additions & 0 deletions src/agentex/lib/adk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@

# Data-source refs for lineage (SGP-6513); implementation lives in core.tracing
from agentex.lib.core.tracing import lineage

# Opt-in commit-SHA stamping (AGX1-969); implementation in core.tracing
from agentex.lib.core.tracing import code_revision
from agentex.lib.core.tracing.lineage import DataSourceRef, data_sources

# Unified harness surface (AGX1-375)
Expand Down Expand Up @@ -73,6 +76,7 @@
"TurnSpan",
# Lineage data-source refs (SGP-6513)
"lineage",
"code_revision",
"DataSourceRef",
"data_sources",
# Checkpointing / LangGraph
Expand Down
13 changes: 12 additions & 1 deletion src/agentex/lib/cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def create_project_structure(
# Create root files
root_templates = {
".dockerignore.j2": ".dockerignore",
".gitignore.j2": ".gitignore",
Comment thread
greptile-apps[bot] marked this conversation as resolved.
".env.example.j2": ".env.example",
"manifest.yaml.j2": "manifest.yaml",
"README.md.j2": "README.md",
Expand All @@ -118,7 +119,17 @@ def create_project_structure(

for template, output in root_templates.items():
output_path = project_dir / output
output_path.write_text(render_template(template, context, template_type))
rendered = render_template(template, context, template_type)
if output == ".gitignore" and output_path.exists():
# Re-running init on an existing project: keep the user's rules and
# append only the scaffold entries that are missing.
existing = output_path.read_text()
existing_lines = {line.strip() for line in existing.splitlines()}
missing = [line for line in rendered.splitlines() if line.strip() and not line.startswith("#") and line.strip() not in existing_lines]
if missing:
Comment on lines +127 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Gitignore Rule Order Breaks

When an existing .gitignore already contains !.env.example but does not contain .env.*, this order-insensitive check skips the existing negation and appends .env.* after it. Git uses the last matching rule, so rerunning agentex init then ignores .env.example even though the scaffold promises that the example file remains committable. Preserve the template rules as an ordered block or otherwise account for their effective order when merging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/cli/commands/init.py
Line: 127-129

Comment:
**Gitignore Rule Order Breaks**

When an existing `.gitignore` already contains `!.env.example` but does not contain `.env.*`, this order-insensitive check skips the existing negation and appends `.env.*` after it. Git uses the last matching rule, so rerunning `agentex init` then ignores `.env.example` even though the scaffold promises that the example file remains committable. Preserve the template rules as an ordered block or otherwise account for their effective order when merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

output_path.write_text(existing.rstrip("\n") + "\n\n# Added by agentex init\n" + "\n".join(missing) + "\n")
continue
output_path.write_text(rendered)

console.print(f"\n[green]✓[/green] Created project structure at: {project_dir}")

Expand Down
3 changes: 3 additions & 0 deletions src/agentex/lib/cli/debug/debug_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
pass

from agentex.lib.utils.logging import make_logger
from agentex.lib.cli.utils.cli_utils import SUBPROCESS_STREAM_LIMIT

from .debug_config import DebugConfig, resolve_debug_port

Expand Down Expand Up @@ -66,6 +67,7 @@ async def start_temporal_worker_debug(
env=debug_env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
limit=SUBPROCESS_STREAM_LIMIT,
)


Expand Down Expand Up @@ -119,6 +121,7 @@ async def start_acp_server_debug(
env=debug_env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
limit=SUBPROCESS_STREAM_LIMIT,
)


Expand Down
60 changes: 56 additions & 4 deletions src/agentex/lib/cli/handlers/run_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from agentex.lib.cli.debug import DebugConfig, start_acp_server_debug, start_temporal_worker_debug
from agentex.lib.utils.logging import make_logger
from agentex.config.agent_manifest import AgentManifest
from agentex.lib.cli.utils.cli_utils import SUBPROCESS_STREAM_LIMIT
from agentex.lib.cli.utils.path_utils import (
get_file_paths,
calculate_uvicorn_target_for_local,
Expand All @@ -23,6 +24,11 @@
logger = make_logger(__name__)
console = Console()

# How many consecutive unreadable lines to skip before giving up on the stream.
# Skipping is only known-safe for the limit-overrun case; this bounds the damage
# if some other error repeats without consuming anything.
MAX_CONSECUTIVE_READ_ERRORS = 100


class RunError(Exception):
"""An error occurred during agent run"""
Expand Down Expand Up @@ -215,6 +221,7 @@ async def start_acp_server(
env=env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
limit=SUBPROCESS_STREAM_LIMIT,
)


Expand All @@ -234,23 +241,68 @@ async def start_temporal_worker(
env=env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
limit=SUBPROCESS_STREAM_LIMIT,
)


async def stream_process_output(process: asyncio.subprocess.Process, prefix: str):
"""Stream process output with prefix"""
"""Stream process output with prefix.

This loop is the only reader of the child's stdout pipe. If it ever stops
reading, the pipe fills and the child blocks forever inside ``write()``,
which presents as a silent freeze: 0% CPU, no further logs, no traceback.
So a single unreadable line must never end the loop.
"""
try:
if process.stdout is None:
return
consecutive_read_errors = 0
while True:
line = await process.stdout.readline()
try:
line = await process.stdout.readline()
except ValueError as e:
# readline() raises ValueError when a line exceeds the stream limit.
# In *that* case it has already discarded the line and resumed the
# transport, so skipping it makes guaranteed progress. Any other
# ValueError carries no such guarantee, and retrying it forever would
# spin without draining. We cannot tell the two apart (readline
# flattens LimitOverrunError into a bare ValueError), so bound the
# retries and let the outer handler report the hang risk.
consecutive_read_errors += 1
if consecutive_read_errors > MAX_CONSECUTIVE_READ_ERRORS:
raise
logger.warning(
f"Skipping an unreadable line from {prefix}: {e!r} "
f"(consecutive failure {consecutive_read_errors}/{MAX_CONSECUTIVE_READ_ERRORS}). "
f"If this says the chunk exceeded the limit, raise limit= on this "
f"process's create_subprocess_exec."
)
continue

consecutive_read_errors = 0

if not line:
break
decoded_line = line.decode("utf-8").rstrip()

try:
decoded_line = line.decode("utf-8").rstrip()
except UnicodeDecodeError as e:
logger.warning(f"Dropped an undecodable log line from {prefix} ({e}).")
continue

if decoded_line: # Only print non-empty lines
console.print(f"[dim]{prefix}:[/dim] {decoded_line}")
except Exception as e:
logger.debug(f"Output streaming ended for {prefix}: {e}")
# The escalation path, including for the re-raise above. Anything reaching
# here ends the loop, so the child is now at risk of blocking on a full pipe.
# Warning rather than debug: this used to be a debug() that make_logger could
# never emit, which is why three freezes produced no clue.
# CancelledError derives from BaseException, so the auto-reload path that
# cancels these tasks passes straight through and is unaffected.
logger.warning(
f"Output streaming for {prefix} stopped on {e!r}. "
f"Nothing is draining its stdout now, so {prefix} will hang once the pipe fills."
)


async def run_agent(manifest_path: str, debug_config: "DebugConfig | None" = None):
Expand Down
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/default-claude-code/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/default-codex/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/default-langgraph/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/default-openai-agents/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/default-pydantic-ai/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/default/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/sync-claude-code/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/sync-codex/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
20 changes: 20 additions & 0 deletions src/agentex/lib/cli/templates/sync-langgraph/.gitignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets and local config
.env
.env.*
!.env.example

# Python
__pycache__/
*.py[cod]
.venv/
venv/
*.egg-info/
build/
dist/

# Tooling
.ipynb_checkpoints/
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
Loading