Compare commits
2 Commits
e00702164d
...
ed6577ccf9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed6577ccf9 | ||
|
|
4f17bbd2c3 |
@@ -563,9 +563,21 @@ async def main(payload: dict, context):
|
||||
tools=all_tools,
|
||||
)
|
||||
|
||||
# Intercept heartbeat: replace bare [HEARTBEAT] with a strict-format instruction.
|
||||
# Agent-runner suppresses replies that start with HEARTBEAT_OK, so only real alerts reach Telegram.
|
||||
prompt = payload.get('prompt', '')
|
||||
if prompt.strip() == '[HEARTBEAT]':
|
||||
prompt = (
|
||||
'HEARTBEAT CHECK: Silently check for anything urgent Daniel should know about '
|
||||
'(calendar events starting within 2 hours, unread urgent emails, overdue reminders). '
|
||||
'Do NOT narrate your checking process. '
|
||||
'If nothing is urgent: reply with the single word HEARTBEAT_OK and nothing else. '
|
||||
'If something IS urgent: reply with 2-3 lines max summarising only the urgent items.'
|
||||
)
|
||||
|
||||
final_message = None
|
||||
try:
|
||||
async for event in agent.stream_async(payload.get('prompt', '')):
|
||||
async for event in agent.stream_async(prompt):
|
||||
if 'result' in event:
|
||||
final_message = event['result'].message
|
||||
yield event
|
||||
|
||||
@@ -5,6 +5,11 @@ import { AgentClawStack } from '../lib/agent-claw-stack';
|
||||
|
||||
const app = new cdk.App();
|
||||
|
||||
// Billing tags applied to all resources in the stack
|
||||
cdk.Tags.of(app).add('project', 'agent-claw');
|
||||
cdk.Tags.of(app).add('env', 'prod');
|
||||
cdk.Tags.of(app).add('owner', 'daniel');
|
||||
|
||||
new AgentClawStack(app, 'AgentClawStack', {
|
||||
env: {
|
||||
account: process.env.CDK_DEFAULT_ACCOUNT,
|
||||
|
||||
155
scripts/create-inference-profiles.py
Normal file
155
scripts/create-inference-profiles.py
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create Bedrock Application Inference Profiles for agent-claw and update SSM.
|
||||
|
||||
Run after: aws sso login --profile ai1
|
||||
|
||||
Usage:
|
||||
python3 scripts/create-inference-profiles.py [--dry-run]
|
||||
|
||||
Creates:
|
||||
agent-claw-opus — main agent
|
||||
agent-claw-sonnet — aws_agent + coding_agent
|
||||
agent-claw-haiku — document_agent
|
||||
|
||||
Then updates SSM:
|
||||
/agent-claw/model-id → agent-claw-opus ARN
|
||||
/agent-claw/subagents → inline model_id fields replaced with profile ARNs
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
PROFILE = 'ai1'
|
||||
REGION = 'us-east-1'
|
||||
|
||||
BILLING_TAGS = [
|
||||
{'key': 'project', 'value': 'agent-claw'},
|
||||
{'key': 'env', 'value': 'prod'},
|
||||
{'key': 'owner', 'value': 'daniel'},
|
||||
]
|
||||
|
||||
# Map: profile name → cross-region model ID to copy from
|
||||
PROFILES_TO_CREATE = {
|
||||
'agent-claw-opus': 'us.anthropic.claude-opus-4-6-v1:0',
|
||||
'agent-claw-sonnet': 'us.anthropic.claude-sonnet-4-6-20251001-v1:0',
|
||||
'agent-claw-haiku': 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
}
|
||||
|
||||
# SSM subagent model_id values → which profile ARN to swap in
|
||||
SUBAGENT_MODEL_MAP = {
|
||||
'us.anthropic.claude-sonnet-4-6': 'agent-claw-sonnet',
|
||||
'us.anthropic.claude-sonnet-4-6-20251001-v1:0':'agent-claw-sonnet',
|
||||
'us.anthropic.claude-haiku-4-5-20251001-v1:0': 'agent-claw-haiku',
|
||||
}
|
||||
|
||||
|
||||
def get_system_inference_profile_arn(bedrock, model_id: str) -> str:
|
||||
"""Find the system inference profile ARN for a given cross-region model ID."""
|
||||
paginator = bedrock.get_paginator('list_inference_profiles')
|
||||
for page in paginator.paginate(typeEquals='SYSTEM_DEFINED'):
|
||||
for p in page.get('inferenceProfileSummaries', []):
|
||||
if p.get('inferenceProfileId', '') == model_id or \
|
||||
any(m.get('modelArn', '').endswith(model_id) for m in p.get('models', [])):
|
||||
return p['inferenceProfileArn']
|
||||
# Fallback: construct ARN directly (works for cross-region profiles)
|
||||
return f'arn:aws:bedrock:{REGION}::foundation-model/{model_id}'
|
||||
|
||||
|
||||
def get_existing_profile(bedrock, name: str) -> dict | None:
|
||||
"""Return existing application profile by name, or None."""
|
||||
paginator = bedrock.get_paginator('list_inference_profiles')
|
||||
for page in paginator.paginate(typeEquals='APPLICATION'):
|
||||
for p in page.get('inferenceProfileSummaries', []):
|
||||
if p.get('inferenceProfileName') == name:
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def create_or_get_profile(bedrock, name: str, model_id: str, dry_run: bool) -> str:
|
||||
"""Create application inference profile (idempotent). Returns ARN."""
|
||||
existing = get_existing_profile(bedrock, name)
|
||||
if existing:
|
||||
arn = existing['inferenceProfileArn']
|
||||
print(f' [exists] {name} → {arn}')
|
||||
return arn
|
||||
|
||||
source_arn = get_system_inference_profile_arn(bedrock, model_id)
|
||||
print(f' [create] {name}')
|
||||
print(f' source: {source_arn}')
|
||||
|
||||
if dry_run:
|
||||
print(f' [dry-run] skipping create')
|
||||
return f'arn:aws:bedrock:{REGION}:{{}account}}:application-inference-profile/{name}-DRY-RUN'
|
||||
|
||||
resp = bedrock.create_inference_profile(
|
||||
inferenceProfileName=name,
|
||||
description=f'agent-claw {name.split("-")[-1]} model with billing tags',
|
||||
modelSource={'copyFrom': source_arn},
|
||||
tags=BILLING_TAGS,
|
||||
)
|
||||
arn = resp['inferenceProfileArn']
|
||||
print(f' → {arn}')
|
||||
return arn
|
||||
|
||||
|
||||
def update_ssm(ssm, param: str, value: str, dry_run: bool):
|
||||
print(f' [ssm] {param} = {value[:80]}...' if len(value) > 80 else f' [ssm] {param} = {value}')
|
||||
if not dry_run:
|
||||
ssm.put_parameter(Name=param, Value=value, Type='String', Overwrite=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
session = boto3.Session(profile_name=PROFILE, region_name=REGION)
|
||||
bedrock = session.client('bedrock')
|
||||
ssm = session.client('ssm')
|
||||
|
||||
print('=== Creating inference profiles ===')
|
||||
arns = {}
|
||||
for name, model_id in PROFILES_TO_CREATE.items():
|
||||
arns[name] = create_or_get_profile(bedrock, name, model_id, args.dry_run)
|
||||
|
||||
print('\n=== Updating SSM ===')
|
||||
|
||||
# Main agent model
|
||||
update_ssm(ssm, '/agent-claw/model-id', arns['agent-claw-opus'], args.dry_run)
|
||||
|
||||
# Subagents JSON — swap model_id fields
|
||||
try:
|
||||
resp = ssm.get_parameter(Name='/agent-claw/subagents')
|
||||
defs = json.loads(resp['Parameter']['Value'])
|
||||
except ClientError as e:
|
||||
print(f' [error] Could not read /agent-claw/subagents: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
changed = False
|
||||
for agent in defs:
|
||||
mid = agent.get('model_id', '')
|
||||
profile_name = SUBAGENT_MODEL_MAP.get(mid)
|
||||
if profile_name:
|
||||
new_arn = arns[profile_name]
|
||||
print(f' [subagent] {agent["name"]}: {mid} → {profile_name}')
|
||||
agent['model_id'] = new_arn
|
||||
changed = True
|
||||
else:
|
||||
print(f' [subagent] {agent["name"]}: {mid} (no mapping, left as-is)')
|
||||
|
||||
if changed:
|
||||
update_ssm(ssm, '/agent-claw/subagents', json.dumps(defs, indent=2), args.dry_run)
|
||||
|
||||
print('\n=== Done ===')
|
||||
print('Profiles created and SSM updated.')
|
||||
print('Redeploy not required — agent reads model IDs from SSM at startup.')
|
||||
if args.dry_run:
|
||||
print('\n[dry-run mode — no AWS changes were made]')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user